Technical Advisory – U-Boot – Unchecked Download Size and Direction in USB DFU (CVE-2022-2347)

Vendor: DENX Software Engineering
Vendor URL: https://www.denx.de/wiki/U-Boot
Versions affected: v2012.10-rc1 to v2023.01-rc1
Systems Affected: All systems with CONFIG_DFU_OVER_USB or CONFIG_SPL_DFU enabled
Author: <Sultan Qasim Khan> <sultan.qasimkhan[at]nccgroup[dot]com>
CVE Identifier: CVE-2022-2347
Risk: High 7.1 (CVSS:3.1/AV:P/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H)

Summary

U-Boot is a popular and feature-rich bootloader for embedded systems. It includes optional support for the USB Device Firmware Update (DFU) protocol, which can be used by devices to download new firmware, or upload their current firmware.

The U-Boot DFU implementation does not bound the length field in USB DFU download setup packets, and it does not verify that the transfer direction corresponds to the specified command. Consequently, if a physical attacker crafts a USB DFU download setup packet with a wLength greater than 4096 bytes, they can write beyond the heap-allocated request buffer. It is also possible to read its content (and beyond it) if the direction bit for the setup packet indicates a device to host direction.

Location

drivers/usb/gadget/f_dfu.c

Functions: dfu_handle, state_dfu_idle, state_dfu_dnload_idle, handle_dnload

Impact

Data beyond the heap-allocated req->buf buffer may be corrupted or read by a connected USB host when a device running U-Boot is in DFU mode. This may enable a malicious host to gain code execution on the device running U-Boot or read sensitive data from the device.

Details

USB DFU setup packets are handled by the dfu_handle function. The DFU command is specified by the ctrl->bRequest field, and the transfer direction for the data phase is specified by the direction bit ctrl->bRequestType & USB_DIR_IN. The dfu_handle function calls state-specific handlers such as state_dfu_idle or state_dfu_dnload_idle, and uses the value returned by the state handler as the length for the data phase of the transfer. The buffer that will be written to or read from in the data phase of the transfer is req->buf, which is heap allocated as 4096 (USB_BUFSIZ) bytes in composite_bind of drivers/usb/gadget/composite.c.

The request structure that is set up is then queued with the USB controller driver via a call to usb_ep_queue. There are several USB controllers supported by U-Boot, such as the popular Designware DWC2 whose support is implemented in drivers/usb/gadget/dwc2_udc_otg.c and drivers/usb/gadget/dwc2_udc_otg_xfer_dma.c. These drivers are unaware of the allocated size for the request buffer (req->buf), and assume the supplied length field (req->length) is safe for the allocated buffer.

static int
dfu_handle(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
{
struct usb_gadget *gadget = f->config->cdev->gadget;
struct usb_request *req = f->config->cdev->req;
struct f_dfu *f_dfu = f->config->cdev->req->context;
if (req_type == USB_TYPE_STANDARD) {
} else /* DFU specific request */
value = dfu_state[f_dfu->dfu_state] (f_dfu, ctrl, gadget, req);
if (value >= 0) {
req->length = value;
req->zero = value < len;
value = usb_ep_queue(gadget->ep0, req, 0);
if (value < 0) {
debug("ep_queue –> %d\n", value);
req->status = 0;
}
}
return value;
}

The DFU state handlers which support the download command (state_dfu_idle and state_dfu_dnload_idle) return the value returned by handle_dnload when ctrl->bRequest is USB_REQ_DFU_DNLOAD. No checking of the transfer direction is performed; DFU download requests are assumed to always be OUT transfers (host to device). However, a malicious or compromised host could issue a download request setup packet with the USB_DIR_IN bit set (device to host). A DFU download request with the USB_DIR_IN bit set would cause data in req->buf to be sent to the host, rather than filling the buffer with data received from the host. Likewise, there no checks to verify that DFU upload operations are USB IN transfers.

The handle_dnload function simply returns the length argument passed to it without any bounds checking. Both state handlers that call handle_dnload pass it the wLength field of the setup packet without any bounds checks. Consequently, a malicious host that sends a DFU setup packet with a length longer than 4096 bytes would result in a read or write beyond req->buf. The DFU functional descriptor does declare a maximum wTransferSize of DFU_USB_BUFSIZ (4096 bytes), and compliant hosts would abide by not sending setup packets specifying lengths longer than this. However, a malicious or non-compliant host may send a DFU setup packet for a transfer longer than this.

static int handle_dnload(struct usb_gadget *gadget, u16 len)
{
struct usb_composite_dev *cdev = get_gadget_data(gadget);
struct usb_request *req = cdev->req;
struct f_dfu *f_dfu = req->context;
if (len == 0)
f_dfu->dfu_state = DFU_STATE_dfuMANIFEST_SYNC;
req->complete = dnload_request_complete;
return len;
}
static int state_dfu_idle(struct f_dfu *f_dfu,
const struct usb_ctrlrequest *ctrl,
struct usb_gadget *gadget,
struct usb_request *req)
{
u16 w_value = le16_to_cpu(ctrl->wValue);
u16 len = le16_to_cpu(ctrl->wLength);
int value = 0;
switch (ctrl->bRequest) {
case USB_REQ_DFU_DNLOAD:
if (len == 0) {
f_dfu->dfu_state = DFU_STATE_dfuERROR;
value = RET_STALL;
break;
}
f_dfu->dfu_state = DFU_STATE_dfuDNLOAD_SYNC;
f_dfu->blk_seq_num = w_value;
value = handle_dnload(gadget, len);
break;
}
return value;
}

Recommendation

Limit USB transfer lengths to a maximum of DFU_USB_BUFSIZ before adding them to the endpoint transfer queue in dfu_handle. In every DFU setup packet handler, also verify that the direction bit ctrl->bRequestType & USB_DIR_IN matches the request type (such as upload or download).

Disclosure Timeline

  1. Feb 27, 2022 – Initial disclosure to security at denx.de – there was no response from this email address.
  2. April 30, 2022 – Follow up email sent to security at denx.de after 60 days to confirm receipt of the disclosure. Again, no response was received.
  3. June 3, 2022 – Disclosure to U-Boot founder Wolfgang Denk (wd at denx.de). This email bounced but provided alternative contacts, specifically Stefano Babic (sbabic at denx.de) and Stefan Roese (sr at denx.de).
  4. June 7, 2022 – Disclosure via email to Stefano Banic and Stefan Roese. Stefan Roese advised submitting the report to the public mailing list (u-boot at lists.denx.de), as all U-Boot development takes place in the open on that list.
  5. July 8, 2022 – Public disclosure via email to mailing list (u-boot at lists.denx.de).
  6. November 3, 2022 – Patch submitted by Venkatesh Yadav Abbarapu of AMD to mailing list.
  7. November 21, 2022 – Patch accepted and merged.
  8. November 21, 2022 – U-Boot v2023.01-rc2 tagged.
  9. November 28, 2022 – DFU functionality breakage reported on mailing list by Marek Vasut.
  10. November 29, 2022 – Cause of breakage due to error in patch identified and reported to the mailing list by Sultan Qasim Khan.
  11. November 30, 2022 – Patch to correct DFU breakage submitted by Hugo Simeliere of Witekio.
  12. December 8, 2022 – Patch accepted and merged.
  13. December 19, 2022 – U-Boot v2023.01-rc4 tagged.
  14. January 9, 2023 – U-Boot v2023.01 released.
  15. January 20, 2023 – Advisory published by NCC Group.

Thanks to

Jeremy Boone for providing feedback on this disclosure writeup.

About NCC Group

NCC Group is a global expert in cybersecurity and risk mitigation, working with businesses to protect their brand, value and reputation against the ever-evolving threat landscape. With our knowledge, experience and global footprint, we are best placed to help businesses identify, assess, mitigate & respond to the risks they face. We are passionate about making the Internet safer and revolutionizing the way in which organizations think about cybersecurity.