* [PATCH v1 0/2] ufs: rpmb: make RPMB usable with OP-TEE key derivation
@ 2026-07-16 8:37 ` Jorge Ramirez-Ortiz via OP-TEE
0 siblings, 0 replies; 7+ messages in thread
From: Jorge Ramirez-Ortiz via OP-TEE @ 2026-07-16 8:37 UTC (permalink / raw)
To: jorge.ramirez, alim.akhtar, avri.altman, bvanassche,
James.Bottomley, martin.petersen, beanhuo, can.guo
Cc: linux-scsi, linux-kernel, op-tee, jenswi, sumit.garg
This series makes UFS RPMB work out of the box with an OP-TEE that
implements the standard eMMC RPMB key-derivation flow, without requiring
any fundamental changes on the OP-TEE side.
RPMB provides an authenticated, replay-protected storage area whose
security relies on a secret authentication key. In our setup that key is
never exposed to the kernel: OP-TEE derives it in the secure world from
its hardware-unique key and a device identifier (dev_id) that the RPMB
core hands down. OP-TEE's implementation targets eMMC, where dev_id is
the 16-byte eMMC CID, and both the fixed length and the raw-CID layout
are baked into its key derivation.
Two things stand in the way of reusing that same, unmodified OP-TEE flow
for UFS RPMB:
1. On a cold boot the very first frame sent to the RPMB well-known LU
comes back with a power-on UNIT ATTENTION (ASC 0x29), which the SCSI
core reports rather than retries. RPMB has no earlier guaranteed
access that could clear the condition first, so RPMB fails on every
power cycle. Patch 1 asks the SCSI core to retry the power-on UNIT
ATTENTION on the RPMB WLUN.
2. The UFS RPMB id is "<device_id>-R<region>", which is variable length
and longer than 16 bytes. Passing it verbatim would tie the derived
key to a length OP-TEE does not expect and diverge from the fixed
eMMC CID ABI. Patch 2 hashes it into a fixed 16-byte dev_id with
blake2s, keeping the key stable and unique per region while matching
the eMMC CID layout OP-TEE relies on. The hash algorithm and input
string are thus part of the key-derivation ABI and must stay stable.
With both patches, UFS RPMB is functional from the first access after a
cold boot and derives keys through the existing eMMC-style OP-TEE flow,
(requires minimal OP-TEE changes pending on the CID proposal done here).
Tested on IQ-9075 with Open Firmware [1], pending OP-TEE changes
[1]https://ldts.github.io/qcom-buildroot
v1:
* ufs: rpmb: retry power-on UNIT ATTENTION on the RPMB WLUN:
- fix using uses SCMD_FAILURE_ASC_ANY to retry any Unit Attention
- fix unused variable
* ufs: rpmb: use a fixed-length RPMB dev_id
- fix selecting a non-existent Kconfig symbol
Jorge Ramirez-Ortiz (2):
ufs: rpmb: retry power-on UNIT ATTENTION on the RPMB WLUN
ufs: rpmb: use a fixed-length RPMB dev_id
drivers/ufs/core/ufs-rpmb.c | 39 ++++++++++++++++++++++++++++++++++---
1 file changed, 36 insertions(+), 3 deletions(-)
--
2.54.0
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH v1 1/2] ufs: rpmb: retry power-on UNIT ATTENTION on the RPMB WLUN
2026-07-16 8:37 ` Jorge Ramirez-Ortiz via OP-TEE
@ 2026-07-16 8:37 ` Jorge Ramirez-Ortiz via OP-TEE
-1 siblings, 0 replies; 7+ messages in thread
From: Jorge Ramirez-Ortiz @ 2026-07-16 8:37 UTC (permalink / raw)
To: jorge.ramirez, alim.akhtar, avri.altman, bvanassche,
James.Bottomley, martin.petersen, beanhuo, can.guo
Cc: linux-scsi, linux-kernel, op-tee, jenswi, sumit.garg
After a power cycle, the first command sent to a UFS logical unit
completes with CHECK CONDITION and a power-on UNIT ATTENTION (ASC
0x29). The SCSI core reports this to the caller instead of retrying
it. For the RPMB well-known LU, that first command is the first RPMB
frame sent after boot, so the frame fails. RPMB has no earlier,
guaranteed access that could clear the condition beforehand, so this
breaks RPMB on every cold boot.
Ask the SCSI core to retry the power-on UNIT ATTENTION on the RPMB WLUN
so that RPMB works from the very first access after a power cycle.
Signed-off-by: Jorge Ramirez-Ortiz <jorge.ramirez@oss.qualcomm.com>
---
drivers/ufs/core/ufs-rpmb.c | 20 +++++++++++++++++++-
1 file changed, 19 insertions(+), 1 deletion(-)
diff --git a/drivers/ufs/core/ufs-rpmb.c b/drivers/ufs/core/ufs-rpmb.c
index ffad049872b9..d0c7ea7a36f4 100644
--- a/drivers/ufs/core/ufs-rpmb.c
+++ b/drivers/ufs/core/ufs-rpmb.c
@@ -40,6 +40,23 @@ struct ufs_rpmb_dev {
static int ufs_sec_submit(struct ufs_hba *hba, u16 spsp, void *buffer, size_t len, bool send)
{
struct scsi_device *sdev = hba->ufs_rpmb_wlun;
+ /* Retry the power-on UNIT ATTENTION (ASC 0x29); the SCSI core does not. */
+ struct scsi_failure failure_defs[] = {
+ {
+ .sense = UNIT_ATTENTION,
+ .asc = 0x29,
+ .ascq = SCMD_FAILURE_ASCQ_ANY,
+ .allowed = 3,
+ .result = SAM_STAT_CHECK_CONDITION,
+ },
+ {}
+ };
+ struct scsi_failures failures = {
+ .failure_definitions = failure_defs,
+ };
+ const struct scsi_exec_args exec_args = {
+ .failures = &failures,
+ };
u8 cdb[12] = { };
cdb[0] = send ? SECURITY_PROTOCOL_OUT : SECURITY_PROTOCOL_IN;
@@ -48,7 +65,8 @@ static int ufs_sec_submit(struct ufs_hba *hba, u16 spsp, void *buffer, size_t le
put_unaligned_be32(len, &cdb[6]);
return scsi_execute_cmd(sdev, cdb, send ? REQ_OP_DRV_OUT : REQ_OP_DRV_IN,
- buffer, len, /*timeout=*/30 * HZ, 0, NULL);
+ buffer, len, /*timeout=*/30 * HZ, /*retries=*/0,
+ &exec_args);
}
/* UFS RPMB route frames implementation */
--
2.54.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v1 1/2] ufs: rpmb: retry power-on UNIT ATTENTION on the RPMB WLUN
@ 2026-07-16 8:37 ` Jorge Ramirez-Ortiz via OP-TEE
0 siblings, 0 replies; 7+ messages in thread
From: Jorge Ramirez-Ortiz via OP-TEE @ 2026-07-16 8:37 UTC (permalink / raw)
To: jorge.ramirez, alim.akhtar, avri.altman, bvanassche,
James.Bottomley, martin.petersen, beanhuo, can.guo
Cc: linux-scsi, linux-kernel, op-tee, jenswi, sumit.garg
After a power cycle, the first command sent to a UFS logical unit
completes with CHECK CONDITION and a power-on UNIT ATTENTION (ASC
0x29). The SCSI core reports this to the caller instead of retrying
it. For the RPMB well-known LU, that first command is the first RPMB
frame sent after boot, so the frame fails. RPMB has no earlier,
guaranteed access that could clear the condition beforehand, so this
breaks RPMB on every cold boot.
Ask the SCSI core to retry the power-on UNIT ATTENTION on the RPMB WLUN
so that RPMB works from the very first access after a power cycle.
Signed-off-by: Jorge Ramirez-Ortiz <jorge.ramirez@oss.qualcomm.com>
---
drivers/ufs/core/ufs-rpmb.c | 20 +++++++++++++++++++-
1 file changed, 19 insertions(+), 1 deletion(-)
diff --git a/drivers/ufs/core/ufs-rpmb.c b/drivers/ufs/core/ufs-rpmb.c
index ffad049872b9..d0c7ea7a36f4 100644
--- a/drivers/ufs/core/ufs-rpmb.c
+++ b/drivers/ufs/core/ufs-rpmb.c
@@ -40,6 +40,23 @@ struct ufs_rpmb_dev {
static int ufs_sec_submit(struct ufs_hba *hba, u16 spsp, void *buffer, size_t len, bool send)
{
struct scsi_device *sdev = hba->ufs_rpmb_wlun;
+ /* Retry the power-on UNIT ATTENTION (ASC 0x29); the SCSI core does not. */
+ struct scsi_failure failure_defs[] = {
+ {
+ .sense = UNIT_ATTENTION,
+ .asc = 0x29,
+ .ascq = SCMD_FAILURE_ASCQ_ANY,
+ .allowed = 3,
+ .result = SAM_STAT_CHECK_CONDITION,
+ },
+ {}
+ };
+ struct scsi_failures failures = {
+ .failure_definitions = failure_defs,
+ };
+ const struct scsi_exec_args exec_args = {
+ .failures = &failures,
+ };
u8 cdb[12] = { };
cdb[0] = send ? SECURITY_PROTOCOL_OUT : SECURITY_PROTOCOL_IN;
@@ -48,7 +65,8 @@ static int ufs_sec_submit(struct ufs_hba *hba, u16 spsp, void *buffer, size_t le
put_unaligned_be32(len, &cdb[6]);
return scsi_execute_cmd(sdev, cdb, send ? REQ_OP_DRV_OUT : REQ_OP_DRV_IN,
- buffer, len, /*timeout=*/30 * HZ, 0, NULL);
+ buffer, len, /*timeout=*/30 * HZ, /*retries=*/0,
+ &exec_args);
}
/* UFS RPMB route frames implementation */
--
2.54.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v1 2/2] ufs: rpmb: use a fixed-length RPMB dev_id
2026-07-16 8:37 ` Jorge Ramirez-Ortiz via OP-TEE
@ 2026-07-16 8:37 ` Jorge Ramirez-Ortiz via OP-TEE
-1 siblings, 0 replies; 7+ messages in thread
From: Jorge Ramirez-Ortiz @ 2026-07-16 8:37 UTC (permalink / raw)
To: jorge.ramirez, alim.akhtar, avri.altman, bvanassche,
James.Bottomley, martin.petersen, beanhuo, can.guo
Cc: linux-scsi, linux-kernel, op-tee, jenswi, sumit.garg
The RPMB authentication key is derived from the dev_id handed to the
RPMB subsystem. OP-TEE implements the eMMC RPMB flow, where the dev_id
is the eMMC CID, a fixed 16-byte value, and it derives the key on that
assumption.
The UFS RPMB id built here is "<device_id>-R<region>", which is variable
length and longer than 16 bytes. Passing it verbatim would tie the
derived key to a length OP-TEE does not expect and diverge from the
fixed-CID eMMC ABI, requiring OP-TEE to be taught about variable-length
UFS ids.
Hash the UFS id into a fixed 16-byte dev_id with blake2s instead. This
keeps the derived key stable and unique per region while matching the
eMMC CID layout OP-TEE relies on, so the key-derivation ABI stays
identical and no OP-TEE change is needed.
Signed-off-by: Jorge Ramirez-Ortiz <jorge.ramirez@oss.qualcomm.com>
---
drivers/ufs/core/ufs-rpmb.c | 19 +++++++++++++++++--
1 file changed, 17 insertions(+), 2 deletions(-)
diff --git a/drivers/ufs/core/ufs-rpmb.c b/drivers/ufs/core/ufs-rpmb.c
index d0c7ea7a36f4..b800871269bb 100644
--- a/drivers/ufs/core/ufs-rpmb.c
+++ b/drivers/ufs/core/ufs-rpmb.c
@@ -10,6 +10,7 @@
* Can Guo <can.guo@oss.qualcomm.com>
*/
+#include <crypto/blake2s.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/kernel.h>
@@ -21,6 +22,7 @@
#include <linux/unaligned.h>
#include "ufshcd-priv.h"
+#define UFS_RPMB_ID_LEN 16 /* Match eMMC CID Length */
#define UFS_RPMB_SEC_PROTOCOL 0xEC /* JEDEC UFS application */
#define UFS_RPMB_SEC_PROTOCOL_ID 0x01 /* JEDEC UFS RPMB protocol ID, CDB byte3 */
@@ -154,6 +156,7 @@ int ufs_rpmb_probe(struct ufs_hba *hba)
{
struct ufs_rpmb_dev *ufs_rpmb, *it, *tmp;
struct rpmb_dev *rdev;
+ char *dev_id = NULL;
char *cid = NULL;
int region;
u32 cap;
@@ -213,8 +216,17 @@ int ufs_rpmb_probe(struct ufs_hba *hba)
goto err_out;
}
- descr.dev_id = cid;
- descr.dev_id_len = strlen(cid);
+ dev_id = kzalloc(UFS_RPMB_ID_LEN, GFP_KERNEL);
+ if (!dev_id) {
+ device_unregister(&ufs_rpmb->dev);
+ ret = -ENOMEM;
+ goto err_out;
+ }
+
+ blake2s(NULL, 0, cid, strlen(cid), dev_id, UFS_RPMB_ID_LEN);
+
+ descr.dev_id = dev_id;
+ descr.dev_id_len = UFS_RPMB_ID_LEN;
descr.capacity = cap;
/* Register RPMB device */
@@ -228,6 +240,8 @@ int ufs_rpmb_probe(struct ufs_hba *hba)
kfree(cid);
cid = NULL;
+ kfree(dev_id);
+ dev_id = NULL;
ufs_rpmb->rdev = rdev;
ufs_rpmb->region_id = region;
@@ -240,6 +254,7 @@ int ufs_rpmb_probe(struct ufs_hba *hba)
return 0;
err_out:
kfree(cid);
+ kfree(dev_id);
list_for_each_entry_safe(it, tmp, &hba->rpmbs, node) {
list_del(&it->node);
device_unregister(&it->dev);
--
2.54.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v1 2/2] ufs: rpmb: use a fixed-length RPMB dev_id
@ 2026-07-16 8:37 ` Jorge Ramirez-Ortiz via OP-TEE
0 siblings, 0 replies; 7+ messages in thread
From: Jorge Ramirez-Ortiz via OP-TEE @ 2026-07-16 8:37 UTC (permalink / raw)
To: jorge.ramirez, alim.akhtar, avri.altman, bvanassche,
James.Bottomley, martin.petersen, beanhuo, can.guo
Cc: linux-scsi, linux-kernel, op-tee, jenswi, sumit.garg
The RPMB authentication key is derived from the dev_id handed to the
RPMB subsystem. OP-TEE implements the eMMC RPMB flow, where the dev_id
is the eMMC CID, a fixed 16-byte value, and it derives the key on that
assumption.
The UFS RPMB id built here is "<device_id>-R<region>", which is variable
length and longer than 16 bytes. Passing it verbatim would tie the
derived key to a length OP-TEE does not expect and diverge from the
fixed-CID eMMC ABI, requiring OP-TEE to be taught about variable-length
UFS ids.
Hash the UFS id into a fixed 16-byte dev_id with blake2s instead. This
keeps the derived key stable and unique per region while matching the
eMMC CID layout OP-TEE relies on, so the key-derivation ABI stays
identical and no OP-TEE change is needed.
Signed-off-by: Jorge Ramirez-Ortiz <jorge.ramirez@oss.qualcomm.com>
---
drivers/ufs/core/ufs-rpmb.c | 19 +++++++++++++++++--
1 file changed, 17 insertions(+), 2 deletions(-)
diff --git a/drivers/ufs/core/ufs-rpmb.c b/drivers/ufs/core/ufs-rpmb.c
index d0c7ea7a36f4..b800871269bb 100644
--- a/drivers/ufs/core/ufs-rpmb.c
+++ b/drivers/ufs/core/ufs-rpmb.c
@@ -10,6 +10,7 @@
* Can Guo <can.guo@oss.qualcomm.com>
*/
+#include <crypto/blake2s.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/kernel.h>
@@ -21,6 +22,7 @@
#include <linux/unaligned.h>
#include "ufshcd-priv.h"
+#define UFS_RPMB_ID_LEN 16 /* Match eMMC CID Length */
#define UFS_RPMB_SEC_PROTOCOL 0xEC /* JEDEC UFS application */
#define UFS_RPMB_SEC_PROTOCOL_ID 0x01 /* JEDEC UFS RPMB protocol ID, CDB byte3 */
@@ -154,6 +156,7 @@ int ufs_rpmb_probe(struct ufs_hba *hba)
{
struct ufs_rpmb_dev *ufs_rpmb, *it, *tmp;
struct rpmb_dev *rdev;
+ char *dev_id = NULL;
char *cid = NULL;
int region;
u32 cap;
@@ -213,8 +216,17 @@ int ufs_rpmb_probe(struct ufs_hba *hba)
goto err_out;
}
- descr.dev_id = cid;
- descr.dev_id_len = strlen(cid);
+ dev_id = kzalloc(UFS_RPMB_ID_LEN, GFP_KERNEL);
+ if (!dev_id) {
+ device_unregister(&ufs_rpmb->dev);
+ ret = -ENOMEM;
+ goto err_out;
+ }
+
+ blake2s(NULL, 0, cid, strlen(cid), dev_id, UFS_RPMB_ID_LEN);
+
+ descr.dev_id = dev_id;
+ descr.dev_id_len = UFS_RPMB_ID_LEN;
descr.capacity = cap;
/* Register RPMB device */
@@ -228,6 +240,8 @@ int ufs_rpmb_probe(struct ufs_hba *hba)
kfree(cid);
cid = NULL;
+ kfree(dev_id);
+ dev_id = NULL;
ufs_rpmb->rdev = rdev;
ufs_rpmb->region_id = region;
@@ -240,6 +254,7 @@ int ufs_rpmb_probe(struct ufs_hba *hba)
return 0;
err_out:
kfree(cid);
+ kfree(dev_id);
list_for_each_entry_safe(it, tmp, &hba->rpmbs, node) {
list_del(&it->node);
device_unregister(&it->dev);
--
2.54.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* Re: [PATCH v1 2/2] ufs: rpmb: use a fixed-length RPMB dev_id
2026-07-16 8:37 ` Jorge Ramirez-Ortiz via OP-TEE
(?)
@ 2026-07-16 8:52 ` sashiko-bot
-1 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-07-16 8:52 UTC (permalink / raw)
To: Jorge Ramirez-Ortiz; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] The `ufs_rpmb->dev` device release function is structurally prevented from running, leaking RPMB child devices and their associated memory.
- [High] The `struct ufs_rpmb_dev` embeds a refcounted `struct device` but is allocated via `devm_kzalloc()`, leading to a use-after-free.
--
--- Patch [2]: [PATCH v1 2/2] ufs: rpmb: use a fixed-length RPMB dev_id ---
commit b0359490487ec8ad9999a553b0e7ff80b787f32c
Author: Jorge Ramirez-Ortiz <jorge.ramirez@oss.qualcomm.com>
ufs: rpmb: use a fixed-length RPMB dev_id
This commit modifies the UFS RPMB driver to generate a fixed 16-byte
device ID by hashing the variable-length UFS device ID and region string
using blake2s. This aligns the UFS device ID length with the eMMC CID
format, allowing OP-TEE's key derivation to work without modification.
> diff --git a/drivers/ufs/core/ufs-rpmb.c b/drivers/ufs/core/ufs-rpmb.c
> index d0c7ea7a36f49..b800871269bb3 100644
> --- a/drivers/ufs/core/ufs-rpmb.c
> +++ b/drivers/ufs/core/ufs-rpmb.c
[ ... ]
> @@ -154,6 +156,7 @@ int ufs_rpmb_probe(struct ufs_hba *hba)
> {
> struct ufs_rpmb_dev *ufs_rpmb, *it, *tmp;
[Severity: High]
This is a pre-existing issue, but does embedding a refcounted struct device
in a devres-managed allocation lead to a use-after-free?
The ufs_rpmb structure is allocated via devm_kzalloc() in the probe loop.
Its lifecycle is tied to the host controller's devres, which frees the
memory when the controller unbinds.
However, the embedded struct device is refcounted independently. If any
references (such as sysfs files) remain when devres frees the structure,
the final reference drop will attempt to access the already-freed struct
device to call ufs_rpmb_device_release(), leading to a use-after-free.
> struct rpmb_dev *rdev;
> + char *dev_id = NULL;
> char *cid = NULL;
[ ... ]
> @@ -240,6 +254,7 @@ int ufs_rpmb_probe(struct ufs_hba *hba)
> return 0;
> err_out:
> kfree(cid);
> + kfree(dev_id);
> list_for_each_entry_safe(it, tmp, &hba->rpmbs, node) {
> list_del(&it->node);
> device_unregister(&it->dev);
[Severity: High]
This is a pre-existing issue, but does this error path permanently leak
RPMB child devices and their associated memory?
The rpmb_dev_register() call creates a child device (rdev) with
ufs_rpmb->dev as its parent, which automatically takes a reference on the
parent.
When device_unregister(&it->dev) is called here, the parent's refcount
does not drop to zero because the child still holds a reference. This
prevents ufs_rpmb_device_release() from ever running, meaning
rpmb_dev_unregister() is never called for the child, forming an
unbreakable circular dependency.
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260716083728.2226422-1-jorge.ramirez@oss.qualcomm.com?part=2
^ permalink raw reply [flat|nested] 7+ messages in thread