GHSA-f2fv-fqrc-h8jr: Out-of-bounds Read via Check-after-use in the USBX HID Report Descriptor Parser
GHSA ID |
|
|---|---|
CVE ID |
CVE‑2026‑96582 |
Severity |
Medium |
CVSS score |
4.9 ( |
CWE |
|
Published |
2026‑10‑02 |
Last updated |
2026‑10‑02 |
Description
Summary
A HID report descriptor is a sequence of items, each declaring in its header how many data bytes follow. The USBX host walks that sequence by advancing over each header and parsing the data behind it.
The walk checked that an item fitted in the descriptor only after it had advanced past the header and parsed the payload, and the item analysis it relied on never measured the declared sizes against the bytes actually received. A HID peripheral returning a short report descriptor therefore drove a heap out-of-bounds read during ordinary enumeration.
Details
_ux_host_class_hid_report_item_analyse derived an item’s header size and data length from the item prefix and returned success without checking that the two fitted inside the remaining length. It validated only that the descriptor was non-empty, and that at least three bytes remained for a long item.
The walk in _ux_host_class_hid_report_descriptor_get then ran in an order that put the bound last: analyse the item, advance past its header, parse the payload, test the status, advance past the data, and only then compare the remaining length. The comparison sat three steps after the read it was meant to guard. Worse, the status set when the analysis failed was overwritten by the payload parse before it was tested, so an item the host had already recognised as malformed was parsed anyway whenever that parse happened to succeed.
Two shapes reached it. A one-byte descriptor 0x07 declares four data bytes, and the item is dispatched as a Global Usage Page, so the host reads four bytes out of a one-byte buffer. A long item is worse: the analysis reported its header size as the item tag mask, 0xf0 — 240 — rather than the three bytes a long-item header occupies, so the walk advanced 240 bytes down the descriptor to clear a three-byte header and then parsed from there.
The class carries a second copy of this walk. The standalone host enumeration state machine, in _ux_host_class_hid_report_descriptor_parse, called the analysis and discarded its return value entirely — no status, no test — before advancing and parsing. That copy also required a remaining length to cover an item’s data but not its header, so a descriptor whose last item ended exactly at the buffer passed the guard and then had the header subtracted from a length that no longer held it, wrapping it toward ULONG_MAX.
Impact
An attacker-controlled HID peripheral causes the host to read heap memory adjacent to the report descriptor buffer during enumeration. The bytes are consumed as parser state rather than returned to the device, so there is no demonstrated path back to the attacker; the practical outcomes are a fault where the 240-byte advance crosses into an unmapped page, and corrupted HID parser state otherwise. The threat model is physical: presenting a malicious or BadUSB HID device to the host.
Affected configurations
Builds including the USBX host HID class, on both host models. The RTOS host reaches the defect through _ux_host_class_hid_report_descriptor_get; a UX_HOST_STANDALONE build reaches it through the enumeration state machine, where the analysis verdict was discarded outright.
⇐ 6.5.1.202602.
Severity
CVSS v3.1 base score 4.9 (Medium)
CVSS:3.1/AV:P/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:H
Physical access is required to present the device, and a user action to connect it. Nothing is written and nothing observed is returned to the attacker, so confidentiality is low and integrity none, while a fault during enumeration stops the class.
Patches
Fixed in USBX 6.5.2.202603.
The item analysis refuses an item whose header and declared data do not fit in the remaining length, which puts the bound ahead of every advance and every payload read rather than three steps behind them. Both walks now leave the loop on that verdict instead of advancing over a refused item, and the standalone copy acts on a return value it previously threw away. A long item reports a header of three bytes; nothing reads that field as a marker, so narrowing it from the tag mask to a size changes no other caller. The two walks' remaining-length guards now agree that a length has to cover an item’s header as well as its data.
Two regression tests. One drives the analysis directly with a one-byte descriptor declaring four data bytes, a long item whose header alone exceeds what follows, a well-formed short item, a well-formed long item pinning the three-byte header, and an empty descriptor. The other enumerates a device whose report descriptor ends in a USAGE_PAGE header with its two declared bytes missing, and counts the refusals the walk reports — the only trace the over-read leaves, since the guard at the bottom of the loop caught the same descriptor one item later and the caller saw the same error either way. It runs in both host builds, and reverting either call site fails it.
Credits
Reported independently three times: by Cipher / Causal Security (@cipher-creator), covering both trigger variants; by Team Atlanta, covering the short-item variant and identifying the second call site; and in a later submission covering the discarded analysis verdict. Consolidated here because one fix closes all of them.
Fixed by Frédéric Desbiens (@fdesbiens). The fix and its regression tests were produced with AI assistance under human review, in accordance with the Eclipse Foundation’s Generative AI Usage Guidelines.