Browse Source

Check Stream.Read return value

pull/2633/head
Ynse Hoornenborg 2 years ago
parent
commit
d146069bc5
  1. 137
      src/ImageSharp/Formats/Heif/HeifDecoderCore.cs

137
src/ImageSharp/Formats/Heif/HeifDecoderCore.cs

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

Loading…
Cancel
Save