]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
USB: legousbtower: fix use-after-free on disconnect race
authorJohan Hovold <johan@kernel.org>
Mon, 22 Jun 2026 15:26:12 +0000 (17:26 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 25 Jun 2026 14:15:03 +0000 (15:15 +0100)
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 <stable@kernel.org>
Cc: Daniel Walker <dwalker@mvista.com>
Signed-off-by: Johan Hovold <johan@kernel.org>
Link: https://patch.msgid.link/20260622152612.116422-5-johan@kernel.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/misc/legousbtower.c

index 052ffc2e71eea49474ccdcf958f6586fc56b3332..18dd4115befb2f84e5254ecfd1ddbf60874f5ace 100644 (file)
@@ -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 @@ exit:
        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));
 }