From 3b1b8956cf3733d098b84679e5536ae2eb024ca0 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 18 Apr 2026 21:40:27 +1000 Subject: [PATCH] Stop parsing after SOS in JPEG decoder in ProcessStartOfFrameMarker --- .../Formats/Jpeg/JpegDecoderCore.cs | 30 +++++++++++++++---- .../Formats/Jpg/JpegDecoderTests.cs | 10 +++++++ tests/ImageSharp.Tests/TestImages.cs | 1 + .../Jpg/issues/issue3118-multiple-sof.jpg | 3 ++ 4 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 tests/Images/Input/Jpg/issues/issue3118-multiple-sof.jpg diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index d4517e9f19..d503323d2f 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -385,7 +385,11 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData case JpegConstants.Markers.SOF1: case JpegConstants.Markers.SOF2: - this.ProcessStartOfFrameMarker(stream, markerContentByteSize, fileMarker, ComponentType.Huffman, metadataOnly); + if (!this.ProcessStartOfFrameMarker(stream, markerContentByteSize, fileMarker, ComponentType.Huffman, metadataOnly)) + { + return; + } + break; case JpegConstants.Markers.SOF9: @@ -398,7 +402,11 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData this.scanDecoder.ResetInterval = this.resetInterval.Value; } - this.ProcessStartOfFrameMarker(stream, markerContentByteSize, fileMarker, ComponentType.Arithmetic, metadataOnly); + if (!this.ProcessStartOfFrameMarker(stream, markerContentByteSize, fileMarker, ComponentType.Arithmetic, metadataOnly)) + { + return; + } + break; case JpegConstants.Markers.SOF5: @@ -429,7 +437,9 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData } // It's highly unlikely that APPn related data will be found after the SOS marker - // We should have gathered everything we need by now. + // So we can stop parsing here and return the metadata we have parsed so far, instead + // of trying to parse any APPn markers after the SOS marker and risking running out of + // memory or other exceptions. return; case JpegConstants.Markers.DHT: @@ -1212,13 +1222,19 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData /// The current frame marker. /// The jpeg decoding component type. /// Whether to parse metadata only. - private void ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining, in JpegFileMarker frameMarker, ComponentType decodingComponentType, bool metadataOnly) + private bool ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining, in JpegFileMarker frameMarker, ComponentType decodingComponentType, bool metadataOnly) { if (this.Frame != null) { - if (metadataOnly) + // If we have found the SOS marker, we can stop parsing as we have all + // the information we need to decode the image. + // It's possible that there are APPn related markers after the SOS marker, + // but it's highly unlikely and we would be better off stopping parsing + // and decoding the image instead of trying to parse those APPn markers + // and risking running out of memory or other exceptions. + if (this.hasSOSMarker) { - return; + return false; } JpegThrowHelper.ThrowInvalidImageContentException("Multiple SOF markers. Only single frame jpegs supported."); @@ -1351,6 +1367,8 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData this.Frame.Init(maxH, maxV); this.scanDecoder.InjectFrameData(this.Frame, this); } + + return true; } /// diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 36847536b3..d7d81bdfde 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -457,4 +457,14 @@ public partial class JpegDecoderTests byte[] data = [0xFF, 0xD8, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30]; using Image image = Image.Load(data); }); + + // https://github.com/SixLabors/ImageSharp/issues/3118 + [Theory] + [WithFile(TestImages.Jpeg.Issues.Issue3118, PixelTypes.Rgb24)] + public void Issue3118_Multiple_SOF_WithSOS_DoesNotThrow(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(JpegDecoder.Instance); + image.DebugSave(provider); + } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index f7e130d66a..1a8cf948e8 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -361,6 +361,7 @@ public static class TestImages public const string Issue2758 = "Jpg/issues/issue-2758.jpg"; public const string Issue2857 = "Jpg/issues/issue-2857-subsub-ifds.jpg"; public const string Issue2948 = "Jpg/issues/issue-2948-sos.jpg"; + public const string Issue3118 = "Jpg/issues/issue3118-multiple-sof.jpg"; public static class Fuzz { diff --git a/tests/Images/Input/Jpg/issues/issue3118-multiple-sof.jpg b/tests/Images/Input/Jpg/issues/issue3118-multiple-sof.jpg new file mode 100644 index 0000000000..e8897a511b --- /dev/null +++ b/tests/Images/Input/Jpg/issues/issue3118-multiple-sof.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:29d9073bc60cb8f5965993654ec5a949cdbacebe6365db9831ece860095ca85f +size 11541050