[PATCH 1/2] x86/setup_data: validate indirect entry sizes before dereferencing them

From: Pengpeng Hou

Date: Sat Apr 04 2026 - 09:56:12 EST


Several x86 setup_data consumers treat SETUP_INDIRECT entries as though
struct setup_indirect is always fully present once the outer setup_data
header has been read.

That assumption is too strong. A malformed boot-time setup_data entry can
carry a short or overflowing data->len, causing the kernel to remap less
than a full indirect header and then dereference indirect->type, addr, or
len outside the mapped range.

Add a small shared helper for validating setup_data entry sizes, and use it
before remapping or dereferencing indirect payloads in the runtime x86
setup_data readers. When an indirect entry is malformed, keep treating the
outer setup_data payload as opaque data instead of walking the embedded
header.

For setup_data reservation, still reserve the outer entry header before
skipping malformed oversized payloads so early allocations cannot overlap
the bootloader metadata itself.

Signed-off-by: Pengpeng Hou <pengpeng@xxxxxxxxxxx>
---
arch/x86/include/asm/setup.h | 17 +++++++++++++++++
arch/x86/kernel/kdebugfs.c | 5 ++---
arch/x86/kernel/ksysfs.c | 16 +++++++---------
arch/x86/kernel/setup.c | 16 +++++++++++-----
arch/x86/mm/ioremap.c | 5 ++---
5 files changed, 39 insertions(+), 20 deletions(-)

diff --git a/arch/x86/include/asm/setup.h b/arch/x86/include/asm/setup.h
index 914eb32581c7..e309e3e9586c 100644
--- a/arch/x86/include/asm/setup.h
+++ b/arch/x86/include/asm/setup.h
@@ -29,6 +29,7 @@

#ifndef __ASSEMBLER__
#include <linux/cache.h>
+#include <linux/overflow.h>

#include <asm/bootparam.h>
#include <asm/x86_init.h>
@@ -82,6 +83,22 @@ static inline void x86_ce4100_early_setup(void) { }
extern struct boot_params boot_params;
extern char _text[];

+static inline bool setup_data_entry_size(u32 data_len, size_t *size)
+{
+ return !check_add_overflow(sizeof(struct setup_data), (size_t)data_len,
+ size);
+}
+
+static inline bool setup_data_indirect_valid(const struct setup_data *data,
+ size_t *size)
+{
+ if (data->type != SETUP_INDIRECT)
+ return false;
+ if (data->len < sizeof(struct setup_indirect))
+ return false;
+ return setup_data_entry_size(data->len, size);
+}
+
static inline bool kaslr_enabled(void)
{
return IS_ENABLED(CONFIG_RANDOMIZE_MEMORY) &&
diff --git a/arch/x86/kernel/kdebugfs.c b/arch/x86/kernel/kdebugfs.c
index e14ace32009f..31c15342b4cb 100644
--- a/arch/x86/kernel/kdebugfs.c
+++ b/arch/x86/kernel/kdebugfs.c
@@ -94,7 +94,7 @@ static int __init create_setup_data_nodes(struct dentry *parent)
u64 pa_data, pa_next;
struct dentry *d;
int error;
- u32 len;
+ size_t len;
int no = 0;

d = debugfs_create_dir("setup_data", parent);
@@ -116,8 +116,7 @@ static int __init create_setup_data_nodes(struct dentry *parent)
}
pa_next = data->next;

- if (data->type == SETUP_INDIRECT) {
- len = sizeof(*data) + data->len;
+ if (setup_data_indirect_valid(data, &len)) {
memunmap(data);
data = memremap(pa_data, len, MEMREMAP_WB);
if (!data) {
diff --git a/arch/x86/kernel/ksysfs.c b/arch/x86/kernel/ksysfs.c
index 1a6e1f89f294..eacd13892926 100644
--- a/arch/x86/kernel/ksysfs.c
+++ b/arch/x86/kernel/ksysfs.c
@@ -95,7 +95,7 @@ static int __init get_setup_data_size(int nr, size_t *size)
struct setup_indirect *indirect;
struct setup_data *data;
int i = 0;
- u32 len;
+ size_t len;

while (pa_data) {
data = memremap(pa_data, sizeof(*data), MEMREMAP_WB);
@@ -104,8 +104,7 @@ static int __init get_setup_data_size(int nr, size_t *size)
pa_next = data->next;

if (nr == i) {
- if (data->type == SETUP_INDIRECT) {
- len = sizeof(*data) + data->len;
+ if (setup_data_indirect_valid(data, &len)) {
memunmap(data);
data = memremap(pa_data, len, MEMREMAP_WB);
if (!data)
@@ -139,7 +138,7 @@ static ssize_t type_show(struct kobject *kobj,
struct setup_data *data;
int nr, ret;
u64 paddr;
- u32 len;
+ size_t len;

ret = kobj_to_setup_data_nr(kobj, &nr);
if (ret)
@@ -152,8 +151,7 @@ static ssize_t type_show(struct kobject *kobj,
if (!data)
return -ENOMEM;

- if (data->type == SETUP_INDIRECT) {
- len = sizeof(*data) + data->len;
+ if (setup_data_indirect_valid(data, &len)) {
memunmap(data);
data = memremap(paddr, len, MEMREMAP_WB);
if (!data)
@@ -179,7 +177,8 @@ static ssize_t setup_data_data_read(struct file *fp,
struct setup_indirect *indirect;
struct setup_data *data;
int nr, ret = 0;
- u64 paddr, len;
+ u64 paddr;
+ size_t len;
void *p;

ret = kobj_to_setup_data_nr(kobj, &nr);
@@ -193,8 +192,7 @@ static ssize_t setup_data_data_read(struct file *fp,
if (!data)
return -ENOMEM;

- if (data->type == SETUP_INDIRECT) {
- len = sizeof(*data) + data->len;
+ if (setup_data_indirect_valid(data, &len)) {
memunmap(data);
data = memremap(paddr, len, MEMREMAP_WB);
if (!data)
diff --git a/arch/x86/kernel/setup.c b/arch/x86/kernel/setup.c
index eebcc9db1a1b..c66e337323a6 100644
--- a/arch/x86/kernel/setup.c
+++ b/arch/x86/kernel/setup.c
@@ -570,7 +570,7 @@ static void __init memblock_x86_reserve_range_setup_data(void)
struct setup_indirect *indirect;
struct setup_data *data;
u64 pa_data, pa_next;
- u32 len;
+ size_t len, data_len;

pa_data = boot_params.hdr.setup_data;
while (pa_data) {
@@ -580,13 +580,19 @@ static void __init memblock_x86_reserve_range_setup_data(void)
return;
}

- len = sizeof(*data);
pa_next = data->next;
+ if (!setup_data_entry_size(data->len, &data_len)) {
+ memblock_reserve_kern(pa_data, sizeof(*data));
+ pr_warn("setup: ignoring setup_data entry with oversized length\n");
+ early_memunmap(data, sizeof(*data));
+ pa_data = pa_next;
+ continue;
+ }

- memblock_reserve_kern(pa_data, sizeof(*data) + data->len);
+ len = sizeof(*data);
+ memblock_reserve_kern(pa_data, data_len);

- if (data->type == SETUP_INDIRECT) {
- len += data->len;
+ if (setup_data_indirect_valid(data, &len)) {
early_memunmap(data, sizeof(*data));
data = early_memremap(pa_data, len);
if (!data) {
diff --git a/arch/x86/mm/ioremap.c b/arch/x86/mm/ioremap.c
index 12c8180ca1ba..8a0b55763d65 100644
--- a/arch/x86/mm/ioremap.c
+++ b/arch/x86/mm/ioremap.c
@@ -647,7 +647,7 @@ static bool __ref __memremap_is_setup_data(resource_size_t phys_addr, bool early

paddr = boot_params.hdr.setup_data;
while (paddr) {
- unsigned int len, size;
+ size_t len, size;

if (phys_addr == paddr)
return true;
@@ -675,8 +675,7 @@ static bool __ref __memremap_is_setup_data(resource_size_t phys_addr, bool early
return true;
}

- if (data->type == SETUP_INDIRECT) {
- size += len;
+ if (setup_data_indirect_valid(data, &size)) {
if (early) {
early_memunmap(data, setup_data_sz);
data = early_memremap_decrypted(paddr, size);
--
2.50.1 (Apple Git-155)