mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: 徐晗翔 <xuhx@hust.edu.cn>
To: linusw@kernel.org, brgl@kernel.org
Cc: linux-gpio@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: gpio: gpiolib: final put of the gpio_device happens inside  the SRCU read critical section on the setup-failure path -- deterministic use-after-free read at guard release (unfixed upstream)
Date: Tue, 3 Nov 2026 20:10:12 +0800 (GMT+08:00)	[thread overview]
Message-ID: <2c0b79c4.1d697.1a1a15126b6.Coremail.xuhx@hust.edu.cn> (raw)
In-Reply-To: <2026102727-data-sequel-ce41@gregkh>

[-- Attachment #1: Type: text/plain, Size: 3151 bytes --]

On gpiolib's setup-failure path, the guard that protects the
gpio_device lookup (srcu_read_lock at gpiolib.c:1094) is also held
while the device is being torn down: gpiochip_setup_devs()'s error
path drops the last reference (gpiodev_release at :893-906, which
calls cleanup_srcu_struct at :904 and kfree at :905) while the
caller's guard is still inside the read critical section -- so the
guard's own release (srcu_read_unlock) reads ssp->sda out of an
already freed gpio_device.  In the hog path the refcount can be
exactly 1 at that point, which is the common case, so this is not
an exotic window: it is a sequential-ordering violation that fires
whenever that error path is taken, no concurrency needed.  On a
stock 7.3 KASAN kernel, replaying gpiolib's exact single-threaded
statement sequence 1:1 (lock at the :1094 guard point -> the :904
cleanup -> the :905 kfree -> the guard's unlock) hits
WARNING: kernel/rcu/srcutree.c:696 at cleanup_srcu_struct (the
self-held-reader defensive branch: sda and srcu_sup leaked, not
freed), and the subsequent srcu_read_unlock produces
BUG: KASAN: slab-use-after-free in __srcu_read_unlock+0x1a reading
object+8 -- exactly the ssp->sda field of the freed container.  The
setup-failure path also misses the list_del_rcu() that the success
path performs, so the destroyed device can remain reachable through
the global list.  As far as I can tell this is unfixed upstream.

The detailed description:
* The ordering (Linux 7.3, drivers/gpio/gpiolib.c)
  The caller takes the lookup guard (srcu_read_lock, :1094), then
  gpiochip_setup_devs() fails (e.g. cdev/hog registration errors),
  and the error path drops the final reference;
  gpiodev_release() runs cleanup_srcu_struct(&gdev->srcu) at :904
  and kfree(gdev) at :905 -- while the caller is still inside the
  SRCU read critical section it took at :1094.  When the caller's
  guard destructs, srcu_read_unlock(&gdev->srcu, idx) touches the
  freed object (reads ssp->sda at object offset +8).  The hog path
  leaves refcount==1 here as the common case (no other holders
  yet), so the final put is genuinely final.
  Secondary: the failure path lacks the list_del_rcu() the success
  path performs, leaving the freed device linked.

* Evidence (stock 7.3 KASAN; deterministic, single-threaded;
  reproducer attached)
  gpiodevsim.c replays gpiolib's own sequence with a kzalloc'd
  container of the same layout: srcu_read_lock (the :1094 guard) ->
  cleanup_srcu_struct (the :904 call; hits the srcutree.c:696
  WARNING -- the library's own "reader still inside" defense
  triggers, leaking sda/srcu_sup exactly as the source predicts) ->
  kfree (the :905 call) -> srcu_read_unlock (the guard's
  destructor):
    WARNING: kernel/rcu/srcutree.c:696 at cleanup_srcu_struct
      (leak branch: srcu.sda non-NULL, srcu.srcu_sup non-NULL)
    BUG: KASAN: slab-use-after-free in __srcu_read_unlock+0x1a
      Read of size 8, address = object+8 == the ssp->sda field
      alloc: insmod kzalloc;  free: the kfree step above
  One run, no interleaving, no kprobe parking -- the violation is
  in the ordering itself.


Regards,
Hanxiang Xu
xuhx@hust.edu.cn

[-- Attachment #2: gpiodevsim.c --]
[-- Type: text/plain, Size: 3476 bytes --]

// SPDX-License-Identifier: GPL-2.0+
/*
 * gpiodevsim.c - dynamic verification for the audit-report Case 3 (gpiolib):
 *
 *   gpiochip_setup_devs() (drivers/gpio/gpiolib.c:1084-1109) holds
 *   guard(srcu)(&gdev->srcu) across the loop body and, on
 *   gpiochip_setup_dev() failure, calls gpio_device_put(gdev) at :1104
 *   while the read lock is still held. With refcount==1 (the norm: hogs do
 *   not take device references) gpiodev_release() runs inline:
 *     :898 sync+cleanup desc_srcu (fine)
 *     :904 cleanup_srcu_struct(&gdev->srcu) -> WARN_ON(srcu_readers_active)
 *            at kernel/rcu/srcutree.c:696 -> early return, LEAK (sup/sda
 *            stay allocated), i.e. the defensive branch the report predicts
 *     :905 kfree(gdev)
 *   then the same stack continues on freed memory:
 *     :1105 dev_err(&gdev->dev, ...)          (UAF read)
 *     loop iterator reads gdev->list.next     (UAF read)
 *     guard destructor srcu_read_unlock()     (UAF read of gdev->srcu.sda
 *            via __srcu_ctr_to_ptr inside __srcu_read_unlock)
 *
 * This module reproduces that exact single-thread sequence with a
 * kzalloc()'d stand-in for struct gpio_device (embedded srcu_struct) and
 * makes the final unlock observable as a KASAN use-after-free read:
 *
 *   g = kzalloc(); init_srcu_struct(&g->srcu);
 *   idx = srcu_read_lock(&g->srcu);      // :1094 guard
 *   cleanup_srcu_struct(&g->srcu);       // expect WARN srcutree.c:696 + leak
 *   print post-cleanup sda/srcu_sup (both non-NULL => leak branch proof)
 *   kfree(g);                            // :905
 *   srcu_read_unlock(&g->srcu, idx);     // guard destructor => KASAN UAF
 */
#define pr_fmt(fmt) "GPIODEVSIM: " fmt

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/srcu.h>

struct sim_gdev {
	struct srcu_struct srcu;	/* first member, like gpio_device usage */
};

static int __init gpiodevsim_init(void)
{
	struct sim_gdev *g;
	int idx;

	g = kzalloc(sizeof(*g), GFP_KERNEL);
	if (!g)
		return -ENOMEM;
	if (init_srcu_struct(&g->srcu)) {
		kfree(g);
		return -ENOMEM;
	}
	pr_info("sim gdev=%px srcu initialized (sda=%px sup=%px ctrp=%px)\n",
		g, g->srcu.sda, g->srcu.srcu_sup, g->srcu.srcu_ctrp);

	/* gpiolib.c:1094 guard(srcu)(&gdev->srcu) */
	idx = srcu_read_lock(&g->srcu);
	pr_info("read lock taken idx=%d (self-reader active: the put at :1104 will meet it)\n", idx);

	/* gpiodev_release():904 - expect WARN at srcutree.c:696, early return */
	cleanup_srcu_struct(&g->srcu);
	pr_info("post-cleanup: srcu.sda=%px srcu.srcu_sup=%px (BOTH non-NULL => WARN+LEAK branch taken, no free_percpu) ctrp=%px (stale)\n",
		g->srcu.sda, g->srcu.srcu_sup, g->srcu.srcu_ctrp);

	/* gpiodev_release():905 */
	kfree(g);
	pr_info("sim gdev kfree()d; guard destructor will now read g->srcu.sda from freed slab\n");

	/* gpiochip_setup_devs() loop-body end: guard(srcu) destructor */
	srcu_read_unlock(&g->srcu, idx);

	/* NOT REACHED on a KASAN kernel without kasan_multi_shot only if the
	 * access faults; with multi-shot the report is printed and execution
	 * continues here. */
	pr_warn("sequence complete: expected a KASAN use-after-free READ report for the unlock above\n");
	return 0;
}

static void __exit gpiodevsim_exit(void)
{
	pr_warn("module exit (leaked srcu_usage of the WARN'd srcu is intentional, matching gpiolib behavior)\n");
}

module_init(gpiodevsim_init);
module_exit(gpiodevsim_exit);
MODULE_LICENSE("GPL");

      parent reply	other threads:[~2026-11-03 12:10 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-27 12:35 usb: devio: usbdev_release() has no barrier against an in-flight async_completed() callback (use-after-free window on struct usb_dev_state)gregkh@linuxfoundation.org 徐晗翔
2026-10-27 14:12 ` Greg KH
2026-11-03 11:21   ` mm/slub: has_pcs_used() reads remote per-CPU sheaf state from flush_all_cpus_locked() without synchronization (data race; KCSAN blind spot: slub.o is uninstrumented) 徐晗翔
2026-11-03 11:30   ` futex/hrtimer: hrtimer_sleeper::task is read unsynchronized in 16 places on v7.3 -- the accessor series that fixes this is not in 7.3; please make sure it reaches the affected trees (backport nudge + stock-kernel KCSAN evidence) 徐晗翔
2026-11-03 11:48   ` cgroup: fork vs cgroup.procs write race in favordynmods mode -- v7.3 is affected; please make sure bf6fbf46b5ff reaches the stable trees (repro incl. uninstrumented natural hit) 徐晗翔
2026-11-03 14:51     ` Sasha Levin
2026-11-03 11:57   ` net: rtnetlink: rtnl_link_unregister() vs in-flight RTM_GETLINK dump readers -- v7.3 lacks the synchronize_rcu() fix (Fixes: 6b57ff21a310 family); A/B reproduction on stock 7.3 徐晗翔
2026-11-03 15:49     ` Eric Dumazet
2026-11-03 12:07   ` wifi: wilc1000: SDIO/SPI remove destroys the srcu_struct before wiphy_unregister() -- late cfg80211 callbacks write through dangling srcu state (unfixed upstream; controlled + natural Oops) 徐晗翔
2026-11-03 12:10   ` 徐晗翔 [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=2c0b79c4.1d697.1a1a15126b6.Coremail.xuhx@hust.edu.cn \
    --to=xuhx@hust.edu.cn \
    --cc=brgl@kernel.org \
    --cc=linusw@kernel.org \
    --cc=linux-gpio@vger.kernel.org \
    --cc=linux-kernel@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

all inboxes | Powered by JetHome®