mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Bean Huo <beanhuo@iokpp.de>
To: Stanley Jhu <stanleyjhu@google.com>, jenswi@kernel.org, mkp@kernel.org
Cc: gregkh@linuxfoundation.org, arnd@arndb.de, bvanassche@acm.org,
	 avri.altman@sandisk.com, alim.akhtar@samsung.com,
	beanhuo@micron.com,  can.guo@oss.qualcomm.com, ulfh@kernel.org,
	linusw@kernel.org,  tomas.winkler@intel.com,
	shyamsaini@linux.microsoft.com, alex.bennee@linaro.org,
	 James.Bottomley@HansenPartnership.com,
	linux-scsi@vger.kernel.org,  linux-kernel@vger.kernel.org,
	stable@vger.kernel.org
Subject: Re: [PATCH v4 1/3] rpmb: core: Guard frame requests and teardown with mutex
Date: Sun, 13 Sep 2026 19:43:17 +0200	[thread overview]
Message-ID: <72bf5465fec11fb9442e24c4679f50f288bedb00.camel@iokpp.de> (raw)
In-Reply-To: <20260913033633.3159296-2-stanleyjhu@google.com>

On Sun, 2026-09-13 at 11:36 +0800, Stanley Jhu wrote:
> rpmb_route_frames() has no serialisation. That has two independent
> consequences.
> 
> Concurrent requests on one rpmb_dev corrupt each other. Two kthreads on
> different CPUs issuing RPMB_GET_WRITE_COUNTER against the same UFS RPMB
> region, 20000 iterations each:
> 
>   thread 0 done: 20000 iterations, mismatches=10889, errors=0
>   thread 1 done: 20000 iterations, mismatches=6141, errors=0
>   race complete: MISMATCH=17030 ERRORS=0
> 
> A mismatch is a response whose echoed nonce belongs to the other thread,
> so 43% of requests returned somebody else's frame. The errors count is
> transport failures, so nothing failed and the corruption is silent to
> the caller. An authenticated RPMB operation is not one command. The UFS
> provider issues SECURITY PROTOCOL OUT carrying the request, then
> SECURITY PROTOCOL IN to collect the response, with a result read request
> in between for write-type operations. JESD220F 12.4.7 states that any
> request other than a result read overwrites the result register of the
> region, so a request landing in the middle of another initiator's
> sequence destroys its response. The same chapter states that a region
> processes one authenticated operation at a time, so the rpmb_dev is the
> granularity the device itself assumes.
> 
> A request can also still be in flight when the provider tears down. The
> core reaches the provider through rdev->dev.parent, and the provider is
> free to release that device as soon as rpmb_dev_unregister() returns.
> Reference counting on the rpmb_dev does not prevent this:
> rpmb_dev_unregister() calls device_del(), which drops the reference that
> device_add() took on the parent, so the parent can be freed while the
> rpmb_dev is still alive and still routable. Unbinding a UFS host while a
> consumer holds an rpmb_dev reference and keeps issuing requests:
> 
>   BUG: KASAN: slab-use-after-free in ufs_rpmb_route_frames+0x328/0x420
>   Read of size 8 at addr fff00000c833bce8 by task rpmb_hold/100
>   Call trace:
>    ufs_rpmb_route_frames+0x328/0x420
>    rpmb_route_frames+0x64/0xd0
>   Freed by task 1:
>    kfree+0x2b8/0x5c4
>    ufs_rpmb_device_release+0x3c/0x60
>    device_release+0xa0/0x1fc
>    device_unregister+0x20/0x38
>    ufs_rpmb_remove+0x130/0x230
>    ufshcd_remove+0x54/0x22c
> 
> Both measurements needed patches 2/3 and 3/3 of this series applied,
> because UFS RPMB registration fails on mainline. The stable tag is for
> eMMC, which registers today. mmc_route_rpmb_frames() packs the whole
> sequence into one block request, so eMMC is not exposed to the
> interleaving above, but it does have the teardown window:
> mmc_blk_remove() reaches rpmb_dev_unregister() through
> mmc_blk_remove_parts() near its start,while the queue that
> mmc_route_rpmb_frames() submits to is only torn down at the end by
> mmc_blk_remove_req(), whose comment notes that it is freeing the queue
> that stops new requests being accepted. The eMMC window is from source
> reading; I have not reproduced it.
> 
> The kerneldoc change is part of the fix. Calling rpmb_dev_unregister()
> from a release callback cannot work, because the child rpmb_dev holds a
> reference on its parent, so the parent's release callback never runs.
> The UFS provider does exactly that today, inert only because its
> registration fails; patch 2/3 moves the call to the remove path.
> 
> Add a mutex and a dead flag to struct rpmb_dev. rpmb_route_frames()
> holds the mutex across the whole sequence and returns -ENODEV once the
> flag is set. rpmb_dev_unregister() sets the flag under the mutex before
> device_del(), so it cannot return while a request is inside the
> provider. Closing the teardown window requires excluding unregistration
> for the whole duration of a request, which is the same exclusion that
> serialises two requests, so one mutex covers both.
> 
> On the same test with this patch applied, MISMATCH=0 and the unbind is
> clean.
> 

Hi Stanley,

The fix looks right to me, the commit message is too long, it is easier to read
the code changes than the message.


> Fixes: 1e9046e3a154 ("rpmb: add Replay Protected Memory Block (RPMB)
> subsystem")
> Cc: stable@vger.kernel.org
> Signed-off-by: Stanley Jhu <stanleyjhu@google.com>
> ---
>  drivers/misc/rpmb-core.c | 34 +++++++++++++++++++++++++++++-----
>  include/linux/rpmb.h     |  6 ++++++
>  2 files changed, 35 insertions(+), 5 deletions(-)
> 
> diff --git a/drivers/misc/rpmb-core.c b/drivers/misc/rpmb-core.c
> index ecf14acf230a..bbc3c404ad6f 100644
> --- a/drivers/misc/rpmb-core.c
> +++ b/drivers/misc/rpmb-core.c
> @@ -45,16 +45,28 @@ EXPORT_SYMBOL_GPL(rpmb_dev_put);
>   * @rsp:       rpmb response frames
>   * @rsp_len:   length of rpmb response frames in bytes
>   *
> + * Context: Might sleep.
> + *
>   * Returns: < 0 on failure
>   */
>  int rpmb_route_frames(struct rpmb_dev *rdev, u8 *req,
>                       unsigned int req_len, u8 *rsp, unsigned int rsp_len)
>  {
> -       if (!req || !req_len || !rsp || !rsp_len)
> +       int ret;
> +
> +       if (!rdev || !req || !req_len || !rsp || !rsp_len)
>                 return -EINVAL;
>  
> -       return rdev->descr.route_frames(rdev->dev.parent, req, req_len,
> -                                       rsp, rsp_len);
> +       mutex_lock(&rdev->lock);
> +       if (rdev->dead) {
> +               I (&rdev->lock);
> +               return -ENODEV;
> +       }
> +
> +       ret = rdev->descr.route_frames(rdev->dev.parent, req, req_len,
> +                                      rsp, rsp_len);
> +       mutex_unlock(&rdev->lock);
> +       return ret;
>  }
>  EXPORT_SYMBOL_GPL(rpmb_route_frames);
>  
> @@ -62,6 +74,7 @@ static void rpmb_dev_release(struct device *dev)
>  {
>         struct rpmb_dev *rdev = to_rpmb_dev(dev);
>  
> +       mutex_destroy(&rdev->lock);
>         ida_free(&rpmb_ida, rdev->id);
>         kfree(rdev->descr.dev_id);
>         kfree(rdev);
> @@ -123,8 +136,9 @@ EXPORT_SYMBOL_GPL(rpmb_interface_unregister);
>   * rpmb_dev_unregister() - unregister RPMB partition from the RPMB subsystem
>   * @rdev: the rpmb device to unregister
>   *
> - * This function should be called from the release function of the
> - * underlying device used when the RPMB device was registered.
> + * This function should be called from the remove or unbind callback of the
> + * underlying device used when the RPMB device was registered, never from
> + * a device release callback.
>   *
>   * Returns: < 0 on failure
>   */
> @@ -133,6 +147,14 @@ int rpmb_dev_unregister(struct rpmb_dev *rdev)
>         if (!rdev)
>                 return -EINVAL;
>  
> +       mutex_lock(&rdev->lock);
> +       if (rdev->dead) {
> +               mutex_unlock(&rdev->lock);
> +               return 0;
> +       }
> +       rdev->dead = true;
> +       mutex_unlock(&rdev->lock);
> +
>         device_del(&rdev->dev);
>  
>         rpmb_dev_put(rdev);
> @@ -164,6 +186,7 @@ struct rpmb_dev *rpmb_dev_register(struct device *dev,
>         rdev = kzalloc_obj(*rdev);
>         if (!rdev)
>                 return ERR_PTR(-ENOMEM);
> +       mutex_init(&rdev->lock);
>         rdev->descr = *descr;
>         rdev->descr.dev_id = kmemdup(descr->dev_id, descr->dev_id_len,
>                                      GFP_KERNEL);
> @@ -194,6 +217,7 @@ struct rpmb_dev *rpmb_dev_register(struct device *dev,
>  err_free_dev_id:
>         kfree(rdev->descr.dev_id);
>  err_free_rdev:
> +       mutex_destroy(&rdev->lock);
>         kfree(rdev);
>         return ERR_PTR(ret);
>  }
> diff --git a/include/linux/rpmb.h b/include/linux/rpmb.h
> index ed3f8e431eff..814ac3e69337 100644
> --- a/include/linux/rpmb.h
> +++ b/include/linux/rpmb.h
> @@ -7,6 +7,7 @@
>  #define __RPMB_H__
>  
>  #include <linux/device.h>
> +#include <linux/mutex.h>
>  #include <linux/types.h>
>  
>  /**
> @@ -48,15 +49,20 @@ struct rpmb_descr {
>   * struct rpmb_dev - device which can support RPMB partition
>   *
>   * @dev              : device
> + * @lock             : protects in-flight operations against teardown
>   * @id               : device_id
>   * @list_node        : linked list node
>   * @descr            : RPMB description
> + * @dead             : set to true when device is unregistered
>   */
>  struct rpmb_dev {
>         struct device dev;
> +       /* Protects in-flight operations against teardown */
> +       struct mutex lock;

This lock also makes route_frames calls run one at a time, not only protect
against teardown.


>         int id;
>         struct list_head list_node;
>         struct rpmb_descr descr;
> +       bool dead;
>  };
>  
>  #define to_rpmb_dev(x)         container_of((x), struct rpmb_dev, dev)

Both are nits, feel free to add:

Reviewed-by: Bean Huo <beanhuo@micron.com>

Kind regards,
Bean

  reply	other threads:[~2026-09-13 17:46 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-13  3:36 [PATCH v4 0/3] rpmb: Fix request serialisation and teardown races Stanley Jhu
2026-09-13  3:36 ` [PATCH v4 1/3] rpmb: core: Guard frame requests and teardown with mutex Stanley Jhu
2026-09-13 17:43   ` Bean Huo [this message]
2026-09-13  3:36 ` [PATCH v4 2/3] scsi: ufs: rpmb: Decouple device lifecycle from devres to avoid UAF Stanley Jhu
     [not found]   ` <20260913034726.28F751F000FF@smtp.kernel.org>
2026-09-13  5:07     ` Stanley Jhu
2026-09-14  9:55   ` Bean Huo
2026-09-14 14:48     ` Stanley Jhu
2026-09-13  3:36 ` [PATCH v4 3/3] scsi: ufs: rpmb: Drop the unregistered ufs_rpmb bus Stanley Jhu
2026-09-14 10:05   ` Bean Huo
2026-09-14 14:48     ` Stanley Jhu

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=72bf5465fec11fb9442e24c4679f50f288bedb00.camel@iokpp.de \
    --to=beanhuo@iokpp.de \
    --cc=James.Bottomley@HansenPartnership.com \
    --cc=alex.bennee@linaro.org \
    --cc=alim.akhtar@samsung.com \
    --cc=arnd@arndb.de \
    --cc=avri.altman@sandisk.com \
    --cc=beanhuo@micron.com \
    --cc=bvanassche@acm.org \
    --cc=can.guo@oss.qualcomm.com \
    --cc=gregkh@linuxfoundation.org \
    --cc=jenswi@kernel.org \
    --cc=linusw@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-scsi@vger.kernel.org \
    --cc=mkp@kernel.org \
    --cc=shyamsaini@linux.microsoft.com \
    --cc=stable@vger.kernel.org \
    --cc=stanleyjhu@google.com \
    --cc=tomas.winkler@intel.com \
    --cc=ulfh@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®