Skip to content

Skip stray TEM markers and reserved markers before restart markers when decoding JPEGs - #3225

Open
jafin wants to merge 1 commit into
SixLabors:mainfrom
jafin:jf/jpeg-marker-resync
Open

jafin wants to merge 1 commit into
SixLabors:mainfrom
jafin:jf/jpeg-marker-resync

Conversation

@jafin

@jafin jafin commented Oct 17, 2026 •

Copy link
Copy Markdown

Prerequisites

  • I have written a descriptive pull-request title
  • I have verified that there are no overlapping pull-requests open
  • I have verified that I am following the existing coding patterns and practice as demonstrated in the repository. These follow strict Stylecop rules 👮.
  • I have provided test coverage for my change (where applicable)

Description

Scanners and other imperfect producers write JPEGs that libjpeg-based viewers open without trouble but that ImageSharp rejected or decoded wrongly. This PR fixes two such cases.

Stray TEM marker between header segments

FindNextFileMarker already skips stray RSTn markers between segments. It returned TEM (FF 01) as an ordinary marker, so ParseStream read the next two bytes as a length field. Those two bytes were really the next segment's marker. Identify and Decode then failed with InvalidImageContentException ("not enough bytes to parse declared contents of the 01 marker"). ITU T.81 B.1.1.4 says TEM stands alone with no length field, just like RSTn, so the decoder now skips it in the same way. I added a JpegConstants.Markers.TEM constant (internal) for this.

Extraneous bytes before restart markers that form a reserved marker

Some producers write junk bytes before the RSTn markers in the entropy-coded data. The decoder already handles plain junk such as 00 00, FF 00 or a single byte. But when the junk formed a TEM or reserved marker (FF 01 to FF BF), HuffmanScanDecoder.HandleRestart took it as the end of the scan. The rest of the image was then corrupted silently (baseline interleaved), or the marker parser failed on the junk (baseline non-interleaved and progressive). A marker in that range can never end a restart interval or a scan. So HandleRestart now skips such markers while it looks for the marker that ends the interval, and resyncs at the next marker, as libjpeg's jpeg_resync_to_restart does. The new JpegBitReader.HasReservedMarker helper does the range check. The hot decoding loop is unchanged: the extra check runs only once per restart interval.

Testing

  • New JpegDecoderTests.Malformed.cs. The tests encode the input in memory with JpegEncoder, then change the bytes. No new test images.
    • Decode_ReservedMarkerBeforeRestartMarkers_IsSkipped: puts FF 12, FF 01, FF BF or FF 12 FF 34 before every RST marker of a baseline interleaved, baseline non-interleaved or progressive 4:2:0 encode with RestartInterval = 1. The decoded result must match the clean file exactly.
    • Decode_TemMarkerBetweenHeaderSegments_IsSkipped: puts FF 01 after APP0. Identify must give the right size, and the decoded result must match the clean file exactly.
  • All 7 new test cases fail on main and pass with this change.
  • All Jpg and Tiff tests pass in Release on net10.0: 1517 passed, 1 skipped, 0 failed.
  • I also tried every single-byte junk pattern (b, b 00 and FF b for every b) before the RST markers of these encodes and of an existing test image. Nothing raised NullReferenceException. The only failures were InvalidImageContentException, for junk that forms a real marker (SOFn, DHT, DQT, SOS, APPn and similar).

Fixes #3237

…en decoding jpegs

A TEM marker (FF 01) between header segments was read as a marker with a
length field, so the next segment header was consumed as its length and
decoding failed. TEM stands alone like RSTn, so skip it the same way.

Some producers write extraneous bytes before restart markers. Those that
form a TEM or reserved marker (FF 01 to FF BF) were taken as the end of the
scan, which corrupted the rest of the image or made the marker parser fail.
Skip them while looking for the marker that ends a restart interval and
resync at the next marker, as libjpeg does.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JPEG decoder fails or corrupts the image when a stray TEM marker or reserved marker bytes appear in the stream

1 participant