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