summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohan Hovold <johan@kernel.org>2026-08-21 17:45:43 +0200
committerJohan Hovold <johan@kernel.org>2026-09-08 11:00:48 +0200
commit33c920f7c5952dfc42d1b8ea6efe16b1f0bb0c5b (patch)
tree4e0f094a5e515c2c697a26118e885a6b1f1ea818
parentb51faf3f2e307f114854bbe6d495f03cd5370686 (diff)
downloadlinux-next-33c920f7c5952dfc42d1b8ea6efe16b1f0bb0c5b.tar.gz
linux-next-33c920f7c5952dfc42d1b8ea6efe16b1f0bb0c5b.zip
USB: serial: fix port tear down use-after-free
Some drivers for multiport devices access port driver data from completion handlers of shared URBs submitted at attach() or first open() and stopped at disconnect() or last close(), respectively. A simple NULL check before accessing the driver data makes sure that a port state container has at least been allocated, but a completion handler can still race with port tear down. Reorder the disconnect handling so that ports are not deregistered (and their driver data freed) until after all ports have been hung up and the driver disconnect() callback has run so that all I/O has been stopped. Fixes: 2d93148ab698 ("USB: serial: fix lifetime and locking problems") Reported-by: syzbot+e5e28c3e953b2eebb16e@syzkaller.appspotmail.com Link: https://lore.kernel.org/all/6a7e6fb9.ec5dc6cc.21cb3f.00c0.GAE@google.com/ Cc: stable@vger.kernel.org # 2.6.30 Cc: Alan Stern <stern@rowland.harvard.edu> Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Signed-off-by: Johan Hovold <johan@kernel.org>
-rw-r--r--drivers/usb/serial/usb-serial.c9
1 files changed, 7 insertions, 2 deletions
diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c
index 17edc057a311..a4fbc849c0fa 100644
--- a/drivers/usb/serial/usb-serial.c
+++ b/drivers/usb/serial/usb-serial.c
@@ -1191,12 +1191,17 @@ static void usb_serial_disconnect(struct usb_interface *interface)
usb_serial_port_poison_urbs(port);
wake_up_interruptible(&port->port.delta_msr_wait);
cancel_work_sync(&port->work);
- if (device_is_registered(&port->dev))
- device_del(&port->dev);
}
+
if (serial->type->disconnect)
serial->type->disconnect(serial);
+ for (i = 0; i < serial->num_ports; ++i) {
+ port = serial->port[i];
+ if (device_is_registered(&port->dev))
+ device_del(&port->dev);
+ }
+
release_sibling(serial, interface);
/* let the last holder of this object cause it to be cleaned up */