Re: [PATCH v2] x86/hibernate: Ignore page-zero RAM in E820 checksum

From: Rafael J. Wysocki (Intel)

Date: Wed Sep 30 2026 - 16:06:43 EST


On Sat, Sep 26, 2026 at 7:21 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.
> A QEMU guest showed this transition:
>
> firmware: RAM [0-0x9fbff]
> kexec_load: gap [0-0x3ff], RAM [0x400-0x9fbff]
> firmware: RAM [0-0x9fbff]
>
> The checksum covers e820_table_firmware, which trim_bios_range() leaves
> alone. The nosave regions come from e820_table instead, where
> trim_bios_range() has already turned conventional RAM in page zero into
> reserved memory, so page zero never occurs in the image, whatever the
> firmware map says about it.
>
> 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.
>
> This is deliberately narrower than Marco Scardovi's 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.
>
> Bump RESTORE_MAGIC, since the checksum semantics change, so that an
> image written by a kernel without this change is refused by the magic
> check instead of being compared under different rules.
>
> In the same guest, with the page-zero difference kept, hibernation and
> resume now work after both a direct boot and a kexec_load boot. An
> image written by an unpatched kernel is refused at resume with
> "Unrecognized hibernate image header format!", and the firmware-booted
> kernel carries on.
>
> 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/
> ---
> Changes in v2:
> - Bump RESTORE_MAGIC, as Rafael asked:
> https://lore.kernel.org/all/CAJZ5v0izV3g1mSKJtKMcc=TfeUjJofSXCX92y4XX55Lam28qOg@xxxxxxxxxxxxxx/
> - Say which E820 table the checksum covers and why page zero still
> cannot be in the image; drop the paragraph on cross-version images,
> which the magic now covers, and report that such an image is refused.
> - Rebased on 6812ce4e4379 ("Merge tag 'drm-fixes-2026-09-26' of
> https://gitlab.freedesktop.org/drm/kernel";).
>
> arch/x86/power/hibernate.c | 51 +++++++++++++++++++++++++++++++++-----
> 1 file changed, 45 insertions(+), 6 deletions(-)
>
> diff --git a/arch/x86/power/hibernate.c b/arch/x86/power/hibernate.c
> index a2294c1649f6..dbb340fb24ad 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,18 +89,38 @@ 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
> -#define RESTORE_MAGIC 0x23456789ABCDEF02UL
> +#define RESTORE_MAGIC 0x23456789ABCDEF03UL
> #else
> -#define RESTORE_MAGIC 0x12345679UL
> +#define RESTORE_MAGIC 0x1234567AUL
> #endif
>
> /**
>

Applied as 7.4 material, thanks!