« Back to home

Linux USBIP overflow (CVE-2016-3955)

Recently I was forwarded a link to a patch within the Linux kernel which mitigates an overflow vulnerability within the USBIP functionality. For those that have never encountered USBIP, this is a protocol offered to allow remote clients to access USB devices plugged into a host machine.

Reviewing the patch, the issue was immediately visible as being a heap overflow vulnerability, exploitable due to a user controlled size value being trusted without validation.

So first let’s look at the patch, which can be found here. This shows an obvious validation step which has been added to the “usbip_common.c” source:

if (size > urb->transfer_buffer_length) {
  /* should not happen, probably malicious packet */
  if (ud->side == USBIP_STUB) {
    usbip_event_add(ud, SDEV_EVENT_ERROR_TCP);
    return 0;
  } else {
    usbip_event_add(ud, VDEV_EVENT_ERROR_TCP);
    return -EPIPE;
  }
}

This validation step ensures that the “size” variable is less than, or equal to the value of “urb->transfer_buffer_length”. To put this into context, the next statement to follow this check is:

ret = usbip_recv(ud->tcp_socket, urb->transfer_buffer, size);

This function call is responsible for reading data from the network into a memory location pointed to by “urb->transfer_buffer”, which is consists of “urb->transfer_buffer_length” allocaed bytes. As we can see, the “size” variable is used to indicate the number of bytes that should be read into the buffer rather than “urb->transfer_buffer_length”. So where is “size” defined? Well we find the answer at the top of the same function:

if (ud->side == USBIP_STUB) {
  /* the direction of urb must be OUT. */
  if (usb_pipein(urb->pipe))
    return 0;

  size = urb->transfer_buffer_length;
} else {
  /* the direction of urb must be IN. */
  if (usb_pipeout(urb->pipe))
    return 0;

  size = urb->actual_length;
}

So here we see that “size” can be one of two values depending on the condition on which it is assigned. The statement that interests us is:

size = urb->actual_length

Reviewing possible locations in which “urb->actual_length” is assigned, we find our answer within “usbip_pack_ret_submit()”, a function responsible for taking a USB PDU (for our purpose, this structure consists of parameters received via the network) and unpacking the values into an “urb” struct. This function contains the following, which shows the assignment of the “actual_length” variable:

static void usbip_pack_ret_submit(struct usbip_header *pdu, struct urb *urb, int pack)
{
struct usbip_header_ret_submit *rpdu = &pdu->u.ret_submit;

  if (pack) {
  ...
  } else {
  urb->status                 = rpdu->status;
  urb->actual_length       = rpdu->actual_length;
  urb->start_frame         = rpdu->start_frame;
  urb->number_of_packets = rpdu->number_of_packets;
  urb->error_count         = rpdu->error_count;
  }
}

Working backwards, we see that this function is called by “usbip_pack_pdu” on the receipt of a “USBIP_RET_SUBMIT” command:

void usbip_pack_pdu(struct usbip_header *pdu, struct urb *urb, int cmd, int pack)
{
 switch (cmd) {
 ...
case USBIP_RET_SUBMIT:
  usbip_pack_ret_submit(pdu, urb, pack);
  break;
  ...
  }
}

Finally, this function is called by “vhci_recv_ret_submit” during the processing of a received network packet:

static void vhci_recv_ret_submit(struct vhci_device *vdev, struct usbip_header *pdu)
{
...
/* unpack the pdu to a urb */
usbip_pack_pdu(pdu, urb, USBIP_RET_SUBMIT, 0);

/* recv transfer buffer */
if (usbip_recv_xbuff(ud, urb) tcp_socket, &pdu, sizeof(pdu));
...
switch (pdu.base.command) {
case USBIP_RET_SUBMIT:
  vhci_recv_ret_submit(vdev, &pdu);
  break;
...
}

So to summarise our above Memento analysis of the vulnerability, the exploit for this issue would be as follows:

  1. The USBIP kernel code must receive a network packet containing a command of “USBIP_RET_SUBMIT”. This is sent as a response to the “USBIP_CMD_SUBMIT” request, meaning that the vulnerability would need to be triggered via a responding USBIP packet rather than a request.
  2. The”actual_length” value of the packet must be greater than the URB “transfer_buffer_length”, which is the length of the “transfer_buffer” heap allocation.
  3. Finally, the exploit then needs to send enough bytes via the network to write beyond the bounds of the allocated heap buffer.

To help with the creation of a POC, a writeup of the protocol can be found within the Linux kernel documentation here.

The final POC exploit can be found here, which causes a DOS with the following stack trace:

CVE-2016-3955

Due to the clean overwrite that this vulnerability provides, the next stage will be to explore the possibility of RCE using this vulnerability, I will provide a further update once this has been explored.