From: Eli Billauer eli.billauer@gmail.com
[ Upstream commit 282a4b71816b6076029017a7bab3a9dcee12a920 ]
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 Signed-off-by: Bin Lan bin.lan.cn@windriver.com --- drivers/char/xillybus/xillyusb.c | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-)
diff --git a/drivers/char/xillybus/xillyusb.c b/drivers/char/xillybus/xillyusb.c index 3a2a0fb3d928..45771b1a3716 100644 --- a/drivers/char/xillybus/xillyusb.c +++ b/drivers/char/xillybus/xillyusb.c @@ -185,6 +185,14 @@ struct xillyusb_dev { 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 */ enum { OPCODE_DATA = 0, @@ -1234,9 +1242,16 @@ static int xillyusb_open(struct inode *inode, struct file *filp) int rc; int index;
+ mutex_lock(&kref_mutex); + rc = xillybus_find_inode(inode, (void **)&xdev, &index); - if (rc) + if (rc) { + mutex_unlock(&kref_mutex); return rc; + } + + kref_get(&xdev->kref); + mutex_unlock(&kref_mutex);
chan = &xdev->channels[index]; filp->private_data = chan; @@ -1272,8 +1287,6 @@ static int xillyusb_open(struct inode *inode, struct file *filp) ((filp->f_mode & FMODE_WRITE) && chan->open_for_write)) goto unmutex_fail;
- kref_get(&xdev->kref); - if (filp->f_mode & FMODE_READ) chan->open_for_read = 1;
@@ -1410,6 +1423,7 @@ static int xillyusb_open(struct inode *inode, struct file *filp) return rc;
unmutex_fail: + kref_put(&xdev->kref, cleanup_dev); mutex_unlock(&chan->lock); return rc; } @@ -2244,7 +2258,9 @@ static void xillyusb_disconnect(struct usb_interface *interface)
xdev->dev = NULL;
+ mutex_lock(&kref_mutex); kref_put(&xdev->kref, cleanup_dev); + mutex_unlock(&kref_mutex); }
static struct usb_driver xillyusb_driver = {
[ Sasha's backport helper bot ]
Hi,
The upstream commit SHA1 provided is correct: 282a4b71816b6076029017a7bab3a9dcee12a920
WARNING: Author mismatch between patch and upstream commit: Backport author: Bin Lan bin.lan.cn@windriver.com Commit author: Eli Billauer eli.billauer@gmail.com
Status in newer kernel trees: 6.11.y | Present (exact SHA1) 6.6.y | Present (exact SHA1) 6.1.y | Not found
Note: The patch differs from the upstream commit: --- --- - 2024-11-20 07:57:09.359392384 -0500 +++ /tmp/tmp.xddhwSQUsX 2024-11-20 07:57:09.349600178 -0500 @@ -1,3 +1,5 @@ +[ Upstream commit 282a4b71816b6076029017a7bab3a9dcee12a920 ] + 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 @@ -41,15 +43,16 @@ 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 +Signed-off-by: Bin Lan bin.lan.cn@windriver.com --- drivers/char/xillybus/xillyusb.c | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-)
diff --git a/drivers/char/xillybus/xillyusb.c b/drivers/char/xillybus/xillyusb.c -index 39bcbfd908b46..5a5afa14ca8cb 100644 +index 3a2a0fb3d928..45771b1a3716 100644 --- a/drivers/char/xillybus/xillyusb.c +++ b/drivers/char/xillybus/xillyusb.c -@@ -184,6 +184,14 @@ struct xillyusb_dev { +@@ -185,6 +185,14 @@ struct xillyusb_dev { struct mutex process_in_mutex; /* synchronize wakeup_all() */ };
@@ -64,7 +67,7 @@ /* FPGA to host opcodes */ enum { OPCODE_DATA = 0, -@@ -1237,9 +1245,16 @@ static int xillyusb_open(struct inode *inode, struct file *filp) +@@ -1234,9 +1242,16 @@ static int xillyusb_open(struct inode *inode, struct file *filp) int rc; int index;
@@ -82,7 +85,7 @@
chan = &xdev->channels[index]; filp->private_data = chan; -@@ -1275,8 +1290,6 @@ static int xillyusb_open(struct inode *inode, struct file *filp) +@@ -1272,8 +1287,6 @@ static int xillyusb_open(struct inode *inode, struct file *filp) ((filp->f_mode & FMODE_WRITE) && chan->open_for_write)) goto unmutex_fail;
@@ -91,7 +94,7 @@ if (filp->f_mode & FMODE_READ) chan->open_for_read = 1;
-@@ -1413,6 +1426,7 @@ static int xillyusb_open(struct inode *inode, struct file *filp) +@@ -1410,6 +1423,7 @@ static int xillyusb_open(struct inode *inode, struct file *filp) return rc;
unmutex_fail: @@ -99,7 +102,7 @@ mutex_unlock(&chan->lock); return rc; } -@@ -2227,7 +2241,9 @@ static void xillyusb_disconnect(struct usb_interface *interface) +@@ -2244,7 +2258,9 @@ static void xillyusb_disconnect(struct usb_interface *interface)
xdev->dev = NULL;
@@ -109,3 +112,6 @@ }
static struct usb_driver xillyusb_driver = { +-- +2.43.0 + ---
Results of testing on various branches:
| Branch | Patch Apply | Build Test | |---------------------------|-------------|------------| | stable/linux-6.1.y | Success | Success |
linux-stable-mirror@lists.linaro.org