Linux GPIO subsystem development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Yu Zhang" <yuz08559@gmail.com>
Cc: linux-gpio@vger.kernel.org
Subject: Re: [PATCH] gpio: sim: snapshot configfs leaf items before depending on them
Date: Mon, 26 Oct 2026 09:25:30 +0000	[thread overview]
Message-ID: <sashiko-outbox-184777@kernel.org> (raw)
In-Reply-To: <20261026060049.60599-1-yuz08559@gmail.com>

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] gpio: sim: race with mkdir/rmdir in gpio_sim_device_lockup_configfs()
- [Critical] gpio: sim: extra leaf ref in gpio_sim_device_lockup_configfs() UAFs parent
- [High] gpio: sim: unchecked kcalloc() failure in gpio_sim_device_lockup_configfs()
--

commit 2fe24a96c3fb83559b6e4860a61df83ca81d16bb
Author: Yu Zhang <yuz08559@gmail.com>

gpio: sim: snapshot configfs leaf items before depending on them

This commit takes a reference-counted snapshot of leaf configfs items in
gpio_sim_device_lockup_configfs() while holding dev->lock, then calls
configfs_depend_item_unlocked() or configfs_undepend_item_unlocked() after
dropping the lock to avoid lock-order inversion with configfs rmdir.

> diff --git a/drivers/gpio/gpio-sim.c b/drivers/gpio/gpio-sim.c
> index 898638e2794cb..e4d1318b3019c 100644
> --- a/drivers/gpio/gpio-sim.c
> +++ b/drivers/gpio/gpio-sim.c
> @@ -978,27 +978,61 @@ static void
>  gpio_sim_device_lockup_configfs(struct gpio_sim_device *dev, bool lock)
>  {
>  	struct configfs_subsystem *subsys = dev->group.cg_subsys;
> +	struct config_item **items = NULL, *item;
>  	struct gpio_sim_bank *bank;
>  	struct gpio_sim_line *line;
> -	struct config_item *item;
> +	size_t i, n;
>  
>  	/*
>  	 * The device only needs to depend on leaf entries. This is
>  	 * sufficient to lock up all the configfs entries that the
>  	 * instantiated, alive device depends on.
> +	 *
> +	 * The bank and line lists are only stable under dev->lock, but
> +	 * configfs_[un]depend_item_unlocked() must not be called under it:
> +	 * they take configfs-internal locks and that would invert the lock
> +	 * order against the configfs rmdir path, which removes lines and
> +	 * banks from these lists and frees them under dev->lock. So take a
> +	 * pinned snapshot of the leaf items under the lock - skipping any
> +	 * that are already being torn down - and then depend/undepend the
> +	 * snapshot with the lock dropped.
>  	 */
> -	list_for_each_entry(bank, &dev->bank_list, siblings) {
> -		list_for_each_entry(line, &bank->line_list, siblings) {
> -			item = line->hog ? &line->hog->item
> -					 : &line->group.cg_item;
> -
> -			if (lock)
> -				WARN_ON(configfs_depend_item_unlocked(subsys,
> -								      item));
> -			else
> -				configfs_undepend_item_unlocked(item);
> +	scoped_guard(mutex, &dev->lock) {
> +		n = 0;
> +		list_for_each_entry(bank, &dev->bank_list, siblings)
> +			list_for_each_entry(line, &bank->line_list, siblings)
> +				n++;
> +
> +		if (!n)
> +			return;
> +
> +		items = kcalloc(n, sizeof(*items), GFP_KERNEL);
> +		if (!items)
> +			return;

[Severity: High]
What happens if kcalloc() fails here when called from
gpio_sim_device_config_live_store()?

drivers/gpio/gpio-sim.c:gpio_sim_device_config_live_store() {
    ...
	if (live)
		gpio_sim_device_lockup_configfs(dev, true);

	scoped_guard(mutex, &dev->lock) {
		if (live == gpio_sim_device_is_live(dev))
			ret = -EPERM;
		else if (live)
			ret = gpio_sim_device_activate(dev);
		else
			gpio_sim_device_deactivate(dev);
	}

	if (live == !!ret)
		gpio_sim_device_lockup_configfs(dev, false);
}

Because gpio_sim_device_lockup_configfs() returns void, an allocation failure
on the lock == true path is silently ignored without depending on any leaf
items. gpio_sim_device_config_live_store() still proceeds, and when
gpio_sim_device_lockup_configfs(dev, false) later runs (either immediately on
activation failure / -EPERM or later on deactivation) and its kcalloc()
succeeds, configfs_undepend_item_unlocked() -> configfs_undepend_item() hits:

fs/configfs/dir.c:configfs_undepend_item() {
    ...
	sd = target->ci_dentry->d_fsdata;
	BUG_ON(sd->s_dependent_count < 1);

	sd->s_dependent_count -= 1;
    ...
}

Conversely, if kcalloc() fails on the lock == false path during deactivation,
gpio_sim_device_deactivate() has already torn down the device, so the
s_dependent_count references on all leaf items are permanently leaked and the
configfs directories can never be removed.

Should gpio_sim_device_lockup_configfs() propagate allocation errors, or can
the items be tracked without a dynamic allocation on the undepend path?

> +
> +		i = 0;
> +		list_for_each_entry(bank, &dev->bank_list, siblings) {
> +			list_for_each_entry(line, &bank->line_list, siblings) {
> +				item = line->hog ? &line->hog->item
> +						 : &line->group.cg_item;
> +				item = config_item_get_unless_zero(item);
> +				if (item)
> +					items[i++] = item;
> +			}
>  		}
> +		n = i;
>  	}

[Severity: Critical]
Can this snapshot logic still race with concurrent configfs mkdir and rmdir
operations in three ways?

First, when a hog directory is removed via configfs_rmdir(), the final
config_item_put() drops hog->item.ci_kref to 0 and calls
gpio_sim_hog_config_item_release(), which waits on dev->lock before clearing
line->hog:

drivers/gpio/gpio-sim.c:gpio_sim_hog_config_item_release() {
    ...
	scoped_guard(mutex, &dev->lock)
		line->hog = NULL;
    ...
}

If gpio_sim_device_lockup_configfs(dev, true) holds dev->lock at that point,
it sees line->hog != NULL, calls config_item_get_unless_zero(&line->hog->item)
which returns NULL, and skips the line completely without falling back to
&line->group.cg_item. Once dev->lock is dropped, line->hog becomes NULL, so a
subsequent gpio_sim_device_lockup_configfs(dev, false) snapshots
&line->group.cg_item and triggers BUG_ON(sd->s_dependent_count < 1) in
configfs_undepend_item().

Second, in configfs_rmdir(), unlink_obj() clears item->ci_group = NULL before
the final config_item_put() drops ci_kref to 0 and runs the release callback:

fs/configfs/dir.c:unlink_obj() {
    ...
	item->ci_group = NULL;
	item->ci_parent = NULL;

	/* Drop the reference for ci_entry */
	config_item_put(item);
}

An item can therefore be snapshotted via config_item_get_unless_zero() after
unlink_obj() has already cleared ci_group (or a concurrent configfs_rmdir()
can run unlink_obj() after dev->lock is dropped but before
configfs_depend_item_unlocked() runs). When configfs_depend_item_unlocked()
then inspects the snapshotted item, target->ci_group is NULL and
parent->cg_subsys dereferences a NULL pointer:

fs/configfs/dir.c:configfs_depend_item_unlocked() {
    ...
	parent = target->ci_group;
	if (configfs_is_root(&parent->cg_item)) {
		target_subsys = to_configfs_subsystem(to_config_group(target));
		root = parent;
	} else {
		target_subsys = parent->cg_subsys;
    ...
}

Third, in configfs_mkdir(), gpio_sim_bank_config_make_line_group() and
gpio_sim_line_config_make_hog_item() add the new item to bank->line_list or
line->hog with ci_kref == 1 under dev->lock before link_obj() sets
item->ci_group and before configfs_dir_set_ready() clears
CONFIGFS_USET_CREATING. Snapshotting the item in that window either
dereferences NULL target->ci_group in configfs_depend_item_unlocked() or fails
with -ENOENT in configfs_depend_prep() (leaving s_dependent_count == 0 and
later hitting BUG_ON(sd->s_dependent_count < 1) on undepend).

> +
> +	for (i = 0; i < n; i++) {
> +		if (lock)
> +			WARN_ON(configfs_depend_item_unlocked(subsys,
> +							      items[i]));
> +		else
> +			configfs_undepend_item_unlocked(items[i]);
> +		config_item_put(items[i]);
> +	}

[Severity: Critical]
Does holding an extra ci_kref reference on items[i] across the unlocked window
allow a child's release callback to run after its parent has already been
freed?

In configfs, unlink_obj() drops the child's reference on its parent
config_group during configfs_rmdir(child) via config_group_put(group), rather
than when the child's ci_kref reaches 0.

If a concurrent rmdir removes the leaf item (for example, right after
configfs_undepend_item_unlocked(items[i]) on deactivation, or before
configfs_depend_item_unlocked() on activation) while items[i] still holds a
reference from config_item_get_unless_zero(), configfs_rmdir(child) finishes
and drops the reference on the parent directory while child->ci_kref remains
1. Userspace can then immediately rmdir the now-empty parent line or bank
directory, freeing struct gpio_sim_line or struct gpio_sim_bank.

When config_item_put(items[i]) then drops the last reference on the child,
gpio_sim_hog_config_item_release() or gpio_sim_line_config_group_release()
dereferences the uncounted hog->parent or line->parent pointer into the freed
parent structure:

drivers/gpio/gpio-sim.c:gpio_sim_hog_config_item_release() {
	struct gpio_sim_hog *hog = to_gpio_sim_hog(item);
	struct gpio_sim_line *line = hog->parent;
	struct gpio_sim_device *dev = gpio_sim_hog_get_device(hog);

	scoped_guard(mutex, &dev->lock)
		line->hog = NULL;
    ...
}

drivers/gpio/gpio-sim.c:gpio_sim_line_config_group_release() {
	struct gpio_sim_line *line = to_gpio_sim_line(item);
	struct gpio_sim_device *dev = gpio_sim_line_get_device(line);

	scoped_guard(mutex, &dev->lock)
		list_del(&line->siblings);
    ...
}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261026060049.60599-1-yuz08559@gmail.com?part=1

  reply	other threads:[~2026-10-26  9:25 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-26  6:00 [PATCH] gpio: sim: snapshot configfs leaf items before depending on them Yu Zhang
2026-10-26  9:25 ` sashiko-bot [this message]
2026-10-27  8:56 ` [PATCH v2] gpio: sim: fix the configfs lockup racing with mkdir/rmdir Yu Zhang
2026-10-28 10:07   ` Bartosz Golaszewski
2026-10-31  8:49     ` Yu Zhang
2026-10-31  8:58   ` [PATCH v3] " Yu Zhang
2026-10-31  9:17     ` sashiko-bot
2026-11-02  9:49     ` Bartosz Golaszewski

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=sashiko-outbox-184777@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=linux-gpio@vger.kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=yuz08559@gmail.com \
    /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