* [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* Re: [PATCH] gpib: fix use-after-free in iboffline() detach path
2026-06-22 11:14 [PATCH] gpib: fix use-after-free in iboffline() detach path Pavitra Jha
@ 2026-06-29 16:10 ` Dave Penkler
0 siblings, 0 replies; 2+ messages in thread
From: Dave Penkler @ 2026-06-29 16:10 UTC (permalink / raw)
To: Pavitra Jha; +Cc: gregkh, linux-kernel, stable
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
>
^ permalink raw reply [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