The Linux Kernel Mailing List
 help / color / mirror / Atom feed
* [PATCH] gpib: fix use-after-free in iboffline() detach path
@ 2026-06-22 11:14 Pavitra Jha
  2026-06-29 16:10 ` Dave Penkler
  0 siblings, 1 reply; 2+ messages in thread
From: Pavitra Jha @ 2026-06-22 11:14 UTC (permalink / raw)
  To: dpenkler; +Cc: gregkh, linux-kernel, jhapavitra98, stable

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);
 	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


^ permalink raw reply related	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-06-29 16:10 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-22 11:14 [PATCH] gpib: fix use-after-free in iboffline() detach path Pavitra Jha
2026-06-29 16:10 ` Dave Penkler

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox