[PATCH] ACPI: RHCT: validate node reference bounds

Pengpeng Hou pengpeng at iscas.ac.cn
Mon Jul 6 02:46:19 PDT 2026


RHCT hart-info nodes carry offset arrays that reference other
nodes in the same firmware table.  Validate the current node,
the hart-info offset array, and each referenced ISA or CMO node
before reading type-specific fields.

The ISA string path also checks that the advertised string range
is present before returning it to the RISC-V ISA parser.

Signed-off-by: Pengpeng Hou <pengpeng at iscas.ac.cn>
---
 drivers/acpi/riscv/rhct.c | 146 +++++++++++++++++++++++++++++++++-----
 1 file changed, 128 insertions(+), 18 deletions(-)

diff --git a/drivers/acpi/riscv/rhct.c b/drivers/acpi/riscv/rhct.c
index 8f3f38c64a88..bb8f8d4ab137 100644
--- a/drivers/acpi/riscv/rhct.c
+++ b/drivers/acpi/riscv/rhct.c
@@ -9,6 +9,94 @@
 
 #include <linux/acpi.h>
 #include <linux/bits.h>
+#include <linux/overflow.h>
+#include <linux/string.h>
+
+static bool rhct_node_valid(struct acpi_table_rhct *rhct,
+			    struct acpi_rhct_node_header *node)
+{
+	struct acpi_rhct_node_header *end;
+	size_t remaining;
+
+	end = ACPI_ADD_PTR(struct acpi_rhct_node_header, rhct,
+			   rhct->header.length);
+	if (node >= end)
+		return false;
+
+	remaining = (char *)end - (char *)node;
+	if (remaining < sizeof(*node) ||
+	    node->length < sizeof(*node) ||
+	    node->length > remaining) {
+		pr_err(FW_BUG "Invalid RHCT node length\n");
+		return false;
+	}
+
+	return true;
+}
+
+static struct acpi_rhct_node_header *rhct_node_from_offset(struct acpi_table_rhct *rhct,
+							   u32 offset)
+{
+	struct acpi_rhct_node_header *node;
+
+	if (offset > rhct->header.length)
+		return NULL;
+
+	node = ACPI_ADD_PTR(struct acpi_rhct_node_header, rhct, offset);
+	return rhct_node_valid(rhct, node) ? node : NULL;
+}
+
+static bool rhct_node_has_data(struct acpi_rhct_node_header *node,
+			       size_t data_size)
+{
+	if (node->length < sizeof(*node) + data_size) {
+		pr_err(FW_BUG "Truncated RHCT node type %u\n", node->type);
+		return false;
+	}
+
+	return true;
+}
+
+static bool rhct_hart_info_valid(struct acpi_rhct_node_header *node)
+{
+	struct acpi_rhct_hart_info *hart_info;
+	size_t offsets_size;
+
+	if (!rhct_node_has_data(node, sizeof(*hart_info)))
+		return false;
+
+	hart_info = ACPI_ADD_PTR(struct acpi_rhct_hart_info, node,
+				 sizeof(*node));
+	if (check_mul_overflow(hart_info->num_offsets, sizeof(u32),
+			       &offsets_size) ||
+	    offsets_size > node->length - sizeof(*node) -
+			   sizeof(*hart_info)) {
+		pr_err(FW_BUG "Invalid RHCT hart-info offset array\n");
+		return false;
+	}
+
+	return true;
+}
+
+static bool rhct_isa_string_valid(struct acpi_rhct_node_header *node)
+{
+	struct acpi_rhct_isa_string *isa_node;
+	size_t remaining;
+
+	if (!rhct_node_has_data(node, sizeof(*isa_node)))
+		return false;
+
+	isa_node = ACPI_ADD_PTR(struct acpi_rhct_isa_string, node,
+				sizeof(*node));
+	remaining = node->length - sizeof(*node) - sizeof(*isa_node);
+	if (isa_node->isa_length > remaining ||
+	    !memchr(isa_node->isa, '\0', isa_node->isa_length)) {
+		pr_err(FW_BUG "Invalid RHCT ISA string\n");
+		return false;
+	}
+
+	return true;
+}
 
 static struct acpi_table_rhct *acpi_get_rhct(void)
 {
@@ -38,8 +126,6 @@ static struct acpi_table_rhct *acpi_get_rhct(void)
 int acpi_get_riscv_isa(struct acpi_table_header *table, unsigned int cpu, const char **isa)
 {
 	struct acpi_rhct_node_header *node, *ref_node, *end;
-	u32 size_hdr = sizeof(struct acpi_rhct_node_header);
-	u32 size_hartinfo = sizeof(struct acpi_rhct_hart_info);
 	struct acpi_rhct_hart_info *hart_info;
 	struct acpi_rhct_isa_string *isa_node;
 	struct acpi_table_rhct *rhct;
@@ -66,18 +152,32 @@ int acpi_get_riscv_isa(struct acpi_table_header *table, unsigned int cpu, const
 	for (node = ACPI_ADD_PTR(struct acpi_rhct_node_header, rhct, rhct->node_offset);
 	     node < end;
 	     node = ACPI_ADD_PTR(struct acpi_rhct_node_header, node, node->length)) {
+		if (!rhct_node_valid(rhct, node))
+			return -EINVAL;
+
 		if (node->type == ACPI_RHCT_NODE_TYPE_HART_INFO) {
-			hart_info = ACPI_ADD_PTR(struct acpi_rhct_hart_info, node, size_hdr);
-			hart_info_node_offset = ACPI_ADD_PTR(u32, hart_info, size_hartinfo);
+			if (!rhct_hart_info_valid(node))
+				return -EINVAL;
+
+			hart_info = ACPI_ADD_PTR(struct acpi_rhct_hart_info, node,
+						 sizeof(*node));
+			hart_info_node_offset = ACPI_ADD_PTR(u32, hart_info,
+							     sizeof(*hart_info));
 			if (acpi_cpu_id != hart_info->uid)
 				continue;
 
 			for (int i = 0; i < hart_info->num_offsets; i++) {
-				ref_node = ACPI_ADD_PTR(struct acpi_rhct_node_header,
-							rhct, hart_info_node_offset[i]);
+				ref_node = rhct_node_from_offset(rhct,
+								 hart_info_node_offset[i]);
+				if (!ref_node)
+					return -EINVAL;
+
 				if (ref_node->type == ACPI_RHCT_NODE_TYPE_ISA_STRING) {
+					if (!rhct_isa_string_valid(ref_node))
+						return -EINVAL;
+
 					isa_node = ACPI_ADD_PTR(struct acpi_rhct_isa_string,
-								ref_node, size_hdr);
+								ref_node, sizeof(*ref_node));
 					*isa = isa_node->isa;
 					return 0;
 				}
@@ -89,22 +189,32 @@ int acpi_get_riscv_isa(struct acpi_table_header *table, unsigned int cpu, const
 }
 
 static void acpi_parse_hart_info_cmo_node(struct acpi_table_rhct *rhct,
-					  struct acpi_rhct_hart_info *hart_info,
+					  struct acpi_rhct_node_header *node,
 					  u32 *cbom_size, u32 *cboz_size, u32 *cbop_size)
 {
-	u32 size_hartinfo = sizeof(struct acpi_rhct_hart_info);
-	u32 size_hdr = sizeof(struct acpi_rhct_node_header);
 	struct acpi_rhct_node_header *ref_node;
+	struct acpi_rhct_hart_info *hart_info;
 	struct acpi_rhct_cmo_node *cmo_node;
 	u32 *hart_info_node_offset;
 
-	hart_info_node_offset = ACPI_ADD_PTR(u32, hart_info, size_hartinfo);
+	if (!rhct_hart_info_valid(node))
+		return;
+
+	hart_info = ACPI_ADD_PTR(struct acpi_rhct_hart_info, node,
+				 sizeof(*node));
+	hart_info_node_offset = ACPI_ADD_PTR(u32, hart_info,
+					     sizeof(*hart_info));
 	for (int i = 0; i < hart_info->num_offsets; i++) {
-		ref_node = ACPI_ADD_PTR(struct acpi_rhct_node_header,
-					rhct, hart_info_node_offset[i]);
+		ref_node = rhct_node_from_offset(rhct, hart_info_node_offset[i]);
+		if (!ref_node)
+			return;
+
 		if (ref_node->type == ACPI_RHCT_NODE_TYPE_CMO) {
+			if (!rhct_node_has_data(ref_node, sizeof(*cmo_node)))
+				return;
+
 			cmo_node = ACPI_ADD_PTR(struct acpi_rhct_cmo_node,
-						ref_node, size_hdr);
+						ref_node, sizeof(*ref_node));
 			if (cbom_size && cmo_node->cbom_size <= 30) {
 				if (!*cbom_size)
 					*cbom_size = BIT(cmo_node->cbom_size);
@@ -137,9 +247,7 @@ static void acpi_parse_hart_info_cmo_node(struct acpi_table_rhct *rhct,
 void acpi_get_cbo_block_size(struct acpi_table_header *table, u32 *cbom_size,
 			     u32 *cboz_size, u32 *cbop_size)
 {
-	u32 size_hdr = sizeof(struct acpi_rhct_node_header);
 	struct acpi_rhct_node_header *node, *end;
-	struct acpi_rhct_hart_info *hart_info;
 	struct acpi_table_rhct *rhct;
 
 	if (acpi_disabled)
@@ -166,9 +274,11 @@ void acpi_get_cbo_block_size(struct acpi_table_header *table, u32 *cbom_size,
 	for (node = ACPI_ADD_PTR(struct acpi_rhct_node_header, rhct, rhct->node_offset);
 	     node < end;
 	     node = ACPI_ADD_PTR(struct acpi_rhct_node_header, node, node->length)) {
+		if (!rhct_node_valid(rhct, node))
+			return;
+
 		if (node->type == ACPI_RHCT_NODE_TYPE_HART_INFO) {
-			hart_info = ACPI_ADD_PTR(struct acpi_rhct_hart_info, node, size_hdr);
-			acpi_parse_hart_info_cmo_node(rhct, hart_info, cbom_size,
+			acpi_parse_hart_info_cmo_node(rhct, node, cbom_size,
 						      cboz_size, cbop_size);
 		}
 	}
-- 
2.43.0




More information about the linux-riscv mailing list