arch/x86/power/hibernate.c | 51 +++++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 6 deletions(-)
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@r-ricci.it>
Signed-off-by: Matthias Goergens <matthias.goergens@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
/**
base-commit: 6812ce4e4379ffc99c52401ec28f0d7ffbc36206
--
2.55.0
On Sat, Sep 26, 2026 at 7:21 AM Matthias Goergens
<matthias.goergens@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@r-ricci.it>
> Signed-off-by: Matthias Goergens <matthias.goergens@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!
> 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
> [ ... ]
> kernel carries on.
>
> Fixes: 62a03defeabd ("PM / hibernate: Verify the consistent of e820 memory map by md5 digest")
> Reported-by: Roberto Ricci <io@r-ricci.it>
> Signed-off-by: Matthias Goergens <matthias.goergens@gmail.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260926052117.3613637-1-matthias.goergens@gmail.com?part=1
© 2016 - 2026 Red Hat, Inc.