From d146069bc52cf8e0688d0d41ff82ca2ab7103da0 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 17 Mar 2024 13:44:18 +0100 Subject: [PATCH] Check Stream.Read return value --- .../Formats/Heif/HeifDecoderCore.cs | 137 +++++++++--------- 1 file changed, 70 insertions(+), 67 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs index 5fdf6fe231..79b65a296d 100644 --- a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs +++ b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs @@ -34,8 +34,6 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals private readonly List itemLinks; - private readonly byte[] buffer; - /// /// Initializes a new instance of the class. /// @@ -47,7 +45,6 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals this.metadata = new ImageMetadata(); this.items = new List(); this.itemLinks = new List(); - this.buffer = new byte[80]; } /// @@ -140,8 +137,9 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals private bool CheckFileTypeBox(BufferedReadStream stream) { long boxLength = this.ReadBoxHeader(stream, out Heif4CharCode boxType); - Span buffer = this.ReadIntoBuffer(stream, boxLength); - uint majorBrand = BinaryPrimitives.ReadUInt32BigEndian(buffer); + using IMemoryOwner boxMemory = this.ReadIntoBuffer(stream, boxLength); + Span boxBuffer = boxMemory.GetSpan(); + uint majorBrand = BinaryPrimitives.ReadUInt32BigEndian(boxBuffer); bool correctBrand = majorBrand is (uint)Heif4CharCode.Heic or (uint)Heif4CharCode.Heix or (uint)Heif4CharCode.Avif; // TODO: Interpret minorVersion and compatible brands. @@ -218,7 +216,7 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals while (stream.Position < endPosition) { long length = this.ReadBoxHeader(stream, out Heif4CharCode boxType); - EnsureBoxBoundary(length, boxLength); + EnsureBoxInsideParent(length, boxLength); switch (boxType) { case Heif4CharCode.Iprp: @@ -256,11 +254,12 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals private void ParseHandler(BufferedReadStream stream, long boxLength) { - Span buffer = this.ReadIntoBuffer(stream, boxLength); + using IMemoryOwner boxMemory = this.ReadIntoBuffer(stream, boxLength); + Span boxBuffer = boxMemory.GetSpan(); // Only read the handler type, to check if this is not a movie file. int bytesRead = 8; - Heif4CharCode handlerType = (Heif4CharCode)BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); + Heif4CharCode handlerType = (Heif4CharCode)BinaryPrimitives.ReadUInt32BigEndian(boxBuffer[bytesRead..]); if (handlerType != Heif4CharCode.Pict) { throw new ImageFormatException("Not a picture file."); @@ -269,16 +268,17 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals private void ParseItemInfo(BufferedReadStream stream, long boxLength) { - Span buffer = this.ReadIntoBuffer(stream, boxLength); + using IMemoryOwner boxMemory = this.ReadIntoBuffer(stream, boxLength); + Span boxBuffer = boxMemory.GetSpan(); uint entryCount; int bytesRead = 0; - byte version = buffer[bytesRead]; + byte version = boxBuffer[bytesRead]; bytesRead += 4; - entryCount = ReadUInt16Or32(buffer, version != 0, ref bytesRead); + entryCount = ReadUInt16Or32(boxBuffer, version != 0, ref bytesRead); for (uint i = 0; i < entryCount; i++) { - bytesRead += this.ParseItemInfoEntry(buffer[bytesRead..]); + bytesRead += this.ParseItemInfoEntry(boxBuffer[bytesRead..]); } } @@ -364,21 +364,22 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals private void ParseItemReference(BufferedReadStream stream, long boxLength) { - Span buffer = this.ReadIntoBuffer(stream, boxLength); + using IMemoryOwner boxMemory = this.ReadIntoBuffer(stream, boxLength); + Span boxBuffer = boxMemory.GetSpan(); int bytesRead = 0; - bool largeIds = buffer[bytesRead] != 0; + bool largeIds = boxBuffer[bytesRead] != 0; bytesRead += 4; while (bytesRead < boxLength) { - bytesRead += ParseBoxHeader(buffer[bytesRead..], out long subBoxLength, out Heif4CharCode linkType); - uint sourceId = ReadUInt16Or32(buffer, largeIds, ref bytesRead); + bytesRead += ParseBoxHeader(boxBuffer[bytesRead..], out long subBoxLength, out Heif4CharCode linkType); + uint sourceId = ReadUInt16Or32(boxBuffer, largeIds, ref bytesRead); HeifItemLink link = new(linkType, sourceId); - int count = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); + int count = BinaryPrimitives.ReadUInt16BigEndian(boxBuffer[bytesRead..]); bytesRead += 2; for (uint i = 0; i < count; i++) { - uint destId = ReadUInt16Or32(buffer, largeIds, ref bytesRead); + uint destId = ReadUInt16Or32(boxBuffer, largeIds, ref bytesRead); link.DestinationIds.Add(destId); } @@ -389,10 +390,11 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals private void ParsePrimaryItem(BufferedReadStream stream, long boxLength) { // BoxLength should be 6 or 8. - Span buffer = this.ReadIntoBuffer(stream, boxLength); - byte version = buffer[0]; + using IMemoryOwner boxMemory = this.ReadIntoBuffer(stream, boxLength); + Span boxBuffer = boxMemory.GetSpan(); + byte version = boxBuffer[0]; int bytesRead = 4; - this.primaryItem = ReadUInt16Or32(buffer, version != 0, ref bytesRead); + this.primaryItem = ReadUInt16Or32(boxBuffer, version != 0, ref bytesRead); } private void ParseItemProperties(BufferedReadStream stream, long boxLength) @@ -403,7 +405,7 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals while (stream.Position < endBoxPosition) { long containerLength = this.ReadBoxHeader(stream, out Heif4CharCode containerType); - EnsureBoxBoundary(containerLength, boxLength); + EnsureBoxInsideParent(containerLength, boxLength); if (containerType == Heif4CharCode.Ipco) { // Parse Item Property Container, which is just an array of property boxes. @@ -427,40 +429,41 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals while (stream.Position < endPosition) { int itemLength = (int)this.ReadBoxHeader(stream, out Heif4CharCode itemType); - EnsureBoxBoundary(itemLength, boxLength); - Span buffer = this.ReadIntoBuffer(stream, itemLength); + EnsureBoxInsideParent(itemLength, boxLength); + using IMemoryOwner boxMemory = this.ReadIntoBuffer(stream, itemLength); + Span boxBuffer = boxMemory.GetSpan(); switch (itemType) { case Heif4CharCode.Ispe: // Skip over version (8 bits) and flags (24 bits). - int width = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]); - int height = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[8..]); + int width = (int)BinaryPrimitives.ReadUInt32BigEndian(boxBuffer[4..]); + int height = (int)BinaryPrimitives.ReadUInt32BigEndian(boxBuffer[8..]); properties.Add(new KeyValuePair(Heif4CharCode.Ispe, new Size(width, height))); break; case Heif4CharCode.Pasp: - int horizontalSpacing = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer); - int verticalSpacing = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]); + int horizontalSpacing = (int)BinaryPrimitives.ReadUInt32BigEndian(boxBuffer); + int verticalSpacing = (int)BinaryPrimitives.ReadUInt32BigEndian(boxBuffer[4..]); properties.Add(new KeyValuePair(Heif4CharCode.Pasp, new Size(horizontalSpacing, verticalSpacing))); break; case Heif4CharCode.Pixi: // Skip over version (8 bits) and flags (24 bits). - int channelCount = buffer[4]; + int channelCount = boxBuffer[4]; int offset = 5; int bitsPerPixel = 0; for (int i = 0; i < channelCount; i++) { - bitsPerPixel += buffer[offset + i]; + bitsPerPixel += boxBuffer[offset + i]; } properties.Add(new KeyValuePair(Heif4CharCode.Pixi, new int[] { channelCount, bitsPerPixel })); break; case Heif4CharCode.Colr: - Heif4CharCode profileType = (Heif4CharCode)BinaryPrimitives.ReadUInt32BigEndian(buffer); + Heif4CharCode profileType = (Heif4CharCode)BinaryPrimitives.ReadUInt32BigEndian(boxBuffer); if (profileType is Heif4CharCode.RICC or Heif4CharCode.Prof) { byte[] iccData = new byte[itemLength - 4]; - buffer[4..].CopyTo(iccData); + boxBuffer[4..].CopyTo(iccData); this.metadata.IccProfile = new IccProfile(iccData); } @@ -484,24 +487,25 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals private void ParsePropertyAssociation(BufferedReadStream stream, long boxLength, List> properties) { - Span buffer = this.ReadIntoBuffer(stream, boxLength); - byte version = buffer[0]; - byte flags = buffer[3]; + using IMemoryOwner boxMemory = this.ReadIntoBuffer(stream, boxLength); + Span boxBuffer = boxMemory.GetSpan(); + byte version = boxBuffer[0]; + byte flags = boxBuffer[3]; int bytesRead = 4; - int itemId = (int)ReadUInt16Or32(buffer, version >= 1, ref bytesRead); + int itemId = (int)ReadUInt16Or32(boxBuffer, version >= 1, ref bytesRead); - int associationCount = buffer[bytesRead++]; + int associationCount = boxBuffer[bytesRead++]; for (int i = 0; i < associationCount; i++) { uint propId; if (flags == 1) { - propId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]) & 0x4FFFU; + propId = BinaryPrimitives.ReadUInt16BigEndian(boxBuffer[bytesRead..]) & 0x4FFFU; bytesRead += 2; } else { - propId = buffer[bytesRead++] & 0x4FU; + propId = boxBuffer[bytesRead++] & 0x4FU; } KeyValuePair prop = properties[(int)propId]; @@ -524,12 +528,13 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals private void ParseItemLocation(BufferedReadStream stream, long boxLength) { - Span buffer = this.ReadIntoBuffer(stream, boxLength); + using IMemoryOwner boxMemory = this.ReadIntoBuffer(stream, boxLength); + Span boxBuffer = boxMemory.GetSpan(); int bytesRead = 0; - byte version = buffer[bytesRead]; + byte version = boxBuffer[bytesRead]; bytesRead += 4; - byte b1 = buffer[bytesRead++]; - byte b2 = buffer[bytesRead++]; + byte b1 = boxBuffer[bytesRead++]; + byte b2 = boxBuffer[bytesRead++]; int offsetSize = (b1 >> 4) & 0x0f; int lengthSize = b1 & 0x0f; int baseOffsetSize = (b2 >> 4) & 0x0f; @@ -539,32 +544,32 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals indexSize = b2 & 0x0f; } - uint itemCount = ReadUInt16Or32(buffer, version == 2, ref bytesRead); + uint itemCount = ReadUInt16Or32(boxBuffer, version == 2, ref bytesRead); for (uint i = 0; i < itemCount; i++) { - uint itemId = ReadUInt16Or32(buffer, version == 2, ref bytesRead); + uint itemId = ReadUInt16Or32(boxBuffer, version == 2, ref bytesRead); HeifItem? item = this.FindItemById(itemId); if (version is 1 or 2) { bytesRead++; - byte b3 = buffer[bytesRead++]; + byte b3 = boxBuffer[bytesRead++]; int constructionMethod = b3 & 0x0f; } - uint dataReferenceIndex = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); + uint dataReferenceIndex = BinaryPrimitives.ReadUInt16BigEndian(boxBuffer[bytesRead..]); bytesRead += 2; - ulong baseOffset = ReadUIntVariable(buffer, baseOffsetSize, ref bytesRead); - uint extentCount = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); + ulong baseOffset = ReadUIntVariable(boxBuffer, baseOffsetSize, ref bytesRead); + uint extentCount = BinaryPrimitives.ReadUInt16BigEndian(boxBuffer[bytesRead..]); bytesRead += 2; for (uint j = 0; j < extentCount; j++) { if (version is 1 or 2 && indexSize > 0) { - _ = ReadUIntVariable(buffer, indexSize, ref bytesRead); + _ = ReadUIntVariable(boxBuffer, indexSize, ref bytesRead); } - ulong extentOffset = ReadUIntVariable(buffer, offsetSize, ref bytesRead); - ulong extentLength = ReadUIntVariable(buffer, lengthSize, ref bytesRead); + ulong extentOffset = ReadUIntVariable(boxBuffer, offsetSize, ref bytesRead); + ulong extentLength = ReadUIntVariable(boxBuffer, lengthSize, ref bytesRead); HeifLocation loc = new HeifLocation((long)extentOffset, (long)extentLength); item?.DataLocations.Add(loc); } @@ -617,7 +622,8 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals private Image ParseMediaData(BufferedReadStream stream, long boxLength) where TPixel : unmanaged, IPixel { - // FIXME: No HVC decoding yet, so parse only a JPEG thumbnail. + EnsureBoxBoundary(boxLength, stream); + // FIXME: No specific decoding yet, so parse only a JPEG thumbnail. HeifItemLink? thumbLink = this.itemLinks.FirstOrDefault(link => link.Type == Heif4CharCode.Thmb); if (thumbLink == null) { @@ -633,9 +639,9 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals int thumbFileOffset = (int)thumbItem.DataLocations[0].Offset; int thumbFileLength = (int)thumbItem.DataLocations[0].Length; stream.Skip((int)(thumbFileOffset - stream.Position)); - using IMemoryOwner thumbMemory = this.configuration.MemoryAllocator.Allocate(thumbFileLength); + EnsureBoxBoundary(thumbFileLength, stream); + using IMemoryOwner thumbMemory = this.ReadIntoBuffer(stream, thumbFileLength); Span thumbSpan = thumbMemory.GetSpan(); - stream.Read(thumbSpan); HeifMetadata meta = this.metadata.GetHeifMetadata(); meta.CompressionMethod = HeifCompressionMethod.LegacyJpeg; @@ -646,25 +652,22 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals private static void SkipBox(BufferedReadStream stream, long boxLength) => stream.Skip((int)boxLength); - private Span ReadIntoBuffer(BufferedReadStream stream, long length) + private IMemoryOwner ReadIntoBuffer(BufferedReadStream stream, long length) { - if (length <= this.buffer.Length) + IMemoryOwner buffer = this.configuration.MemoryAllocator.Allocate((int)length); + int bytesRead = stream.Read(buffer.GetSpan()); + if (bytesRead != length) { - int bytesRead = stream.Read(this.buffer, 0, (int)length); - return this.buffer.AsSpan(0, bytesRead); - } - else - { - Span temp = new byte[length]; - int bytesRead = stream.Read(temp); - return temp; + throw new InvalidImageContentException("Stream length not sufficient for box content"); } + + return buffer; } private static void EnsureBoxBoundary(long boxLength, Stream stream) - => EnsureBoxBoundary(boxLength, stream.Length - stream.Position); + => EnsureBoxInsideParent(boxLength, stream.Length - stream.Position); - private static void EnsureBoxBoundary(long boxLength, long parentLength) + private static void EnsureBoxInsideParent(long boxLength, long parentLength) { if (boxLength > parentLength) {