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))
{