GHSA-pppm-h3g4-986x: JPEG decoder: truncated segments are read past the image buffer, and a zero sampling factor hangs the decode
GHSA ID |
|
|---|---|
CVE ID |
CVE‑2026‑92454 |
Severity |
Medium |
CVSS score |
6.1 ( |
CWE |
|
Published |
2026‑10‑02 |
Last updated |
2026‑10‑02 |
Description
Summary
The GUIX software JPEG decoder reads four kinds of marker segment without checking that each is long enough to hold what it reads. A truncated segment is therefore read past the end of the caller’s image buffer. Separately, a zero chroma sampling factor makes the block decode loop non-terminating. Both reproduce in the default configuration, in which GX_SOFTWARE_DECODER_SUPPORT is enabled.
Details
Segment handlers read beyond the declared length. The marker dispatcher in _gx_image_reader_jpeg_decode_blocks checks the declared segment length against the remaining buffer, but each handler then reads a fixed number of bytes without regard to that length. Four are affected: _gx_image_reader_jpeg_quantization_table_set for DQT, _gx_image_reader_jpeg_frame_header_read for SOF0, _gx_image_reader_jpeg_scan_header_read for SOS, and the DRI handling inline in the dispatcher.
The DQT handler does have a length check, but it runs only after the 64 byte read it should guard. The same handler subtracts the two byte length field from the segment length without checking it first, so a DQT segment declaring a length of 0 or 1 wraps to a very large count and the handler keeps reading 65 byte tables past the segment. It stops only when it meets a byte whose precision nibble is non-zero, or when the read faults.
Unchecked sampling factors. The vertical luma sampling factor is bounded above but not checked for zero, and the horizontal factor is not checked at all. Both set the step of the decode loop, so a zero in either makes that loop non-terminating. The later stride check in _gx_image_reader_jpeg_one_block_decode bounds the horizontal factor only from above, so it does not catch a zero.
The scan header’s status is discarded. The dispatcher ignores the return value of _gx_image_reader_jpeg_scan_header_read, so a scan declaring more components than the decoder supports is not rejected, and the out of range count stays in the decoder state. The handler returns before it fills the Huffman table selectors, so no array is overrun, and the count is used afterwards only as a greater-than-one test. This is a correctness defect with no memory safety consequence of its own, and it is fixed together with the defects above.
The reporter demonstrated each of the four read sites with a minimal file, each overflowing the caller supplied image buffer allocated at exactly the file size, and demonstrated the non-terminating loop with a file declaring a zero sampling factor. All four files exceed the decoder’s minimum size check, so none is rejected before reaching the handler.
Impact
There are heap out-of-bounds reads at four independent sites. Each reads up to 65 bytes, except the DQT length underflow, which can read further. The bytes are not returned to the attacker, but the decoder uses them as state. The SOF0 bytes become the image width, height and sampling factors, and so steer the whole decode. The DQT bytes become quantisation coefficients that are multiplied into the output pixels. Adjacent heap content can therefore influence what is rendered. In practice the result is a crash or limited influence from heap content, not a clean disclosure oracle.
The non-terminating loop is a denial of service. The decode runs on the GUI thread, so the interface stays unresponsive until a watchdog fires. If the offending file is persistent, the device can reset repeatedly.
No exploit was developed, and no claim of code execution is made.
Affected configurations
Any build with GX_SOFTWARE_DECODER_SUPPORT, which is the default, that decodes a JPEG image it does not control. A build without the software decoder never reaches these handlers.
Every GUIX release up to and including 6.5.1.202602a is affected. The segment handlers have read past their declared length since v6.0_rel; the length test that should guard the DQT read appeared in 6.2.1, already after the read.
Severity
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:H, 6.1, Medium.
Confidentiality is low rather than high because the bytes never leave the decoder: they influence the rendered image but are not returned to the attacker. Availability is high on account of the non-terminating loop, which is the more serious of the two defects in practice.
Patches
Fixed in GUIX 6.5.2.202603. Each handler now validates the declared segment length against what it will read, before reading. The scan header’s status is acted on, and the component count is validated before it is stored. Zero sampling factors are rejected, and the product of the luma sampling factors is bounded at 4, the number of luma blocks the decoder’s MCU buffer holds.
The regression test validation_guix_image_reader_jpeg_decode_no_output carries 15 malformed cases that together reach every branch the fix adds. With the fix reverted, 11 of them fail. The remaining four return an error either way and are kept for coverage rather than as proof: the two short DQT segments, whose over-read shows only under a sanitiser, the scan with no frame header, and a vertical sampling factor of 3, which the decoder already refused.