lkml.org 
[lkml]   [2019]   [May]   [15]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH 4.14 006/115] USB: serial: fix unthrottle races
    Date
    From: Johan Hovold <johan@kernel.org>

    commit 3f5edd58d040bfa4b74fb89bc02f0bc6b9cd06ab 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: d83b405383c9 ("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>

    ---
    drivers/usb/serial/generic.c | 39 ++++++++++++++++++++++++++++++++-------
    1 file changed, 32 insertions(+), 7 deletions(-)

    --- a/drivers/usb/serial/generic.c
    +++ b/drivers/usb/serial/generic.c
    @@ -379,6 +379,7 @@ void usb_serial_generic_read_bulk_callba
    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_callba
    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(struc
    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);
    }

    \
     
     \ /
      Last update: 2019-05-15 13:59    [W:4.311 / U:0.268 seconds]
    ©2003-2018 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site