Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

usb: cdc-acm: Check control transfer buffer size before access

If the first fragment is shorter than struct usb_cdc_notification, we can't
calculate an expected_size. Log an error and discard the notification
instead of reading lengths from memory outside the received data, which can
lead to memory corruption when the expected_size decreases between
fragments, causing `expected_size - acm->nb_index` to wrap.

This issue has been present since the beginning of git history; however,
it only leads to memory corruption since commit ea2583529cd1
("cdc-acm: reassemble fragmented notifications").

A mitigating factor is that acm_ctrl_irq() can only execute after userspace
has opened /dev/ttyACM*; but if ModemManager is running, ModemManager will
do that automatically depending on the USB device's vendor/product IDs and
its other interfaces.

Cc: stable <stable@kernel.org>
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Jann Horn <jannh@google.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Jann Horn and committed by
Greg Kroah-Hartman
e563b012 c81d9fcd

+14 -3
+14 -3
drivers/usb/class/cdc-acm.c
··· 371 371 static void acm_ctrl_irq(struct urb *urb) 372 372 { 373 373 struct acm *acm = urb->context; 374 - struct usb_cdc_notification *dr = urb->transfer_buffer; 374 + struct usb_cdc_notification *dr; 375 375 unsigned int current_size = urb->actual_length; 376 376 unsigned int expected_size, copy_size, alloc_size; 377 377 int retval; ··· 398 398 399 399 usb_mark_last_busy(acm->dev); 400 400 401 - if (acm->nb_index) 401 + if (acm->nb_index == 0) { 402 + /* 403 + * The first chunk of a message must contain at least the 404 + * notification header with the length field, otherwise we 405 + * can't get an expected_size. 406 + */ 407 + if (current_size < sizeof(struct usb_cdc_notification)) { 408 + dev_dbg(&acm->control->dev, "urb too short\n"); 409 + goto exit; 410 + } 411 + dr = urb->transfer_buffer; 412 + } else { 402 413 dr = (struct usb_cdc_notification *)acm->notification_buffer; 403 - 414 + } 404 415 /* size = notification-header + (optional) data */ 405 416 expected_size = sizeof(struct usb_cdc_notification) + 406 417 le16_to_cpu(dr->wLength);