Messages in this thread |  | | Date | Mon, 29 Jun 2026 18:10:46 +0200 | | From | Dave Penkler <> | | Subject | Re: [PATCH] gpib: fix use-after-free in iboffline() detach path |
| |
On Mon, Jun 22, 2026 at 07:14:37AM -0400, Pavitra Jha wrote: > iboffline() calls board->interface->detach() without holding > board->big_gpib_mutex. Every userspace-reachable I/O path (read, > write, command, and all ioctls that call them) acquires this mutex > before entering the driver callbacks. The mutex therefore creates an > apparent serialization guarantee that does not extend to the detach > teardown path. > > The race window is wide and practically exploitable. Board driver > read callbacks (cb7210, ines_gpib, tnt4882) all delegate to > nec7210_read() -> pio_read(), which blocks in > wait_event_interruptible() for up to board->usec_timeout microseconds > (default 3,000,000 us = 3 seconds) while holding a cached pointer to > board->private_data on the stack: > > Thread A (I/O path, holds big_gpib_mutex): > cb7210_read() > priv = board->private_data <- cached on stack > nec7210_read(board, priv, ...) > pio_read() > wait_event_interruptible(board->wait, ..., usec_timeout) > /* BLOCKS HERE UP TO 3 SECONDS */ > priv->state ... <- UAF if detach fires > > Thread B (detach path, no mutex): > gpib_unregister_driver() > iboffline() > board->interface->detach() > kfree(board->private_data) <- frees what A still holds > > The bug is in the core framework (iboffline() in iblib.c), not in any > individual board driver. The three affected drivers (cb7210, ines_gpib, > tnt4882) are all vulnerable by the same mechanism because they share > the nec7210_read()/pio_read() path. > > The iboffline() comment already flagged this gap: > > 'XXX need to make sure board is generally not in use (grab board lock?)' > > Fix by acquiring board->big_gpib_mutex before calling detach() and > releasing it afterward, serializing teardown against in-flight I/O. > mutex_lock() (non-interruptible) is used rather than > mutex_lock_interruptible() because iboffline() is called from module > unload context where signal delivery is not meaningful. > > A prior attempt (Thomas Andreatta, May 2025) used user_mutex + > use_count to guard iboffline(). That approach was NAK'd: user_mutex > is not held consistently across ibopen()/ibclose(), making it racy > (Dan Carpenter), and use_count is never zero for an initialized board > so the check would always return -EBUSY, preventing any offline > transition (Dave Penkler). This patch instead serializes on > big_gpib_mutex, which is exactly the lock the ioctl dispatch path > uses and is therefore the correct exclusion boundary. > > KASAN report (kernel 7.1.0+, QEMU/x86_64, KASLR disabled, > reproducer: kprobe on read callback + concurrent kfree from detach > kthread): > > gpib_common: GPIB core driver > gpib_race_harness: loading out-of-tree module taints kernel. > gpib_race: attach priv=ffff88800331e7e8 canary=0xdeadbeef > gpib_race: kprobe on dummy_read installed > gpib_race: detach_fn waiting for read_entered > gpib_race: reader_fn calling dummy_read > gpib_race: kprobe fired -- dummy_read entered > gpib_race: dummy_read priv=ffff88800331e7e8 canary=0xdeadbeef > gpib_race: detach_fn firing detach on fake_board > gpib_race: detach kfree priv=ffff88800331e7e8 > gpib_race: detach_fn done -- priv is now freed > ================================================================== > BUG: KASAN: slab-use-after-free in dummy_read+0xb0/0x120 [gpib_race_harness] > Read of size 4 at addr ffff88800331e7e8 by task gpib_reader/25 > > CPU: 0 UID: 0 PID: 25 Comm: gpib_reader Tainted: G O 7.1.0+ #26 PREEMPTLAZY > Tainted: [O]=OOT_MODULE > Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-debian-1.17.0-1 04/01/2014 > Call Trace: > <TASK> > dump_stack_lvl+0x2b/0x40 > print_report+0x14f/0x4d0 > ? timer_delete_sync+0x68/0x90 > ? dummy_disable_eos+0x10/0x10 [gpib_race_harness] > kasan_report+0xd4/0x100 > ? dummy_read+0xb0/0x120 [gpib_race_harness] > ? dummy_read+0xb0/0x120 [gpib_race_harness] > dummy_read+0xb0/0x120 [gpib_race_harness] > reader_fn+0xbf/0xf0 [gpib_race_harness] > ? dummy_disable_eos+0x10/0x10 [gpib_race_harness] > ? __kthread_parkme+0x56/0x1a0 > kthread+0x32a/0x470 > ? kthread_affine_node+0x280/0x280 > ret_from_fork+0x32d/0x5a0 > ? exit_thread+0x70/0x70 > ? __switch_to+0x83f/0xc30 > ? kthread_affine_node+0x280/0x280 > ret_from_fork_asm+0x11/0x20 > </TASK> > > Allocated by task 23: > kasan_save_stack+0x2c/0x50 > kasan_save_track+0x10/0x30 > __kasan_kmalloc+0x77/0x90 > dummy_attach+0x39/0x90 [gpib_race_harness] > 0xffffffffa000d073 > do_one_initcall+0xb0/0x230 > do_init_module+0x263/0x810 > load_module+0x3e12/0x51e0 > init_module_from_file+0x136/0x150 > __x64_sys_finit_module+0x39f/0x7a0 > do_syscall_64+0x56/0x3f0 > entry_SYSCALL_64_after_hwframe+0x4b/0x53 > > Freed by task 24: > kasan_save_stack+0x2c/0x50 > kasan_save_track+0x10/0x30 > kasan_save_free_info+0x37/0x50 > __kasan_slab_free+0x3f/0x60 > kfree+0xf1/0x390 > detach_fn+0x105/0x130 [gpib_race_harness] > kthread+0x32a/0x470 > ret_from_fork+0x32d/0x5a0 > ret_from_fork_asm+0x11/0x20 > > The buggy address belongs to the object at ffff88800331e7e8 > which belongs to the cache kmalloc-8 of size 8 > The buggy address is located 0 bytes inside of > freed 8-byte region [ffff88800331e7e8, ffff88800331e7f0) > > Memory state around the buggy address: > ffff88800331e680: fc fc fc fc fc fc fc fc fc 00 fc fc fc fc fc fc > ffff88800331e700: fc fc fc fc fc fc fc fc fc fc fc 00 fc fc fc fc > >ffff88800331e780: fc fc fc fc fc fc fc fc fc fc fc fc fc fa fc fc > ^ > ffff88800331e800: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc > ffff88800331e880: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc > ================================================================== > gpib_race: UAF dereference priv=ffff88800331e7e8 canary=0xdeadbeef > gpib_race: reader_fn returned > > Note: CVE-2026-31769 (Adam Crosser) fixed a separate UAF between > IBRD/IBWRT/IBCMD/IBWAIT ioctl handlers and concurrent IBCLOSEDEV > via descriptor refcounting. This patch addresses an independent race > between the I/O callback path and iboffline()/detach() teardown, > which is not covered by that fix. > > Fixes: e6ab504633e4 ("staging: gpib: Destage gpib") > Cc: stable@vger.kernel.org > Signed-off-by: Pavitra Jha <jhapavitra98@gmail.com> > --- > drivers/gpib/common/iblib.c | 14 ++++++++++++++ > 1 file changed, 14 insertions(+) > > diff --git a/drivers/gpib/common/iblib.c b/drivers/gpib/common/iblib.c > index b672dd6aa..07a30d520 100644 > --- a/drivers/gpib/common/iblib.c > +++ b/drivers/gpib/common/iblib.c > @@ -256,9 +256,23 @@ int iboffline(struct gpib_board *board) > board->autospoll_task = NULL; > } > > + /* > + * Acquire big_gpib_mutex before calling detach() to prevent a > + * use-after-free race. I/O callbacks (read/write/command) hold > + * big_gpib_mutex while caching board->private_data on their stack. > + * Without this lock, iboffline() can kfree(board->private_data) > + * inside detach() while an I/O callback is still running and holds > + * a stale pointer to the freed memory. > + * > + * Affected board drivers: cb7210, ines_gpib, tnt4882 (all delegate > + * to nec7210_read/pio_read which blocks in wait_event_interruptible > + * for up to board->usec_timeout microseconds while holding priv). > + */ > + mutex_lock(&board->big_gpib_mutex);
Unfortunately this will not work. The read/write and command ioctls release this lock before calling the drivers because these ioctls can take a long time. Also the IBONL ioctl for moving the board offline would hang on the lock as it has already been taken before the call to online_ioctl(). The board lock is a separate lock that is controllable from user space with the IBMUTEX ioctl.
> board->interface->detach(board); > gpib_deallocate_board(board); > board->online = 0; > + mutex_unlock(&board->big_gpib_mutex); > dev_dbg(board->gpib_dev, "board offline\n"); > > return 0; > -- > 2.53.0 >
|  |