]> exis.tech > repos - linux.git/commitdiff
USB: idmouse: fix use-after-free on disconnect race
authorJohan Hovold <johan@kernel.org>
Mon, 22 Jun 2026 15:26:10 +0000 (17:26 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 25 Jun 2026 14:14:52 +0000 (15:14 +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: 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>
Link: https://patch.msgid.link/20260622152612.116422-3-johan@kernel.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/misc/idmouse.c

index 0f6b3464c2d6a81f52aba31f8d82ea8ed79e2eeb..3e37adf2bb5734db10516b097e176f4ff6de8a56 100644 (file)
@@ -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");
 }