[PATCH v2] firmware: arm_ffa: honor descriptor size in PARTITION_INFO_GET_REGS

Jamie Nguyen posted 1 patch 1 week, 2 days ago
There is a newer version of this series
drivers/firmware/arm_ffa/driver.c | 20 +++++++++++++++++---
1 file changed, 17 insertions(+), 3 deletions(-)
[PATCH v2] firmware: arm_ffa: honor descriptor size in PARTITION_INFO_GET_REGS
Posted by Jamie Nguyen 1 week, 2 days ago
__ffa_partition_info_get_regs() walks the response with a hardcoded
24-byte stride (regs += 3) even though the SPMC tells us the actual
per-descriptor size via PARTITION_INFO_SZ in x2[63:48]. The size is
read into buf_sz and then thrown away.

That works while every SPMC returns the FF-A v1.1 layout, but it falls
apart against a v1.3 SPMC returning the 48-byte descriptor. The loop
strides over half a descriptor at a time and ends up parsing every
other entry from a slice of two adjacent ones.

The FF-A spec (v1.2, section 18.5) says that the producer should
report the descriptor size, and the consumer is supposed to stride by
that size and ignore any trailing fields it doesn't understand. The
non-REGS path (__ffa_partition_info_get) does this already, and the
REGS path should match.

Use buf_sz for the stride, and bail out with -EINVAL if the SPMC
reports something we can't safely walk.

Fixes: ba85c644ac8d ("firmware: arm_ffa: Add support for FFA_PARTITION_INFO_GET_REGS")
Signed-off-by: Jamie Nguyen <jamien@nvidia.com>
---
Changes in v2:
- Rebase onto linux-next; reuse the
  FFA_PART_INFO_GET_REGS_{REGS_PER_DESC,MAX_DESC} macros it added instead of
  introducing new ones.
- Return -EINVAL instead of -EPROTO to match surrounding checks.
- Update Fixes: tag to the commit that introduced the hardcoded stride.
---
 drivers/firmware/arm_ffa/driver.c | 20 +++++++++++++++++---
 1 file changed, 17 insertions(+), 3 deletions(-)

diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c
index b9f17fda7243..38ae4476e864 100644
--- a/drivers/firmware/arm_ffa/driver.c
+++ b/drivers/firmware/arm_ffa/driver.c
@@ -374,9 +374,23 @@ __ffa_partition_info_get_regs(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3,
 			return -EINVAL;
 
 		tag = UUID_INFO_TAG(partition_info.a2);
+
+		/*
+		 * Per FF-A v1.2 section 18.5 the SPMC reports the per-
+		 * descriptor size and consumers must stride by that size,
+		 * reading only the fields they understand and ignoring any
+		 * trailing ones. Reject sizes that cannot hold the v1.1
+		 * fields parsed below, are not u64-aligned, or whose total
+		 * payload would walk past the x3..x17 window (e.g. a v1.3
+		 * 48-byte descriptor with nr_desc > 2).
+		 */
 		buf_sz = PARTITION_INFO_SZ(partition_info.a2);
-		if (buf_sz > sizeof(*buffer))
-			buf_sz = sizeof(*buffer);
+		if (buf_sz < FFA_PART_INFO_GET_REGS_REGS_PER_DESC * sizeof(u64) ||
+		    buf_sz % sizeof(u64) ||
+		    nr_desc * (buf_sz / sizeof(u64)) >
+		    FFA_PART_INFO_GET_REGS_MAX_DESC *
+		    FFA_PART_INFO_GET_REGS_REGS_PER_DESC)
+			return -EINVAL;
 
 		regs = (void *)&partition_info.a3;
 		for (idx = 0; idx < nr_desc; idx++, buf++) {
@@ -395,7 +409,7 @@ __ffa_partition_info_get_regs(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3,
 			buf->exec_ctxt = PART_INFO_EXEC_CXT(val);
 			buf->properties = PART_INFO_PROPERTIES(val);
 			uuid_copy(&buf->uuid, &uuid_regs.uuid);
-			regs += 3;
+			regs += buf_sz / sizeof(u64);
 		}
 		start_idx = cur_idx + 1;
 

base-commit: e98d21c170b01ddef366f023bbfcf6b31509fa83
-- 
2.34.1
Re: [PATCH v2] firmware: arm_ffa: honor descriptor size in PARTITION_INFO_GET_REGS
Posted by Sudeep Holla 1 week ago
On Fri, May 15, 2026 at 10:44:32AM -0700, Jamie Nguyen wrote:
> __ffa_partition_info_get_regs() walks the response with a hardcoded
> 24-byte stride (regs += 3) even though the SPMC tells us the actual
> per-descriptor size via PARTITION_INFO_SZ in x2[63:48]. The size is
> read into buf_sz and then thrown away.
> 
> That works while every SPMC returns the FF-A v1.1 layout, but it falls
> apart against a v1.3 SPMC returning the 48-byte descriptor. The loop
> strides over half a descriptor at a time and ends up parsing every
> other entry from a slice of two adjacent ones.
> 
> The FF-A spec (v1.2, section 18.5) says that the producer should
> report the descriptor size, and the consumer is supposed to stride by
> that size and ignore any trailing fields it doesn't understand. The
> non-REGS path (__ffa_partition_info_get) does this already, and the
> REGS path should match.
> 
> Use buf_sz for the stride, and bail out with -EINVAL if the SPMC
> reports something we can't safely walk.
> 
> Fixes: ba85c644ac8d ("firmware: arm_ffa: Add support for FFA_PARTITION_INFO_GET_REGS")
> Signed-off-by: Jamie Nguyen <jamien@nvidia.com>
> ---
> Changes in v2:
> - Rebase onto linux-next; reuse the
>   FFA_PART_INFO_GET_REGS_{REGS_PER_DESC,MAX_DESC} macros it added instead of
>   introducing new ones.
> - Return -EINVAL instead of -EPROTO to match surrounding checks.
> - Update Fixes: tag to the commit that introduced the hardcoded stride.
> ---
>  drivers/firmware/arm_ffa/driver.c | 20 +++++++++++++++++---
>  1 file changed, 17 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c
> index b9f17fda7243..38ae4476e864 100644
> --- a/drivers/firmware/arm_ffa/driver.c
> +++ b/drivers/firmware/arm_ffa/driver.c
> @@ -374,9 +374,23 @@ __ffa_partition_info_get_regs(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3,
>  			return -EINVAL;
>  
>  		tag = UUID_INFO_TAG(partition_info.a2);
> +
> +		/*
> +		 * Per FF-A v1.2 section 18.5 the SPMC reports the per-
> +		 * descriptor size and consumers must stride by that size,
> +		 * reading only the fields they understand and ignoring any
> +		 * trailing ones. Reject sizes that cannot hold the v1.1
> +		 * fields parsed below, are not u64-aligned, or whose total
> +		 * payload would walk past the x3..x17 window (e.g. a v1.3
> +		 * 48-byte descriptor with nr_desc > 2).
> +		 */

I am not sure if this above note is necessary.

>  		buf_sz = PARTITION_INFO_SZ(partition_info.a2);
> -		if (buf_sz > sizeof(*buffer))
> -			buf_sz = sizeof(*buffer);
> +		if (buf_sz < FFA_PART_INFO_GET_REGS_REGS_PER_DESC * sizeof(u64) ||
> +		    buf_sz % sizeof(u64) ||
> +		    nr_desc * (buf_sz / sizeof(u64)) >
> +		    FFA_PART_INFO_GET_REGS_MAX_DESC *
> +		    FFA_PART_INFO_GET_REGS_REGS_PER_DESC)
> +			return -EINVAL;
>

The logic above is correct but bit hard to read, I am thinking of splitting
this

>  		regs = (void *)&partition_info.a3;
>  		for (idx = 0; idx < nr_desc; idx++, buf++) {
> @@ -395,7 +409,7 @@ __ffa_partition_info_get_regs(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3,
>  			buf->exec_ctxt = PART_INFO_EXEC_CXT(val);
>  			buf->properties = PART_INFO_PROPERTIES(val);
>  			uuid_copy(&buf->uuid, &uuid_regs.uuid);
> -			regs += 3;
> +			regs += buf_sz / sizeof(u64);
>  		}
>  		start_idx = cur_idx + 1;

Is it OK if I adjust something like below patch ?

Regards,
Sudeep

-->8

diff --git c/drivers/firmware/arm_ffa/driver.c w/drivers/firmware/arm_ffa/driver.c
index fb97b01c282a..54984e1b9741 100644
--- c/drivers/firmware/arm_ffa/driver.c
+++ w/drivers/firmware/arm_ffa/driver.c
@@ -329,11 +329,9 @@ __ffa_partition_info_get(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3,
 #define PART_INFO_EXEC_CXT_MASK        GENMASK(31, 16)
 #define PART_INFO_PROPS_MASK   GENMASK(63, 32)
 #define FFA_PART_INFO_GET_REGS_FIRST_REG       3
-#define FFA_PART_INFO_GET_REGS_REGS_PER_DESC   3
-#define FFA_PART_INFO_GET_REGS_MAX_DESC \
-       (((sizeof(ffa_value_t) / sizeof_field(ffa_value_t, a0)) - \
-         FFA_PART_INFO_GET_REGS_FIRST_REG) / \
-        FFA_PART_INFO_GET_REGS_REGS_PER_DESC)
+#define FFA_PART_INFO_GET_REGS_MIN_REGS_PER_DESC       3
+#define FFA_PART_INFO_GET_REGS_NUM_REGS \
+       (sizeof(ffa_value_t) / sizeof_field(ffa_value_t, a0))
 #define PART_INFO_ID(x)                ((u16)(FIELD_GET(PART_INFO_ID_MASK, (x))))
 #define PART_INFO_EXEC_CXT(x)  ((u16)(FIELD_GET(PART_INFO_EXEC_CXT_MASK, (x))))
 #define PART_INFO_PROPERTIES(x)        ((u32)(FIELD_GET(PART_INFO_PROPS_MASK, (x))))
@@ -347,7 +345,7 @@ __ffa_partition_info_get_regs(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3,

        do {
                __le64 *regs;
-               int idx, nr_desc, buf_idx;
+               int idx, nr_desc, buf_idx, regs_per_desc, max_desc;

                invoke_ffa_fn((ffa_value_t){
                              .a0 = FFA_PARTITION_INFO_GET_REGS,
@@ -370,8 +368,18 @@ __ffa_partition_info_get_regs(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3,
                if (cur_idx < start_idx || cur_idx >= count)
                        return -EINVAL;

+               buf_sz = PARTITION_INFO_SZ(partition_info.a2);
+               if (buf_sz % sizeof(*regs))
+                       return -EINVAL;
+
+               regs_per_desc = buf_sz / sizeof(*regs);
+               if (regs_per_desc < FFA_PART_INFO_GET_REGS_MIN_REGS_PER_DESC)
+                       return -EINVAL;
+
                nr_desc = cur_idx - start_idx + 1;
-               if (nr_desc > FFA_PART_INFO_GET_REGS_MAX_DESC)
+               max_desc = (FFA_PART_INFO_GET_REGS_NUM_REGS -
+                           FFA_PART_INFO_GET_REGS_FIRST_REG) / regs_per_desc;
+               if (nr_desc > max_desc)
                        return -EINVAL;

                buf_idx = buf - buffer;
@@ -379,9 +387,6 @@ __ffa_partition_info_get_regs(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3,
                        return -EINVAL;

                tag = UUID_INFO_TAG(partition_info.a2);
-               buf_sz = PARTITION_INFO_SZ(partition_info.a2);
-               if (buf_sz > sizeof(*buffer))
-                       buf_sz = sizeof(*buffer);

                regs = (void *)&partition_info.a3;
                for (idx = 0; idx < nr_desc; idx++, buf++) {
@@ -400,7 +405,7 @@ __ffa_partition_info_get_regs(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3,
                        buf->exec_ctxt = PART_INFO_EXEC_CXT(val);
                        buf->properties = PART_INFO_PROPERTIES(val);
                        uuid_copy(&buf->uuid, &uuid_regs.uuid);
-                       regs += 3;
+                       regs += regs_per_desc;
                }
                start_idx = cur_idx + 1;
[PATCH v3] firmware: arm_ffa: honor descriptor size in PARTITION_INFO_GET_REGS
Posted by Jamie Nguyen 6 days, 11 hours ago
__ffa_partition_info_get_regs() walks the response with a hardcoded
24-byte stride (regs += 3) even though the SPMC tells us the actual
per-descriptor size via PARTITION_INFO_SZ in x2[63:48]. The size is
read into buf_sz and then thrown away.

That works while every SPMC returns the FF-A v1.1 layout, but it falls
apart against a v1.3 SPMC returning the 48-byte descriptor. The loop
strides over half a descriptor at a time and ends up parsing every
other entry from a slice of two adjacent ones.

The FF-A spec (v1.2, section 18.5) says that the producer should
report the descriptor size, and the consumer is supposed to stride by
that size and ignore any trailing fields it doesn't understand. The
non-REGS path (__ffa_partition_info_get) does this already, and the
REGS path should match.

Use buf_sz for the stride, and bail out with -EINVAL if the SPMC
reports something we can't safely walk.

Fixes: ba85c644ac8d ("firmware: arm_ffa: Add support for FFA_PARTITION_INFO_GET_REGS")
Suggested-by: Sudeep Holla <sudeep.holla@kernel.org>
Signed-off-by: Jamie Nguyen <jamien@nvidia.com>
---
Changes in v3:
- Per Sudeep's review: drop the explanatory comment and split the buf_sz
  validation into three named checks (u64 alignment, minimum size for the
  v1.1 layout we parse, fit in the x3..x17 window for nr_desc).
- Replace FFA_PART_INFO_GET_REGS_REGS_PER_DESC with
  FFA_PART_INFO_GET_REGS_MIN_REGS_PER_DESC and replace
  FFA_PART_INFO_GET_REGS_MAX_DESC with FFA_PART_INFO_GET_REGS_NUM_REGS,
  computing max_desc per call from the SPMC-reported descriptor size.
- Drop the now-redundant `if (buf_sz > sizeof(*buffer))` clamp.

Changes in v2:
- Rebase onto linux-next; reuse the
  FFA_PART_INFO_GET_REGS_{REGS_PER_DESC,MAX_DESC} macros it added instead of
  introducing new ones.
- Return -EINVAL instead of -EPROTO to match surrounding checks.
- Update Fixes: tag to the commit that introduced the hardcoded stride.
---
 drivers/firmware/arm_ffa/driver.c | 27 ++++++++++++++++-----------
 1 file changed, 16 insertions(+), 11 deletions(-)

diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c
index b9f17fda7243..cab32cfdac42 100644
--- a/drivers/firmware/arm_ffa/driver.c
+++ b/drivers/firmware/arm_ffa/driver.c
@@ -324,11 +324,9 @@ __ffa_partition_info_get(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3,
 #define PART_INFO_EXEC_CXT_MASK	GENMASK(31, 16)
 #define PART_INFO_PROPS_MASK	GENMASK(63, 32)
 #define FFA_PART_INFO_GET_REGS_FIRST_REG	3
-#define FFA_PART_INFO_GET_REGS_REGS_PER_DESC	3
-#define FFA_PART_INFO_GET_REGS_MAX_DESC \
-	(((sizeof(ffa_value_t) / sizeof_field(ffa_value_t, a0)) - \
-	  FFA_PART_INFO_GET_REGS_FIRST_REG) / \
-	 FFA_PART_INFO_GET_REGS_REGS_PER_DESC)
+#define FFA_PART_INFO_GET_REGS_MIN_REGS_PER_DESC	3
+#define FFA_PART_INFO_GET_REGS_NUM_REGS \
+	(sizeof(ffa_value_t) / sizeof_field(ffa_value_t, a0))
 #define PART_INFO_ID(x)		((u16)(FIELD_GET(PART_INFO_ID_MASK, (x))))
 #define PART_INFO_EXEC_CXT(x)	((u16)(FIELD_GET(PART_INFO_EXEC_CXT_MASK, (x))))
 #define PART_INFO_PROPERTIES(x)	((u32)(FIELD_GET(PART_INFO_PROPS_MASK, (x))))
@@ -342,7 +340,7 @@ __ffa_partition_info_get_regs(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3,
 
 	do {
 		__le64 *regs;
-		int idx, nr_desc, buf_idx;
+		int idx, nr_desc, buf_idx, regs_per_desc, max_desc;
 
 		invoke_ffa_fn((ffa_value_t){
 			      .a0 = FFA_PARTITION_INFO_GET_REGS,
@@ -365,8 +363,18 @@ __ffa_partition_info_get_regs(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3,
 		if (cur_idx < start_idx || cur_idx >= count)
 			return -EINVAL;
 
+		buf_sz = PARTITION_INFO_SZ(partition_info.a2);
+		if (buf_sz % sizeof(*regs))
+			return -EINVAL;
+
+		regs_per_desc = buf_sz / sizeof(*regs);
+		if (regs_per_desc < FFA_PART_INFO_GET_REGS_MIN_REGS_PER_DESC)
+			return -EINVAL;
+
 		nr_desc = cur_idx - start_idx + 1;
-		if (nr_desc > FFA_PART_INFO_GET_REGS_MAX_DESC)
+		max_desc = (FFA_PART_INFO_GET_REGS_NUM_REGS -
+			    FFA_PART_INFO_GET_REGS_FIRST_REG) / regs_per_desc;
+		if (nr_desc > max_desc)
 			return -EINVAL;
 
 		buf_idx = buf - buffer;
@@ -374,9 +382,6 @@ __ffa_partition_info_get_regs(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3,
 			return -EINVAL;
 
 		tag = UUID_INFO_TAG(partition_info.a2);
-		buf_sz = PARTITION_INFO_SZ(partition_info.a2);
-		if (buf_sz > sizeof(*buffer))
-			buf_sz = sizeof(*buffer);
 
 		regs = (void *)&partition_info.a3;
 		for (idx = 0; idx < nr_desc; idx++, buf++) {
@@ -395,7 +400,7 @@ __ffa_partition_info_get_regs(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3,
 			buf->exec_ctxt = PART_INFO_EXEC_CXT(val);
 			buf->properties = PART_INFO_PROPERTIES(val);
 			uuid_copy(&buf->uuid, &uuid_regs.uuid);
-			regs += 3;
+			regs += regs_per_desc;
 		}
 		start_idx = cur_idx + 1;
 

base-commit: e98d21c170b01ddef366f023bbfcf6b31509fa83
-- 
2.34.1