mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-16 07:35:14 +00:00
USB: serial: fix unthrottle races
commit3f5edd58d0
upstream. Fix two long-standing bugs which could potentially lead to memory corruption or leave the port throttled until it is reopened (on weakly ordered systems), respectively, when read-URB completion races with unthrottle(). First, the URB must not be marked as free before processing is complete to prevent it from being submitted by unthrottle() on another CPU. CPU 1 CPU 2 ================ ================ complete() unthrottle() process_urb(); smp_mb__before_atomic(); set_bit(i, free); if (test_and_clear_bit(i, free)) submit_urb(); Second, the URB must be marked as free before checking the throttled flag to prevent unthrottle() on another CPU from failing to observe that the URB needs to be submitted if complete() sees that the throttled flag is set. CPU 1 CPU 2 ================ ================ complete() unthrottle() set_bit(i, free); throttled = 0; smp_mb__after_atomic(); smp_mb(); if (throttled) if (test_and_clear_bit(i, free)) return; submit_urb(); Note that test_and_clear_bit() only implies barriers when the test is successful. To handle the case where the URB is still in use an explicit barrier needs to be added to unthrottle() for the second race condition. Fixes:d83b405383
("USB: serial: add support for multiple read urbs") Signed-off-by: Johan Hovold <johan@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
4c26f0f9d0
commit
fedc5219fd
1 changed files with 32 additions and 7 deletions
|
@ -379,6 +379,7 @@ void usb_serial_generic_read_bulk_callback(struct urb *urb)
|
|||
struct usb_serial_port *port = urb->context;
|
||||
unsigned char *data = urb->transfer_buffer;
|
||||
unsigned long flags;
|
||||
bool stopped = false;
|
||||
int status = urb->status;
|
||||
int i;
|
||||
|
||||
|
@ -386,33 +387,51 @@ void usb_serial_generic_read_bulk_callback(struct urb *urb)
|
|||
if (urb == port->read_urbs[i])
|
||||
break;
|
||||
}
|
||||
set_bit(i, &port->read_urbs_free);
|
||||
|
||||
dev_dbg(&port->dev, "%s - urb %d, len %d\n", __func__, i,
|
||||
urb->actual_length);
|
||||
switch (status) {
|
||||
case 0:
|
||||
usb_serial_debug_data(&port->dev, __func__, urb->actual_length,
|
||||
data);
|
||||
port->serial->type->process_read_urb(urb);
|
||||
break;
|
||||
case -ENOENT:
|
||||
case -ECONNRESET:
|
||||
case -ESHUTDOWN:
|
||||
dev_dbg(&port->dev, "%s - urb stopped: %d\n",
|
||||
__func__, status);
|
||||
return;
|
||||
stopped = true;
|
||||
break;
|
||||
case -EPIPE:
|
||||
dev_err(&port->dev, "%s - urb stopped: %d\n",
|
||||
__func__, status);
|
||||
return;
|
||||
stopped = true;
|
||||
break;
|
||||
default:
|
||||
dev_dbg(&port->dev, "%s - nonzero urb status: %d\n",
|
||||
__func__, status);
|
||||
goto resubmit;
|
||||
break;
|
||||
}
|
||||
|
||||
usb_serial_debug_data(&port->dev, __func__, urb->actual_length, data);
|
||||
port->serial->type->process_read_urb(urb);
|
||||
/*
|
||||
* Make sure URB processing is done before marking as free to avoid
|
||||
* racing with unthrottle() on another CPU. Matches the barriers
|
||||
* implied by the test_and_clear_bit() in
|
||||
* usb_serial_generic_submit_read_urb().
|
||||
*/
|
||||
smp_mb__before_atomic();
|
||||
set_bit(i, &port->read_urbs_free);
|
||||
/*
|
||||
* Make sure URB is marked as free before checking the throttled flag
|
||||
* to avoid racing with unthrottle() on another CPU. Matches the
|
||||
* smp_mb() in unthrottle().
|
||||
*/
|
||||
smp_mb__after_atomic();
|
||||
|
||||
if (stopped)
|
||||
return;
|
||||
|
||||
resubmit:
|
||||
/* Throttle the device if requested by tty */
|
||||
spin_lock_irqsave(&port->lock, flags);
|
||||
port->throttled = port->throttle_req;
|
||||
|
@ -487,6 +506,12 @@ void usb_serial_generic_unthrottle(struct tty_struct *tty)
|
|||
port->throttled = port->throttle_req = 0;
|
||||
spin_unlock_irq(&port->lock);
|
||||
|
||||
/*
|
||||
* Matches the smp_mb__after_atomic() in
|
||||
* usb_serial_generic_read_bulk_callback().
|
||||
*/
|
||||
smp_mb();
|
||||
|
||||
if (was_throttled)
|
||||
usb_serial_generic_submit_read_urbs(port, GFP_KERNEL);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue