Browse Source

Honor EXR image data recovery without exposing incomplete blocks

pull/3187/head
James Jackson-South 3 weeks ago
parent
commit
f113fa836f
  1. 26
      src/ImageSharp/Formats/Exr/ExrDecoderCore.cs
  2. 51
      tests/ImageSharp.Tests/Formats/Exr/ExrZipDecoderTests.cs

26
src/ImageSharp/Formats/Exr/ExrDecoderCore.cs

@ -203,7 +203,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
uint rowsInBlock = Math.Min(rowsPerBlock, (uint)height - rowStartIndex); uint rowsInBlock = Math.Min(rowsPerBlock, (uint)height - rowStartIndex);
uint uncompressedBytesCount = (uint)(bytesPerRow * rowsInBlock); uint uncompressedBytesCount = (uint)(bytesPerRow * rowsInBlock);
decompressor.Decompress(stream, compressedBytesCount, uncompressedBytesCount, decompressedPixelData); this.DecompressBlock(decompressor, stream, compressedBytesCount, uncompressedBytesCount, decompressedPixelData);
int offset = 0; int offset = 0;
for (uint rowIndex = rowStartIndex; rowIndex < rowStartIndex + rowsPerBlock && rowIndex < height; rowIndex++) for (uint rowIndex = rowStartIndex; rowIndex < rowStartIndex + rowsPerBlock && rowIndex < height; rowIndex++)
@ -292,7 +292,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
uint rowsInBlock = Math.Min(rowsPerBlock, (uint)height - rowStartIndex); uint rowsInBlock = Math.Min(rowsPerBlock, (uint)height - rowStartIndex);
uint uncompressedBytesCount = (uint)(bytesPerRow * rowsInBlock); uint uncompressedBytesCount = (uint)(bytesPerRow * rowsInBlock);
decompressor.Decompress(stream, compressedBytesCount, uncompressedBytesCount, decompressedPixelData); this.DecompressBlock(decompressor, stream, compressedBytesCount, uncompressedBytesCount, decompressedPixelData);
int offset = 0; int offset = 0;
for (uint rowIndex = rowStartIndex; rowIndex < rowStartIndex + rowsPerBlock && rowIndex < height; rowIndex++) for (uint rowIndex = rowStartIndex; rowIndex < rowStartIndex + rowsPerBlock && rowIndex < height; rowIndex++)
@ -321,6 +321,28 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
} }
} }
/// <summary>
/// Decompresses a block according to the configured image-data integrity policy.
/// </summary>
/// <param name="decompressor">The decompressor for the stored compression type.</param>
/// <param name="stream">The encoded block stream.</param>
/// <param name="compressedBytes">The declared compressed byte count.</param>
/// <param name="uncompressedBytes">The expected byte count for the rows in this block.</param>
/// <param name="buffer">The reusable decompressed pixel buffer.</param>
private void DecompressBlock(ExrBaseDecompressor decompressor, BufferedReadStream stream, uint compressedBytes, uint uncompressedBytes, Span<byte> buffer)
{
try
{
decompressor.Decompress(stream, compressedBytes, uncompressedBytes, buffer);
}
catch (Exception ex) when (this.Options.SegmentIntegrityHandling == SegmentIntegrityHandling.IgnoreImageData && ex is InvalidImageContentException or InvalidDataException)
{
// The offset table locates the next block independently of this damaged payload.
// Discard the entire failed block so partial output or pooled bytes cannot become pixels.
buffer[..(int)uncompressedBytes].Clear();
}
}
/// <summary> /// <summary>
/// Reads float image channel data. /// Reads float image channel data.
/// </summary> /// </summary>

51
tests/ImageSharp.Tests/Formats/Exr/ExrZipDecoderTests.cs

@ -16,12 +16,55 @@ namespace SixLabors.ImageSharp.Tests.Formats.Exr;
[ValidateDisposedMemoryAllocations] [ValidateDisposedMemoryAllocations]
public class ExrZipDecoderTests public class ExrZipDecoderTests
{ {
[Fact] /// <summary>
public void Decode_ShortInflatedBlock_Throws() /// Incomplete and oversized blocks are rejected unless image-data recovery is enabled.
/// </summary>
/// <param name="length">The inflated payload length.</param>
/// <param name="integrity">The image-data integrity policy.</param>
[Theory]
[InlineData(0, SegmentIntegrityHandling.Strict)]
[InlineData(8, SegmentIntegrityHandling.Strict)]
[InlineData(1025, SegmentIntegrityHandling.Strict)]
[InlineData(0, SegmentIntegrityHandling.IgnoreAncillary)]
[InlineData(8, SegmentIntegrityHandling.IgnoreAncillary)]
[InlineData(1025, SegmentIntegrityHandling.IgnoreAncillary)]
public void Decode_InvalidInflatedBlock_Throws(int length, SegmentIntegrityHandling integrity)
{
byte[] data = BuildExr(ZlibCompress(new byte[length]), ExrPixelType.Float, 2);
DecoderOptions options = new() { SegmentIntegrityHandling = integrity };
Assert.Throws<InvalidImageContentException>(() => Image.Load<RgbaVector>(options, data));
}
/// <summary>
/// Recovering an invalid image-data block must not expose partially decoded or pooled bytes.
/// </summary>
/// <param name="pixelType">The stored sample type.</param>
/// <param name="length">The inflated payload length.</param>
[Theory]
[InlineData(ExrPixelType.Half, 0)]
[InlineData(ExrPixelType.Half, 8)]
[InlineData(ExrPixelType.Half, 1025)]
[InlineData(ExrPixelType.Float, 0)]
[InlineData(ExrPixelType.Float, 8)]
[InlineData(ExrPixelType.Float, 1025)]
[InlineData(ExrPixelType.UnsignedInt, 0)]
[InlineData(ExrPixelType.UnsignedInt, 8)]
[InlineData(ExrPixelType.UnsignedInt, 1025)]
public void Decode_InvalidInflatedBlock_IgnoreImageData_ClearsPixels(ExrPixelType pixelType, int length)
{ {
byte[] data = BuildExr(ZlibCompress(new byte[8]), ExrPixelType.Float, 2); byte[] data = BuildExr(ZlibCompress(new byte[length]), pixelType, 2);
Configuration configuration = Configuration.Default.Clone();
configuration.MemoryAllocator = new TestMemoryAllocator(0x3F);
DecoderOptions options = new() { Configuration = configuration, SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreImageData };
using Image<RgbaVector> image = Image.Load<RgbaVector>(options, data);
Assert.Equal(new Size(256, 1), image.Size);
Assert.Throws<InvalidImageContentException>(() => Image.Load<RgbaVector>(data)); for (int x = 0; x < image.Width; x++)
{
Assert.Equal(new Vector4(0, 0, 0, 1), image[x, 0].ToVector4());
}
} }
/// <summary> /// <summary>

Loading…
Cancel
Save