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
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).
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.
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.
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.
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.
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.
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