mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-05-24 10:39:52 +00:00
char: xillybus: Prevent use-after-free due to race condition
The driver for XillyUSB devices maintains a kref reference count on each xillyusb_dev structure, which represents a physical device. This reference count reaches zero when the device has been disconnected and there are no open file descriptors that are related to the device. When this occurs, kref_put() calls cleanup_dev(), which clears up the device's data, including the structure itself. However, when xillyusb_open() is called, this reference count becomes tricky: This function needs to obtain the xillyusb_dev structure that relates to the inode's major and minor (as there can be several such). xillybus_find_inode() (which is defined in xillybus_class.c) is called for this purpose. xillybus_find_inode() holds a mutex that is global in xillybus_class.c to protect the list of devices, and releases this mutex before returning. As a result, nothing protects the xillyusb_dev's reference counter from being decremented to zero before xillyusb_open() increments it on its own behalf. Hence the structure can be freed due to a rare race condition. To solve this, a mutex is added. It is locked by xillyusb_open() before the call to xillybus_find_inode() and is released only after the kref counter has been incremented on behalf of the newly opened inode. This protects the kref reference counters of all xillyusb_dev structs from being decremented by xillyusb_disconnect() during this time segment, as the call to kref_put() in this function is done with the same lock held. There is no need to hold the lock on other calls to kref_put(), because if xillybus_find_inode() finds a struct, xillyusb_disconnect() has not made the call to remove it, and hence not made its call to kref_put(), which takes place afterwards. Hence preventing xillyusb_disconnect's call to kref_put() is enough to ensure that the reference doesn't reach zero before it's incremented by xillyusb_open(). It would have been more natural to increment the reference count in xillybus_find_inode() of course, however this function is also called by Xillybus' driver for PCIe / OF, which registers a completely different structure. Therefore, xillybus_find_inode() treats these structures as void pointers, and accordingly can't make any changes. Reported-by: Hyunwoo Kim <imv4bel@gmail.com> Suggested-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Eli Billauer <eli.billauer@gmail.com> Link: https://lore.kernel.org/r/20221030094209.65916-1-eli.billauer@gmail.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
0d4a030b3d
commit
282a4b7181
1 changed files with 19 additions and 3 deletions
|
@ -184,6 +184,14 @@ struct xillyusb_dev {
|
||||||
struct mutex process_in_mutex; /* synchronize wakeup_all() */
|
struct mutex process_in_mutex; /* synchronize wakeup_all() */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* kref_mutex is used in xillyusb_open() to prevent the xillyusb_dev
|
||||||
|
* struct from being freed during the gap between being found by
|
||||||
|
* xillybus_find_inode() and having its reference count incremented.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static DEFINE_MUTEX(kref_mutex);
|
||||||
|
|
||||||
/* FPGA to host opcodes */
|
/* FPGA to host opcodes */
|
||||||
enum {
|
enum {
|
||||||
OPCODE_DATA = 0,
|
OPCODE_DATA = 0,
|
||||||
|
@ -1237,9 +1245,16 @@ static int xillyusb_open(struct inode *inode, struct file *filp)
|
||||||
int rc;
|
int rc;
|
||||||
int index;
|
int index;
|
||||||
|
|
||||||
|
mutex_lock(&kref_mutex);
|
||||||
|
|
||||||
rc = xillybus_find_inode(inode, (void **)&xdev, &index);
|
rc = xillybus_find_inode(inode, (void **)&xdev, &index);
|
||||||
if (rc)
|
if (rc) {
|
||||||
|
mutex_unlock(&kref_mutex);
|
||||||
return rc;
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
kref_get(&xdev->kref);
|
||||||
|
mutex_unlock(&kref_mutex);
|
||||||
|
|
||||||
chan = &xdev->channels[index];
|
chan = &xdev->channels[index];
|
||||||
filp->private_data = chan;
|
filp->private_data = chan;
|
||||||
|
@ -1275,8 +1290,6 @@ static int xillyusb_open(struct inode *inode, struct file *filp)
|
||||||
((filp->f_mode & FMODE_WRITE) && chan->open_for_write))
|
((filp->f_mode & FMODE_WRITE) && chan->open_for_write))
|
||||||
goto unmutex_fail;
|
goto unmutex_fail;
|
||||||
|
|
||||||
kref_get(&xdev->kref);
|
|
||||||
|
|
||||||
if (filp->f_mode & FMODE_READ)
|
if (filp->f_mode & FMODE_READ)
|
||||||
chan->open_for_read = 1;
|
chan->open_for_read = 1;
|
||||||
|
|
||||||
|
@ -1413,6 +1426,7 @@ unopen:
|
||||||
return rc;
|
return rc;
|
||||||
|
|
||||||
unmutex_fail:
|
unmutex_fail:
|
||||||
|
kref_put(&xdev->kref, cleanup_dev);
|
||||||
mutex_unlock(&chan->lock);
|
mutex_unlock(&chan->lock);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
@ -2227,7 +2241,9 @@ static void xillyusb_disconnect(struct usb_interface *interface)
|
||||||
|
|
||||||
xdev->dev = NULL;
|
xdev->dev = NULL;
|
||||||
|
|
||||||
|
mutex_lock(&kref_mutex);
|
||||||
kref_put(&xdev->kref, cleanup_dev);
|
kref_put(&xdev->kref, cleanup_dev);
|
||||||
|
mutex_unlock(&kref_mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct usb_driver xillyusb_driver = {
|
static struct usb_driver xillyusb_driver = {
|
||||||
|
|
Loading…
Add table
Reference in a new issue