From: Tetsuo Handa <penguin-ker...@i-love.sakura.ne.jp>

commit 5e5ff0b4b6bcb4d17b7a26ec8bcfc7dd4651684f upstream.

syzbot is reporting UAF at usb_submit_urb() [1], for
service_outstanding_interrupt() is not checking WDM_DISCONNECTING
before calling usb_submit_urb(). Close the race by doing same checks
wdm_read() does upon retry.

Also, while wdm_read() checks WDM_DISCONNECTING with desc->rlock held,
service_interrupt_work() does not hold desc->rlock. Thus, it is possible
that usb_submit_urb() is called from service_outstanding_interrupt() from
service_interrupt_work() after WDM_DISCONNECTING was set and kill_urbs()
 from wdm_disconnect() completed. Thus, move kill_urbs() in
wdm_disconnect() to after cancel_work_sync() (which makes sure that
service_interrupt_work() is no longer running) completed.

Although it seems to be safe to dereference desc->intf->dev in
service_outstanding_interrupt() even if WDM_DISCONNECTING was already set
because desc->rlock or cancel_work_sync() prevents wdm_disconnect() from
reaching list_del() before service_outstanding_interrupt() completes,
let's not emit error message if WDM_DISCONNECTING is set by
wdm_disconnect() while usb_submit_urb() is in progress.

[1] https://syzkaller.appspot.com/bug?extid=9e04e2df4a32fb661daf

Reported-by: syzbot <syzbot+9e04e2df4a32fb661...@syzkaller.appspotmail.com>
Signed-off-by: Tetsuo Handa <penguin-ker...@i-love.sakura.ne.jp>
Cc: stable <sta...@vger.kernel.org>
Link: 
https://lore.kernel.org/r/620e2ee0-b9a3-dbda-a25b-a93e0ed03...@i-love.sakura.ne.jp
Signed-off-by: Greg Kroah-Hartman <gre...@linuxfoundation.org>

---
 drivers/usb/class/cdc-wdm.c |   16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

--- a/drivers/usb/class/cdc-wdm.c
+++ b/drivers/usb/class/cdc-wdm.c
@@ -465,13 +465,23 @@ static int service_outstanding_interrupt
        if (!desc->resp_count || !--desc->resp_count)
                goto out;
 
+       if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
+               rv = -ENODEV;
+               goto out;
+       }
+       if (test_bit(WDM_RESETTING, &desc->flags)) {
+               rv = -EIO;
+               goto out;
+       }
+
        set_bit(WDM_RESPONDING, &desc->flags);
        spin_unlock_irq(&desc->iuspin);
        rv = usb_submit_urb(desc->response, GFP_KERNEL);
        spin_lock_irq(&desc->iuspin);
        if (rv) {
-               dev_err(&desc->intf->dev,
-                       "usb_submit_urb failed with result %d\n", rv);
+               if (!test_bit(WDM_DISCONNECTING, &desc->flags))
+                       dev_err(&desc->intf->dev,
+                               "usb_submit_urb failed with result %d\n", rv);
 
                /* make sure the next notification trigger a submit */
                clear_bit(WDM_RESPONDING, &desc->flags);
@@ -1026,9 +1036,9 @@ static void wdm_disconnect(struct usb_in
        wake_up_all(&desc->wait);
        mutex_lock(&desc->rlock);
        mutex_lock(&desc->wlock);
-       kill_urbs(desc);
        cancel_work_sync(&desc->rxwork);
        cancel_work_sync(&desc->service_outs_intr);
+       kill_urbs(desc);
        mutex_unlock(&desc->wlock);
        mutex_unlock(&desc->rlock);
 


Reply via email to