From b11f2eb6472a2732f6336ac26b8d5003732f1640 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 5 Sep 2026 12:51:25 +1000 Subject: [PATCH] Handle incomplete EXR zlib headers --- .../Exr/Compression/ExrBaseDecompressor.cs | 10 ++++- .../Formats/Exr/ExrZipDecoderTests.cs | 43 +++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs b/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs index afd32ed663..b85c37546e 100644 --- a/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs +++ b/src/ImageSharp/Formats/Exr/Compression/ExrBaseDecompressor.cs @@ -53,8 +53,14 @@ internal abstract class ExrBaseDecompressor : ExrBaseCompression int left = (int)(compressedBytes - (stream.Position - pos)); return left > 0 ? left : 0; }); - inflateStream.AllocateNewBytes((int)compressedBytes, true); - using DeflateStream dataStream = inflateStream.CompressedStream!; + + // Incomplete headers return false even for critical chunks, leaving no stream to read. + if (!inflateStream.AllocateNewBytes((int)compressedBytes, true)) + { + ExrThrowHelper.ThrowInvalidImageContentException("ZIP compressed EXR block has an incomplete zlib header."); + } + + using DeflateStream dataStream = inflateStream.CompressedStream; int totalRead = 0; while (totalRead < uncompressedBytes) diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrZipDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrZipDecoderTests.cs index d40564d4ad..1a7632d5e3 100644 --- a/tests/ImageSharp.Tests/Formats/Exr/ExrZipDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrZipDecoderTests.cs @@ -67,6 +67,42 @@ public class ExrZipDecoderTests } } + /// + /// Missing or truncated zlib headers obey the image-data integrity policy. + /// + /// The number of available zlib header bytes. + /// The image-data integrity policy. + [Theory] + [InlineData(0, SegmentIntegrityHandling.Strict)] + [InlineData(1, SegmentIntegrityHandling.Strict)] + [InlineData(0, SegmentIntegrityHandling.IgnoreAncillary)] + [InlineData(1, SegmentIntegrityHandling.IgnoreAncillary)] + [InlineData(0, SegmentIntegrityHandling.IgnoreImageData)] + [InlineData(1, SegmentIntegrityHandling.IgnoreImageData)] + public void Decode_IncompleteZlibHeader_RespectsIntegrityHandling(int length, SegmentIntegrityHandling integrity) + { + byte[] header = [0x78, 0x9C]; + byte[] data = BuildExr(header[..length], ExrPixelType.Float, 2, 0); + Configuration configuration = Configuration.Default.Clone(); + configuration.MemoryAllocator = new TestMemoryAllocator(0x3F); + DecoderOptions options = new() { Configuration = configuration, SegmentIntegrityHandling = integrity }; + + if (integrity == SegmentIntegrityHandling.IgnoreImageData) + { + using Image image = Image.Load(options, data); + Assert.Equal(new Size(256, 1), image.Size); + + for (int x = 0; x < image.Width; x++) + { + Assert.Equal(new Vector4(0, 0, 0, 1), image[x, 0].ToVector4()); + } + } + else + { + Assert.Throws(() => Image.Load(options, data)); + } + } + /// /// Missing color channels must not inherit the allocator's previous contents. /// @@ -113,6 +149,13 @@ public class ExrZipDecoderTests /// The zlib stream. private static byte[] ZlibCompress(byte[] data) { + if (data.Length == 0) + { + // An empty write produces no output on some runtimes. Use a complete zlib stream + // containing an empty final DEFLATE block and Adler-32 checksum instead. + return [0x78, 0x9C, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01]; + } + using MemoryStream output = new(); using (ZLibStream zlib = new(output, CompressionLevel.Optimal, leaveOpen: true)) {