* [PATCH 1/4] USB: iowarrior: fix use-after-free on disconnect race
2026-06-22 15:26 [PATCH 0/4] USB: fix use-after-free on disconnect race Johan Hovold
@ 2026-06-22 15:26 ` Johan Hovold
2026-06-22 15:26 ` [PATCH 2/4] USB: idmouse: " Johan Hovold
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Johan Hovold @ 2026-06-22 15:26 UTC (permalink / raw)
To: Greg Kroah-Hartman
Cc: Juergen Stuber, Yue Sun, linux-usb, linux-kernel, Johan Hovold,
stable
mutex_unlock() may access the mutex structure after releasing the lock
and therefore cannot be used to manage lifetime of objects directly
(unlike spinlocks and refcounts). [1][2]
Use a kref to release the driver data to avoid use-after-free in
mutex_unlock() when release() races with disconnect().
[1] a51749ab34d9 ("locking/mutex: Document that mutex_unlock() is non-atomic")
[2] 2b9d9e0a9ba0 ("locking/mutex: Clarify that mutex_unlock(), and most
other sleeping locks, can still use the lock object
after it's unlocked")
Fixes: 946b960d13c1 ("USB: add driver for iowarrior devices.")
Cc: stable@vger.kernel.org # 2.6.21
Reported-by: Yue Sun <samsun1006219@gmail.com>
Link: https://lore.kernel.org/r/20260618080204.38322-1-samsun1006219@gmail.com
Signed-off-by: Johan Hovold <johan@kernel.org>
---
drivers/usb/misc/iowarrior.c | 57 +++++++++++++++---------------------
1 file changed, 24 insertions(+), 33 deletions(-)
diff --git a/drivers/usb/misc/iowarrior.c b/drivers/usb/misc/iowarrior.c
index 88c6d1d1da11..de2b236ef903 100644
--- a/drivers/usb/misc/iowarrior.c
+++ b/drivers/usb/misc/iowarrior.c
@@ -72,6 +72,7 @@ static struct usb_driver iowarrior_driver;
/* Structure to hold all of our device specific stuff */
struct iowarrior {
+ struct kref kref;
struct mutex mutex; /* locks this structure */
struct usb_device *udev; /* save off the usb device pointer */
struct usb_interface *interface; /* the interface for this device */
@@ -240,8 +241,10 @@ static void iowarrior_write_callback(struct urb *urb)
/*
* iowarrior_delete
*/
-static inline void iowarrior_delete(struct iowarrior *dev)
+static inline void iowarrior_delete(struct kref *kref)
{
+ struct iowarrior *dev = container_of(kref, struct iowarrior, kref);
+
kfree(dev->int_in_buffer);
usb_free_urb(dev->int_in_urb);
kfree(dev->read_queue);
@@ -637,6 +640,9 @@ static int iowarrior_open(struct inode *inode, struct file *file)
}
/* increment our usage count for the driver */
++dev->opened;
+
+ kref_get(&dev->kref);
+
/* save our object in the file's private structure */
file->private_data = dev;
retval = 0;
@@ -652,7 +658,6 @@ static int iowarrior_open(struct inode *inode, struct file *file)
static int iowarrior_release(struct inode *inode, struct file *file)
{
struct iowarrior *dev;
- int retval = 0;
dev = file->private_data;
if (!dev)
@@ -660,29 +665,18 @@ static int iowarrior_release(struct inode *inode, struct file *file)
/* lock our device */
mutex_lock(&dev->mutex);
+ dev->opened = 0; /* we're closing now */
- if (dev->opened <= 0) {
- retval = -ENODEV; /* close called more than once */
- mutex_unlock(&dev->mutex);
- } else {
- dev->opened = 0; /* we're closing now */
- retval = 0;
- if (dev->present) {
- /*
- The device is still connected so we only shutdown
- pending read-/write-ops.
- */
- usb_kill_urb(dev->int_in_urb);
- wake_up_interruptible(&dev->read_wait);
- wake_up_interruptible(&dev->write_wait);
- mutex_unlock(&dev->mutex);
- } else {
- /* The device was unplugged, cleanup resources */
- mutex_unlock(&dev->mutex);
- iowarrior_delete(dev);
- }
+ if (dev->present) {
+ usb_kill_urb(dev->int_in_urb);
+ wake_up_interruptible(&dev->read_wait);
+ wake_up_interruptible(&dev->write_wait);
}
- return retval;
+ mutex_unlock(&dev->mutex);
+
+ kref_put(&dev->kref, iowarrior_delete);
+
+ return 0;
}
static __poll_t iowarrior_poll(struct file *file, poll_table * wait)
@@ -767,6 +761,7 @@ static int iowarrior_probe(struct usb_interface *interface,
if (!dev)
return retval;
+ kref_init(&dev->kref);
mutex_init(&dev->mutex);
atomic_set(&dev->intr_idx, 0);
@@ -885,7 +880,8 @@ static int iowarrior_probe(struct usb_interface *interface,
return retval;
error:
- iowarrior_delete(dev);
+ kref_put(&dev->kref, iowarrior_delete);
+
return retval;
}
@@ -909,19 +905,14 @@ static void iowarrior_disconnect(struct usb_interface *interface)
usb_kill_anchored_urbs(&dev->submitted);
if (dev->opened) {
- /* There is a process that holds a filedescriptor to the device ,
- so we only shutdown read-/write-ops going on.
- Deleting the device is postponed until close() was called.
- */
usb_kill_urb(dev->int_in_urb);
wake_up_interruptible(&dev->read_wait);
wake_up_interruptible(&dev->write_wait);
- mutex_unlock(&dev->mutex);
- } else {
- /* no process is using the device, cleanup now */
- mutex_unlock(&dev->mutex);
- iowarrior_delete(dev);
}
+
+ mutex_unlock(&dev->mutex);
+
+ kref_put(&dev->kref, iowarrior_delete);
}
/* usb specific object needed to register this driver with the usb subsystem */
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH 2/4] USB: idmouse: fix use-after-free on disconnect race
2026-06-22 15:26 [PATCH 0/4] USB: fix use-after-free on disconnect race Johan Hovold
2026-06-22 15:26 ` [PATCH 1/4] USB: iowarrior: " Johan Hovold
@ 2026-06-22 15:26 ` Johan Hovold
2026-06-22 15:26 ` [PATCH 3/4] USB: ldusb: " Johan Hovold
2026-06-22 15:26 ` [PATCH 4/4] USB: legousbtower: " Johan Hovold
3 siblings, 0 replies; 5+ messages in thread
From: Johan Hovold @ 2026-06-22 15:26 UTC (permalink / raw)
To: Greg Kroah-Hartman
Cc: Juergen Stuber, Yue Sun, linux-usb, linux-kernel, Johan Hovold,
stable, Oliver Neukum
mutex_unlock() may access the mutex structure after releasing the lock
and therefore cannot be used to manage lifetime of objects directly
(unlike spinlocks and refcounts). [1][2]
Use a kref to release the driver data to avoid use-after-free in
mutex_unlock() when release() races with disconnect().
[1] a51749ab34d9 ("locking/mutex: Document that mutex_unlock() is
non-atomic")
[2] 2b9d9e0a9ba0 ("locking/mutex: Clarify that mutex_unlock(), and most
other sleeping locks, can still use the lock object
after it's unlocked")
Fixes: 54d2bc068fd2 ("USB: fix locking in idmouse")
Cc: stable@vger.kernel.org # 2.6.24
Cc: Oliver Neukum <oliver@neukum.org>
Signed-off-by: Johan Hovold <johan@kernel.org>
---
drivers/usb/misc/idmouse.c | 45 +++++++++++++++++++-------------------
1 file changed, 22 insertions(+), 23 deletions(-)
diff --git a/drivers/usb/misc/idmouse.c b/drivers/usb/misc/idmouse.c
index 0f6b3464c2d6..3e37adf2bb57 100644
--- a/drivers/usb/misc/idmouse.c
+++ b/drivers/usb/misc/idmouse.c
@@ -63,6 +63,7 @@ MODULE_DEVICE_TABLE(usb, idmouse_table);
/* structure to hold all of our device specific stuff */
struct usb_idmouse {
+ struct kref kref;
struct usb_device *udev; /* save off the usb device pointer */
struct usb_interface *interface; /* the interface for this device */
@@ -209,8 +210,10 @@ static int idmouse_resume(struct usb_interface *intf)
return 0;
}
-static inline void idmouse_delete(struct usb_idmouse *dev)
+static inline void idmouse_delete(struct kref *kref)
{
+ struct usb_idmouse *dev = container_of(kref, struct usb_idmouse, kref);
+
kfree(dev->bulk_in_buffer);
kfree(dev);
}
@@ -254,6 +257,8 @@ static int idmouse_open(struct inode *inode, struct file *file)
/* increment our usage count for the driver */
++dev->open;
+ kref_get(&dev->kref);
+
/* save our object in the file's private structure */
file->private_data = dev;
@@ -277,16 +282,11 @@ static int idmouse_release(struct inode *inode, struct file *file)
/* lock our device */
mutex_lock(&dev->lock);
-
--dev->open;
+ mutex_unlock(&dev->lock);
+
+ kref_put(&dev->kref, idmouse_delete);
- if (!dev->present) {
- /* the device was unplugged before the file was released */
- mutex_unlock(&dev->lock);
- idmouse_delete(dev);
- } else {
- mutex_unlock(&dev->lock);
- }
return 0;
}
@@ -334,6 +334,7 @@ static int idmouse_probe(struct usb_interface *interface,
if (dev == NULL)
return -ENOMEM;
+ kref_init(&dev->kref);
mutex_init(&dev->lock);
dev->udev = udev;
dev->interface = interface;
@@ -342,8 +343,7 @@ static int idmouse_probe(struct usb_interface *interface,
result = usb_find_bulk_in_endpoint(iface_desc, &endpoint);
if (result) {
dev_err(&interface->dev, "Unable to find bulk-in endpoint.\n");
- idmouse_delete(dev);
- return result;
+ goto err_put_kref;
}
dev->orig_bi_size = usb_endpoint_maxp(endpoint);
@@ -351,8 +351,8 @@ static int idmouse_probe(struct usb_interface *interface,
dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;
dev->bulk_in_buffer = kmalloc(IMGSIZE + dev->bulk_in_size, GFP_KERNEL);
if (!dev->bulk_in_buffer) {
- idmouse_delete(dev);
- return -ENOMEM;
+ result = -ENOMEM;
+ goto err_put_kref;
}
/* allow device read, write and ioctl */
@@ -364,14 +364,18 @@ static int idmouse_probe(struct usb_interface *interface,
if (result) {
/* something prevented us from registering this device */
dev_err(&interface->dev, "Unable to allocate minor number.\n");
- idmouse_delete(dev);
- return result;
+ goto err_put_kref;
}
/* be noisy */
dev_info(&interface->dev,"%s now attached\n",DRIVER_DESC);
return 0;
+
+err_put_kref:
+ kref_put(&dev->kref, idmouse_delete);
+
+ return result;
}
static void idmouse_disconnect(struct usb_interface *interface)
@@ -387,14 +391,9 @@ static void idmouse_disconnect(struct usb_interface *interface)
/* prevent device read, write and ioctl */
dev->present = 0;
- /* if the device is opened, idmouse_release will clean this up */
- if (!dev->open) {
- mutex_unlock(&dev->lock);
- idmouse_delete(dev);
- } else {
- /* unlock */
- mutex_unlock(&dev->lock);
- }
+ mutex_unlock(&dev->lock);
+
+ kref_put(&dev->kref, idmouse_delete);
dev_info(&interface->dev, "disconnected\n");
}
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH 3/4] USB: ldusb: fix use-after-free on disconnect race
2026-06-22 15:26 [PATCH 0/4] USB: fix use-after-free on disconnect race Johan Hovold
2026-06-22 15:26 ` [PATCH 1/4] USB: iowarrior: " Johan Hovold
2026-06-22 15:26 ` [PATCH 2/4] USB: idmouse: " Johan Hovold
@ 2026-06-22 15:26 ` Johan Hovold
2026-06-22 15:26 ` [PATCH 4/4] USB: legousbtower: " Johan Hovold
3 siblings, 0 replies; 5+ messages in thread
From: Johan Hovold @ 2026-06-22 15:26 UTC (permalink / raw)
To: Greg Kroah-Hartman
Cc: Juergen Stuber, Yue Sun, linux-usb, linux-kernel, Johan Hovold,
stable, Daniel Walker
mutex_unlock() may access the mutex structure after releasing the lock
and therefore cannot be used to manage lifetime of objects directly
(unlike spinlocks and refcounts). [1][2]
Use a kref to release the driver data to avoid use-after-free in
mutex_unlock() when release() races with disconnect().
[1] a51749ab34d9 ("locking/mutex: Document that mutex_unlock() is
non-atomic")
[2] 2b9d9e0a9ba0 ("locking/mutex: Clarify that mutex_unlock(), and most
other sleeping locks, can still use the lock object
after it's unlocked")
Fixes: ce0d7d3f575f ("usb: ldusb: ld_usb semaphore to mutex")
Cc: stable@vger.kernel.org # 2.6.26
Cc: Daniel Walker <dwalker@mvista.com>
Signed-off-by: Johan Hovold <johan@kernel.org>
---
drivers/usb/misc/ldusb.c | 38 ++++++++++++++++++--------------------
1 file changed, 18 insertions(+), 20 deletions(-)
diff --git a/drivers/usb/misc/ldusb.c b/drivers/usb/misc/ldusb.c
index c74f142f6637..71132a15e771 100644
--- a/drivers/usb/misc/ldusb.c
+++ b/drivers/usb/misc/ldusb.c
@@ -150,6 +150,7 @@ MODULE_PARM_DESC(min_interrupt_out_interval, "Minimum interrupt out interval in
/* Structure to hold all of our device specific stuff */
struct ld_usb {
+ struct kref kref;
struct mutex mutex; /* locks this structure */
struct usb_interface *intf; /* save off the usb interface pointer */
unsigned long disconnected:1;
@@ -201,8 +202,10 @@ static void ld_usb_abort_transfers(struct ld_usb *dev)
/*
* ld_usb_delete
*/
-static void ld_usb_delete(struct ld_usb *dev)
+static void ld_usb_delete(struct kref *kref)
{
+ struct ld_usb *dev = container_of(kref, struct ld_usb, kref);
+
/* free data structures */
usb_free_urb(dev->interrupt_in_urb);
usb_free_urb(dev->interrupt_out_urb);
@@ -355,6 +358,8 @@ static int ld_usb_open(struct inode *inode, struct file *file)
goto unlock_exit;
}
+ kref_get(&dev->kref);
+
/* save device in the file's private structure */
file->private_data = dev;
@@ -381,17 +386,8 @@ static int ld_usb_release(struct inode *inode, struct file *file)
mutex_lock(&dev->mutex);
- if (dev->open_count != 1) {
- retval = -ENODEV;
+ if (dev->disconnected)
goto unlock_exit;
- }
- if (dev->disconnected) {
- /* the device was unplugged before the file was released */
- mutex_unlock(&dev->mutex);
- /* unlock here as ld_usb_delete frees dev */
- ld_usb_delete(dev);
- goto exit;
- }
/* wait until write transfer is finished */
if (dev->interrupt_out_busy)
@@ -401,7 +397,7 @@ static int ld_usb_release(struct inode *inode, struct file *file)
unlock_exit:
mutex_unlock(&dev->mutex);
-
+ kref_put(&dev->kref, ld_usb_delete);
exit:
return retval;
}
@@ -659,6 +655,8 @@ static int ld_usb_probe(struct usb_interface *intf, const struct usb_device_id *
dev = kzalloc_obj(*dev);
if (!dev)
goto exit;
+
+ kref_init(&dev->kref);
mutex_init(&dev->mutex);
spin_lock_init(&dev->rbsl);
dev->intf = intf;
@@ -740,7 +738,7 @@ static int ld_usb_probe(struct usb_interface *intf, const struct usb_device_id *
return retval;
error:
- ld_usb_delete(dev);
+ kref_put(&dev->kref, ld_usb_delete);
return retval;
}
@@ -768,18 +766,18 @@ static void ld_usb_disconnect(struct usb_interface *intf)
mutex_lock(&dev->mutex);
- /* if the device is not opened, then we clean up right now */
- if (!dev->open_count) {
- mutex_unlock(&dev->mutex);
- ld_usb_delete(dev);
- } else {
- dev->disconnected = 1;
+ dev->disconnected = 1;
+
+ if (dev->open_count) {
/* wake up pollers */
wake_up_interruptible_all(&dev->read_wait);
wake_up_interruptible_all(&dev->write_wait);
- mutex_unlock(&dev->mutex);
}
+ mutex_unlock(&dev->mutex);
+
+ kref_put(&dev->kref, ld_usb_delete);
+
dev_info(&intf->dev, "LD USB Device #%d now disconnected\n",
(minor - USB_LD_MINOR_BASE));
}
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH 4/4] USB: legousbtower: fix use-after-free on disconnect race
2026-06-22 15:26 [PATCH 0/4] USB: fix use-after-free on disconnect race Johan Hovold
` (2 preceding siblings ...)
2026-06-22 15:26 ` [PATCH 3/4] USB: ldusb: " Johan Hovold
@ 2026-06-22 15:26 ` Johan Hovold
3 siblings, 0 replies; 5+ messages in thread
From: Johan Hovold @ 2026-06-22 15:26 UTC (permalink / raw)
To: Greg Kroah-Hartman
Cc: Juergen Stuber, Yue Sun, linux-usb, linux-kernel, Johan Hovold,
stable, Daniel Walker
mutex_unlock() may access the mutex structure after releasing the lock
and therefore cannot be used to manage lifetime of objects directly
(unlike spinlocks and refcounts). [1][2]
Use a kref to release the driver data to avoid use-after-free in
mutex_unlock() when release() races with disconnect().
[1] a51749ab34d9 ("locking/mutex: Document that mutex_unlock() is
non-atomic")
[2] 2b9d9e0a9ba0 ("locking/mutex: Clarify that mutex_unlock(), and most
other sleeping locks, can still use the lock object
after it's unlocked")
Fixes: 18bcbcfe9ca2 ("USB: misc: legousbtower: semaphore to mutex")
Cc: stable@vger.kernel.org # 2.6.25
Cc: Daniel Walker <dwalker@mvista.com>
Signed-off-by: Johan Hovold <johan@kernel.org>
---
drivers/usb/misc/legousbtower.c | 37 +++++++++++++++++----------------
1 file changed, 19 insertions(+), 18 deletions(-)
diff --git a/drivers/usb/misc/legousbtower.c b/drivers/usb/misc/legousbtower.c
index 052ffc2e71ee..18dd4115befb 100644
--- a/drivers/usb/misc/legousbtower.c
+++ b/drivers/usb/misc/legousbtower.c
@@ -185,6 +185,7 @@ MODULE_DEVICE_TABLE(usb, tower_table);
/* Structure to hold all of our device specific stuff */
struct lego_usb_tower {
+ struct kref kref;
struct mutex lock; /* locks this structure */
struct usb_device *udev; /* save off the usb device pointer */
unsigned char minor; /* the starting minor number for this device */
@@ -220,7 +221,6 @@ struct lego_usb_tower {
/* local function prototypes */
static ssize_t tower_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos);
static ssize_t tower_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos);
-static inline void tower_delete(struct lego_usb_tower *dev);
static int tower_open(struct inode *inode, struct file *file);
static int tower_release(struct inode *inode, struct file *file);
static __poll_t tower_poll(struct file *file, poll_table *wait);
@@ -286,8 +286,10 @@ static inline void lego_usb_tower_debug_data(struct device *dev,
/*
* tower_delete
*/
-static inline void tower_delete(struct lego_usb_tower *dev)
+static inline void tower_delete(struct kref *kref)
{
+ struct lego_usb_tower *dev = container_of(kref, struct lego_usb_tower, kref);
+
/* free data structures */
usb_free_urb(dev->interrupt_in_urb);
usb_free_urb(dev->interrupt_out_urb);
@@ -381,6 +383,8 @@ static int tower_open(struct inode *inode, struct file *file)
dev->open_count = 1;
+ kref_get(&dev->kref);
+
unlock_exit:
mutex_unlock(&dev->lock);
@@ -404,14 +408,8 @@ static int tower_release(struct inode *inode, struct file *file)
mutex_lock(&dev->lock);
- if (dev->disconnected) {
- /* the device was unplugged before the file was released */
-
- /* unlock here as tower_delete frees dev */
- mutex_unlock(&dev->lock);
- tower_delete(dev);
- goto exit;
- }
+ if (dev->disconnected)
+ goto out_unlock;
/* wait until write transfer is finished */
if (dev->interrupt_out_busy) {
@@ -425,7 +423,9 @@ static int tower_release(struct inode *inode, struct file *file)
dev->open_count = 0;
+out_unlock:
mutex_unlock(&dev->lock);
+ kref_put(&dev->kref, tower_delete);
exit:
return retval;
}
@@ -752,6 +752,7 @@ static int tower_probe(struct usb_interface *interface, const struct usb_device_
if (!dev)
goto exit;
+ kref_init(&dev->kref);
mutex_init(&dev->lock);
dev->udev = usb_get_dev(udev);
spin_lock_init(&dev->read_buffer_lock);
@@ -828,7 +829,7 @@ static int tower_probe(struct usb_interface *interface, const struct usb_device_
return retval;
error:
- tower_delete(dev);
+ kref_put(&dev->kref, tower_delete);
return retval;
}
@@ -856,18 +857,18 @@ static void tower_disconnect(struct usb_interface *interface)
mutex_lock(&dev->lock);
- /* if the device is not opened, then we clean up right now */
- if (!dev->open_count) {
- mutex_unlock(&dev->lock);
- tower_delete(dev);
- } else {
- dev->disconnected = 1;
+ dev->disconnected = 1;
+
+ if (dev->open_count) {
/* wake up pollers */
wake_up_interruptible_all(&dev->read_wait);
wake_up_interruptible_all(&dev->write_wait);
- mutex_unlock(&dev->lock);
}
+ mutex_unlock(&dev->lock);
+
+ kref_put(&dev->kref, tower_delete);
+
dev_info(&interface->dev, "LEGO USB Tower #%d now disconnected\n",
(minor - LEGO_USB_TOWER_MINOR_BASE));
}
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread