USB: ldusb: fix use-after-free on disconnect race

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] a51749ab34 ("locking/mutex: Document that mutex_unlock() is
                   non-atomic")
[2] 2b9d9e0a9b ("locking/mutex: Clarify that mutex_unlock(), and most
                   other sleeping locks, can still use the lock object
                   after it's unlocked")

Fixes: ce0d7d3f57 ("usb: ldusb: ld_usb 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-4-johan@kernel.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Johan Hovold
2026-06-22 17:26:11 +02:00
committed by Greg Kroah-Hartman
parent ff002c153f
commit 19bdfc7b3c

View File

@@ -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));
}