mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-22 03:27:30 -04:00
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:
committed by
Greg Kroah-Hartman
parent
ff002c153f
commit
19bdfc7b3c
@@ -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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user