From: Dave Penkler <dpenkler@gmail.com>
To: Pavitra Jha <jhapavitra98@gmail.com>
Cc: gregkh@linuxfoundation.org, linux-kernel@vger.kernel.org,
stable@vger.kernel.org
Subject: Re: [PATCH] gpib: fix use-after-free in iboffline() detach path
Date: Mon, 29 Jun 2026 18:10:46 +0200 [thread overview]
Message-ID: <akKZBpW135YW2Def@egonzo> (raw)
In-Reply-To: <20260622111437.852082-1-jhapavitra98@gmail.com>
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
>
prev parent reply other threads:[~2026-06-29 16:10 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
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 message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=akKZBpW135YW2Def@egonzo \
--to=dpenkler@gmail.com \
--cc=gregkh@linuxfoundation.org \
--cc=jhapavitra98@gmail.com \
--cc=linux-kernel@vger.kernel.org \
--cc=stable@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox