GHSA-8m9v-jvjp-gmxq: Inadequate bounds check and potential underflow in _ux_host_class_hid_report_descriptor_get()

GHSA ID

GHSA‑8m9v‑jvjp‑gmxq

CVE ID

CVE‑2025‑55096

Severity

Low

CWE

CWE-191

Published

2025‑10‑17

Last updated

2025‑10‑17

Affected packages

Package Vulnerable versions Patched versions

USBX

⇐ 6.4.2

6.4.3

Description

The _ux_host_class_hid_report_descriptor_get() retrieves and parses a HID report descriptor from a USB HID device. It iteratively calls _ux_host_class_hid_report_item_analyse() to parse individual HID items, adjusting the descriptor pointer and remaining length as it goes.

A serious vulnerability exists in how this loop handles memory bounds:

1\. Insufficient bounds check:

The check:

if (length < item.ux_host_class_hid_item_report_length)

verifies only one part of the read (the report_length), but not the full read length:

descriptor += item.ux_host_class_hid_item_report_format;
descriptor += item.ux_host_class_hid_item_report_length;

2\. Integer underflow:

The line:

length -= (ULONG)(item.ux_host_class_hid_item_report_length + item.ux_host_class_hid_item_report_format);

assumes length is always greater than or equal to the total consumed size. If that’s not the case (e.g., length is 1, but the item reports a size of 16), length underflows and becomes a very large unsigned integer. The loop then continues indefinitely with a corrupted descriptor pointer, leading to out-of-bounds memory reads. This is possible because the length bounds check (see above [and below]) doesn’t account for item.ux_host_class_hid_item_report_format

code:

UINT  _ux_host_class_hid_report_descriptor_get(UX_HOST_CLASS_HID *hid, ULONG length)

{

...

    descriptor =  _ux_utility_memory_allocate(UX_SAFE_ALIGN, UX_CACHE_SAFE_MEMORY, length);

...

    transfer_request -> ux_transfer_request_data_pointer =      descriptor;

    transfer_request -> ux_transfer_request_requested_length =  length;

    transfer_request -> ux_transfer_request_function =          UX_GET_DESCRIPTOR;

    transfer_request -> ux_transfer_request_type =              UX_REQUEST_IN | UX_REQUEST_TYPE_STANDARD | UX_REQUEST_TARGET_INTERFACE;

...

    status =  _ux_host_stack_transfer_request(transfer_request);

...

        /* Parse the report descriptor and build the report items.  */

        while (length)

        {

...

            _ux_host_class_hid_report_item_analyse(descriptor, &item);

...

            descriptor +=  item.ux_host_class_hid_item_report_format;

...

            descriptor +=  item.ux_host_class_hid_item_report_length;



            /* Verify that the report descriptor is not corrupted.  */

            if (length < item.ux_host_class_hid_item_report_length) <-- this bounds check is insufficient

            {



                /* Return error status.  */

                status = (UX_DESCRIPTOR_CORRUPTED);

                break;

            }



            /* Adjust the length.  */

            length -=  (ULONG)(item.ux_host_class_hid_item_report_length + item.ux_host_class_hid_item_report_format); <-- length can underflow, at which point this loop goes off the rails

        }

...

}

#define UX_HOST_CLASS_HID_ITEM_TAG_LONG                         0xf0

UINT  _ux_host_class_hid_report_item_analyse(UCHAR *descriptor, UX_HOST_CLASS_HID_ITEM *item)

{

UCHAR       item_byte;

    /* Get the first byte from the descriptor.  */

    item_byte =  *descriptor;

....

    if ((item_byte & UX_HOST_CLASS_HID_ITEM_TAG_MASK) == UX_HOST_CLASS_HID_ITEM_TAG_LONG)

    {

        /* We have a long item, mark its format.  */

        item -> ux_host_class_hid_item_report_format =  UX_HOST_CLASS_HID_ITEM_TAG_LONG;



        /* Set the type.  */

        item -> ux_host_class_hid_item_report_type =  (item_byte >> 2) & 3;



        /* Get its length (byte 1).  */

        item -> ux_host_class_hid_item_report_length =  (USHORT) *(descriptor + 1);



        /* Then the tag (byte 2).  */

        item -> ux_host_class_hid_item_report_tag =  *(descriptor + 2);

    }

...

    return(UX_SUCCESS);

}