* [PATCH v4 1/5] mm/memory-failure: efi: add the LINUX_EFI_POISONED_MEMORY configuration table
2026-09-09 13:05 [PATCH v4 0/5] mm/memory-failure: keep hardware-poisoned pages out of the next kexec Breno Leitao
@ 2026-09-09 13:05 ` Breno Leitao
2026-09-09 13:13 ` sashiko-bot
2026-09-09 13:05 ` [PATCH v4 2/5] mm/memory-failure: libstub: install the poisoned-memory EFI table Breno Leitao
` (3 subsequent siblings)
4 siblings, 1 reply; 15+ messages in thread
From: Breno Leitao @ 2026-09-09 13:05 UTC (permalink / raw)
To: Ard Biesheuvel, Ilias Apalodimas, Miaohe Lin, Naoya Horiguchi,
Andrew Morton, kas, kexec, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin,
Brendan Jackman, Johannes Weiner, Zi Yan
Cc: linux-efi, linux-kernel, linux-mm, rmikey, riel, Breno Leitao,
harry, kernel-team
Hardware-poisoned page frames are tracked only in the running kernel's
data structures, so a kexec loses them and the next kernel doesn't have
this information, thus, tripping into them again.
Add an EFI configuration table to carry that information across kexec.
It is a bitmap with one bit per EFI_POISON_UNIT_SIZE (2MiB) of physical
memory starting at phys_base, modeled on the LINUX_EFI_UNACCEPTED_MEMORY
table, and it rides the EFI system table to every kernel in the chain.
Basing the bitmap keeps a machine whose RAM starts high from paying for
the hole below it.
List the table in the x86 efi_tables[] too, so an SME host maps it
unencrypted like every other EFI table.
The Kconfig symbol has no prompt. There is nothing for a user to decide,
so it is on wherever it can be, and it only costs 64K per TiB of RAM on
a kernel that already has the EFI stub and MEMORY_FAILURE. It is
restricted to 64BIT because the unit arithmetic would need div_u64() on
32-bit, and there is no 32-bit EFI configuration with MEMORY_FAILURE to
test that on.
This is a similar approach as used as unaccepted memory.
Suggested-by: Kiryl Shutsemau <kas@kernel.org>
Signed-off-by: Breno Leitao <leitao@debian.org>
---
arch/x86/platform/efi/efi.c | 3 +++
drivers/firmware/efi/Kconfig | 8 ++++++++
drivers/firmware/efi/efi.c | 6 ++++++
include/linux/efi.h | 14 ++++++++++++++
4 files changed, 31 insertions(+)
diff --git a/arch/x86/platform/efi/efi.c b/arch/x86/platform/efi/efi.c
index 0c39adb96b912..2b37b96a36e09 100644
--- a/arch/x86/platform/efi/efi.c
+++ b/arch/x86/platform/efi/efi.c
@@ -93,6 +93,9 @@ static const unsigned long * const efi_tables[] = {
#ifdef CONFIG_UNACCEPTED_MEMORY
&efi.unaccepted,
#endif
+#ifdef CONFIG_EFI_POISONED_MEMORY
+ &efi.poisoned_memory,
+#endif
};
u64 efi_setup; /* efi setup_data physical address */
diff --git a/drivers/firmware/efi/Kconfig b/drivers/firmware/efi/Kconfig
index 29e0729299f5b..aafcd41bc0063 100644
--- a/drivers/firmware/efi/Kconfig
+++ b/drivers/firmware/efi/Kconfig
@@ -263,6 +263,14 @@ config EFI_COCO_SECRET
virt/coco/efi_secret module to access the secrets, which in turn
allows userspace programs to access the injected secrets.
+config EFI_POISONED_MEMORY
+ def_bool y
+ depends on EFI_STUB && MEMORY_FAILURE && 64BIT
+ help
+ Record page frames that are hardware-poisoned while this kernel runs
+ into an EFI configuration table, and honor that table early on the
+ next kernel so a kexec does not hand known-bad RAM back out.
+
config OVMF_DEBUG_LOG
bool "Expose OVMF firmware debug log via sysfs"
depends on EFI
diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c
index 6d987d7f97781..af1fa443839c4 100644
--- a/drivers/firmware/efi/efi.c
+++ b/drivers/firmware/efi/efi.c
@@ -55,6 +55,9 @@ struct efi __read_mostly efi = {
#ifdef CONFIG_UNACCEPTED_MEMORY
.unaccepted = EFI_INVALID_TABLE_ADDR,
#endif
+#ifdef CONFIG_EFI_POISONED_MEMORY
+ .poisoned_memory = EFI_INVALID_TABLE_ADDR,
+#endif
};
EXPORT_SYMBOL(efi);
@@ -677,6 +680,9 @@ static const efi_config_table_type_t common_tables[] __initconst = {
#ifdef CONFIG_UNACCEPTED_MEMORY
{LINUX_EFI_UNACCEPTED_MEM_TABLE_GUID, &efi.unaccepted, "Unaccepted" },
#endif
+#ifdef CONFIG_EFI_POISONED_MEMORY
+ {LINUX_EFI_POISONED_MEMORY_TABLE_GUID, &efi.poisoned_memory, "POISON" },
+#endif
#ifdef CONFIG_EFI_GENERIC_STUB
{LINUX_EFI_PRIMARY_DISPLAY_TABLE_GUID, &primary_display_table },
#endif
diff --git a/include/linux/efi.h b/include/linux/efi.h
index c35446a0b66fa..efaf63f9a54ed 100644
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -23,6 +23,7 @@
#include <linux/pstore.h>
#include <linux/range.h>
#include <linux/reboot.h>
+#include <linux/sizes.h>
#include <linux/uuid.h>
#include <asm/page.h>
@@ -422,6 +423,7 @@ void efi_native_runtime_setup(void);
#define LINUX_EFI_COCO_SECRET_AREA_GUID EFI_GUID(0xadf956ad, 0xe98c, 0x484c, 0xae, 0x11, 0xb5, 0x1c, 0x7d, 0x33, 0x64, 0x47)
#define LINUX_EFI_BOOT_MEMMAP_GUID EFI_GUID(0x800f683f, 0xd08b, 0x423a, 0xa2, 0x93, 0x96, 0x5c, 0x3c, 0x6f, 0xe2, 0xb4)
#define LINUX_EFI_UNACCEPTED_MEM_TABLE_GUID EFI_GUID(0xd5d1de3c, 0x105c, 0x44f9, 0x9e, 0xa9, 0xbc, 0xef, 0x98, 0x12, 0x00, 0x31)
+#define LINUX_EFI_POISONED_MEMORY_TABLE_GUID EFI_GUID(0xaf828a15, 0x0ef4, 0x439a, 0xb8, 0x6a, 0xd6, 0xd6, 0x9e, 0xaf, 0xba, 0xfa)
#define RISCV_EFI_BOOT_PROTOCOL_GUID EFI_GUID(0xccd15fec, 0x6f73, 0x4eec, 0x83, 0x95, 0x3e, 0x69, 0xe4, 0xb9, 0x40, 0xbf)
@@ -650,6 +652,7 @@ extern struct efi {
unsigned long mokvar_table; /* MOK variable config table */
unsigned long coco_secret; /* Confidential computing secret table */
unsigned long unaccepted; /* Unaccepted memory table */
+ unsigned long poisoned_memory; /* Hardware-poisoned memory table */
efi_get_time_t *get_time;
efi_set_time_t *set_time;
@@ -1272,6 +1275,17 @@ struct linux_efi_memreserve {
#define EFI_MEMRESERVE_COUNT(size) (((size) - sizeof(struct linux_efi_memreserve)) \
/ sizeof_field(struct linux_efi_memreserve, entry[0]))
+/* Bit N covers the unit at @phys_base + N * @unit_size. */
+struct linux_efi_poisoned_memory {
+ u32 version;
+ u32 unit_size; /* bytes of phys space per bitmap bit */
+ u64 phys_base; /* address the first bit covers */
+ u64 size; /* bitmap size in bytes */
+ unsigned long bitmap[];
+};
+
+#define EFI_POISON_UNIT_SIZE SZ_2M
+
void __init efi_arch_mem_reserve(phys_addr_t addr, u64 size);
/*
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH v4 1/5] mm/memory-failure: efi: add the LINUX_EFI_POISONED_MEMORY configuration table
2026-09-09 13:05 ` [PATCH v4 1/5] mm/memory-failure: efi: add the LINUX_EFI_POISONED_MEMORY configuration table Breno Leitao
@ 2026-09-09 13:13 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-09-09 13:13 UTC (permalink / raw)
To: Andrew Morton, Ingo Molnar, Borislav Petkov, Michal Hocko,
Zi Yan, H. Peter Anvin, Dave Hansen, Naoya Horiguchi, Miaohe Lin,
Mike Rapoport, kas, Suren Baghdasaryan, x86, Vlastimil Babka,
Brendan Jackman, Johannes Weiner, Lorenzo Stoakes,
Ard Biesheuvel, David Hildenbrand, Liam R. Howlett,
Thomas Gleixner, Ilias Apalodimas, Breno Leitao
Cc: rmikey, linux-kernel, riel, harry, linux-efi, kernel-team, kexec,
linux-mm
> Hardware-poisoned page frames are tracked only in the running kernel's
> data structures, so a kexec loses them and the next kernel doesn't have
> this information, thus, tripping into them again.
>
> Add an EFI configuration table to carry that information across kexec.
>
> It is a bitmap with one bit per EFI_POISON_UNIT_SIZE (2MiB) of physical
> memory starting at phys_base, modeled on the LINUX_EFI_UNACCEPTED_MEMORY
> table, and it rides the EFI system table to every kernel in the chain.
> Basing the bitmap keeps a machine whose RAM starts high from paying for
> the hole below it.
>
> List the table in the x86 efi_tables[] too, so an SME host maps it
> unencrypted like every other EFI table.
>
> The Kconfig symbol has no prompt. There is nothing for a user to decide,
> so it is on wherever it can be, and it only costs 64K per TiB of RAM on
> a kernel that already has the EFI stub and MEMORY_FAILURE. It is
> restricted to 64BIT because the unit arithmetic would need div_u64() on
> 32-bit, and there is no 32-bit EFI configuration with MEMORY_FAILURE to
> test that on.
>
> This is a similar approach as used as unaccepted memory.
>
> Suggested-by: Kiryl Shutsemau <kas@kernel.org>
> Signed-off-by: Breno Leitao <leitao@debian.org>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260909-hwpoison-kho-v4-0-359313564495@debian.org?part=1
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v4 2/5] mm/memory-failure: libstub: install the poisoned-memory EFI table
2026-09-09 13:05 [PATCH v4 0/5] mm/memory-failure: keep hardware-poisoned pages out of the next kexec Breno Leitao
2026-09-09 13:05 ` [PATCH v4 1/5] mm/memory-failure: efi: add the LINUX_EFI_POISONED_MEMORY configuration table Breno Leitao
@ 2026-09-09 13:05 ` Breno Leitao
2026-09-09 13:19 ` sashiko-bot
2026-09-09 14:47 ` Ard Biesheuvel
2026-09-09 13:05 ` [PATCH v4 3/5] mm/memory-failure: efi: record hardware-poisoned frames into the poisoned-memory table Breno Leitao
` (2 subsequent siblings)
4 siblings, 2 replies; 15+ messages in thread
From: Breno Leitao @ 2026-09-09 13:05 UTC (permalink / raw)
To: Ard Biesheuvel, Ilias Apalodimas, Miaohe Lin, Naoya Horiguchi,
Andrew Morton, kas, kexec, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin,
Brendan Jackman, Johannes Weiner, Zi Yan
Cc: linux-efi, linux-kernel, linux-mm, rmikey, riel, Breno Leitao,
harry, kernel-team
A EFI config table can only be installed while boot services are still
up, so the stub has to create it; the running kernel can only flip bits
in a table that already exists.
Size the bitmap from the span the UEFI memory map describes, which
efi_get_ram_range() walks since the stub has no max_pfn. Bit 0 covers
the bottom of that span, recorded in phys_base, so a machine whose RAM
starts high does not pay for the hole below it. Memory the firmware
hot-adds later sits outside the span and is not carried across a kexec.
One table has to serve every architecture, and they do not agree on what
becomes RAM: x86 decides by descriptor type, arm64 by attribute. So
efi_get_ram_range() does not filter at all and spans every descriptor in
the map. Sizing wide only costs bitmap bytes; sizing narrow silently
drops the records for every frame outside the span.
At one bit per 2M that is 64K per TiB, and 256M at the 4PB x86
architectural maximum. The 2M granule is called "unit" here, and the
table carries it so the granule can change later without breaking the
kernels already reading it.
Allocate it as EFI_ACPI_RECLAIM_MEMORY so the next kernel does not take
it for free RAM, and install it empty.
A table installed by an earlier boot rides the system table across kexec
and is reused as-is.
x86 does not go through efi_stub_common(), so the generic stub and the
x86 stub each need the call; on x86 it has to come before exit_boot(),
which is the last point a configuration table can be installed.
Signed-off-by: Breno Leitao <leitao@debian.org>
---
drivers/firmware/efi/libstub/efi-stub-helper.c | 100 +++++++++++++++++++++++++
drivers/firmware/efi/libstub/efi-stub.c | 1 +
drivers/firmware/efi/libstub/efistub.h | 6 ++
drivers/firmware/efi/libstub/x86-stub.c | 2 +
4 files changed, 109 insertions(+)
diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c
index 48f93f7758e9e..5cbe675491333 100644
--- a/drivers/firmware/efi/libstub/efi-stub-helper.c
+++ b/drivers/firmware/efi/libstub/efi-stub-helper.c
@@ -774,3 +774,103 @@ void efi_remap_image(unsigned long image_base, unsigned alloc_size,
efi_warn("Failed to remap data region non-executable\n");
}
}
+
+#ifdef CONFIG_EFI_POISONED_MEMORY
+/*
+ * Find the base and top of the memory, so, we can create the bitmap for
+ * the full range.
+ */
+static efi_status_t efi_get_ram_range(u64 *base, u64 *top)
+{
+ struct efi_boot_memmap *map __free(efi_pool) = NULL;
+ u64 ram_base = ULLONG_MAX, ram_top = 0;
+ efi_status_t status;
+ int i, nr_desc;
+
+ status = efi_get_memory_map(&map, false);
+ if (status != EFI_SUCCESS)
+ return status;
+
+ nr_desc = map->map_size / map->desc_size;
+ for (i = 0; i < nr_desc; i++) {
+ efi_memory_desc_t *d;
+
+ d = efi_memdesc_ptr((unsigned long)map->map, map->desc_size, i);
+ ram_base = min(ram_base, d->phys_addr);
+ ram_top = max(ram_top,
+ d->phys_addr + d->num_pages * EFI_PAGE_SIZE);
+ }
+ if (!ram_top || ram_base == ULLONG_MAX)
+ return EFI_NOT_FOUND;
+
+ *base = round_down(ram_base, EFI_POISON_UNIT_SIZE);
+ *top = round_up(ram_top, EFI_POISON_UNIT_SIZE);
+
+ return EFI_SUCCESS;
+}
+
+/* The size of the bitmap */
+static u64 efi_poison_bitmap_size(u64 span)
+{
+ u64 bytes = DIV_ROUND_UP(DIV_ROUND_UP(span, EFI_POISON_UNIT_SIZE),
+ BITS_PER_BYTE);
+
+ return round_up(bytes, sizeof(unsigned long));
+}
+
+static struct linux_efi_poisoned_memory *efi_poison_alloc(u64 phys_base,
+ u64 bitmap_size)
+{
+ struct linux_efi_poisoned_memory *pm;
+ efi_status_t status;
+
+ status = efi_bs_call(allocate_pool, EFI_ACPI_RECLAIM_MEMORY,
+ sizeof(*pm) + bitmap_size, (void **)&pm);
+ if (status != EFI_SUCCESS)
+ return NULL;
+
+ pm->version = 1;
+ pm->unit_size = EFI_POISON_UNIT_SIZE;
+ pm->phys_base = phys_base;
+ pm->size = bitmap_size;
+ memset(pm->bitmap, 0, bitmap_size);
+
+ return pm;
+}
+
+/* This needs to be done while boot service is still active */
+void install_poisoned_memory_table(void)
+{
+ efi_guid_t poisoned_memory_table_guid = LINUX_EFI_POISONED_MEMORY_TABLE_GUID;
+ struct linux_efi_poisoned_memory *pm;
+ u64 ram_base, ram_top, bitmap_size;
+ efi_status_t status;
+
+ /* A table installed by an earlier boot rides the system table across kexec. */
+ pm = get_efi_config_table(poisoned_memory_table_guid);
+ if (pm) {
+ if (pm->version != 1)
+ efi_err("Unknown version of poisoned-memory table\n");
+ return;
+ }
+
+ if (efi_get_ram_range(&ram_base, &ram_top) != EFI_SUCCESS) {
+ efi_err("Failed to size the poisoned-memory table!\n");
+ return;
+ }
+
+ bitmap_size = efi_poison_bitmap_size(ram_top - ram_base);
+ pm = efi_poison_alloc(ram_base, bitmap_size);
+ if (!pm) {
+ efi_err("Failed to allocate poisoned-memory table!\n");
+ return;
+ }
+
+ status = efi_bs_call(install_configuration_table,
+ &poisoned_memory_table_guid, pm);
+ if (status != EFI_SUCCESS) {
+ efi_bs_call(free_pool, pm);
+ efi_err("Failed to install poisoned-memory config table!\n");
+ }
+}
+#endif
diff --git a/drivers/firmware/efi/libstub/efi-stub.c b/drivers/firmware/efi/libstub/efi-stub.c
index 235c9738da2d6..22a315e2814a1 100644
--- a/drivers/firmware/efi/libstub/efi-stub.c
+++ b/drivers/firmware/efi/libstub/efi-stub.c
@@ -179,6 +179,7 @@ efi_status_t efi_stub_common(efi_handle_t handle,
EFI_RT_SUPPORTED_SET_VIRTUAL_ADDRESS_MAP);
install_memreserve_table();
+ install_poisoned_memory_table();
status = efi_boot_kernel(handle, image, image_addr, cmdline_ptr);
diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h
index fd91fc15ec810..44436869c4efe 100644
--- a/drivers/firmware/efi/libstub/efistub.h
+++ b/drivers/firmware/efi/libstub/efistub.h
@@ -1169,6 +1169,12 @@ efi_enable_reset_attack_mitigation(void) { }
void efi_retrieve_eventlog(void);
+#ifdef CONFIG_EFI_POISONED_MEMORY
+void install_poisoned_memory_table(void);
+#else
+static inline void install_poisoned_memory_table(void) { }
+#endif
+
struct sysfb_display_info *alloc_primary_display(void);
struct sysfb_display_info *__alloc_primary_display(void);
void free_primary_display(struct sysfb_display_info *dpy);
diff --git a/drivers/firmware/efi/libstub/x86-stub.c b/drivers/firmware/efi/libstub/x86-stub.c
index 0bae0f06b6763..3136132b9628a 100644
--- a/drivers/firmware/efi/libstub/x86-stub.c
+++ b/drivers/firmware/efi/libstub/x86-stub.c
@@ -1024,6 +1024,8 @@ void __noreturn efi_stub_entry(efi_handle_t handle,
setup_unaccepted_memory();
+ install_poisoned_memory_table();
+
status = exit_boot(boot_params, handle);
if (status != EFI_SUCCESS) {
efi_err("exit_boot() failed!\n");
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH v4 2/5] mm/memory-failure: libstub: install the poisoned-memory EFI table
2026-09-09 13:05 ` [PATCH v4 2/5] mm/memory-failure: libstub: install the poisoned-memory EFI table Breno Leitao
@ 2026-09-09 13:19 ` sashiko-bot
2026-09-09 14:00 ` Breno Leitao
2026-09-09 14:47 ` Ard Biesheuvel
1 sibling, 1 reply; 15+ messages in thread
From: sashiko-bot @ 2026-09-09 13:19 UTC (permalink / raw)
To: David Hildenbrand, Vlastimil Babka, Naoya Horiguchi,
Breno Leitao, Mike Rapoport, Michal Hocko, Ingo Molnar, Zi Yan,
Dave Hansen, Thomas Gleixner, Borislav Petkov, Johannes Weiner,
H. Peter Anvin, Lorenzo Stoakes, Miaohe Lin, Andrew Morton,
Ard Biesheuvel, kas, Ilias Apalodimas, Liam R. Howlett, x86,
Suren Baghdasaryan, Brendan Jackman
Cc: harry, linux-efi, riel, linux-kernel, linux-mm, kexec, rmikey,
kernel-team
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] Integer truncation in efi_poison_alloc leads to a massive heap buffer overflow during memset on 32-bit or mixed-mode systems.
--
commit 626632820fe1fdcddc73aaeb109705cbddc9828c
Author: Breno Leitao <leitao@debian.org>
Subject: mm/memory-failure: libstub: install the poisoned-memory EFI table
This patch introduces functionality in the EFI stub to create and install a
poisoned-memory EFI configuration table. It walks the UEFI memory map to find
the RAM range and allocates a bitmap to track poisoned memory granules.
> diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c
> index 48f93f7758e9e..5cbe675491333 100644
> --- a/drivers/firmware/efi/libstub/efi-stub-helper.c
> +++ b/drivers/firmware/efi/libstub/efi-stub-helper.c
[ ... ]
> +static struct linux_efi_poisoned_memory *efi_poison_alloc(u64 phys_base,
> + u64 bitmap_size)
> +{
> + struct linux_efi_poisoned_memory *pm;
> + efi_status_t status;
> +
> + status = efi_bs_call(allocate_pool, EFI_ACPI_RECLAIM_MEMORY,
> + sizeof(*pm) + bitmap_size, (void **)&pm);
[Severity: Critical]
Could sizeof(*pm) + bitmap_size overflow 32 bits on 32-bit or mixed-mode
systems? If the EFI memory map describes a very large physical address space
(such as a 32PB physical address provided by an untrusted hypervisor in a
TDX/SEV environment), bitmap_size could approach 4GB.
Because the size argument to allocate_pool is a UINTN, a value like
0x100000008 (sizeof(*pm) + 0xFFFFFFF0) would truncate to 8 bytes on 32-bit
EFI systems, resulting in an undersized allocation.
> + if (status != EFI_SUCCESS)
> + return NULL;
> +
> + pm->version = 1;
> + pm->unit_size = EFI_POISON_UNIT_SIZE;
> + pm->phys_base = phys_base;
> + pm->size = bitmap_size;
> + memset(pm->bitmap, 0, bitmap_size);
[Severity: Critical]
If the allocate_pool calculation overflowed and truncated as described above,
does this memset trigger an out-of-bounds write regression?
Since bitmap_size (e.g., 0xFFFFFFF0) fits within a 32-bit size_t, memset
will use the large untruncated size, potentially overwriting up to 4GB of EFI
boot services memory following the undersized allocation.
> +
> + return pm;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260909-hwpoison-kho-v4-0-359313564495@debian.org?part=2
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH v4 2/5] mm/memory-failure: libstub: install the poisoned-memory EFI table
2026-09-09 13:19 ` sashiko-bot
@ 2026-09-09 14:00 ` Breno Leitao
0 siblings, 0 replies; 15+ messages in thread
From: Breno Leitao @ 2026-09-09 14:00 UTC (permalink / raw)
To: sashiko-reviews
Cc: David Hildenbrand, Vlastimil Babka, Naoya Horiguchi,
Mike Rapoport, Michal Hocko, Ingo Molnar, Zi Yan, Dave Hansen,
Thomas Gleixner, Borislav Petkov, Johannes Weiner,
H. Peter Anvin, Lorenzo Stoakes, Miaohe Lin, Andrew Morton,
Ard Biesheuvel, kas, Ilias Apalodimas, Liam R. Howlett, x86,
Suren Baghdasaryan, Brendan Jackman, harry, linux-efi, riel,
linux-kernel, linux-mm, kexec, rmikey, kernel-team
On Wed, Sep 09, 2026 at 01:19:11PM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Critical] Integer truncation in efi_poison_alloc leads to a massive heap buffer overflow during memset on 32-bit or mixed-mode systems.
> --
>
> commit 626632820fe1fdcddc73aaeb109705cbddc9828c
> Author: Breno Leitao <leitao@debian.org>
> Subject: mm/memory-failure: libstub: install the poisoned-memory EFI table
>
> This patch introduces functionality in the EFI stub to create and install a
> poisoned-memory EFI configuration table. It walks the UEFI memory map to find
> the RAM range and allocates a bitmap to track poisoned memory granules.
>
> > diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c b/drivers/firmware/efi/libstub/efi-stub-helper.c
> > index 48f93f7758e9e..5cbe675491333 100644
> > --- a/drivers/firmware/efi/libstub/efi-stub-helper.c
> > +++ b/drivers/firmware/efi/libstub/efi-stub-helper.c
> [ ... ]
> > +static struct linux_efi_poisoned_memory *efi_poison_alloc(u64 phys_base,
> > + u64 bitmap_size)
> > +{
> > + struct linux_efi_poisoned_memory *pm;
> > + efi_status_t status;
> > +
> > + status = efi_bs_call(allocate_pool, EFI_ACPI_RECLAIM_MEMORY,
> > + sizeof(*pm) + bitmap_size, (void **)&pm);
>
> [Severity: Critical]
> Could sizeof(*pm) + bitmap_size overflow 32 bits on 32-bit or mixed-mode
> systems?
This feature (EFI_POISONED_MEMORY) is only enabled on 32-bits system,
so, 32-bit issues is not a real issue. See very first patch:
+config EFI_POISONED_MEMORY
+ def_bool y
+ depends on EFI_STUB && MEMORY_FAILURE && 64BIT
So, this is a false positive
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v4 2/5] mm/memory-failure: libstub: install the poisoned-memory EFI table
2026-09-09 13:05 ` [PATCH v4 2/5] mm/memory-failure: libstub: install the poisoned-memory EFI table Breno Leitao
2026-09-09 13:19 ` sashiko-bot
@ 2026-09-09 14:47 ` Ard Biesheuvel
2026-09-10 13:11 ` Breno Leitao
1 sibling, 1 reply; 15+ messages in thread
From: Ard Biesheuvel @ 2026-09-09 14:47 UTC (permalink / raw)
To: Breno Leitao, Ilias Apalodimas, Miaohe Lin, Naoya Horiguchi,
Andrew Morton, Kiryl Shutsemau (Meta),
kexec, David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
H . Peter Anvin, Brendan Jackman, Johannes Weiner, Zi Yan
Cc: linux-efi, linux-kernel, linux-mm, rmikey, riel, harry, kernel-team
Hello Breno,
Apologies for chiming in late.
On Wed, 9 Sep 2026, at 15:05, Breno Leitao wrote:
> A EFI config table can only be installed while boot services are still
> up, so the stub has to create it; the running kernel can only flip bits
> in a table that already exists.
>
This is true, but that also means a config table could have a 'next' field
pointing to an allocation that was added later.
There is a EFI memreserve table based on this principle: this is a hack
that we added for the arm64 GICv3 LPI table handling, which is a braindead
piece of kit that must use the same physical allocation as the previous
kernel. It is not currently enabled on x86.
Please consider whether or not that is more suitable, and can be repurposed
or shared. (Feel free to make changes to the current format if needed).
I don't have a strong preference either way, but I feel the 2M granularity
may be a bit wasteful, no?
> Size the bitmap from the span the UEFI memory map describes, which
> efi_get_ram_range() walks since the stub has no max_pfn. Bit 0 covers
> the bottom of that span, recorded in phys_base, so a machine whose RAM
> starts high does not pay for the hole below it. Memory the firmware
> hot-adds later sits outside the span and is not carried across a kexec.
>
> One table has to serve every architecture, and they do not agree on what
> becomes RAM: x86 decides by descriptor type, arm64 by attribute. So
> efi_get_ram_range() does not filter at all and spans every descriptor in
> the map. Sizing wide only costs bitmap bytes; sizing narrow silently
> drops the records for every frame outside the span.
>
> At one bit per 2M that is 64K per TiB, and 256M at the 4PB x86
> architectural maximum. The 2M granule is called "unit" here, and the
> table carries it so the granule can change later without breaking the
> kernels already reading it.
>
> Allocate it as EFI_ACPI_RECLAIM_MEMORY so the next kernel does not take
> it for free RAM, and install it empty.
>
> A table installed by an earlier boot rides the system table across kexec
> and is reused as-is.
>
> x86 does not go through efi_stub_common(), so the generic stub and the
> x86 stub each need the call; on x86 it has to come before exit_boot(),
> which is the last point a configuration table can be installed.
>
> Signed-off-by: Breno Leitao <leitao@debian.org>
> ---
> drivers/firmware/efi/libstub/efi-stub-helper.c | 100 +++++++++++++++++++++++++
> drivers/firmware/efi/libstub/efi-stub.c | 1 +
> drivers/firmware/efi/libstub/efistub.h | 6 ++
> drivers/firmware/efi/libstub/x86-stub.c | 2 +
> 4 files changed, 109 insertions(+)
>
> diff --git a/drivers/firmware/efi/libstub/efi-stub-helper.c
> b/drivers/firmware/efi/libstub/efi-stub-helper.c
> index 48f93f7758e9e..5cbe675491333 100644
> --- a/drivers/firmware/efi/libstub/efi-stub-helper.c
> +++ b/drivers/firmware/efi/libstub/efi-stub-helper.c
> @@ -774,3 +774,103 @@ void efi_remap_image(unsigned long image_base,
> unsigned alloc_size,
> efi_warn("Failed to remap data region non-executable\n");
> }
> }
> +
> +#ifdef CONFIG_EFI_POISONED_MEMORY
> +/*
> + * Find the base and top of the memory, so, we can create the bitmap
> for
> + * the full range.
> + */
> +static efi_status_t efi_get_ram_range(u64 *base, u64 *top)
> +{
> + struct efi_boot_memmap *map __free(efi_pool) = NULL;
> + u64 ram_base = ULLONG_MAX, ram_top = 0;
> + efi_status_t status;
> + int i, nr_desc;
> +
> + status = efi_get_memory_map(&map, false);
> + if (status != EFI_SUCCESS)
> + return status;
> +
> + nr_desc = map->map_size / map->desc_size;
> + for (i = 0; i < nr_desc; i++) {
> + efi_memory_desc_t *d;
> +
> + d = efi_memdesc_ptr((unsigned long)map->map, map->desc_size, i);
> + ram_base = min(ram_base, d->phys_addr);
> + ram_top = max(ram_top,
> + d->phys_addr + d->num_pages * EFI_PAGE_SIZE);
> + }
> + if (!ram_top || ram_base == ULLONG_MAX)
> + return EFI_NOT_FOUND;
> +
> + *base = round_down(ram_base, EFI_POISON_UNIT_SIZE);
> + *top = round_up(ram_top, EFI_POISON_UNIT_SIZE);
> +
> + return EFI_SUCCESS;
> +}
> +
> +/* The size of the bitmap */
> +static u64 efi_poison_bitmap_size(u64 span)
> +{
> + u64 bytes = DIV_ROUND_UP(DIV_ROUND_UP(span, EFI_POISON_UNIT_SIZE),
> + BITS_PER_BYTE);
> +
> + return round_up(bytes, sizeof(unsigned long));
> +}
> +
> +static struct linux_efi_poisoned_memory *efi_poison_alloc(u64
> phys_base,
> + u64 bitmap_size)
> +{
> + struct linux_efi_poisoned_memory *pm;
> + efi_status_t status;
> +
> + status = efi_bs_call(allocate_pool, EFI_ACPI_RECLAIM_MEMORY,
> + sizeof(*pm) + bitmap_size, (void **)&pm);
> + if (status != EFI_SUCCESS)
> + return NULL;
> +
> + pm->version = 1;
> + pm->unit_size = EFI_POISON_UNIT_SIZE;
> + pm->phys_base = phys_base;
> + pm->size = bitmap_size;
> + memset(pm->bitmap, 0, bitmap_size);
> +
> + return pm;
> +}
> +
> +/* This needs to be done while boot service is still active */
> +void install_poisoned_memory_table(void)
> +{
> + efi_guid_t poisoned_memory_table_guid =
> LINUX_EFI_POISONED_MEMORY_TABLE_GUID;
> + struct linux_efi_poisoned_memory *pm;
> + u64 ram_base, ram_top, bitmap_size;
> + efi_status_t status;
> +
> + /* A table installed by an earlier boot rides the system table across
> kexec. */
> + pm = get_efi_config_table(poisoned_memory_table_guid);
> + if (pm) {
> + if (pm->version != 1)
> + efi_err("Unknown version of poisoned-memory table\n");
> + return;
> + }
> +
> + if (efi_get_ram_range(&ram_base, &ram_top) != EFI_SUCCESS) {
> + efi_err("Failed to size the poisoned-memory table!\n");
> + return;
> + }
> +
> + bitmap_size = efi_poison_bitmap_size(ram_top - ram_base);
> + pm = efi_poison_alloc(ram_base, bitmap_size);
> + if (!pm) {
> + efi_err("Failed to allocate poisoned-memory table!\n");
> + return;
> + }
> +
> + status = efi_bs_call(install_configuration_table,
> + &poisoned_memory_table_guid, pm);
> + if (status != EFI_SUCCESS) {
> + efi_bs_call(free_pool, pm);
> + efi_err("Failed to install poisoned-memory config table!\n");
> + }
> +}
> +#endif
> diff --git a/drivers/firmware/efi/libstub/efi-stub.c
> b/drivers/firmware/efi/libstub/efi-stub.c
> index 235c9738da2d6..22a315e2814a1 100644
> --- a/drivers/firmware/efi/libstub/efi-stub.c
> +++ b/drivers/firmware/efi/libstub/efi-stub.c
> @@ -179,6 +179,7 @@ efi_status_t efi_stub_common(efi_handle_t handle,
> EFI_RT_SUPPORTED_SET_VIRTUAL_ADDRESS_MAP);
>
> install_memreserve_table();
> + install_poisoned_memory_table();
>
> status = efi_boot_kernel(handle, image, image_addr, cmdline_ptr);
>
> diff --git a/drivers/firmware/efi/libstub/efistub.h
> b/drivers/firmware/efi/libstub/efistub.h
> index fd91fc15ec810..44436869c4efe 100644
> --- a/drivers/firmware/efi/libstub/efistub.h
> +++ b/drivers/firmware/efi/libstub/efistub.h
> @@ -1169,6 +1169,12 @@ efi_enable_reset_attack_mitigation(void) { }
>
> void efi_retrieve_eventlog(void);
>
> +#ifdef CONFIG_EFI_POISONED_MEMORY
> +void install_poisoned_memory_table(void);
> +#else
> +static inline void install_poisoned_memory_table(void) { }
> +#endif
> +
> struct sysfb_display_info *alloc_primary_display(void);
> struct sysfb_display_info *__alloc_primary_display(void);
> void free_primary_display(struct sysfb_display_info *dpy);
> diff --git a/drivers/firmware/efi/libstub/x86-stub.c
> b/drivers/firmware/efi/libstub/x86-stub.c
> index 0bae0f06b6763..3136132b9628a 100644
> --- a/drivers/firmware/efi/libstub/x86-stub.c
> +++ b/drivers/firmware/efi/libstub/x86-stub.c
> @@ -1024,6 +1024,8 @@ void __noreturn efi_stub_entry(efi_handle_t
> handle,
>
> setup_unaccepted_memory();
>
> + install_poisoned_memory_table();
> +
> status = exit_boot(boot_params, handle);
> if (status != EFI_SUCCESS) {
> efi_err("exit_boot() failed!\n");
>
> --
> 2.53.0-Meta
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH v4 2/5] mm/memory-failure: libstub: install the poisoned-memory EFI table
2026-09-09 14:47 ` Ard Biesheuvel
@ 2026-09-10 13:11 ` Breno Leitao
0 siblings, 0 replies; 15+ messages in thread
From: Breno Leitao @ 2026-09-10 13:11 UTC (permalink / raw)
To: Ard Biesheuvel
Cc: Ilias Apalodimas, Miaohe Lin, Naoya Horiguchi, Andrew Morton,
Kiryl Shutsemau (Meta),
kexec, David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
H . Peter Anvin, Brendan Jackman, Johannes Weiner, Zi Yan,
linux-efi, linux-kernel, linux-mm, rmikey, riel, harry,
kernel-team
Hello Ard,
Thanks for the feedback!
On Wed, Sep 09, 2026 at 04:47:27PM +0200, Ard Biesheuvel wrote:
> On Wed, 9 Sep 2026, at 15:05, Breno Leitao wrote:
> > A EFI config table can only be installed while boot services are still
> > up, so the stub has to create it; the running kernel can only flip bits
> > in a table that already exists.
>
> This is true, but that also means a config table could have a 'next' field
> pointing to an allocation that was added later.
>
> There is a EFI memreserve table based on this principle: this is a hack
> that we added for the arm64 GICv3 LPI table handling, which is a braindead
> piece of kit that must use the same physical allocation as the previous
> kernel. It is not currently enabled on x86.
>
> Please consider whether or not that is more suitable, and can be repurposed
> or shared. (Feel free to make changes to the current format if needed).
That's actually what I tried in the RFC. I passed an almost-empty EFI
table, then built a linked list of poisoned pages on top of it.
It looked like this:
struct linux_efi_poisoned_memory {
int size; // allocated size of the array
atomic_t count; // number of entries used
phys_addr_t next; // pa of next struct instance
struct {
phys_addr_t base;
phys_addr_t size;
} entry[];
};
The allocation walks that list the same way; see
efi_hwpoison_record_pfn() in patch "[PATCH RFC 2/3] efi: record hardware-poisoned frames into the poisoned-memory table"
This is the RFC patchset:
https://lore.kernel.org/all/20260717-hwpoison-kho-v1-1-9c5eda551998@debian.org/#t
> I don't have a strong preference either way, but I feel the 2M granularity
> may be a bit wasteful, no?
Maybe. It's the same trade-off as unaccepted memory: reducing the
granularity means growing the bitmap.
We can reduce the granularity, but I don't think we should reduce the
granularity if we're going to KEEP using a bitmap.
So I'd say we have two options:
1) Keep it similar to unaccepted memory, with 2M granularity.
- Pro : Similar mental model as unnacepted memory
- Cons: 2 MB might be a bit wasteful
2) Move to a linked list like the RFC, keeping it outside of the EFI
table.
- Pro: Reduce the memory granularities to page instead of 2M blocs.
- Cons: Another way of passing memory information between kexec
kernels.
Any any other option or strong preference?
--breno
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v4 3/5] mm/memory-failure: efi: record hardware-poisoned frames into the poisoned-memory table
2026-09-09 13:05 [PATCH v4 0/5] mm/memory-failure: keep hardware-poisoned pages out of the next kexec Breno Leitao
2026-09-09 13:05 ` [PATCH v4 1/5] mm/memory-failure: efi: add the LINUX_EFI_POISONED_MEMORY configuration table Breno Leitao
2026-09-09 13:05 ` [PATCH v4 2/5] mm/memory-failure: libstub: install the poisoned-memory EFI table Breno Leitao
@ 2026-09-09 13:05 ` Breno Leitao
2026-09-09 13:21 ` sashiko-bot
2026-09-09 13:05 ` [PATCH v4 4/5] mm/memory-failure: efi: answer whether a range is poisoned Breno Leitao
2026-09-09 13:05 ` [PATCH v4 5/5] mm/memory-failure: keep inherited poisoned frames out of the buddy allocator Breno Leitao
4 siblings, 1 reply; 15+ messages in thread
From: Breno Leitao @ 2026-09-09 13:05 UTC (permalink / raw)
To: Ard Biesheuvel, Ilias Apalodimas, Miaohe Lin, Naoya Horiguchi,
Andrew Morton, kas, kexec, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin,
Brendan Jackman, Johannes Weiner, Zi Yan
Cc: linux-efi, linux-kernel, linux-mm, rmikey, riel, Breno Leitao,
harry, kernel-team
action_result() is where memory_failure() reports the outcome of a hard
offline, so hook it to set the frame's bit in the
LINUX_EFI_POISONED_MEMORY bitmap.
Soft-offlined pages reach num_poisoned_pages_inc() through
page_handle_poison() and are deliberately left out: they are still
functional and were offlined predictively, so recording them would turn
a prediction into a permanent loss for every kernel further down the
kexec chain.
A bit is only ever set, never cleared, given that multiple pages can set
the same bit, and it is not trivial to decide if the bit should be unset
when a page is unrecorded.
Unpoisoning a frame therefore does not hand its unit back to the next
kernel. That is a known limitation.
The table is EFI ACPI reclaim memory, which becomes E820_TYPE_ACPI and so
reaches neither memblock nor the direct map; touching it then faults.
Hand its pages to memblock from efi_config_parse_tables() the way the
unaccepted memory table already does, and vet the inherited header in the
same pass, so everything afterwards can reach a table it can trust with
phys_to_virt().
memory_failure() has already taken the frame out of this kernel's
allocator, so only the cross-kexec record happens here.
Signed-off-by: Breno Leitao <leitao@debian.org>
---
drivers/firmware/efi/Makefile | 1 +
drivers/firmware/efi/efi.c | 2 +
drivers/firmware/efi/poison.c | 115 ++++++++++++++++++++++++++++++++++++++++++
include/linux/efi.h | 8 +++
mm/memory-failure.c | 3 ++
5 files changed, 129 insertions(+)
diff --git a/drivers/firmware/efi/Makefile b/drivers/firmware/efi/Makefile
index 8efbcf699e4ff..05d0a490923e5 100644
--- a/drivers/firmware/efi/Makefile
+++ b/drivers/firmware/efi/Makefile
@@ -43,4 +43,5 @@ obj-$(CONFIG_EFI_EARLYCON) += earlycon.o
obj-$(CONFIG_UEFI_CPER_ARM) += cper-arm.o
obj-$(CONFIG_UEFI_CPER_X86) += cper-x86.o
obj-$(CONFIG_UNACCEPTED_MEMORY) += unaccepted_memory.o
+obj-$(CONFIG_EFI_POISONED_MEMORY) += poison.o
obj-$(CONFIG_TEE_STMM_EFI) += stmm/tee_stmm_efi.o
diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c
index af1fa443839c4..55b2ee53fc268 100644
--- a/drivers/firmware/efi/efi.c
+++ b/drivers/firmware/efi/efi.c
@@ -883,6 +883,8 @@ int __init efi_config_parse_tables(const efi_config_table_t *config_tables,
}
}
+ efi_poisoned_memory_reserve();
+
return 0;
}
diff --git a/drivers/firmware/efi/poison.c b/drivers/firmware/efi/poison.c
new file mode 100644
index 0000000000000..c18edf111c710
--- /dev/null
+++ b/drivers/firmware/efi/poison.c
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Runtime side of the LINUX_EFI_POISONED_MEMORY table: one bit per
+ * EFI_POISON_UNIT_SIZE, set here as frames go bad, honored by the next kernel.
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ * Copyright (c) 2026 Breno Leitao <leitao@debian.org>
+ */
+
+#define pr_fmt(fmt) "efi: " fmt
+
+#include <linux/bitmap.h>
+#include <linux/efi.h>
+#include <linux/io.h>
+#include <linux/log2.h>
+#include <linux/memblock.h>
+#include <linux/mm.h>
+#include <linux/overflow.h>
+
+static bool __init
+efi_poison_geometry_valid(const struct linux_efi_poisoned_memory *pm)
+{
+ u64 nbits;
+
+ /* Whole words, and a bit count that can be taken without wrapping. */
+ if (!pm->size || !IS_ALIGNED(pm->size, sizeof(unsigned long)) ||
+ check_mul_overflow(pm->size, (u64)BITS_PER_BYTE, &nbits))
+ return false;
+
+ if (pm->unit_size < PAGE_SIZE || !is_power_of_2(pm->unit_size))
+ return false;
+
+ return IS_ALIGNED(pm->phys_base, pm->unit_size);
+}
+
+/* The table may come from an earlier kernel, so vet it before using it. */
+static bool __init
+efi_poison_table_valid(const struct linux_efi_poisoned_memory *pm)
+{
+ if (pm->version != 1) {
+ pr_warn("Ignoring poisoned-memory table with version %u\n",
+ pm->version);
+ return false;
+ }
+
+ if (!efi_poison_geometry_valid(pm)) {
+ pr_warn("Ignoring malformed poisoned-memory table\n");
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Vet the inherited table and hand its pages to memblock, the way the
+ * unaccepted memory table is handled. It is EFI ACPI reclaim memory, which
+ * becomes E820_TYPE_ACPI and would otherwise stay out of the direct map, and
+ * touching it then faults. Called from efi_config_parse_tables(), so
+ * everything later can reach it with efi_poisoned_memory().
+ */
+void __init efi_poisoned_memory_reserve(void)
+{
+ struct linux_efi_poisoned_memory *pm;
+ phys_addr_t start, end;
+
+ if (efi.poisoned_memory == EFI_INVALID_TABLE_ADDR)
+ return;
+
+ pm = early_memremap(efi.poisoned_memory, sizeof(*pm));
+ if (!pm) {
+ pr_warn("Could not map poisoned-memory table\n");
+ efi.poisoned_memory = EFI_INVALID_TABLE_ADDR;
+ return;
+ }
+
+ if (!efi_poison_table_valid(pm)) {
+ efi.poisoned_memory = EFI_INVALID_TABLE_ADDR;
+ early_memunmap(pm, sizeof(*pm));
+ return;
+ }
+
+ start = PAGE_ALIGN_DOWN(efi.poisoned_memory);
+ end = PAGE_ALIGN(efi.poisoned_memory + sizeof(*pm) + pm->size);
+ early_memunmap(pm, sizeof(*pm));
+
+ memblock_add(start, end - start);
+ memblock_reserve(start, end - start);
+}
+
+/* The table, vetted at parse time, or NULL if this boot has none. */
+static struct linux_efi_poisoned_memory *efi_poisoned_memory(void)
+{
+ if (efi.poisoned_memory == EFI_INVALID_TABLE_ADDR)
+ return NULL;
+
+ return phys_to_virt(efi.poisoned_memory);
+}
+
+/*
+ * A bit is never cleared: it stands for a whole EFI_POISON_UNIT_SIZE, so an
+ * unpoison cannot tell whether the unit as a whole is good again.
+ */
+void efi_hwpoison_record_pfn(unsigned long pfn)
+{
+ struct linux_efi_poisoned_memory *pm = efi_poisoned_memory();
+ phys_addr_t addr = PFN_PHYS(pfn);
+ u64 unit;
+
+ if (!pm || addr < pm->phys_base)
+ return;
+
+ unit = (addr - pm->phys_base) / pm->unit_size;
+ if (unit < pm->size * BITS_PER_BYTE)
+ set_bit(unit, pm->bitmap);
+}
diff --git a/include/linux/efi.h b/include/linux/efi.h
index efaf63f9a54ed..56402fdccd114 100644
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -1286,6 +1286,14 @@ struct linux_efi_poisoned_memory {
#define EFI_POISON_UNIT_SIZE SZ_2M
+#ifdef CONFIG_EFI_POISONED_MEMORY
+void __init efi_poisoned_memory_reserve(void);
+void efi_hwpoison_record_pfn(unsigned long pfn);
+#else
+static inline void efi_poisoned_memory_reserve(void) { }
+static inline void efi_hwpoison_record_pfn(unsigned long pfn) { }
+#endif
+
void __init efi_arch_mem_reserve(phys_addr_t addr, u64 size);
/*
diff --git a/mm/memory-failure.c b/mm/memory-failure.c
index a2ca8df501cae..d9b8be696aac3 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -43,6 +43,7 @@
#include <linux/sched/signal.h>
#include <linux/sched/task.h>
#include <linux/dax.h>
+#include <linux/efi.h>
#include <linux/ksm.h>
#include <linux/rmap.h>
#include <linux/export.h>
@@ -1326,6 +1327,8 @@ static int action_result(unsigned long pfn, enum mf_action_page_type type,
if (type != MF_MSG_ALREADY_POISONED && type != MF_MSG_PFN_MAP) {
num_poisoned_pages_inc(pfn);
update_per_node_mf_stats(pfn, result);
+ /* Only hard offlines are carried over to the next kernel. */
+ efi_hwpoison_record_pfn(pfn);
}
pr_err("%#lx: recovery action for %s: %s\n",
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH v4 3/5] mm/memory-failure: efi: record hardware-poisoned frames into the poisoned-memory table
2026-09-09 13:05 ` [PATCH v4 3/5] mm/memory-failure: efi: record hardware-poisoned frames into the poisoned-memory table Breno Leitao
@ 2026-09-09 13:21 ` sashiko-bot
2026-09-09 14:05 ` Breno Leitao
0 siblings, 1 reply; 15+ messages in thread
From: sashiko-bot @ 2026-09-09 13:21 UTC (permalink / raw)
To: Ilias Apalodimas, Michal Hocko, Naoya Horiguchi, Andrew Morton,
kas, Vlastimil Babka, x86, H. Peter Anvin, David Hildenbrand,
Breno Leitao, Liam R. Howlett, Brendan Jackman, Johannes Weiner,
Borislav Petkov, Suren Baghdasaryan, Ard Biesheuvel,
Lorenzo Stoakes, Mike Rapoport, Miaohe Lin, Ingo Molnar,
Dave Hansen, Zi Yan, Thomas Gleixner
Cc: kernel-team, rmikey, harry, linux-efi, riel, linux-kernel,
linux-mm, kexec
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Missing overflow check when computing the end address of the EFI poisoned memory table can lead to an unbounded memblock reservation and memory corruption.
--
commit 7216d7e029930900ac5741a5f5bbfe7a2107b408
Author: Breno Leitao <leitao@debian.org>
mm/memory-failure: efi: record hardware-poisoned frames into the poisoned-memory table
This commit hooks action_result() to efi_hwpoison_record_pfn() to record hard
offlined frames in the LINUX_EFI_POISONED_MEMORY table, ensuring they are
passed down to the next kernel via kexec. It also adds early initialization
code to vet the table inherited from previous boots.
> diff --git a/drivers/firmware/efi/poison.c b/drivers/firmware/efi/poison.c
> new file mode 100644
> index 0000000000000..c18edf111c710
> --- /dev/null
> +++ b/drivers/firmware/efi/poison.c
[ ... ]
> +static bool __init
> +efi_poison_geometry_valid(const struct linux_efi_poisoned_memory *pm)
> +{
> + u64 nbits;
> +
> + /* Whole words, and a bit count that can be taken without wrapping. */
> + if (!pm->size || !IS_ALIGNED(pm->size, sizeof(unsigned long)) ||
> + check_mul_overflow(pm->size, (u64)BITS_PER_BYTE, &nbits))
> + return false;
[Severity: High]
Does this validation fully protect against an excessively large pm->size?
While check_mul_overflow() validates the size for bit conversion, it appears
it does not verify that the table's footprint fits within the physical address
space without overflowing when combined with efi.poisoned_memory.
> +
> + if (pm->unit_size < PAGE_SIZE || !is_power_of_2(pm->unit_size))
> + return false;
> +
> + return IS_ALIGNED(pm->phys_base, pm->unit_size);
> +}
[ ... ]
> +void __init efi_poisoned_memory_reserve(void)
> +{
> + struct linux_efi_poisoned_memory *pm;
> + phys_addr_t start, end;
> +
> + if (efi.poisoned_memory == EFI_INVALID_TABLE_ADDR)
> + return;
> +
> + pm = early_memremap(efi.poisoned_memory, sizeof(*pm));
> + if (!pm) {
> + pr_warn("Could not map poisoned-memory table\n");
> + efi.poisoned_memory = EFI_INVALID_TABLE_ADDR;
> + return;
> + }
> +
> + if (!efi_poison_table_valid(pm)) {
> + efi.poisoned_memory = EFI_INVALID_TABLE_ADDR;
> + early_memunmap(pm, sizeof(*pm));
> + return;
> + }
> +
> + start = PAGE_ALIGN_DOWN(efi.poisoned_memory);
> + end = PAGE_ALIGN(efi.poisoned_memory + sizeof(*pm) + pm->size);
[Severity: High]
Can this computation wrap around if pm->size is maliciously large?
Because pm->size is only constrained to be less than U64_MAX / 8 in
efi_poison_geometry_valid(), an unvalidated addition here could overflow,
resulting in end < start. If that happens, an enormous end - start would
be passed to memblock_add() and memblock_reserve().
This could result in memblock capping the size to PHYS_ADDR_MAX - start,
blindly marking the rest of the physical address space (including MMIO) as
RAM. This forces direct-map cacheable page table entries for MMIO regions,
leading to system crashes or memory layout corruption.
> + early_memunmap(pm, sizeof(*pm));
> +
> + memblock_add(start, end - start);
> + memblock_reserve(start, end - start);
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260909-hwpoison-kho-v4-0-359313564495@debian.org?part=3
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH v4 3/5] mm/memory-failure: efi: record hardware-poisoned frames into the poisoned-memory table
2026-09-09 13:21 ` sashiko-bot
@ 2026-09-09 14:05 ` Breno Leitao
0 siblings, 0 replies; 15+ messages in thread
From: Breno Leitao @ 2026-09-09 14:05 UTC (permalink / raw)
To: sashiko-reviews
Cc: Ilias Apalodimas, Michal Hocko, Naoya Horiguchi, Andrew Morton,
kas, Vlastimil Babka, x86, H. Peter Anvin, David Hildenbrand,
Liam R. Howlett, Brendan Jackman, Johannes Weiner,
Borislav Petkov, Suren Baghdasaryan, Ard Biesheuvel,
Lorenzo Stoakes, Mike Rapoport, Miaohe Lin, Ingo Molnar,
Dave Hansen, Zi Yan, Thomas Gleixner, kernel-team, rmikey, harry,
linux-efi, riel, linux-kernel, linux-mm, kexec
On Wed, Sep 09, 2026 at 01:21:28PM +0000, sashiko-bot@kernel.org wrote:
> > --- /dev/null
> > +++ b/drivers/firmware/efi/poison.c
> >
> > + /* Whole words, and a bit count that can be taken without wrapping. */
> > + if (!pm->size || !IS_ALIGNED(pm->size, sizeof(unsigned long)) ||
> > + check_mul_overflow(pm->size, (u64)BITS_PER_BYTE, &nbits))
> > + return false;
>
> [Severity: High]
> Does this validation fully protect against an excessively large pm->size?
I was clamping the table before, but we decided to drop it in the last
revision. See the discusion in here:
https://lore.kernel.org/all/apGWUWi5-RbhFHpe@thinkstation/
> > + start = PAGE_ALIGN_DOWN(efi.poisoned_memory);
> > + end = PAGE_ALIGN(efi.poisoned_memory + sizeof(*pm) + pm->size);
>
> [Severity: High]
> Can this computation wrap around if pm->size is maliciously large?
Yes, but that means that someone was able to write to EFI memory to
update size?
I can definitely BUG_ON() on "large" pm->size, but, I don't think this
is what we want here.
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v4 4/5] mm/memory-failure: efi: answer whether a range is poisoned
2026-09-09 13:05 [PATCH v4 0/5] mm/memory-failure: keep hardware-poisoned pages out of the next kexec Breno Leitao
` (2 preceding siblings ...)
2026-09-09 13:05 ` [PATCH v4 3/5] mm/memory-failure: efi: record hardware-poisoned frames into the poisoned-memory table Breno Leitao
@ 2026-09-09 13:05 ` Breno Leitao
2026-09-09 13:17 ` sashiko-bot
2026-09-09 13:05 ` [PATCH v4 5/5] mm/memory-failure: keep inherited poisoned frames out of the buddy allocator Breno Leitao
4 siblings, 1 reply; 15+ messages in thread
From: Breno Leitao @ 2026-09-09 13:05 UTC (permalink / raw)
To: Ard Biesheuvel, Ilias Apalodimas, Miaohe Lin, Naoya Horiguchi,
Andrew Morton, kas, kexec, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin,
Brendan Jackman, Johannes Weiner, Zi Yan
Cc: linux-efi, linux-kernel, linux-mm, rmikey, riel, Breno Leitao,
harry, kernel-team
The bitmap an earlier kernel filled in gets in this one through EFI,
but nothing reads it back yet.
Introduce range_contains_poisoned_memory(), which the page allocator
will use to ask about a block before handing it out.
Signed-off-by: Breno Leitao <leitao@debian.org>
---
drivers/firmware/efi/poison.c | 26 ++++++++++++++++++++++++++
include/linux/mm.h | 14 ++++++++++++++
2 files changed, 40 insertions(+)
diff --git a/drivers/firmware/efi/poison.c b/drivers/firmware/efi/poison.c
index c18edf111c710..e16d43f4438ee 100644
--- a/drivers/firmware/efi/poison.c
+++ b/drivers/firmware/efi/poison.c
@@ -96,6 +96,32 @@ static struct linux_efi_poisoned_memory *efi_poisoned_memory(void)
return phys_to_virt(efi.poisoned_memory);
}
+/* Does the range cover a unit an earlier kernel recorded as bad? */
+bool range_contains_poisoned_memory(phys_addr_t start, unsigned long size)
+{
+ struct linux_efi_poisoned_memory *pm = efi_poisoned_memory();
+ u64 first, last, nbits;
+
+ if (!pm)
+ return false;
+
+ nbits = pm->size * BITS_PER_BYTE;
+
+ if (start + size <= pm->phys_base)
+ return false;
+ if (start < pm->phys_base)
+ start = pm->phys_base;
+
+ first = (start - pm->phys_base) / pm->unit_size;
+ if (first >= nbits)
+ return false;
+
+ last = (start + size - 1 - pm->phys_base) / pm->unit_size;
+ last = min(last, nbits - 1);
+
+ return find_next_bit(pm->bitmap, last + 1, first) <= last;
+}
+
/*
* A bit is never cleared: it stands for a whole EFI_POISON_UNIT_SIZE, so an
* unpoison cannot tell whether the unit as a whole is good again.
diff --git a/include/linux/mm.h b/include/linux/mm.h
index 274fa880077c5..b68824fcfbef1 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -5387,6 +5387,20 @@ static inline bool pfn_is_unaccepted_memory(unsigned long pfn)
return range_contains_unaccepted_memory(pfn << PAGE_SHIFT, PAGE_SIZE);
}
+#ifdef CONFIG_EFI_POISONED_MEMORY
+
+bool range_contains_poisoned_memory(phys_addr_t start, unsigned long size);
+
+#else
+
+static inline bool range_contains_poisoned_memory(phys_addr_t start,
+ unsigned long size)
+{
+ return false;
+}
+
+#endif
+
void vma_pgtable_walk_begin(struct vm_area_struct *vma);
void vma_pgtable_walk_end(struct vm_area_struct *vma);
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH v4 4/5] mm/memory-failure: efi: answer whether a range is poisoned
2026-09-09 13:05 ` [PATCH v4 4/5] mm/memory-failure: efi: answer whether a range is poisoned Breno Leitao
@ 2026-09-09 13:17 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-09-09 13:17 UTC (permalink / raw)
To: H. Peter Anvin, Borislav Petkov, Michal Hocko, Zi Yan,
Andrew Morton, Breno Leitao, David Hildenbrand, Naoya Horiguchi,
Liam R. Howlett, Miaohe Lin, Lorenzo Stoakes, Ard Biesheuvel,
Vlastimil Babka, Thomas Gleixner, Ingo Molnar, Brendan Jackman,
Suren Baghdasaryan, Mike Rapoport, x86, Dave Hansen,
Ilias Apalodimas, Johannes Weiner, kas
Cc: linux-kernel, rmikey, harry, linux-mm, kernel-team, riel, kexec,
linux-efi
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Adjusting `start` without proportionally shrinking `size` shifts the checked memory range out of bounds, causing false positive poison detection.
--
commit b492314fda14ef47eff8ff738b90a4749a490163
Author: Breno Leitao <leitao@debian.org>
mm/memory-failure: efi: answer whether a range is poisoned
This commit introduces range_contains_poisoned_memory() which allows the
page allocator to query whether a memory block overlaps with regions
recorded as poisoned by an earlier kernel via EFI.
> diff --git a/drivers/firmware/efi/poison.c b/drivers/firmware/efi/poison.c
> index c18edf111c710..e16d43f4438ee 100644
> --- a/drivers/firmware/efi/poison.c
> +++ b/drivers/firmware/efi/poison.c
[ ... ]
> +/* Does the range cover a unit an earlier kernel recorded as bad? */
> +bool range_contains_poisoned_memory(phys_addr_t start, unsigned long size)
> +{
> + struct linux_efi_poisoned_memory *pm = efi_poisoned_memory();
> + u64 first, last, nbits;
> +
> + if (!pm)
> + return false;
> +
> + nbits = pm->size * BITS_PER_BYTE;
> +
> + if (start + size <= pm->phys_base)
> + return false;
> + if (start < pm->phys_base)
> + start = pm->phys_base;
> +
> + first = (start - pm->phys_base) / pm->unit_size;
> + if (first >= nbits)
> + return false;
> +
> + last = (start + size - 1 - pm->phys_base) / pm->unit_size;
[Severity: High]
When start is clamped to pm->phys_base earlier in this function, size is
not proportionally decreased. Does this result in an end boundary calculation
that exceeds the originally requested range?
If the allocator queries a block that starts before the EFI poisoned memory
table (start < pm->phys_base) but extends into it, the unadjusted size added
to the newly shifted start will push the last bit beyond the intended region.
Could this out-of-bounds query cause false positive poison detections for
healthy memory that resides just after the requested block?
> + last = min(last, nbits - 1);
> +
> + return find_next_bit(pm->bitmap, last + 1, first) <= last;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260909-hwpoison-kho-v4-0-359313564495@debian.org?part=4
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v4 5/5] mm/memory-failure: keep inherited poisoned frames out of the buddy allocator
2026-09-09 13:05 [PATCH v4 0/5] mm/memory-failure: keep hardware-poisoned pages out of the next kexec Breno Leitao
` (3 preceding siblings ...)
2026-09-09 13:05 ` [PATCH v4 4/5] mm/memory-failure: efi: answer whether a range is poisoned Breno Leitao
@ 2026-09-09 13:05 ` Breno Leitao
2026-09-09 13:24 ` sashiko-bot
4 siblings, 1 reply; 15+ messages in thread
From: Breno Leitao @ 2026-09-09 13:05 UTC (permalink / raw)
To: Ard Biesheuvel, Ilias Apalodimas, Miaohe Lin, Naoya Horiguchi,
Andrew Morton, kas, kexec, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin,
Brendan Jackman, Johannes Weiner, Zi Yan
Cc: linux-efi, linux-kernel, linux-mm, rmikey, riel, Breno Leitao,
harry, kernel-team
When the pages are being given to the allocator, check if they are
poisoned, and mark them as such.
Similar to unaccepted memory, hook it in __free_pages_core(), and thus
the frames never enter the allocator, rather than being taken back out
of it.
hwpoison_boot_page() leaves a frame in the state a frame poisoned by this
kernel would be in, so everything that already understands PG_hwpoison
covers it, the kexec segment placement check included.
Suggested-by: Kiryl Shutsemau <kas@kernel.org>
Signed-off-by: Breno Leitao <leitao@debian.org>
---
include/linux/mm.h | 5 +++++
mm/memory-failure.c | 15 +++++++++++++++
mm/page_alloc.c | 25 +++++++++++++++++++++++++
3 files changed, 45 insertions(+)
diff --git a/include/linux/mm.h b/include/linux/mm.h
index b68824fcfbef1..9d9f2e8fdc136 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -5225,6 +5225,7 @@ extern const struct attribute_group memory_failure_attr_group;
extern void memory_failure_queue(unsigned long pfn, int flags);
void num_poisoned_pages_inc(unsigned long pfn);
void num_poisoned_pages_sub(unsigned long pfn, long i);
+void __meminit hwpoison_boot_page(struct page *page);
phys_addr_t range_first_hwpoison(phys_addr_t start, unsigned long size);
phys_addr_t range_last_hwpoison(phys_addr_t start, unsigned long size);
#else
@@ -5232,6 +5233,10 @@ static inline void memory_failure_queue(unsigned long pfn, int flags)
{
}
+static inline void hwpoison_boot_page(struct page *page)
+{
+}
+
static inline void num_poisoned_pages_inc(unsigned long pfn)
{
}
diff --git a/mm/memory-failure.c b/mm/memory-failure.c
index d9b8be696aac3..f6afdb2a89a94 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -137,6 +137,21 @@ phys_addr_t range_last_hwpoison(phys_addr_t start, unsigned long size)
return range_hwpoison(start, size, false);
}
+static void update_per_node_mf_stats(unsigned long pfn, enum mf_result result);
+
+/* Not num_poisoned_pages_inc(): its per block half divides by zero this early. */
+void __meminit hwpoison_boot_page(struct page *page)
+{
+ if (PageHWPoison(page))
+ return;
+
+ SetPageHWPoison(page);
+ set_page_count(page, 1);
+ /* The page has been completely isolated == MF_RECOVERED */
+ update_per_node_mf_stats(page_to_pfn(page), MF_RECOVERED);
+ atomic_long_inc(&num_poisoned_pages);
+}
+
/**
* MF_ATTR_RO - Create sysfs entry for each memory failure statistics.
* @_name: name of the file in the per NUMA sysfs directory.
diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index 404896b53003e..9e2ce833fd409 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -1579,6 +1579,19 @@ static void __free_pages_ok(struct page *page, unsigned int order,
free_one_page(zone, page, pfn, order, fpi_flags);
}
+/* Flag the frames an earlier kernel recorded as bad. */
+static void __meminit poison_block(struct page *page, unsigned int order)
+{
+ unsigned long i, nr_pages = 1UL << order;
+
+ for (i = 0; i < nr_pages; i++) {
+ struct page *p = page + i;
+
+ if (range_contains_poisoned_memory(page_to_phys(p), PAGE_SIZE))
+ hwpoison_boot_page(p);
+ }
+}
+
void __meminit __free_pages_core(struct page *page, unsigned int order,
enum meminit_context context)
{
@@ -1613,6 +1626,18 @@ void __meminit __free_pages_core(struct page *page, unsigned int order,
atomic_long_add(nr_pages, &page_zone(page)->managed_pages);
}
+ /* First: a block parked by __free_unaccepted() never returns here. */
+ if (range_contains_poisoned_memory(page_to_phys(page),
+ PAGE_SIZE << order)) {
+ poison_block(page, order);
+ /*
+ * TODO: free the frames in the block that are not poisoned.
+ * They stay out of the allocator and still count in
+ * managed_pages, so a unit costs up to a block.
+ */
+ return;
+ }
+
if (page_contains_unaccepted(page, order)) {
if (order == MAX_PAGE_ORDER && __free_unaccepted(page))
return;
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH v4 5/5] mm/memory-failure: keep inherited poisoned frames out of the buddy allocator
2026-09-09 13:05 ` [PATCH v4 5/5] mm/memory-failure: keep inherited poisoned frames out of the buddy allocator Breno Leitao
@ 2026-09-09 13:24 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-09-09 13:24 UTC (permalink / raw)
To: Liam R. Howlett, Michal Hocko, Naoya Horiguchi, kas,
David Hildenbrand, Ilias Apalodimas, Thomas Gleixner,
Lorenzo Stoakes, Ingo Molnar, Zi Yan, Breno Leitao,
Andrew Morton, Dave Hansen, Mike Rapoport, Miaohe Lin,
Suren Baghdasaryan, Johannes Weiner, Brendan Jackman, x86,
Ard Biesheuvel, Vlastimil Babka, Borislav Petkov, H. Peter Anvin
Cc: riel, linux-kernel, linux-mm, linux-efi, rmikey, harry,
kernel-team, kexec
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] Healthy pages within a memory block containing a poisoned page are completely leaked, preventing memory offlining.
- [Medium] Concurrent non-atomic updates to per-node memory failure statistics (mf_stats) cause lost updates and corrupted counters.
- [Medium] Memory block poison accounting is skipped during memory hotplug, breaking block-level poison tracking and causing global counter leaks.
- [Medium] Boot-poisoned pages lack the MAGIC_HWPOISON marker, breaking unpoison_memory() logic and causing a memory leak on unpoison.
- [Medium] TOCTOU race when setting the HWPoison flag during memory hotplug, potentially causing double accounting of memory failure stats.
--
commit 804f678a0cc93ac88d9e22aff45a011384c3ac87
Author: Breno Leitao <leitao@debian.org>
mm/memory-failure: keep inherited poisoned frames out of the buddy allocator
This commit checks if pages being given to the allocator are poisoned and marks
them accordingly. It hooks into __free_pages_core() so poisoned frames never
enter the buddy allocator. A new function hwpoison_boot_page() is used to set
the appropriate state for these pages.
> diff --git a/mm/memory-failure.c b/mm/memory-failure.c
> index d9b8be696aac3..f6afdb2a89a94 100644
> --- a/mm/memory-failure.c
> +++ b/mm/memory-failure.c
[ ... ]
> +static void update_per_node_mf_stats(unsigned long pfn, enum mf_result result);
> +
> +/* Not num_poisoned_pages_inc(): its per block half divides by zero this early. */
> +void __meminit hwpoison_boot_page(struct page *page)
> +{
> + if (PageHWPoison(page))
> + return;
> +
> + SetPageHWPoison(page);
[Severity: Medium]
Could a concurrent hardware MCE race with hwpoison_boot_page() here? During
memory hotplug, this path checks PageHWPoison(page) and then non-atomically
calls SetPageHWPoison(page). This might lead to both paths setting the flag
and double accounting in both global and per-node memory failure statistics.
> + set_page_count(page, 1);
> + /* The page has been completely isolated == MF_RECOVERED */
[Severity: Medium]
Does this code need to call SetPageHWPoisonTakenOff(page) here? Without the
MAGIC_HWPOISON marker, if unpoison_memory() is called on a boot-poisoned
page, it might not properly recognize the page as having been taken off the
buddy allocator, potentially causing a permanent memory leak on unpoison.
> + update_per_node_mf_stats(page_to_pfn(page), MF_RECOVERED);
[Severity: Medium]
Are the non-atomic increments inside update_per_node_mf_stats() safe when
called concurrently during boot? Since __free_pages_core() can be executed
concurrently by multiple kthreads, the unprotected non-atomic increments like
++mf_stats->recovered might lead to corrupted per-node memory failure
statistics.
> + atomic_long_inc(&num_poisoned_pages);
[Severity: Medium]
Does bypassing num_poisoned_pages_inc() here break memory hot-remove
accounting? By calling atomic_long_inc(&num_poisoned_pages) instead of
num_poisoned_pages_inc(), the hotplug memory block's nr_hwpoison counter is
left at 0. If the block is later hot-removed, the global num_poisoned_pages
counter would not be decremented properly.
> +}
> +
> /**
> * MF_ATTR_RO - Create sysfs entry for each memory failure statistics.
[ ... ]
> diff --git a/mm/page_alloc.c b/mm/page_alloc.c
> index 404896b53003e..9e2ce833fd409 100644
> --- a/mm/page_alloc.c
> +++ b/mm/page_alloc.c
[ ... ]
> @@ -1613,6 +1626,18 @@ void __meminit __free_pages_core(struct page *page, unsigned int order,
> atomic_long_add(nr_pages, &page_zone(page)->managed_pages);
> }
>
> + /* First: a block parked by __free_unaccepted() never returns here. */
> + if (range_contains_poisoned_memory(page_to_phys(page),
> + PAGE_SIZE << order)) {
> + poison_block(page, order);
> + /*
> + * TODO: free the frames in the block that are not poisoned.
> + * They stay out of the allocator and still count in
> + * managed_pages, so a unit costs up to a block.
> + */
> + return;
> + }
[Severity: High]
Does this early return completely leak the unpoisoned pages within the memory
block? By returning early and bypassing __free_pages_ok(), the healthy pages
in the block are not returned to the buddy allocator. Since these leaked
pages lack PageBuddy, PageOffline, or PageHWPoison, memory offlining
(test_pages_isolated) might permanently fail with -EBUSY for this block.
> +
> if (page_contains_unaccepted(page, order)) {
> if (order == MAX_PAGE_ORDER && __free_unaccepted(page))
> return;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260909-hwpoison-kho-v4-0-359313564495@debian.org?part=5
^ permalink raw reply [flat|nested] 15+ messages in thread