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

Rafael J. Wysocki (Intel) rafael at kernel.org
Wed Sep 30 13:01:52 PDT 2026


On Sat, Sep 26, 2026 at 7:21 AM Matthias Goergens
<matthias.goergens at gmail.com> 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 at r-ricci.it>
> Signed-off-by: Matthias Goergens <matthias.goergens at gmail.com>
> 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@mail.gmail.com/
> Link: https://lore.kernel.org/all/20260623165724.10753-1-scardracs@disroot.org/
> ---
> Changes in v2:
> - Bump RESTORE_MAGIC, as Rafael asked:
>   https://lore.kernel.org/all/CAJZ5v0izV3g1mSKJtKMcc=TfeUjJofSXCX92y4XX55Lam28qOg@mail.gmail.com/
> - 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!



More information about the kexec mailing list