Skip to content

[Bug] multiple out-of-bounds writes in CherryUSB host audio descriptor parser during USB enumeration #11265

@XueDugu

Description

@XueDugu

RT-Thread Version

master (verified on commit 6a635e3); also present in v5.2.2, v5.2.1, and v5.2.0. Not present in v5.1.0 or earlier.

Hardware Type/Architectures

Any BSP enabling CherryUSB host audio/UAC support

Develop Toolchain

GCC

Describe the bug

Affected Components

Field Detail
File 1 components/drivers/usb/cherryusb/class/audio/usbh_audio.c
File 2 components/drivers/usb/cherryusb/class/audio/usbh_audio.h
File 3 components/drivers/usb/cherryusb/class/audio/usb_audio.h
Function static int usbh_audio_ctrl_connect(struct usbh_hubport *hport, uint8_t intf)

Vulnerability Details

1. Fixed-capacity ac_msg_table can be overrun

The function allocates a fixed-size local array:

uint8_t input_offset = 0;
uint8_t output_offset = 0;
uint8_t feature_unit_offset = 0;
struct usbh_audio_ac_msg ac_msg_table[CONFIG_USBHOST_AUDIO_MAX_STREAMS];

CONFIG_USBHOST_AUDIO_MAX_STREAMS defaults to 3. However, the parser increments these offsets with no bounds check:

memcpy(&ac_msg_table[input_offset].ac_input, desc,
       sizeof(struct audio_cs_if_ac_input_terminal_descriptor));
input_offset++;

memcpy(&ac_msg_table[output_offset].ac_output, desc,
       sizeof(struct audio_cs_if_ac_output_terminal_descriptor));
output_offset++;

memcpy(&ac_msg_table[feature_unit_offset].ac_feature_unit, desc, desc->bLength);
feature_unit_offset++;

A malicious USB audio device can provide more than 3 AC descriptors and trigger an out-of-bounds write into the local stack array.


2. Variable-length descriptors copied into fixed-size structures

The parser copies variable-length USB Audio descriptors using the device-controlled bLength field:

memcpy(&ac_msg_table[feature_unit_offset].ac_feature_unit, desc, desc->bLength);
memcpy(&audio_class->as_msg_table[cur_iface - audio_class->ctrl_intf - 1]
            .as_format[cur_alt_setting],
       desc, desc->bLength);

Both destination objects are fixed-size. For example, FEATURE_UNIT is variable-length:

struct audio_cs_if_ac_feature_unit_descriptor {
    uint8_t bLength;
    uint8_t bDescriptorType;
    uint8_t bDescriptorSubtype;
    uint8_t bUnitID;
    uint8_t bSourceID;
    uint8_t bControlSize;
    uint8_t bmaControls[1];
    uint8_t iFeature;
} __PACKED;

And FORMAT_TYPE is also variable-length:

struct audio_cs_if_as_format_type_descriptor {
    uint8_t bLength;
    uint8_t bDescriptorType;
    uint8_t bDescriptorSubtype;
    uint8_t bFormatType;
    uint8_t bNrChannels;
    uint8_t bSubframeSize;
    uint8_t bBitResolution;
    uint8_t bSamFreqType;
    uint8_t tSamFreq[3];
} __PACKED;

A crafted bLength can therefore directly cause out-of-bounds writes during enumeration.

Additionally, the parser trusts the descriptor-derived bSamFreqType count in a later loop:

for (uint8_t k = 0; k < audio_class->as_msg_table[i].as_format[j].bSamFreqType; k++) {
    memcpy(&freq, &audio_class->as_msg_table[i].as_format[j].tSamFreq[3 * k], 3);
}

This can produce additional out-of-bounds reads after the initial corruption.


Trigger Condition

The bug is reached automatically during USB host enumeration:

p = hport->raw_config_desc;
while (p[DESC_bLength]) {
    ...
    case AUDIO_INTERFACE_DESCRIPTOR_TYPE:
        ...
        /* parse attacker-controlled audio descriptors */
    ...
    p += p[DESC_bLength];
}

Note: This happens before usbh_audio_run(), so no extra local action is required. A malicious USB audio device can trigger the vulnerability immediately when connected.


Proof of Concept

A PoC can be implemented with a programmable USB gadget/emulator such as Facedancer, LUNA, GreatFET, Linux USB gadget, or custom USB Audio firmware.

PoC 1: Overflow ac_msg_table

Present a malicious USB Audio configuration descriptor with more than 3 AudioControl descriptors of the same type (e.g. 4 AUDIO_CONTROL_INPUT_TERMINAL descriptors).

Expected result:

  • Enumeration enters usbh_audio_ctrl_connect()
  • input_offset exceeds CONFIG_USBHOST_AUDIO_MAX_STREAMS
  • The 4th copy writes past ac_msg_table[]
  • The target may crash or corrupt stack state

PoC 2: Oversized FORMAT_TYPE or FEATURE_UNIT descriptor

Provide either a FEATURE_UNIT descriptor with an oversized bLength, or a FORMAT_TYPE descriptor with a large bSamFreqType and matching large bLength.

Expected result:

  • The parser performs memcpy(..., desc, desc->bLength) into a fixed-size destination
  • Memory past the destination object is overwritten during enumeration

Impact

A malicious directly-connected USB audio device can trigger memory corruption automatically during enumeration. Depending on target layout and build configuration, this may cause crashes or undefined behavior.


Upstream / Downstream Impact

Upstream

The vulnerable parser logic is present in current upstream master and is reachable in the default host enumeration path when CherryUSB host audio support is enabled.

Downstream

Any downstream RT-Thread product that enables CherryUSB host audio/UAC support and accepts untrusted USB audio devices may be affected.


Suggested Fix

The parser should reject malformed descriptors before copying them.

A. Bound-check all AC descriptor table indexes

if (input_offset >= CONFIG_USBHOST_AUDIO_MAX_STREAMS) {
    USB_LOG_ERR("Too many input terminal descriptors\r\n");
    return -USB_ERR_INVAL;
}

if (output_offset >= CONFIG_USBHOST_AUDIO_MAX_STREAMS) {
    USB_LOG_ERR("Too many output terminal descriptors\r\n");
    return -USB_ERR_INVAL;
}

if (feature_unit_offset >= CONFIG_USBHOST_AUDIO_MAX_STREAMS) {
    USB_LOG_ERR("Too many feature unit descriptors\r\n");
    return -USB_ERR_INVAL;
}

B. Reject oversized FEATURE_UNIT descriptors

if (desc->bLength > sizeof(ac_msg_table[feature_unit_offset].ac_feature_unit)) {
    USB_LOG_ERR("Feature unit descriptor too large: %u\r\n", desc->bLength);
    return -USB_ERR_INVAL;
}
memcpy(&ac_msg_table[feature_unit_offset].ac_feature_unit, desc, desc->bLength);

C. Reject oversized FORMAT_TYPE descriptors

if (desc->bLength > sizeof(audio_class->as_msg_table[0].as_format[0])) {
    USB_LOG_ERR("Format type descriptor too large: %u\r\n", desc->bLength);
    return -USB_ERR_INVAL;
}
memcpy(&audio_class->as_msg_table[cur_iface - audio_class->ctrl_intf - 1]
            .as_format[cur_alt_setting],
       desc, desc->bLength);

D. Validate cur_iface - audio_class->ctrl_intf - 1

uint8_t stream_idx = cur_iface - audio_class->ctrl_intf - 1;
if (stream_idx >= CONFIG_USBHOST_AUDIO_MAX_STREAMS) {
    USB_LOG_ERR("Audio stream index overflow: %u\r\n", stream_idx);
    return -USB_ERR_INVAL;
}

E. Validate bSamFreqType before iteration

if (audio_class->as_msg_table[i].as_format[j].bSamFreqType > 1) {
    USB_LOG_ERR("Unsupported or oversized bSamFreqType: %u\r\n",
                audio_class->as_msg_table[i].as_format[j].bSamFreqType);
    return -USB_ERR_INVAL;
}

A more complete fix is to stop storing variable-length descriptors in fixed-size structures, and instead either copy only the fixed header fields, or allocate a properly sized buffer based on a validated bLength.

Kindly let me know if you intend to request a CVE ID upon confirmation of the vulnerability.

Other additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions