* [PATCH v2 0/2] ACPI: IORT: validate node and RMR extents
@ 2026-08-13 16:01 Pengpeng Hou
2026-08-13 16:04 ` [PATCH v2 1/2] ACPI: IORT: validate table and node extents before traversal Pengpeng Hou
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Pengpeng Hou @ 2026-08-13 16:01 UTC (permalink / raw)
To: Lorenzo Pieralisi, Hanjun Guo
Cc: Sudeep Holla, Catalin Marinas, Will Deacon, Rafael J. Wysocki,
Len Brown, Robin Murphy, Shameer Kolothum, Joerg Roedel,
linux-acpi, linux-arm-kernel, linux-kernel, Pengpeng Hou
The original patch combined the generic IORT node walk with the
IOMMU-specific RMR descriptor and ID-mapping arrays. This version
separates those contracts as requested.
Patch 1 validates the root node area, complete node headers and advertised
node lengths in each generic walker. Patch 2, built only with
CONFIG_IOMMU_API, validates the fixed RMR payload and both arrays before
walking them.
Changes since v1:
https://lore.kernel.org/all/20260706094300.82618-1-pengpeng@iscas.ac.cn/
- split generic traversal from RMR array validation
- validate root node_offset and node_count before pointer construction
- scope RMR helpers with their CONFIG_IOMMU_API users
- document why a zero-count array needs no offset validation
Testing used Linux 3d6d817622b0 plus this series, GCC 13.3 and QEMU
8.2.2 with a Cortex-A57 arm64 CPU model. A temporary KUnit harness in
iort.c called the actual static validation helpers; the harness is not
part of this series. The acpi_iort_bounds suite passed both tests,
covering invalid root offsets, truncated node and RMR payload extents,
and out-of-bounds RMR descriptor and ID-mapping arrays.
This did not exercise a platform-provided IORT table through the complete
boot-time discovery path.
Pengpeng Hou (2):
ACPI: IORT: validate table and node extents before traversal
ACPI: IORT: validate RMR node array extents
drivers/acpi/arm64/iort.c | 117 +++++++++++++++++++++++++++++++++++---
1 file changed, 109 insertions(+), 8 deletions(-)
base-commit: 3d6d817622b0a9721e3cc404df3469171582be13
--
2.50.1 (Apple Git-155)
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v2 1/2] ACPI: IORT: validate table and node extents before traversal
2026-08-13 16:01 [PATCH v2 0/2] ACPI: IORT: validate node and RMR extents Pengpeng Hou
@ 2026-08-13 16:04 ` Pengpeng Hou
2026-08-13 16:05 ` [PATCH v2 2/2] ACPI: IORT: validate RMR node array extents Pengpeng Hou
2026-08-13 18:35 ` [PATCH v2 0/2] ACPI: IORT: validate node and RMR extents Robin Murphy
2 siblings, 0 replies; 4+ messages in thread
From: Pengpeng Hou @ 2026-08-13 16:04 UTC (permalink / raw)
To: Lorenzo Pieralisi, Hanjun Guo
Cc: Sudeep Holla, Catalin Marinas, Will Deacon, Rafael J. Wysocki,
Len Brown, Robin Murphy, Shameer Kolothum, Joerg Roedel,
linux-acpi, linux-arm-kernel, linux-kernel, Pengpeng Hou
IORT walkers construct the first node from firmware node_offset and then
read node fields after checking only whether the current pointer reached
the table end. A bad root offset, truncated node header, or zero or
oversized node length can therefore escape the table or prevent progress.
Validate the root table and minimum node area before constructing the first
pointer. Reuse a remaining-length check in each generic IORT node walk so
every advertised node is contained before its fields are consumed.
Fixes: 88ef16d888a0 ("ACPI: I/O Remapping Table (IORT) initial support")
Assisted-by: Codex:gpt-5
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
drivers/acpi/arm64/iort.c | 64 ++++++++++++++++++++++++++++++++++-----
1 file changed, 56 insertions(+), 8 deletions(-)
diff --git a/drivers/acpi/arm64/iort.c b/drivers/acpi/arm64/iort.c
index af7a9b2fd5bc..101d54eec544 100644
--- a/drivers/acpi/arm64/iort.c
+++ b/drivers/acpi/arm64/iort.c
@@ -148,6 +148,48 @@ typedef acpi_status (*iort_find_node_callback)
/* Root pointer to the mapped IORT table */
static struct acpi_table_header *iort_table;
+static bool iort_table_valid(struct acpi_table_iort *iort)
+{
+ size_t node_bytes;
+
+ if (!iort || iort->header.length < sizeof(*iort))
+ return false;
+
+ if (!iort->node_count)
+ return true;
+
+ if (iort->node_offset < sizeof(*iort) ||
+ iort->node_offset > iort->header.length -
+ sizeof(struct acpi_iort_node))
+ return false;
+
+ node_bytes = iort->header.length - iort->node_offset;
+ return iort->node_count <=
+ node_bytes / sizeof(struct acpi_iort_node);
+}
+
+static bool iort_node_valid(struct acpi_iort_node *node,
+ struct acpi_iort_node *end)
+{
+ size_t remaining;
+
+ if (WARN_TAINT(node >= end, TAINT_FIRMWARE_WORKAROUND,
+ "IORT node pointer overflows, bad table!\n"))
+ return false;
+
+ remaining = (u8 *)end - (u8 *)node;
+ if (WARN_TAINT(remaining < sizeof(*node), TAINT_FIRMWARE_WORKAROUND,
+ "IORT node header is truncated, bad table!\n"))
+ return false;
+
+ if (WARN_TAINT(node->length < sizeof(*node) ||
+ node->length > remaining, TAINT_FIRMWARE_WORKAROUND,
+ "IORT node length overflows, bad table!\n"))
+ return false;
+
+ return true;
+}
+
static LIST_HEAD(iort_msi_chip_list);
static DEFINE_SPINLOCK(iort_msi_chip_lock);
@@ -237,14 +279,16 @@ static struct acpi_iort_node *iort_scan_node(enum acpi_iort_node_type type,
/* Get the first IORT node */
iort = (struct acpi_table_iort *)iort_table;
+ if (!iort_table_valid(iort) || !iort->node_count)
+ return NULL;
+
iort_node = ACPI_ADD_PTR(struct acpi_iort_node, iort,
iort->node_offset);
iort_end = ACPI_ADD_PTR(struct acpi_iort_node, iort_table,
iort_table->length);
for (i = 0; i < iort->node_count; i++) {
- if (WARN_TAINT(iort_node >= iort_end, TAINT_FIRMWARE_WORKAROUND,
- "IORT node pointer overflows, bad table!\n"))
+ if (!iort_node_valid(iort_node, iort_end))
return NULL;
if (iort_node->type == type &&
@@ -1168,6 +1212,8 @@ static void iort_find_rmrs(struct acpi_iort_node *iommu, struct device *dev,
return;
iort = (struct acpi_table_iort *)iort_table;
+ if (!iort_table_valid(iort) || !iort->node_count)
+ return;
iort_node = ACPI_ADD_PTR(struct acpi_iort_node, iort,
iort->node_offset);
@@ -1175,8 +1221,7 @@ static void iort_find_rmrs(struct acpi_iort_node *iommu, struct device *dev,
iort_table->length);
for (i = 0; i < iort->node_count; i++) {
- if (WARN_TAINT(iort_node >= iort_end, TAINT_FIRMWARE_WORKAROUND,
- "IORT node pointer overflows, bad table!\n"))
+ if (!iort_node_valid(iort_node, iort_end))
return;
if (iort_node->type == ACPI_IORT_NODE_RMR)
@@ -2054,6 +2099,8 @@ static void __init iort_init_platform_devices(void)
* have different struct types
*/
iort = (struct acpi_table_iort *)iort_table;
+ if (!iort_table_valid(iort) || !iort->node_count)
+ return;
/* Get the first IORT node */
iort_node = ACPI_ADD_PTR(struct acpi_iort_node, iort,
@@ -2062,10 +2109,8 @@ static void __init iort_init_platform_devices(void)
iort_table->length);
for (i = 0; i < iort->node_count; i++) {
- if (iort_node >= iort_end) {
- pr_err("iort node pointer overflows, bad table\n");
+ if (!iort_node_valid(iort_node, iort_end))
return;
- }
iort_enable_acs(iort_node);
@@ -2132,12 +2177,14 @@ phys_addr_t __init acpi_iort_dma_get_max_cpu_address(void)
(struct acpi_table_header **)&iort);
if (ACPI_FAILURE(status))
return limit;
+ if (!iort_table_valid(iort) || !iort->node_count)
+ goto out;
node = ACPI_ADD_PTR(struct acpi_iort_node, iort, iort->node_offset);
end = ACPI_ADD_PTR(struct acpi_iort_node, iort, iort->header.length);
for (i = 0; i < iort->node_count; i++) {
- if (node >= end)
+ if (!iort_node_valid(node, end))
break;
switch (node->type) {
@@ -2162,6 +2209,7 @@ phys_addr_t __init acpi_iort_dma_get_max_cpu_address(void)
}
node = ACPI_ADD_PTR(struct acpi_iort_node, node, node->length);
}
+out:
acpi_put_table(&iort->header);
return limit;
}
--
2.50.1 (Apple Git-155)
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v2 2/2] ACPI: IORT: validate RMR node array extents
2026-08-13 16:01 [PATCH v2 0/2] ACPI: IORT: validate node and RMR extents Pengpeng Hou
2026-08-13 16:04 ` [PATCH v2 1/2] ACPI: IORT: validate table and node extents before traversal Pengpeng Hou
@ 2026-08-13 16:05 ` Pengpeng Hou
2026-08-13 18:35 ` [PATCH v2 0/2] ACPI: IORT: validate node and RMR extents Robin Murphy
2 siblings, 0 replies; 4+ messages in thread
From: Pengpeng Hou @ 2026-08-13 16:05 UTC (permalink / raw)
To: Lorenzo Pieralisi, Hanjun Guo
Cc: Sudeep Holla, Catalin Marinas, Will Deacon, Rafael J. Wysocki,
Len Brown, Robin Murphy, Shameer Kolothum, Joerg Roedel,
linux-acpi, linux-arm-kernel, linux-kernel, Pengpeng Hou
IORT RMR nodes carry offsets and counts for reserved-memory descriptors and
ID mappings. iort_node_get_rmr_info() trusts both arrays and later loops
over the firmware counts without proving that either array fits in the
containing node.
Require the fixed RMR payload, then validate each non-empty array with
checked multiplication and subtraction-based bounds before constructing
an element pointer. Keep the helpers under CONFIG_IOMMU_API with their
users.
Fixes: 491cf4a6735a ("ACPI/IORT: Add support to retrieve IORT RMR reserved regions")
Assisted-by: Codex:gpt-5
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
drivers/acpi/arm64/iort.c | 53 +++++++++++++++++++++++++++++++++++++++
1 file changed, 53 insertions(+)
diff --git a/drivers/acpi/arm64/iort.c b/drivers/acpi/arm64/iort.c
index 101d54eec544..17d904f4c1ee 100644
--- a/drivers/acpi/arm64/iort.c
+++ b/drivers/acpi/arm64/iort.c
@@ -15,6 +15,7 @@
#include <linux/iommu.h>
#include <linux/kernel.h>
#include <linux/list.h>
+#include <linux/overflow.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
@@ -962,6 +963,48 @@ void acpi_configure_pmsi_domain(struct device *dev)
}
#ifdef CONFIG_IOMMU_API
+static bool iort_node_array_valid(struct acpi_iort_node *node, u32 offset,
+ u32 count, size_t elem_size,
+ size_t min_offset, const char *name)
+{
+ size_t bytes;
+
+ /* An empty array has no elements to access, regardless of its offset. */
+ if (!count)
+ return true;
+
+ if (!offset || offset < min_offset || offset > node->length) {
+ pr_err(FW_BUG "Invalid %s offset in IORT node %p\n", name,
+ node);
+ return false;
+ }
+
+ if (check_mul_overflow(count, elem_size, &bytes) ||
+ bytes > node->length - offset) {
+ pr_err(FW_BUG "Invalid %s array in IORT node %p\n", name,
+ node);
+ return false;
+ }
+
+ return true;
+}
+
+static bool iort_rmr_node_valid(struct acpi_iort_node *node)
+{
+ struct acpi_iort_rmr *rmr;
+
+ if (node->length < sizeof(*node) + sizeof(*rmr)) {
+ pr_err(FW_BUG "Truncated RMR node in IORT table\n");
+ return false;
+ }
+
+ rmr = (struct acpi_iort_rmr *)node->node_data;
+ return iort_node_array_valid(node, rmr->rmr_offset, rmr->rmr_count,
+ sizeof(struct acpi_iort_rmr_desc),
+ sizeof(*node) + sizeof(*rmr),
+ "RMR descriptor");
+}
+
static void iort_rmr_free(struct device *dev,
struct iommu_resv_region *region)
{
@@ -1152,12 +1195,22 @@ static void iort_node_get_rmr_info(struct acpi_iort_node *node,
u32 num_sids = 0;
int i;
+ if (!iort_rmr_node_valid(node))
+ return;
+
if (!node->mapping_offset || !node->mapping_count) {
pr_err(FW_BUG "Invalid ID mapping, skipping RMR node %p\n",
node);
return;
}
+ if (!iort_node_array_valid(node, node->mapping_offset,
+ node->mapping_count,
+ sizeof(struct acpi_iort_id_mapping),
+ sizeof(*node) + sizeof(*rmr),
+ "ID mapping"))
+ return;
+
rmr = (struct acpi_iort_rmr *)node->node_data;
if (!rmr->rmr_offset || !rmr->rmr_count)
return;
--
2.50.1 (Apple Git-155)
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH v2 0/2] ACPI: IORT: validate node and RMR extents
2026-08-13 16:01 [PATCH v2 0/2] ACPI: IORT: validate node and RMR extents Pengpeng Hou
2026-08-13 16:04 ` [PATCH v2 1/2] ACPI: IORT: validate table and node extents before traversal Pengpeng Hou
2026-08-13 16:05 ` [PATCH v2 2/2] ACPI: IORT: validate RMR node array extents Pengpeng Hou
@ 2026-08-13 18:35 ` Robin Murphy
2 siblings, 0 replies; 4+ messages in thread
From: Robin Murphy @ 2026-08-13 18:35 UTC (permalink / raw)
To: Pengpeng Hou, Lorenzo Pieralisi, Hanjun Guo
Cc: Sudeep Holla, Catalin Marinas, Will Deacon, Rafael J. Wysocki,
Len Brown, Shameer Kolothum, Joerg Roedel, linux-acpi,
linux-arm-kernel, linux-kernel
On 13/08/2026 5:01 pm, Pengpeng Hou wrote:
> The original patch combined the generic IORT node walk with the
> IOMMU-specific RMR descriptor and ID-mapping arrays. This version
> separates those contracts as requested.
>
> Patch 1 validates the root node area, complete node headers and advertised
> node lengths in each generic walker. Patch 2, built only with
> CONFIG_IOMMU_API, validates the fixed RMR payload and both arrays before
> walking them.
But why? If a platform really did have an IORT that's so obviously
malformed or corrupted, why would we assume that all its other ACPI
tables are valid enough to even be able to boot as far as running this
code? Or conversely, what if the table does happen to have the correct
structure but is still full of nonsense such that we end up exploding
later when a driver touches bogus iomem?
Mainline Linux is not a firmware validation suite; if we can't assume
basic stuff like table headers are correct then there's basically not
much point in even trying. Also it is highly unlikely that people who
write firmware would ship it without doing any testing; if Linux crashes
horribly the first time they try to boot their new build, they're
probably going to notice that something's wrong. However, from
experience, if Linux does successfully boot to a shell then they may
assume it's fine and ship it even if the IORT node ID mapping offsets
are bogus so MSIs don't work and devices have fallen back to legacy
interrupts...
Thus if anything there's an argument to be made that the more we try to
be robust against unreasonably broken firmware, the more unreasonably
broken firmware will propagate into the wild. And if you think people
will care about kernel warnings, try looking at the boot logs of just
about any production Android device ;)
Thanks,
Robin.
>
> Changes since v1:
> https://lore.kernel.org/all/20260706094300.82618-1-pengpeng@iscas.ac.cn/
> - split generic traversal from RMR array validation
> - validate root node_offset and node_count before pointer construction
> - scope RMR helpers with their CONFIG_IOMMU_API users
> - document why a zero-count array needs no offset validation
>
> Testing used Linux 3d6d817622b0 plus this series, GCC 13.3 and QEMU
> 8.2.2 with a Cortex-A57 arm64 CPU model. A temporary KUnit harness in
> iort.c called the actual static validation helpers; the harness is not
> part of this series. The acpi_iort_bounds suite passed both tests,
> covering invalid root offsets, truncated node and RMR payload extents,
> and out-of-bounds RMR descriptor and ID-mapping arrays.
>
> This did not exercise a platform-provided IORT table through the complete
> boot-time discovery path.
>
> Pengpeng Hou (2):
> ACPI: IORT: validate table and node extents before traversal
> ACPI: IORT: validate RMR node array extents
>
> drivers/acpi/arm64/iort.c | 117 +++++++++++++++++++++++++++++++++++---
> 1 file changed, 109 insertions(+), 8 deletions(-)
>
>
> base-commit: 3d6d817622b0a9721e3cc404df3469171582be13
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-08-13 18:35 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-08-13 16:01 [PATCH v2 0/2] ACPI: IORT: validate node and RMR extents Pengpeng Hou
2026-08-13 16:04 ` [PATCH v2 1/2] ACPI: IORT: validate table and node extents before traversal Pengpeng Hou
2026-08-13 16:05 ` [PATCH v2 2/2] ACPI: IORT: validate RMR node array extents Pengpeng Hou
2026-08-13 18:35 ` [PATCH v2 0/2] ACPI: IORT: validate node and RMR extents Robin Murphy
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®