usb: cdc-acm: Check control transfer buffer size before access
authorJann Horn <jannh@google.com>
Wed, 12 Feb 2025 18:15:15 +0000 (19:15 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 14 Feb 2025 08:21:56 +0000 (09:21 +0100)
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>
drivers/usb/class/cdc-acm.c

index 6b37d1c47fce13853cb98c97e53fcfc9baa0d1b1..39c7db7bcd2163094786df0b04fb485fd33ae198 100644 (file)
@@ -371,7 +371,7 @@ static void acm_process_notification(struct acm *acm, unsigned char *buf)
 static void acm_ctrl_irq(struct urb *urb)
 {
        struct acm *acm = urb->context;
-       struct usb_cdc_notification *dr = urb->transfer_buffer;
+       struct usb_cdc_notification *dr;
        unsigned int current_size = urb->actual_length;
        unsigned int expected_size, copy_size, alloc_size;
        int retval;
@@ -398,9 +398,20 @@ static void acm_ctrl_irq(struct urb *urb)
 
        usb_mark_last_busy(acm->dev);
 
-       if (acm->nb_index)
+       if (acm->nb_index == 0) {
+               /*
+                * The first chunk of a message must contain at least the
+                * notification header with the length field, otherwise we
+                * can't get an expected_size.
+                */
+               if (current_size < sizeof(struct usb_cdc_notification)) {
+                       dev_dbg(&acm->control->dev, "urb too short\n");
+                       goto exit;
+               }
+               dr = urb->transfer_buffer;
+       } else {
                dr = (struct usb_cdc_notification *)acm->notification_buffer;
-
+       }
        /* size = notification-header + (optional) data */
        expected_size = sizeof(struct usb_cdc_notification) +
                                        le16_to_cpu(dr->wLength);