Re: [PATCH] x86/hibernate: Ignore page-zero RAM in E820 checksum
From: Rafael J. Wysocki (Intel)
Date: Fri Sep 25 2026 - 12:59:17 EST
Sorry for the late response.
On Wed, Aug 5, 2026 at 9:09 AM Matthias Goergens
<matthias.goergens@xxxxxxxxx> wrote:
>
> The legacy kexec_load path reconstructs the E820 map exported through
> sysfs. kexec-tools leaves the first 1 KiB unavailable for the real-mode
> transition, so a kernel entered through kexec_load can see conventional
> RAM starting at 0x400. A subsequent firmware boot reports the same RAM
> range starting at zero.
>
> The hibernation E820 checksum compares those byte representations and
> rejects the image, even though the maps agree from page one onwards. The
> reproducer observed this exact transition:
>
> firmware: RAM [0-0x9fbff]
> kexec_load: gap [0-0x3ff], RAM [0x400-0x9fbff]
> firmware: RAM [0-0x9fbff]
>
> Common x86 setup already converts conventional RAM in page zero to
> reserved memory in trim_bios_range() before registering hibernation
> nosave regions. Page zero therefore cannot occur in the image.
>
> Canonicalise only the conventional-RAM portion below PAGE_SIZE before
> calculating the checksum. Preserve RESERVED, ACPI, NVS, UNUSABLE, PMEM
> and all other E820 types so that changes to exceptional mappings remain
> detectable. Maps without conventional RAM intersecting page zero retain
> the previous checksum byte stream.
>
> This is deliberately narrower than the June proposal to checksum only RAM
> and its opt-in relaxed_memmap successor. Rafael noted that ignoring non-RAM
> changes could hide moved ACPI or UEFI regions still used by the resumed
> kernel. This patch preserves every non-RAM entry and ignores only RAM within
> page zero, which common setup already reserves and excludes from the image.
>
> Changing the checksum semantics means an image made by an unpatched
> kernel can fail to resume under a patched kernel, or vice versa, when its
> raw map contains page-zero RAM. Such cross-version attempts remain
> fail-closed; normal same-kernel hibernation is unaffected.
So this should update RESTORE_MAGIC in arch/x86/power/hibernate.c to
indicate the protocol change.
> With the reproduced raw-map difference retained, both the direct-boot
> control and legacy kexec_load hibernation/resume tests passed. The test
> kernel also completed a clean full bzImage build.
>
> Fixes: 62a03defeabd ("PM / hibernate: Verify the consistent of e820 memory map by md5 digest")
> Reported-by: Roberto Ricci <io@xxxxxxxxxx>
> Signed-off-by: Matthias Goergens <matthias.goergens@xxxxxxxxx>
> Link: https://lore.kernel.org/all/Z-hYWc9LtBU1Yhtg@desktop0a/
> Link: https://lists.openwall.net/linux-kernel/2025/04/04/1372
> Link: https://lore.kernel.org/all/CAJZ5v0jmOj0WBtMTvbnaD+2b0bTFowA=JWrqRzaaCYpHpai1Nw@xxxxxxxxxxxxxx/
> Link: https://lore.kernel.org/all/20260623165724.10753-1-scardracs@xxxxxxxxxxx/
> ---
> arch/x86/power/hibernate.c | 47 ++++++++++++++++++++++++++++++++++----
> 1 file changed, 43 insertions(+), 4 deletions(-)
>
> diff --git a/arch/x86/power/hibernate.c b/arch/x86/power/hibernate.c
> index a2294c1649f65..ec53c970c92e6 100644
> --- a/arch/x86/power/hibernate.c
> +++ b/arch/x86/power/hibernate.c
> @@ -63,6 +63,25 @@ struct restore_data_record {
> unsigned long e820_checksum;
> };
>
> +static bool trim_e820_page_zero_ram(struct e820_entry *entry)
> +{
> + u64 lowmem_size;
> +
> + /*
> + * Page zero is BIOS-owned and registered as nosave. Boot loaders may
> + * therefore omit part of its conventional RAM entry without changing
> + * any memory available to the image. Preserve all other E820 types.
> + */
> + if (entry->type != E820_TYPE_RAM || entry->addr >= PAGE_SIZE)
> + return true;
> +
> + lowmem_size = min_t(u64, entry->size, PAGE_SIZE - entry->addr);
> + entry->addr += lowmem_size;
> + entry->size -= lowmem_size;
> +
> + return entry->size;
> +}
> +
> /**
> * compute_e820_crc32 - calculate crc32 of a given e820 table
> *
> @@ -70,12 +89,32 @@ struct restore_data_record {
> *
> * Return: the resulting checksum
> */
> -static inline u32 compute_e820_crc32(struct e820_table *table)
> +static u32 compute_e820_crc32(struct e820_table *table)
> {
> - int size = offsetof(struct e820_table, entries) +
> - sizeof(struct e820_entry) * table->nr_entries;
> + struct e820_entry entry;
> + u32 crc = ~0;
> + u32 nr_entries = 0;
> + u32 i;
> +
> + for (i = 0; i < table->nr_entries; i++) {
> + entry = table->entries[i];
> + if (trim_e820_page_zero_ram(&entry))
> + nr_entries++;
> + }
> +
> + crc = crc32_le(crc, (unsigned char const *)&nr_entries,
> + sizeof(nr_entries));
> +
> + for (i = 0; i < table->nr_entries; i++) {
> + entry = table->entries[i];
> + if (!trim_e820_page_zero_ram(&entry))
> + continue;
> +
> + crc = crc32_le(crc, (unsigned char const *)&entry,
> + sizeof(entry));
> + }
>
> - return ~crc32_le(~0, (unsigned char const *)table, size);
> + return ~crc;
> }
>
> #ifdef CONFIG_X86_64
> --
> 2.55.0
>