Skip to content

Usb bluetooth device ACL read cb buffer overflow

Critical
ceolin published GHSA-hfxq-3w6x-fv2m Feb 16, 2022

Package

zephyr (west)

Affected versions

< v3.0.0

Patched versions

None

Description

Impact

Unfortunately it looks like that the usb device bluetooth class includes a buffer overflow related to implementation of net_buf_add_mem. An attacker may interactively write arbitrary amounts of data utilizing the usb hci out endpoint which will be copied to the destination bypassing buffer boundaries resulting in an overflow. Depending on actual implementation this may result in denial of service, security feature bypass or in worst case scenario execution of arbitrary code.

When a Zephyr-powered usb bluetooth device changes status to either configured or resumed the acl_read_cb function will be called as implemented in bluetooth_status_cb (subsys/usb/class/bluetooth.c).

static void bluetooth_status_cb(struct usb_cfg_data *cfg,
enum usb_dc_status_code status,
const uint8_t *param)
{
ARG_UNUSED(cfg);

/* Check the USB status and do needed action if required */
switch (status) { 
... 
 case USB_DC_CONFIGURED:
LOG_DBG("Device configured");
if (!configured) {
configured = true;
/* Start reading */
acl_read_cb(bluetooth_ep_data[HCI_OUT_EP_IDX].ep_addr,
   0, NULL);
}
break 
 ...
 case USB_DC_RESUME:
LOG_DBG("Device resumed");
if (suspended) {
LOG_DBG("from suspend");
suspended = false;
if (configured) {
/* Start reading */
acl_read_cb(bluetooth_ep_data[HCI_OUT_EP_IDX].ep_addr,
   0, NULL);
}
} else {
LOG_DBG("Spurious resume event");
}
break; 

Since initially provided size equals to zero in during execution of acl_read_cb up to USB_MAX_FS_BULK_MPS bytes will be read from the HCI OUT endpoint.

static void acl_read_cb(uint8_t ep, int size, void *priv)
{
static struct net_buf *buf;
static uint16_t pkt_len;
uint8_t *data = ep_out_buf;
if (size == 0) {
goto restart_out_transfer;
}
...
restart_out_transfer:
usb_transfer(bluetooth_ep_data[HCI_OUT_EP_IDX].ep_addr, ep_out_buf,
    sizeof(ep_out_buf), USB_TRANS_READ | USB_TRANS_NO_ZLP,
    acl_read_cb, NULL);
}

During next callback of acl_read_cb size is up to USB_MAX_FS_BULK_MPS bytes, buf is null so a buffer buf will be allocated in either BT_BUF_H4 or BT_BUF_ACL_OUT pool (depending on used mode) by bt_buf_get_tx. Next pkt_len is determined by analysing the read data by hci_pkt_get_len.

 if (buf == NULL) {
/*
* Obtain the first chunk and determine the length
* of the HCI packet.
*/
if (IS_ENABLED(CONFIG_USB_DEVICE_BLUETOOTH_VS_H4) &&
   bt_hci_raw_get_mode() == BT_HCI_RAW_MODE_H4) {
buf = bt_buf_get_tx(BT_BUF_H4, K_FOREVER, data, size);
pkt_len = hci_pkt_get_len(buf, &data[1], size - 1);
LOG_DBG("pkt_len %u, chunk %u", pkt_len, size);
} else {
buf = bt_buf_get_tx(BT_BUF_ACL_OUT, K_FOREVER, data, size);
pkt_len = hci_pkt_get_len(buf, data, size);
LOG_DBG("pkt_len %u, chunk %u", pkt_len, size);
}

The provided payload needs to be crafted in such a manner that pkt_len != 0 && pkt_len != buf->len. This way the buffer will not be unreferenced or put in the rx_queue. Another read of USB_MAX_FS_BULK_MPS from the host will be executed followed by acl_read_cb callback.

Since buf is not null this time a branch involving net_buf_add_mem will be executed.

 if (buf == NULL) {
/*
* Obtain the first chunk and determine the length
* of the HCI packet.
*/
...
}
if (pkt_len == 0) {
LOG_ERR("Failed to get packet length");
net_buf_unref(buf);
buf = NULL;
}
} else {
/*
* Take over the next chunk if HCI packet is
* larger than USB_MAX_FS_BULK_MPS.
*/
net_buf_add_mem(buf, data, size);
LOG_DBG("len %u, chunk %u", buf->len, size);
}

net_buf_add_mem relies on net_buf_simple_add_mem.

static inline void *net_buf_add_mem(struct net_buf *buf, const void *mem, size_t len)
{
        return net_buf_simple_add_mem(&buf->b, mem, len);
}

net_buf_simple_add_mem copies len bytes of data from mem to the address returned by net_buf_simple add. In this case mem points to ep_out_buf containing data read over usb, len equals to the number of bytes read.

void *net_buf_simple_add_mem(struct net_buf_simple *buf, const void *mem, size_t len)
{
        NET_BUF_SIMPLE_DBG("buf %p len %zu", buf, len);
        return memcpy(net_buf_simple_add(buf, len), mem, len);
}

net_buf_simple_add does not assure that enough room is available in the buffer, it only increments buf->len by provided len and returns the original buffer tail.

void *net_buf_simple_add(struct net_buf_simple *buf, size_t len)
{
        uint8_t *tail = net_buf_simple_tail(buf);
        NET_BUF_SIMPLE_DBG("buf %p len %zu", buf, len);
        __ASSERT_NO_MSG(net_buf_simple_tailroom(buf) >= len);
        buf->len += len;
        return tail;
}

After execution flow returns to acl_read_cb the buffer won't be put into rx_queue as pkt_len != buf->len and another usb read will be performed. The cycle of calls of usb reads followed by net_buf_add_mem calls can be executed for an arbitrary number of times (until zero bytes are read) allowing a large buffer overflow.

For more information

If you have any questions or comments about this advisory:

embargo: 2022-02-12

Severity

Critical
9.6
/ 10

CVSS base metrics

Attack vector
Adjacent
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Changed
Confidentiality
Low
Integrity
High
Availability
High
CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:C/C:L/I:H/A:H

CVE ID

CVE-2021-3966

Weaknesses

Credits