diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
index e2d6cfcd29..a7f93b0e85 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
@@ -330,14 +330,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (!fileMarker.Invalid)
{
// Get the marker length.
- int remaining = this.ReadUint16(stream) - 2;
+ int markerContentByteSize = this.ReadUint16(stream) - 2;
+
+ // Check whether stream actually has enought bytes to read
+ // markerContentByteSize is always positive so we cast
+ // to uint to avoid sign extension
+ if (stream.RemainingBytes < (uint)markerContentByteSize)
+ {
+ JpegThrowHelper.ThrowNotEnoughBytesForMarker(fileMarker.Marker);
+ }
switch (fileMarker.Marker)
{
case JpegConstants.Markers.SOF0:
case JpegConstants.Markers.SOF1:
case JpegConstants.Markers.SOF2:
- this.ProcessStartOfFrameMarker(stream, remaining, fileMarker, metadataOnly);
+ this.ProcessStartOfFrameMarker(stream, markerContentByteSize, fileMarker, metadataOnly);
break;
case JpegConstants.Markers.SOF5:
@@ -365,7 +373,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
case JpegConstants.Markers.SOS:
if (!metadataOnly)
{
- this.ProcessStartOfScanMarker(stream, remaining);
+ this.ProcessStartOfScanMarker(stream, markerContentByteSize);
break;
}
else
@@ -379,41 +387,41 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (metadataOnly)
{
- stream.Skip(remaining);
+ stream.Skip(markerContentByteSize);
}
else
{
- this.ProcessDefineHuffmanTablesMarker(stream, remaining);
+ this.ProcessDefineHuffmanTablesMarker(stream, markerContentByteSize);
}
break;
case JpegConstants.Markers.DQT:
- this.ProcessDefineQuantizationTablesMarker(stream, remaining);
+ this.ProcessDefineQuantizationTablesMarker(stream, markerContentByteSize);
break;
case JpegConstants.Markers.DRI:
if (metadataOnly)
{
- stream.Skip(remaining);
+ stream.Skip(markerContentByteSize);
}
else
{
- this.ProcessDefineRestartIntervalMarker(stream, remaining);
+ this.ProcessDefineRestartIntervalMarker(stream, markerContentByteSize);
}
break;
case JpegConstants.Markers.APP0:
- this.ProcessApplicationHeaderMarker(stream, remaining);
+ this.ProcessApplicationHeaderMarker(stream, markerContentByteSize);
break;
case JpegConstants.Markers.APP1:
- this.ProcessApp1Marker(stream, remaining);
+ this.ProcessApp1Marker(stream, markerContentByteSize);
break;
case JpegConstants.Markers.APP2:
- this.ProcessApp2Marker(stream, remaining);
+ this.ProcessApp2Marker(stream, markerContentByteSize);
break;
case JpegConstants.Markers.APP3:
@@ -426,20 +434,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
case JpegConstants.Markers.APP10:
case JpegConstants.Markers.APP11:
case JpegConstants.Markers.APP12:
- stream.Skip(remaining);
+ stream.Skip(markerContentByteSize);
break;
case JpegConstants.Markers.APP13:
- this.ProcessApp13Marker(stream, remaining);
+ this.ProcessApp13Marker(stream, markerContentByteSize);
break;
case JpegConstants.Markers.APP14:
- this.ProcessApp14Marker(stream, remaining);
+ this.ProcessApp14Marker(stream, markerContentByteSize);
break;
case JpegConstants.Markers.APP15:
case JpegConstants.Markers.COM:
- stream.Skip(remaining);
+ stream.Skip(markerContentByteSize);
break;
case JpegConstants.Markers.DAC:
@@ -1282,7 +1290,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
int selectorsBytes = selectorsCount * 2;
if (remaining != 4 + selectorsBytes)
{
- JpegThrowHelper.ThrowBadMarker("SOS", remaining);
+ JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.SOS), remaining);
}
// selectorsCount*2 bytes: component index + huffman tables indices
diff --git a/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs
index b238e45ef3..1073ffff78 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs
@@ -25,6 +25,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowBadMarker(string marker, int length) => throw new InvalidImageContentException($"Marker {marker} has bad length {length}.");
+ [MethodImpl(InliningOptions.ColdPath)]
+ public static void ThrowNotEnoughBytesForMarker(byte marker) => throw new InvalidImageContentException($"Input stream does not have enough bytes to parse declared contents of the {marker:X2} marker.");
+
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowBadQuantizationTableIndex(int index) => throw new InvalidImageContentException($"Bad Quantization Table index {index}.");
diff --git a/src/ImageSharp/IO/BufferedReadStream.cs b/src/ImageSharp/IO/BufferedReadStream.cs
index 4ab7f312b2..2823b8ed6f 100644
--- a/src/ImageSharp/IO/BufferedReadStream.cs
+++ b/src/ImageSharp/IO/BufferedReadStream.cs
@@ -114,6 +114,15 @@ namespace SixLabors.ImageSharp.IO
///
public override bool CanWrite { get; } = false;
+ ///
+ /// Gets remaining byte count available to read.
+ ///
+ public long RemainingBytes
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.Length - this.Position;
+ }
+
///
/// Gets the underlying stream.
///
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs
index 70cbc3af72..d5e0f081bf 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs
@@ -105,6 +105,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693A,
TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693B,
TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824C,
+ TestImages.Jpeg.Issues.Fuzz.NullReferenceException2085,
};
private static readonly Dictionary CustomToleranceValues = new()
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index ac060fd99f..e0ccef9823 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -292,6 +292,7 @@ namespace SixLabors.ImageSharp.Tests
public const string AccessViolationException922 = "Jpg/issues/fuzz/Issue922-AccessViolationException.jpg";
public const string IndexOutOfRangeException1693A = "Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-A.jpg";
public const string IndexOutOfRangeException1693B = "Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-B.jpg";
+ public const string NullReferenceException2085 = "Jpg/issues/fuzz/Issue2085-NullReferenceException.jpg";
}
}
diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue2085-NullReferenceException.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue2085-NullReferenceException.jpg
new file mode 100644
index 0000000000..8a680ff6a6
--- /dev/null
+++ b/tests/Images/Input/Jpg/issues/fuzz/Issue2085-NullReferenceException.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d478beff34179fda26238a44434607c276f55438ee96824c5af8c0188d358d8d
+size 234