[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