Netdev List
 help / color / mirror / Atom feed
From: Simon Horman <horms@kernel.org>
To: github@szelinsky.de
Cc: 'Simon Horman' <horms@kernel.org>,
	o.rempel@pengutronix.de, kory.maincent@bootlin.com,
	andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
	kuba@kernel.org, pabeni@redhat.com, netdev@vger.kernel.org,
	linux-kernel@vger.kernel.org
Subject: Re: [PATCH net 2/2] net: pse-pd: guard against freed PI data on regulator disable
Date: Wed, 27 May 2026 13:24:19 +0100	[thread overview]
Message-ID: <20260527122418.2410341-2-horms@kernel.org> (raw)
In-Reply-To: <20260524223306.2570676-3-github@szelinsky.de>

From: 'Simon Horman' <horms@kernel.org>

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://sashiko.dev
---
Subject: net: pse-pd: guard against freed PI data on regulator disable

This patch closes a use-after-free reachable from the regulator core's
deferred-disable flush during driver detach by clearing pcdev->pi after
freeing it in pse_release_pis() and bailing out in pse_pi_disable() when
pcdev->pi is NULL.

> diff --git a/drivers/net/pse-pd/pse_core.c b/drivers/net/pse-pd/pse_core.c
> index 1f27884dd4d7..752d987a7004 100644
> --- a/drivers/net/pse-pd/pse_core.c
> +++ b/drivers/net/pse-pd/pse_core.c
> @@ -145,6 +145,7 @@ static void pse_release_pis(struct pse_controller_dev *pcdev)
>  		of_node_put(pcdev->pi[i].np);
>  	}
>  	kfree(pcdev->pi);
> +	pcdev->pi = NULL;
>  }

[High]
Is the pcdev->pi = NULL store correctly synchronized with the readers?

The reader side in pse_pi_disable() observes pcdev->pi under
pcdev->lock:

    mutex_lock(&pcdev->lock);
    if (!pcdev->pi) {
            mutex_unlock(&pcdev->lock);
            return 0;
    }

But pse_release_pis() does the kfree() and the NULL store without
acquiring pcdev->lock, and pse_controller_unregister() does not wrap
the pse_release_pis() call with that lock either:

    void pse_controller_unregister(struct pse_controller_dev *pcdev)
    {
            pse_flush_pw_ds(pcdev);
            if (pcdev->irq)
                    disable_irq(pcdev->irq);
            pse_release_pis(pcdev);
            ...
    }

In the LIFO devres scenario described in the commit message, the later
regulator_unregister() does flush_work() on the deferred disable, which
provides the necessary memory barrier, so that specific case is fixed.

However, regulator consumers in other drivers (PHY/MAC) hold a
struct regulator * to a PSE PI and can call regulator_disable() (which
may take the pse_pi_disable() path directly) on a different CPU,
concurrent with the unbind path running pse_release_pis() on this
device.

That concurrent reader can take pcdev->lock and observe pcdev->pi as
still non-NULL while pse_release_pis() is concurrently kfree()-ing the
array, leading to a UAF in the subsequent _pse_pi_disable(pcdev, id)
and pcdev->pi[id].admin_state_enabled = 0 dereferences.

Would taking pcdev->lock around the kfree()+NULL store in
pse_release_pis() (so the NULL observation under pcdev->lock is
authoritative) close that window? Alternatively, can the regulator
unregister be ordered to run before pse_release_pis() so no consumer
can invoke a regulator op while the PI array is being torn down?

> @@ -702,15 +703,21 @@ static int pse_pi_enable(struct regulator_dev *rdev)
>  static int pse_pi_disable(struct regulator_dev *rdev)
>  {
>  	struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
> -	struct pse_pi *pi;
>  	int id, ret;
>  
>  	id = rdev_get_id(rdev);
> -	pi = &pcdev->pi[id];
>  	mutex_lock(&pcdev->lock);
> +	/* The controller may already be unregistered (pcdev->pi freed) by the
> +	 * time the regulator core flushes a deferred disable during
> +	 * regulator_unregister(). Bail out to avoid touching freed PI data.
> +	 */
> +	if (!pcdev->pi) {
> +		mutex_unlock(&pcdev->lock);
> +		return 0;
> +	}
>  	ret = _pse_pi_disable(pcdev, id);
>  	if (!ret)
> -		pi->admin_state_enabled = 0;
> +		pcdev->pi[id].admin_state_enabled = 0;
>  
>  	mutex_unlock(&pcdev->lock);
>  	return 0;

[High]
Should the same NULL guard be applied to pse_pi_is_enabled() and
pse_pi_enable()?

Both callbacks follow the identical pattern as the disable path:
rdev_get_drvdata(rdev) -> mutex_lock(&pcdev->lock) -> unconditional
pcdev->pi[id].* access.

In pse_pi_is_enabled():

    id = rdev_get_id(rdev);
    mutex_lock(&pcdev->lock);
    if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[id].pw_d)) {
            ret = pcdev->pi[id].admin_state_enabled;
            ...

And in pse_pi_enable():

    id = rdev_get_id(rdev);
    mutex_lock(&pcdev->lock);
    if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[id].pw_d)) {
            if (pcdev->pi[id].isr_pd_detected) {
                    ...
            }
            ...

The lifetime invariant cited in the commit message ("the controller
may already be unregistered by the time the regulator core flushes...")
is a property of the LIFO devres unwind order in which
pse_controller_unregister() runs before the devm-registered regulators
are torn down.

While regulator_unregister() only flushes disable_work, regulator
consumers in other drivers that hold a struct regulator * to a PSE PI
can still call regulator_is_enabled() or regulator_enable() concurrent
with the in-progress pse_controller_unregister() on the parent device.
At that point pcdev->pi may already be NULL/freed and the unguarded
pcdev->pi[id].pw_d / pcdev->pi[id].admin_state_enabled dereferences
would be a UAF or NULL deref.

Should these two callbacks receive the same guard, or alternatively
should the unwind order be changed so that no callback can fire after
the PI array is freed?

      reply	other threads:[~2026-05-27 12:50 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-24 22:33 [PATCH net 0/2] net: pse-pd: fix use-after-free of PI array on controller teardown Carlo Szelinsky
2026-05-24 22:33 ` [PATCH net 1/2] net: pse-pd: disable IRQ before freeing PI data in unregister Carlo Szelinsky
2026-05-27 12:55   ` Simon Horman
2026-05-24 22:33 ` [PATCH net 2/2] net: pse-pd: guard against freed PI data on regulator disable Carlo Szelinsky
2026-05-27 12:24   ` Simon Horman [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=20260527122418.2410341-2-horms@kernel.org \
    --to=horms@kernel.org \
    --cc=andrew+netdev@lunn.ch \
    --cc=davem@davemloft.net \
    --cc=edumazet@google.com \
    --cc=github@szelinsky.de \
    --cc=kory.maincent@bootlin.com \
    --cc=kuba@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=o.rempel@pengutronix.de \
    --cc=pabeni@redhat.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