Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Eli Billauer and committed by
Greg Kroah-Hartman
282a4b71 0d4a030b

+19 -3
+19 -3
drivers/char/xillybus/xillyusb.c
··· 184 184 struct mutex process_in_mutex; /* synchronize wakeup_all() */ 185 185 }; 186 186 187 + /* 188 + * kref_mutex is used in xillyusb_open() to prevent the xillyusb_dev 189 + * struct from being freed during the gap between being found by 190 + * xillybus_find_inode() and having its reference count incremented. 191 + */ 192 + 193 + static DEFINE_MUTEX(kref_mutex); 194 + 187 195 /* FPGA to host opcodes */ 188 196 enum { 189 197 OPCODE_DATA = 0, ··· 1245 1237 int rc; 1246 1238 int index; 1247 1239 1240 + mutex_lock(&kref_mutex); 1241 + 1248 1242 rc = xillybus_find_inode(inode, (void **)&xdev, &index); 1249 - if (rc) 1243 + if (rc) { 1244 + mutex_unlock(&kref_mutex); 1250 1245 return rc; 1246 + } 1247 + 1248 + kref_get(&xdev->kref); 1249 + mutex_unlock(&kref_mutex); 1251 1250 1252 1251 chan = &xdev->channels[index]; 1253 1252 filp->private_data = chan; ··· 1289 1274 if (((filp->f_mode & FMODE_READ) && chan->open_for_read) || 1290 1275 ((filp->f_mode & FMODE_WRITE) && chan->open_for_write)) 1291 1276 goto unmutex_fail; 1292 - 1293 - kref_get(&xdev->kref); 1294 1277 1295 1278 if (filp->f_mode & FMODE_READ) 1296 1279 chan->open_for_read = 1; ··· 1426 1413 return rc; 1427 1414 1428 1415 unmutex_fail: 1416 + kref_put(&xdev->kref, cleanup_dev); 1429 1417 mutex_unlock(&chan->lock); 1430 1418 return rc; 1431 1419 } ··· 2241 2227 2242 2228 xdev->dev = NULL; 2243 2229 2230 + mutex_lock(&kref_mutex); 2244 2231 kref_put(&xdev->kref, cleanup_dev); 2232 + mutex_unlock(&kref_mutex); 2245 2233 } 2246 2234 2247 2235 static struct usb_driver xillyusb_driver = {