HID: usbhid: Fix race between usbhid_close() and usbhid_stop()
The syzbot fuzzer discovered a bad race between in the usbhid driver between usbhid_stop() and usbhid_close(). In particular, usbhid_stop() does: usb_free_urb(usbhid->urbin); ... usbhid->urbin = NULL; /* don't mess up next start */ and usbhid_close() does: usb_kill_urb(usbhid->urbin); with no mutual exclusion. If the two routines happen to run concurrently so that usb_kill_urb() is called in between the usb_free_urb() and the NULL assignment, it will access the deallocated urb structure -- a use-after-free bug. This patch adds a mutex to the usbhid private structure and uses it to enforce mutual exclusion of the usbhid_start(), usbhid_stop(), usbhid_open() and usbhid_close() callbacks. Reported-and-tested-by: syzbot+7bf5a7b0f0a1f9446f4c@syzkaller.appspotmail.com Signed-off-by: Alan Stern <stern@rowland.harvard.edu> CC: <stable@vger.kernel.org> Signed-off-by: Jiri Kosina <jkosina@suse.cz>
This commit is contained in:
parent
b43f977dd2
commit
0ed08faded
@ -682,16 +682,21 @@ static int usbhid_open(struct hid_device *hid)
|
|||||||
struct usbhid_device *usbhid = hid->driver_data;
|
struct usbhid_device *usbhid = hid->driver_data;
|
||||||
int res;
|
int res;
|
||||||
|
|
||||||
|
mutex_lock(&usbhid->mutex);
|
||||||
|
|
||||||
set_bit(HID_OPENED, &usbhid->iofl);
|
set_bit(HID_OPENED, &usbhid->iofl);
|
||||||
|
|
||||||
if (hid->quirks & HID_QUIRK_ALWAYS_POLL)
|
if (hid->quirks & HID_QUIRK_ALWAYS_POLL) {
|
||||||
return 0;
|
res = 0;
|
||||||
|
goto Done;
|
||||||
|
}
|
||||||
|
|
||||||
res = usb_autopm_get_interface(usbhid->intf);
|
res = usb_autopm_get_interface(usbhid->intf);
|
||||||
/* the device must be awake to reliably request remote wakeup */
|
/* the device must be awake to reliably request remote wakeup */
|
||||||
if (res < 0) {
|
if (res < 0) {
|
||||||
clear_bit(HID_OPENED, &usbhid->iofl);
|
clear_bit(HID_OPENED, &usbhid->iofl);
|
||||||
return -EIO;
|
res = -EIO;
|
||||||
|
goto Done;
|
||||||
}
|
}
|
||||||
|
|
||||||
usbhid->intf->needs_remote_wakeup = 1;
|
usbhid->intf->needs_remote_wakeup = 1;
|
||||||
@ -725,6 +730,9 @@ static int usbhid_open(struct hid_device *hid)
|
|||||||
msleep(50);
|
msleep(50);
|
||||||
|
|
||||||
clear_bit(HID_RESUME_RUNNING, &usbhid->iofl);
|
clear_bit(HID_RESUME_RUNNING, &usbhid->iofl);
|
||||||
|
|
||||||
|
Done:
|
||||||
|
mutex_unlock(&usbhid->mutex);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -732,6 +740,8 @@ static void usbhid_close(struct hid_device *hid)
|
|||||||
{
|
{
|
||||||
struct usbhid_device *usbhid = hid->driver_data;
|
struct usbhid_device *usbhid = hid->driver_data;
|
||||||
|
|
||||||
|
mutex_lock(&usbhid->mutex);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Make sure we don't restart data acquisition due to
|
* Make sure we don't restart data acquisition due to
|
||||||
* a resumption we no longer care about by avoiding racing
|
* a resumption we no longer care about by avoiding racing
|
||||||
@ -743,12 +753,13 @@ static void usbhid_close(struct hid_device *hid)
|
|||||||
clear_bit(HID_IN_POLLING, &usbhid->iofl);
|
clear_bit(HID_IN_POLLING, &usbhid->iofl);
|
||||||
spin_unlock_irq(&usbhid->lock);
|
spin_unlock_irq(&usbhid->lock);
|
||||||
|
|
||||||
if (hid->quirks & HID_QUIRK_ALWAYS_POLL)
|
if (!(hid->quirks & HID_QUIRK_ALWAYS_POLL)) {
|
||||||
return;
|
hid_cancel_delayed_stuff(usbhid);
|
||||||
|
usb_kill_urb(usbhid->urbin);
|
||||||
|
usbhid->intf->needs_remote_wakeup = 0;
|
||||||
|
}
|
||||||
|
|
||||||
hid_cancel_delayed_stuff(usbhid);
|
mutex_unlock(&usbhid->mutex);
|
||||||
usb_kill_urb(usbhid->urbin);
|
|
||||||
usbhid->intf->needs_remote_wakeup = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1057,6 +1068,8 @@ static int usbhid_start(struct hid_device *hid)
|
|||||||
unsigned int n, insize = 0;
|
unsigned int n, insize = 0;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
mutex_lock(&usbhid->mutex);
|
||||||
|
|
||||||
clear_bit(HID_DISCONNECTED, &usbhid->iofl);
|
clear_bit(HID_DISCONNECTED, &usbhid->iofl);
|
||||||
|
|
||||||
usbhid->bufsize = HID_MIN_BUFFER_SIZE;
|
usbhid->bufsize = HID_MIN_BUFFER_SIZE;
|
||||||
@ -1177,6 +1190,8 @@ static int usbhid_start(struct hid_device *hid)
|
|||||||
usbhid_set_leds(hid);
|
usbhid_set_leds(hid);
|
||||||
device_set_wakeup_enable(&dev->dev, 1);
|
device_set_wakeup_enable(&dev->dev, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutex_unlock(&usbhid->mutex);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
@ -1187,6 +1202,7 @@ fail:
|
|||||||
usbhid->urbout = NULL;
|
usbhid->urbout = NULL;
|
||||||
usbhid->urbctrl = NULL;
|
usbhid->urbctrl = NULL;
|
||||||
hid_free_buffers(dev, hid);
|
hid_free_buffers(dev, hid);
|
||||||
|
mutex_unlock(&usbhid->mutex);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1202,6 +1218,8 @@ static void usbhid_stop(struct hid_device *hid)
|
|||||||
usbhid->intf->needs_remote_wakeup = 0;
|
usbhid->intf->needs_remote_wakeup = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutex_lock(&usbhid->mutex);
|
||||||
|
|
||||||
clear_bit(HID_STARTED, &usbhid->iofl);
|
clear_bit(HID_STARTED, &usbhid->iofl);
|
||||||
spin_lock_irq(&usbhid->lock); /* Sync with error and led handlers */
|
spin_lock_irq(&usbhid->lock); /* Sync with error and led handlers */
|
||||||
set_bit(HID_DISCONNECTED, &usbhid->iofl);
|
set_bit(HID_DISCONNECTED, &usbhid->iofl);
|
||||||
@ -1222,6 +1240,8 @@ static void usbhid_stop(struct hid_device *hid)
|
|||||||
usbhid->urbout = NULL;
|
usbhid->urbout = NULL;
|
||||||
|
|
||||||
hid_free_buffers(hid_to_usb_dev(hid), hid);
|
hid_free_buffers(hid_to_usb_dev(hid), hid);
|
||||||
|
|
||||||
|
mutex_unlock(&usbhid->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int usbhid_power(struct hid_device *hid, int lvl)
|
static int usbhid_power(struct hid_device *hid, int lvl)
|
||||||
@ -1382,6 +1402,7 @@ static int usbhid_probe(struct usb_interface *intf, const struct usb_device_id *
|
|||||||
INIT_WORK(&usbhid->reset_work, hid_reset);
|
INIT_WORK(&usbhid->reset_work, hid_reset);
|
||||||
timer_setup(&usbhid->io_retry, hid_retry_timeout, 0);
|
timer_setup(&usbhid->io_retry, hid_retry_timeout, 0);
|
||||||
spin_lock_init(&usbhid->lock);
|
spin_lock_init(&usbhid->lock);
|
||||||
|
mutex_init(&usbhid->mutex);
|
||||||
|
|
||||||
ret = hid_add_device(hid);
|
ret = hid_add_device(hid);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
|
@ -80,6 +80,7 @@ struct usbhid_device {
|
|||||||
dma_addr_t outbuf_dma; /* Output buffer dma */
|
dma_addr_t outbuf_dma; /* Output buffer dma */
|
||||||
unsigned long last_out; /* record of last output for timeouts */
|
unsigned long last_out; /* record of last output for timeouts */
|
||||||
|
|
||||||
|
struct mutex mutex; /* start/stop/open/close */
|
||||||
spinlock_t lock; /* fifo spinlock */
|
spinlock_t lock; /* fifo spinlock */
|
||||||
unsigned long iofl; /* I/O flags (CTRL_RUNNING, OUT_RUNNING) */
|
unsigned long iofl; /* I/O flags (CTRL_RUNNING, OUT_RUNNING) */
|
||||||
struct timer_list io_retry; /* Retry timer */
|
struct timer_list io_retry; /* Retry timer */
|
||||||
|
Loading…
Reference in New Issue
Block a user