* [PATCH v2] x86/unaccepted_memory: Do not accept ranges that are no longer E820 RAM
@ 2026-11-13 10:55 Pankaj Gupta
2026-11-13 12:55 ` Kiryl Shutsemau
2026-11-14 11:33 ` sashiko-bot
0 siblings, 2 replies; 4+ messages in thread
From: Pankaj Gupta @ 2026-11-13 10:55 UTC (permalink / raw)
To: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86
Cc: H . Peter Anvin, Tom Lendacky, Ard Biesheuvel, Kiryl Shutsemau,
Ilias Apalodimas, linux-kernel, Mike Rapoport, linux-mm,
linux-efi, Pankaj.gupta.linux, Pankaj Gupta, stable
The EFI stub builds the unaccepted memory bitmap before the kernel parses
its command line. Option memmap=<size>$<addr> removes RAM from the E820 map
while its bits are still set in the bitmap.
The removed range, such as a memmap=...$... hole, is then still treated as
unaccepted RAM and accept_memory() tries to accept it. The hole is not
mapped, so on SEV-SNP the PVALIDATE faults and the guest crashes early in
the boot:
BUG: unable to handle page fault for address: ffff8a14bfe00000
RIP: 0010:pvalidate_pages+0xc0/0x250
set_pages_state+0x41a/0x440
snp_accept_memory+0x3e/0x50
accept_memory+0x186/0x260
__free_pages_core+0xd2/0x240
memblock_free_pages+0x1b/0x30
memblock_free_all+0x275/0x2d0
This was seen with memmap=2M$0x1ffe00000, a 2 MiB hole in RAM, on a 19 GiB
SNP guest with lazy acceptance.
Accept only whole pages of E820 RAM, and clear the bitmap bits only for the
units in which memory was accepted.
Fixes: 2053bc57f367 ("efi: Add unaccepted memory support")
Cc: stable@vger.kernel.org
Signed-off-by: Pankaj Gupta <pankaj.gupta@amd.com>
---
v1 -> v2:
- Clear the bitmap bits only for the accepted memory
- Round E820 RAM range to page boundaries
v1: https://lore.kernel.org/all/20261102113127.161823-1-pankaj.gupta@amd.com/
arch/x86/include/asm/unaccepted_memory.h | 44 +++++++++++++++++++++++-
drivers/firmware/efi/unaccepted_memory.c | 4 +--
2 files changed, 45 insertions(+), 3 deletions(-)
diff --git a/arch/x86/include/asm/unaccepted_memory.h b/arch/x86/include/asm/unaccepted_memory.h
index f5937e9866ac..9e27a9953dd8 100644
--- a/arch/x86/include/asm/unaccepted_memory.h
+++ b/arch/x86/include/asm/unaccepted_memory.h
@@ -2,10 +2,11 @@
#define _ASM_X86_UNACCEPTED_MEMORY_H
#include <linux/efi.h>
+#include <asm/e820/api.h>
#include <asm/tdx.h>
#include <asm/sev.h>
-static inline void arch_accept_memory(phys_addr_t start, phys_addr_t end)
+static inline void __arch_accept_memory(phys_addr_t start, phys_addr_t end)
{
/* Platform-specific memory-acceptance call goes here */
if (cpu_feature_enabled(X86_FEATURE_TDX_GUEST)) {
@@ -18,6 +19,47 @@ static inline void arch_accept_memory(phys_addr_t start, phys_addr_t end)
}
}
+static inline bool e820_ram_pages(struct e820_entry *entry, phys_addr_t start,
+ phys_addr_t end, phys_addr_t *s, phys_addr_t *e)
+{
+ if (entry->type != E820_TYPE_RAM)
+ return false;
+
+ *s = PAGE_ALIGN(max_t(phys_addr_t, start, entry->addr));
+ *e = PAGE_ALIGN_DOWN(min_t(phys_addr_t, end, entry->addr + entry->size));
+ return *s < *e;
+}
+
+static inline void arch_accept_memory(phys_addr_t start, phys_addr_t end)
+{
+ phys_addr_t s, e;
+ u32 i;
+
+ for (i = 0; i < e820_table->nr_entries; i++) {
+ if (e820_ram_pages(&e820_table->entries[i], start, end, &s, &e))
+ __arch_accept_memory(s, e);
+ }
+}
+
+static inline void arch_clear_accepted(unsigned long *bitmap, phys_addr_t base,
+ unsigned long unit_size,
+ phys_addr_t start, phys_addr_t end)
+{
+ phys_addr_t s, e;
+ u32 i;
+
+ for (i = 0; i < e820_table->nr_entries; i++) {
+ unsigned long first, last;
+
+ if (!e820_ram_pages(&e820_table->entries[i], start, end, &s, &e))
+ continue;
+
+ first = (s - base) / unit_size;
+ last = DIV_ROUND_UP(e - base, unit_size);
+ bitmap_clear(bitmap, first, last - first);
+ }
+}
+
static inline struct efi_unaccepted_memory *efi_get_unaccepted_table(void)
{
if (efi.unaccepted == EFI_INVALID_TABLE_ADDR)
diff --git a/drivers/firmware/efi/unaccepted_memory.c b/drivers/firmware/efi/unaccepted_memory.c
index 4a8ec8d6a571..b8a0c0d1d289 100644
--- a/drivers/firmware/efi/unaccepted_memory.c
+++ b/drivers/firmware/efi/unaccepted_memory.c
@@ -130,7 +130,6 @@ void accept_memory(phys_addr_t start, unsigned long size)
for_each_set_bitrange_from(range_start, range_end, unaccepted->bitmap,
range.end) {
unsigned long phys_start, phys_end;
- unsigned long len = range_end - range_start;
phys_start = range_start * unit_size + unaccepted->phys_base;
phys_end = range_end * unit_size + unaccepted->phys_base;
@@ -150,7 +149,8 @@ void accept_memory(phys_addr_t start, unsigned long size)
arch_accept_memory(phys_start, phys_end);
spin_lock(&unaccepted_memory_lock);
- bitmap_clear(unaccepted->bitmap, range_start, len);
+ arch_clear_accepted(unaccepted->bitmap, unaccepted->phys_base,
+ unit_size, phys_start, phys_end);
}
list_del(&range.list);
--
2.34.1
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH v2] x86/unaccepted_memory: Do not accept ranges that are no longer E820 RAM
2026-11-13 10:55 [PATCH v2] x86/unaccepted_memory: Do not accept ranges that are no longer E820 RAM Pankaj Gupta
@ 2026-11-13 12:55 ` Kiryl Shutsemau
2026-11-13 14:10 ` Gupta, Pankaj
2026-11-14 11:33 ` sashiko-bot
1 sibling, 1 reply; 4+ messages in thread
From: Kiryl Shutsemau @ 2026-11-13 12:55 UTC (permalink / raw)
To: Pankaj Gupta
Cc: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
H . Peter Anvin, Tom Lendacky, Ard Biesheuvel, Ilias Apalodimas,
linux-kernel, Mike Rapoport, linux-mm, linux-efi,
Pankaj.gupta.linux, stable
On Fri, Nov 13, 2026 at 04:55:54AM -0600, Pankaj Gupta wrote:
> Accept only whole pages of E820 RAM, and clear the bitmap bits only for the
> units in which memory was accepted.
A bit in the bitmap covers the whole unit. If the hole doesn't start and
end on a unit boundary, the unit at its edge is only partly accepted, but
its bit gets cleared. The rest of the unit stays unaccepted and nothing
records it.
This kernel never touches the hole, but the next one can. memmap= and mem=
only edit e820_table. kexec_file_load() hands the next kernel
e820_table_kexec, which is copied before the command line is parsed and
still has the hole as RAM. The next kernel finds RAM with a clear bit and
uses it. memmap=1M$0x1fff00000, then kexec into a kernel without it.
On TDX this is new. tdx_accept_memory() works on physical addresses, so
today the hole is accepted together with the rest of the unit and the
bitmap stays correct. Only SNP needs the direct mapping.
A unit cannot be half accepted. Given that, should we go with what Ard
suggested and refuse memmap= holes in unaccepted memory instead?
--
Kiryl Shutsemau / Kirill A. Shutemov
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH v2] x86/unaccepted_memory: Do not accept ranges that are no longer E820 RAM
2026-11-13 12:55 ` Kiryl Shutsemau
@ 2026-11-13 14:10 ` Gupta, Pankaj
0 siblings, 0 replies; 4+ messages in thread
From: Gupta, Pankaj @ 2026-11-13 14:10 UTC (permalink / raw)
To: Kiryl Shutsemau
Cc: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
H . Peter Anvin, Tom Lendacky, Ard Biesheuvel, Ilias Apalodimas,
linux-kernel, Mike Rapoport, linux-mm, linux-efi,
Pankaj.gupta.linux, stable
>> Accept only whole pages of E820 RAM, and clear the bitmap bits only for the
>> units in which memory was accepted.
> A bit in the bitmap covers the whole unit. If the hole doesn't start and
> end on a unit boundary, the unit at its edge is only partly accepted, but
> its bit gets cleared. The rest of the unit stays unaccepted and nothing
> records it.
Right.
>
> This kernel never touches the hole, but the next one can. memmap= and mem=
> only edit e820_table. kexec_file_load() hands the next kernel
> e820_table_kexec, which is copied before the command line is parsed and
> still has the hole as RAM. The next kernel finds RAM with a clear bit and
> uses it. memmap=1M$0x1fff00000, then kexec into a kernel without it.
>
> On TDX this is new. tdx_accept_memory() works on physical addresses, so
> today the hole is accepted together with the rest of the unit and the
> bitmap stays correct. Only SNP needs the direct mapping.
o.k.
>
> A unit cannot be half accepted. Given that, should we go with what Ard
> suggested and refuse memmap= holes in unaccepted memory instead?
Sure, I will go with Ard's suggestion and drop this patch. I will send
the series that
refuses memmap= holes in confidential guests instead.
Thanks for looking!
Pankaj
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH v2] x86/unaccepted_memory: Do not accept ranges that are no longer E820 RAM
2026-11-13 10:55 [PATCH v2] x86/unaccepted_memory: Do not accept ranges that are no longer E820 RAM Pankaj Gupta
2026-11-13 12:55 ` Kiryl Shutsemau
@ 2026-11-14 11:33 ` sashiko-bot
1 sibling, 0 replies; 4+ messages in thread
From: sashiko-bot @ 2026-11-14 11:33 UTC (permalink / raw)
To: Pankaj Gupta, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
Dave Hansen, x86
Cc: H . Peter Anvin, Tom Lendacky, Ard Biesheuvel, Kiryl Shutsemau,
Ilias Apalodimas, Mike Rapoport, linux-mm, linux-efi,
Pankaj.gupta.linux
Thank you for your contribution! Sashiko AI review found 2 potential
issues to consider:
- [Medium] x86/unaccepted_memory: uncleared hole bits in
arch_clear_accepted()
- [Medium] x86/unaccepted_memory: outward unit rounding in
arch_clear_accepted()
--
commit ddcd20a91b6aa10c63b863069cf031e61f95c5fc
Author: Pankaj Gupta <pankaj.gupta@amd.com>
x86/unaccepted_memory: Do not accept ranges that are no longer E820 RAM
This commit restricts x86 memory acceptance to page-aligned E820_TYPE_RAM
ranges and updates accept_memory() to clear bitmap bits via
arch_clear_accepted() only for ranges that were accepted.
> diff --git a/arch/x86/include/asm/unaccepted_memory.h b/arch/x86/include/asm/unaccepted_memory.h
> index f5937e9866ac1..9e27a9953dd83 100644
> --- a/arch/x86/include/asm/unaccepted_memory.h
> +++ b/arch/x86/include/asm/unaccepted_memory.h
> @@ -2,10 +2,11 @@
> #define _ASM_X86_UNACCEPTED_MEMORY_H
>
> #include <linux/efi.h>
> +#include <asm/e820/api.h>
> #include <asm/tdx.h>
> #include <asm/sev.h>
>
> -static inline void arch_accept_memory(phys_addr_t start, phys_addr_t end)
> +static inline void __arch_accept_memory(phys_addr_t start, phys_addr_t end)
> {
> /* Platform-specific memory-acceptance call goes here */
> if (cpu_feature_enabled(X86_FEATURE_TDX_GUEST)) {
> @@ -18,6 +19,47 @@ static inline void arch_accept_memory(phys_addr_t start, phys_addr_t end)
> }
> }
>
> +static inline bool e820_ram_pages(struct e820_entry *entry, phys_addr_t start,
> + phys_addr_t end, phys_addr_t *s, phys_addr_t *e)
> +{
> + if (entry->type != E820_TYPE_RAM)
> + return false;
> +
> + *s = PAGE_ALIGN(max_t(phys_addr_t, start, entry->addr));
> + *e = PAGE_ALIGN_DOWN(min_t(phys_addr_t, end, entry->addr + entry->size));
> + return *s < *e;
> +}
> +
> +static inline void arch_accept_memory(phys_addr_t start, phys_addr_t end)
> +{
> + phys_addr_t s, e;
> + u32 i;
> +
> + for (i = 0; i < e820_table->nr_entries; i++) {
> + if (e820_ram_pages(&e820_table->entries[i], start, end, &s, &e))
> + __arch_accept_memory(s, e);
> + }
> +}
> +
> +static inline void arch_clear_accepted(unsigned long *bitmap, phys_addr_t base,
> + unsigned long unit_size,
> + phys_addr_t start, phys_addr_t end)
> +{
> + phys_addr_t s, e;
> + u32 i;
> +
> + for (i = 0; i < e820_table->nr_entries; i++) {
> + unsigned long first, last;
> +
> + if (!e820_ram_pages(&e820_table->entries[i], start, end, &s, &e))
> + continue;
> +
> + first = (s - base) / unit_size;
> + last = DIV_ROUND_UP(e - base, unit_size);
> + bitmap_clear(bitmap, first, last - first);
> + }
> +}
[Severity: Medium]
Does rounding first down and last up to unit_size boundaries clear the
bitmap bit for an entire 2 MiB unit even when only part of that unit was
accepted?
When a memmap= reservation or mem= limit is smaller than unit_size (2 MiB)
or not aligned to it, arch_accept_memory() only accepts the [s, e)
E820_TYPE_RAM sub-range within that unit and leaves the remaining pages of
the unit unaccepted in hardware. Because first is rounded down via
(s - base) / unit_size and last is rounded up via
DIV_ROUND_UP(e - base, unit_size), arch_clear_accepted() clears the unit's
bit in unaccepted->bitmap anyway. If a new kernel is later booted via
kexec (which reuses the EFI unaccepted memory table alongside the
pre-cmdline e820_table_kexec map), it sees the whole 2 MiB unit as
E820_TYPE_RAM with its unaccepted bit already cleared and faults when
accessing the unaccepted pages.
[ ... ]
> diff --git a/drivers/firmware/efi/unaccepted_memory.c b/drivers/firmware/efi/unaccepted_memory.c
> index 4a8ec8d6a571e..b8a0c0d1d2891 100644
> --- a/drivers/firmware/efi/unaccepted_memory.c
> +++ b/drivers/firmware/efi/unaccepted_memory.c
> @@ -130,7 +130,6 @@ void accept_memory(phys_addr_t start, unsigned long size)
> for_each_set_bitrange_from(range_start, range_end, unaccepted->bitmap,
> range.end) {
> unsigned long phys_start, phys_end;
> - unsigned long len = range_end - range_start;
>
> phys_start = range_start * unit_size + unaccepted->phys_base;
> phys_end = range_end * unit_size + unaccepted->phys_base;
> @@ -150,7 +149,8 @@ void accept_memory(phys_addr_t start, unsigned long size)
> arch_accept_memory(phys_start, phys_end);
>
> spin_lock(&unaccepted_memory_lock);
> - bitmap_clear(unaccepted->bitmap, range_start, len);
> + arch_clear_accepted(unaccepted->bitmap, unaccepted->phys_base,
> + unit_size, phys_start, phys_end);
> }
[Severity: Medium]
If a unit_size-aligned hole such as memmap=2M$0x1ffe00000 leaves its bit set
in unaccepted->bitmap, doesn't range_contains_unaccepted_memory() permanently
report the accepted RAM range immediately preceding the hole as unaccepted?
Both accept_memory() and range_contains_unaccepted_memory() extend any
unit_size-aligned end address by one extra unit_size to cover
load_unaligned_zeropad() across the boundary:
drivers/firmware/efi/unaccepted_memory.c:range_contains_unaccepted_memory() {
...
if (!(end % unit_size))
end += unit_size;
...
while (start < end) {
if (test_bit(start / unit_size, unaccepted->bitmap)) {
ret = true;
break;
}
start += unit_size;
}
}
When the valid RAM before the hole is accepted, accept_memory() extends
phys_end into the hole, but arch_clear_accepted() leaves the hole's bitmap
bit set to 1 because it contains no E820_TYPE_RAM. After that, calling
pfn_is_unaccepted_memory() on the valid, already-accepted 4 KiB RAM page
immediately below the hole (for example PFN 0x1ffdff before 0x1ffe00000)
has end aligned to unit_size, so range_contains_unaccepted_memory() steps
into the hole's unit, sees the set bit, and always returns true. As a
result, read_kcore_iter() zero-fills that valid RAM page in /proc/kcore,
unaccepted_memory_vmcore_pfn_is_ram() omits it from /proc/vmcore, and page
freeing paths repeatedly call accept_memory() on ranges ending at the hole
boundary.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113105554.199912-1-pankaj.gupta@amd.com?part=1
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-11-14 11:33 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-13 10:55 [PATCH v2] x86/unaccepted_memory: Do not accept ranges that are no longer E820 RAM Pankaj Gupta
2026-11-13 12:55 ` Kiryl Shutsemau
2026-11-13 14:10 ` Gupta, Pankaj
2026-11-14 11:33 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox