Browse Source

Validate EXR block rows relative to the data window

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

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

@ -193,7 +193,9 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
this.ValidateChunkOffset(rowOffset, stream); this.ValidateChunkOffset(rowOffset, stream);
stream.Position = (long)rowOffset; stream.Position = (long)rowOffset;
uint rowStartIndex = this.ReadUnsignedInteger(stream);
// Chunk coordinates are signed and absolute; pixel rows are relative to the data window.
uint rowStartIndex = (uint)((long)this.ReadSignedInteger(stream) - this.HeaderAttributes.DataWindow.YMin);
if (rowStartIndex >= height) if (rowStartIndex >= height)
{ {
ExrThrowHelper.ThrowInvalidImageContentException("EXR chunk row index is outside the data window."); ExrThrowHelper.ThrowInvalidImageContentException("EXR chunk row index is outside the data window.");
@ -282,7 +284,9 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
this.ValidateChunkOffset(rowOffset, stream); this.ValidateChunkOffset(rowOffset, stream);
stream.Position = (long)rowOffset; stream.Position = (long)rowOffset;
uint rowStartIndex = this.ReadUnsignedInteger(stream);
// Chunk coordinates are signed and absolute; pixel rows are relative to the data window.
uint rowStartIndex = (uint)((long)this.ReadSignedInteger(stream) - this.HeaderAttributes.DataWindow.YMin);
if (rowStartIndex >= height) if (rowStartIndex >= height)
{ {
ExrThrowHelper.ThrowInvalidImageContentException("EXR chunk row index is outside the data window."); ExrThrowHelper.ThrowInvalidImageContentException("EXR chunk row index is outside the data window.");

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

@ -30,7 +30,7 @@ public class ExrZipDecoderTests
[InlineData(1025, SegmentIntegrityHandling.IgnoreAncillary)] [InlineData(1025, SegmentIntegrityHandling.IgnoreAncillary)]
public void Decode_InvalidInflatedBlock_Throws(int length, SegmentIntegrityHandling integrity) public void Decode_InvalidInflatedBlock_Throws(int length, SegmentIntegrityHandling integrity)
{ {
byte[] data = BuildExr(ZlibCompress(new byte[length]), ExrPixelType.Float, 2); byte[] data = BuildExr(ZlibCompress(new byte[length]), ExrPixelType.Float, 2, 0);
DecoderOptions options = new() { SegmentIntegrityHandling = integrity }; DecoderOptions options = new() { SegmentIntegrityHandling = integrity };
Assert.Throws<InvalidImageContentException>(() => Image.Load<RgbaVector>(options, data)); Assert.Throws<InvalidImageContentException>(() => Image.Load<RgbaVector>(options, data));
@ -53,7 +53,7 @@ public class ExrZipDecoderTests
[InlineData(ExrPixelType.UnsignedInt, 1025)] [InlineData(ExrPixelType.UnsignedInt, 1025)]
public void Decode_InvalidInflatedBlock_IgnoreImageData_ClearsPixels(ExrPixelType pixelType, int length) public void Decode_InvalidInflatedBlock_IgnoreImageData_ClearsPixels(ExrPixelType pixelType, int length)
{ {
byte[] data = BuildExr(ZlibCompress(new byte[length]), pixelType, 2); byte[] data = BuildExr(ZlibCompress(new byte[length]), pixelType, 2, 0);
Configuration configuration = Configuration.Default.Clone(); Configuration configuration = Configuration.Default.Clone();
configuration.MemoryAllocator = new TestMemoryAllocator(0x3F); configuration.MemoryAllocator = new TestMemoryAllocator(0x3F);
DecoderOptions options = new() { Configuration = configuration, SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreImageData }; DecoderOptions options = new() { Configuration = configuration, SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreImageData };
@ -72,20 +72,27 @@ public class ExrZipDecoderTests
/// </summary> /// </summary>
/// <param name="pixelType">The stored sample type.</param> /// <param name="pixelType">The stored sample type.</param>
/// <param name="compression">The ZIP compression code.</param> /// <param name="compression">The ZIP compression code.</param>
/// <param name="yMin">The data window's first row coordinate.</param>
[Theory] [Theory]
[InlineData(ExrPixelType.Half, 2)] [InlineData(ExrPixelType.Half, 2, 0)]
[InlineData(ExrPixelType.Float, 2)] [InlineData(ExrPixelType.Float, 2, 0)]
[InlineData(ExrPixelType.UnsignedInt, 2)] [InlineData(ExrPixelType.UnsignedInt, 2, 0)]
[InlineData(ExrPixelType.Half, 3)] [InlineData(ExrPixelType.Half, 3, 0)]
[InlineData(ExrPixelType.Float, 3)] [InlineData(ExrPixelType.Float, 3, 0)]
[InlineData(ExrPixelType.UnsignedInt, 3)] [InlineData(ExrPixelType.UnsignedInt, 3, 0)]
public void Decode_SingleRedChannel_InitializesMissingColorChannels(ExrPixelType pixelType, byte compression) [InlineData(ExrPixelType.Half, 3, -10)]
[InlineData(ExrPixelType.Float, 3, -10)]
[InlineData(ExrPixelType.UnsignedInt, 3, -10)]
[InlineData(ExrPixelType.Half, 3, 10)]
[InlineData(ExrPixelType.Float, 3, 10)]
[InlineData(ExrPixelType.UnsignedInt, 3, 10)]
public void Decode_SingleRedChannel_InitializesMissingColorChannels(ExrPixelType pixelType, byte compression, int yMin)
{ {
byte[] predicted = new byte[256 * (pixelType == ExrPixelType.Half ? 2 : 4)]; byte[] predicted = new byte[256 * (pixelType == ExrPixelType.Half ? 2 : 4)];
// A zero first byte followed by 128-valued differences reconstructs an all-zero sample plane. // A zero first byte followed by 128-valued differences reconstructs an all-zero sample plane.
predicted.AsSpan(1).Fill(128); predicted.AsSpan(1).Fill(128);
byte[] data = BuildExr(ZlibCompress(predicted), pixelType, compression); byte[] data = BuildExr(ZlibCompress(predicted), pixelType, compression, yMin);
Configuration configuration = Configuration.Default.Clone(); Configuration configuration = Configuration.Default.Clone();
configuration.MemoryAllocator = new TestMemoryAllocator(0x3F); configuration.MemoryAllocator = new TestMemoryAllocator(0x3F);
DecoderOptions options = new() { Configuration = configuration }; DecoderOptions options = new() { Configuration = configuration };
@ -121,8 +128,9 @@ public class ExrZipDecoderTests
/// <param name="compressed">The compressed scanline bytes.</param> /// <param name="compressed">The compressed scanline bytes.</param>
/// <param name="pixelType">The stored sample type.</param> /// <param name="pixelType">The stored sample type.</param>
/// <param name="compression">The ZIP compression code.</param> /// <param name="compression">The ZIP compression code.</param>
/// <param name="yMin">The data window's first row coordinate.</param>
/// <returns>The encoded image.</returns> /// <returns>The encoded image.</returns>
private static byte[] BuildExr(byte[] compressed, ExrPixelType pixelType, byte compression) private static byte[] BuildExr(byte[] compressed, ExrPixelType pixelType, byte compression, int yMin)
{ {
const int width = 256; const int width = 256;
const int height = 1; const int height = 1;
@ -154,9 +162,9 @@ public class ExrZipDecoderTests
using (BinaryWriter boxWriter = new(boxStream)) using (BinaryWriter boxWriter = new(boxStream))
{ {
boxWriter.Write(0); boxWriter.Write(0);
boxWriter.Write(0); boxWriter.Write(yMin);
boxWriter.Write(width - 1); boxWriter.Write(width - 1);
boxWriter.Write(height - 1); boxWriter.Write(yMin + height - 1);
byte[] box = boxStream.ToArray(); byte[] box = boxStream.ToArray();
WriteAttribute(writer, "dataWindow", "box2i", box); WriteAttribute(writer, "dataWindow", "box2i", box);
@ -174,7 +182,7 @@ public class ExrZipDecoderTests
long chunkStart = output.Position + sizeof(ulong); long chunkStart = output.Position + sizeof(ulong);
writer.Write((ulong)chunkStart); writer.Write((ulong)chunkStart);
writer.Write(0U); writer.Write(yMin);
writer.Write((uint)compressed.Length); writer.Write((uint)compressed.Length);
writer.Write(compressed); writer.Write(compressed);

Loading…
Cancel
Save