The Linux Kernel Mailing List
 help / color / mirror / Atom feed
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
> 

      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