GHSA-cxf2-pgvg-pcw3: JPEG decoder: a duplicate SOF0 after SOS publishes a pixelmap that misreports its own allocation size

GHSA ID

GHSA‑cxf2‑pgvg‑pcw3

CVE ID

CVE‑2026‑91059

Severity

Medium

CVSS score

6.1 (CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:L)

CWE

CWE-125

Published

2026‑10‑02

Last updated

2026‑10‑02

Affected packages

Package Vulnerable versions Patched versions

GUIX

⇐ 6.5.1.202602a

6.5.2.202603

Description

Summary

The GUIX JPEG software decoder enforces no ordering on JPEG markers. A file that places a second SOF0 frame header after SOS re-runs the frame header parser after the output buffer has already been allocated and its geometry latched. The decoder returns GX_SUCCESS and publishes a GX_PIXELMAP whose reported dimensions and data_size exceed the allocation behind it.

Any consumer that trusts the returned structure reads out of bounds, including GUIX’s own display drivers, reached through the ordinary draw path.

Reproduces in the shipped default configuration; GX_SOFTWARE_DECODER_SUPPORT is enabled by default.

Details

The marker loop is a stateless switch with no ordering state. The SOS case breaks out of the switch rather than the loop, so marker scanning continues afterwards and a later frame header marker re-enters _gx_image_reader_jpeg_frame_header_read() unconditionally, refreshing the image width and height.

Both the output geometry and the output buffer are latched on first use in _gx_image_reader_jpeg_pixel_write_info_set(), each assigned only when it is still unset. The second frame header therefore changes the dimensions while the allocation stays as it was.

The pixelmap is then published from a mixture of both generations of state:

outmap -> gx_pixelmap_data      = jpeg_info -> gx_jpeg_output_buffer;    /* first frame header */
outmap -> gx_pixelmap_data_size = (ULONG)(jpeg_info -> gx_jpeg_output_stride   /* latched */
                                          * jpeg_info -> gx_jpeg_height);      /* refreshed */
outmap -> gx_pixelmap_width     = (GX_VALUE)jpeg_info -> gx_jpeg_width;  /* refreshed */
outmap -> gx_pixelmap_height    = (GX_VALUE)jpeg_info -> gx_jpeg_height; /* refreshed */

gx_jpeg_output_stride derives from the latched output width, so data_size is a stale stride multiplied by a refreshed height, a figure corresponding to no allocation that has ever existed. A consumer that bounds its reads by the structure’s own data_size is therefore out of bounds as well, which is why the defensive check a careful integrator would write does not save them.

The write path is not affected. The MCU write clamps to the stale clip rectangle, so the second decompress does not write out of bounds. This defect is read-only.

The reporter demonstrated the inconsistency through the public API alone, with no drawing involved: a file reporting a 16 by 256 image with a data_size of 8192 bytes, behind an allocation of 512 bytes, returned from gx_image_reader_start() as GX_SUCCESS with no indication to the application that anything was wrong. Drawing that pixelmap then produced an out-of-bounds read inside GUIX’s own 16bpp display driver, reached through the normal draw path rather than in harness code.

Impact

A heap out-of-bounds read of up to the declared height less the real height, multiplied by the width and the bytes per pixel. In the reporter’s proof of concept that is 7680 bytes, beginning immediately after the decoded image allocation, with the bytes written into the framebuffer. Where an attacker can observe the display, that is adjacent heap content rendered on screen; otherwise it is a fault.

Independently of any read, the decoder returns GX_SUCCESS together with a GX_PIXELMAP whose data_size exceeds its allocation. Every consumer of that structure is entitled to trust it, so the inconsistency is itself the defect.

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.

Every release up to and including 6.5.1.202602a is affected. Per the project security policy the fix ships in 6.5.x, with no backport to 6.4.x.

Severity

CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:L, 6.1, Medium.

Confidentiality is high because the bytes read past the allocation are written into the framebuffer, so on a product whose display an attacker can observe they are disclosed directly. Integrity is none because the write path clamps to the stale clip rectangle and the defect is read-only.

Patches

Fixed in GUIX 6.5.2.202603. The decoder now enforces marker ordering: a second SOF0, a frame header after SOS, and a SOS with no preceding frame header or following a completed scan are all refused with GX_INVALID_FORMAT. The published pixelmap takes all its geometry from the state the output buffer was allocated from, so data_size always describes the allocation.

Two malformed files that previously succeeded are now refused: a duplicate frame header before the scan, which decoded as a 16 by 256 image, and a scan with no frame header, which produced a zero by zero pixelmap. All 38 decodable JPEGs under the test, tutorial and sample trees still decode, and the four subsampling variants are byte for byte unchanged.

One limit is worth stating. The after-scan branch of the ordering check is not exercised by the project’s own corpus. A frame header appended after the scan is never dispatched by the files built here, because the decompress advances the read index past the entropy data before that marker is reached; the reporter’s input does reach that state. The regression evidence for the publish change is therefore that valid files are unaffected by it, and the reporter’s generators are being taken up to close the gap.

Workarounds

Decode only JPEG images from trusted sources. Where the software decoder is not needed, building with GX_DISABLE_SOFTWARE_DECODER_SUPPORT removes the affected path entirely. Note that this also removes the PNG decoder.

Credits

Reported by Fatullayev Asadbek. Fixed by Frédéric Desbiens.

The fix and regression tests were produced with AI assistance under human review, in accordance with the Eclipse Foundation’s Generative AI Usage Guidelines.