* [PATCH 0/5] hibernation: make safe_copy_page more robust and remove debug_pagealloc support
@ 2026-09-17 6:07 Mike Rapoport (Microsoft)
2026-09-17 6:07 ` [PATCH 1/5] hibernation: make swsusp_page helpers static Mike Rapoport (Microsoft)
` (4 more replies)
0 siblings, 5 replies; 6+ messages in thread
From: Mike Rapoport (Microsoft) @ 2026-09-17 6:07 UTC (permalink / raw)
To: Andrew Morton, Alexander Potapenko, David Hildenbrand,
Marco Elver, Rafael J. Wysocki
Cc: Dmitry Vyukov, Len Brown, Mike Rapoport, Pavel Machek, kasan-dev,
linux-kernel, linux-mm, linux-pm
When hibernation creates a memory image, it uses set_direct_map() APIs to
temporarily map pages that are marked as not present in the kernel page
tables.
Initially, this was intended to support debug_pagealloc along with
hibernation on x86.
With the increasing desire to use set_direct_map APIs for hardening
features and with their inconsistent implementations across architectures,
using kernel_page_present() + set_direct_map_valid_noflush() to save
non-present pages in the hibernation image is not very safe, to say the
least.
Worse, some combinations of debug features, such as debug_pagealloc and
PAGE_POISON cause a crash during restore.
Keeping debug_pagealloc compatible with hibernation requires a complex
infrastructure for tracking free unmapped pages with a page flag/page type,
verifying that it is actually a free page that hibernate_map_page() tries
to remap and making sure there are no stale or failed page table updates.
With init_on_{alloc,free} and/or PAGE_POISON on top, this also requires the
ability to map and initialize these free pages on restore.
This complexity does not seem justified for a somewhat niche debugging
scenario.
Instead of a complex fix to support hibernation with debug_pagealloc, make
sure that copy_data_pages() and its helpers properly handle errors that may
happen during page table updates, explicitly enable saving of KFENCE pages
and disallow hibernation when debug_pagealloc is enabled.
---
Mike Rapoport (Microsoft) (5):
hibernation: make swsusp_page helpers static
hibernation: ensure secretmem pages don't reach a snapshot
hibernate: handle potential errors in hibernate_{map,unmap}_page()
hibernation, KFENCE: explicitly map/unmap KFENCE pages
hibernation: make hibernation unavailable when debug_pagealloc is on
include/linux/kfence.h | 29 ++++++++++
include/linux/suspend.h | 6 ---
kernel/power/hibernate.c | 6 +++
kernel/power/snapshot.c | 137 +++++++++++++++++++++++++----------------------
mm/kfence/core.c | 52 +++++++++++++++++-
5 files changed, 159 insertions(+), 71 deletions(-)
---
base-commit: 0820e2e85e8aafde66256c567ad37a5b15708d4d
change-id: 20260916-hibernation-7603b86eb981
--
Sincerely yours,
Mike.
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 1/5] hibernation: make swsusp_page helpers static
2026-09-17 6:07 [PATCH 0/5] hibernation: make safe_copy_page more robust and remove debug_pagealloc support Mike Rapoport (Microsoft)
@ 2026-09-17 6:07 ` Mike Rapoport (Microsoft)
2026-09-17 6:07 ` [PATCH 2/5] hibernation: ensure secretmem pages don't reach a snapshot Mike Rapoport (Microsoft)
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Mike Rapoport (Microsoft) @ 2026-09-17 6:07 UTC (permalink / raw)
To: Andrew Morton, Alexander Potapenko, David Hildenbrand,
Marco Elver, Rafael J. Wysocki
Cc: Dmitry Vyukov, Len Brown, Mike Rapoport, Pavel Machek, kasan-dev,
linux-kernel, linux-mm, linux-pm
Since commit 31a1b9d7fe768 ("mm: page_alloc: move mark_free_page() into
snapshot.c") several helpers that set, clear and query free and forbidden
page status are only used inside kernel/power/snapshot.c
Make them static and add necessary forward declarations near the existing
declarations of similar helpers.
Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
include/linux/suspend.h | 6 ------
kernel/power/snapshot.c | 15 +++++++++------
2 files changed, 9 insertions(+), 12 deletions(-)
diff --git a/include/linux/suspend.h b/include/linux/suspend.h
index b02876f1ae38a..b3435c5ef63f7 100644
--- a/include/linux/suspend.h
+++ b/include/linux/suspend.h
@@ -381,9 +381,6 @@ struct platform_hibernation_ops {
#ifdef CONFIG_HIBERNATION
/* kernel/power/snapshot.c */
extern void register_nosave_region(unsigned long b, unsigned long e);
-extern int swsusp_page_is_forbidden(struct page *);
-extern void swsusp_set_page_free(struct page *);
-extern void swsusp_unset_page_free(struct page *);
extern unsigned long get_safe_page(gfp_t gfp_mask);
extern asmlinkage int swsusp_arch_suspend(void);
extern asmlinkage int swsusp_arch_resume(void);
@@ -404,9 +401,6 @@ int arch_hibernation_header_restore(void *addr);
#else /* CONFIG_HIBERNATION */
static inline void register_nosave_region(unsigned long b, unsigned long e) {}
-static inline int swsusp_page_is_forbidden(struct page *p) { return 0; }
-static inline void swsusp_set_page_free(struct page *p) {}
-static inline void swsusp_unset_page_free(struct page *p) {}
static inline void hibernation_set_ops(const struct platform_hibernation_ops *ops) {}
static inline int hibernate(void) { return -ENOSYS; }
diff --git a/kernel/power/snapshot.c b/kernel/power/snapshot.c
index d5dba0e50b2eb..e732bf6389e4c 100644
--- a/kernel/power/snapshot.c
+++ b/kernel/power/snapshot.c
@@ -112,9 +112,12 @@ static inline void hibernate_unmap_page(struct page *page)
}
}
-static int swsusp_page_is_free(struct page *);
-static void swsusp_set_page_forbidden(struct page *);
-static void swsusp_unset_page_forbidden(struct page *);
+static int swsusp_page_is_free(struct page *page);
+static void swsusp_set_page_free(struct page *page);
+static void swsusp_unset_page_free(struct page *page);
+static int swsusp_page_is_forbidden(struct page *page);
+static void swsusp_set_page_forbidden(struct page *page);
+static void swsusp_unset_page_forbidden(struct page *page);
/*
* Number of bytes to reserve for memory allocations made by device drivers
@@ -1036,7 +1039,7 @@ static struct memory_bitmap *free_pages_map;
* corresponding bits in forbidden_pages_map and free_pages_map simultaneously
*/
-void swsusp_set_page_free(struct page *page)
+static void swsusp_set_page_free(struct page *page)
{
if (free_pages_map)
memory_bm_set_bit(free_pages_map, page_to_pfn(page));
@@ -1048,7 +1051,7 @@ static int swsusp_page_is_free(struct page *page)
memory_bm_test_bit(free_pages_map, page_to_pfn(page)) : 0;
}
-void swsusp_unset_page_free(struct page *page)
+static void swsusp_unset_page_free(struct page *page)
{
if (free_pages_map)
memory_bm_clear_bit(free_pages_map, page_to_pfn(page));
@@ -1060,7 +1063,7 @@ static void swsusp_set_page_forbidden(struct page *page)
memory_bm_set_bit(forbidden_pages_map, page_to_pfn(page));
}
-int swsusp_page_is_forbidden(struct page *page)
+static int swsusp_page_is_forbidden(struct page *page)
{
return forbidden_pages_map ?
memory_bm_test_bit(forbidden_pages_map, page_to_pfn(page)) : 0;
--
2.53.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 2/5] hibernation: ensure secretmem pages don't reach a snapshot
2026-09-17 6:07 [PATCH 0/5] hibernation: make safe_copy_page more robust and remove debug_pagealloc support Mike Rapoport (Microsoft)
2026-09-17 6:07 ` [PATCH 1/5] hibernation: make swsusp_page helpers static Mike Rapoport (Microsoft)
@ 2026-09-17 6:07 ` Mike Rapoport (Microsoft)
2026-09-17 6:07 ` [PATCH 3/5] hibernate: handle potential errors in hibernate_{map,unmap}_page() Mike Rapoport (Microsoft)
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Mike Rapoport (Microsoft) @ 2026-09-17 6:07 UTC (permalink / raw)
To: Andrew Morton, Alexander Potapenko, David Hildenbrand,
Marco Elver, Rafael J. Wysocki
Cc: Dmitry Vyukov, Len Brown, Mike Rapoport, Pavel Machek, kasan-dev,
linux-kernel, linux-mm, linux-pm
hibernation_available() checks for active secretmem users, but a secretmem
file can be created after this check and its pages may get into the
hibernation snapshot although they should not.
Recheck secretmem state after userspace is frozen and abort hibernation if
secretmem is active.
Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
kernel/power/hibernate.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c
index d2479c69d71a4..281ad49411481 100644
--- a/kernel/power/hibernate.c
+++ b/kernel/power/hibernate.c
@@ -412,6 +412,11 @@ int hibernation_snapshot(int platform_mode)
if (error)
goto Close;
+ if (secretmem_active()) {
+ error = -EBUSY;
+ goto Thaw;
+ }
+
if (hibernation_test(TEST_FREEZER)) {
/*
--
2.53.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 3/5] hibernate: handle potential errors in hibernate_{map,unmap}_page()
2026-09-17 6:07 [PATCH 0/5] hibernation: make safe_copy_page more robust and remove debug_pagealloc support Mike Rapoport (Microsoft)
2026-09-17 6:07 ` [PATCH 1/5] hibernation: make swsusp_page helpers static Mike Rapoport (Microsoft)
2026-09-17 6:07 ` [PATCH 2/5] hibernation: ensure secretmem pages don't reach a snapshot Mike Rapoport (Microsoft)
@ 2026-09-17 6:07 ` Mike Rapoport (Microsoft)
2026-09-17 6:07 ` [PATCH 4/5] hibernation, KFENCE: explicitly map/unmap KFENCE pages Mike Rapoport (Microsoft)
2026-09-17 6:07 ` [PATCH 5/5] hibernation: make hibernation unavailable when debug_pagealloc is on Mike Rapoport (Microsoft)
4 siblings, 0 replies; 6+ messages in thread
From: Mike Rapoport (Microsoft) @ 2026-09-17 6:07 UTC (permalink / raw)
To: Andrew Morton, Alexander Potapenko, David Hildenbrand,
Marco Elver, Rafael J. Wysocki
Cc: Dmitry Vyukov, Len Brown, Mike Rapoport, Pavel Machek, kasan-dev,
linux-kernel, linux-mm, linux-pm
When safe_copy_page() had to map/unmap pages only because of
debug_pagealloc() there could be no errors in the kernel page table
updates.
However, with the increasing desire to remove pages from the direct map
this assumption becomes a real stretch.
Properly handle errors in hibernate_map_page() and hibernate_unmap_page()
and propagate that error along the page copying path.
Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
kernel/power/snapshot.c | 102 +++++++++++++++++++++++++++++-------------------
1 file changed, 62 insertions(+), 40 deletions(-)
diff --git a/kernel/power/snapshot.c b/kernel/power/snapshot.c
index e732bf6389e4c..52ef0599c2076 100644
--- a/kernel/power/snapshot.c
+++ b/kernel/power/snapshot.c
@@ -79,37 +79,30 @@ static inline int hibernate_restore_unprotect_page(void *page_address) {return 0
#endif /* CONFIG_STRICT_KERNEL_RWX && CONFIG_ARCH_HAS_SET_MEMORY */
-/*
- * The calls to set_direct_map_*() should not fail because remapping a page
- * here means that we only update protection bits in an existing PTE.
- * It is still worth to have a warning here if something changes and this
- * will no longer be the case.
- */
-static inline void hibernate_map_page(struct page *page)
+static inline int hibernate_map_page(struct page *page)
{
if (IS_ENABLED(CONFIG_ARCH_HAS_SET_DIRECT_MAP)) {
- int ret = set_direct_map_default_noflush(page, 1);
-
- if (ret)
- pr_warn_once("Failed to remap page\n");
+ return set_direct_map_default_noflush(page, 1);
} else {
debug_pagealloc_map_pages(page, 1);
+ return 0;
}
}
-static inline void hibernate_unmap_page(struct page *page)
+static inline int hibernate_unmap_page(struct page *page)
{
if (IS_ENABLED(CONFIG_ARCH_HAS_SET_DIRECT_MAP)) {
unsigned long addr = (unsigned long)page_address(page);
int ret = set_direct_map_invalid_noflush(page, 1);
if (ret)
- pr_warn_once("Failed to remap page\n");
+ return ret;
flush_tlb_kernel_range(addr, addr + PAGE_SIZE);
} else {
debug_pagealloc_unmap_pages(page, 1);
}
+ return 0;
}
static int swsusp_page_is_free(struct page *page);
@@ -1449,21 +1442,25 @@ static inline bool do_copy_page(long *dst, long *src)
* Check if the page we are going to copy is marked as present in the kernel
* page tables. This always is the case if CONFIG_DEBUG_PAGEALLOC or
* CONFIG_ARCH_HAS_SET_DIRECT_MAP is not set. In that case kernel_page_present()
- * always returns 'true'. Returns true if the page was entirely composed of
- * zeros, otherwise it will return false.
+ * always returns 'true'.
+ * Sets @zeros_only to true if the page was entirely composed of zeros.
+ *
+ * Returns 0 on success, a negative error code on failure.
*/
-static bool safe_copy_page(void *dst, struct page *s_page)
+static int safe_copy_page(void *dst, struct page *s_page, bool *zeros_only)
{
- bool zeros_only;
+ int err;
if (kernel_page_present(s_page)) {
- zeros_only = do_copy_page(dst, page_address(s_page));
- } else {
- hibernate_map_page(s_page);
- zeros_only = do_copy_page(dst, page_address(s_page));
- hibernate_unmap_page(s_page);
+ *zeros_only = do_copy_page(dst, page_address(s_page));
+ return 0;
}
- return zeros_only;
+
+ err = hibernate_map_page(s_page);
+ if (err)
+ return err;
+ *zeros_only = do_copy_page(dst, page_address(s_page));
+ return hibernate_unmap_page(s_page);
}
#ifdef CONFIG_HIGHMEM
@@ -1473,18 +1470,19 @@ static inline struct page *page_is_saveable(struct zone *zone, unsigned long pfn
saveable_highmem_page(zone, pfn) : saveable_page(zone, pfn);
}
-static bool copy_data_page(unsigned long dst_pfn, unsigned long src_pfn)
+static int copy_data_page(unsigned long dst_pfn, unsigned long src_pfn,
+ bool *zeros_only)
{
struct page *s_page, *d_page;
void *src, *dst;
- bool zeros_only;
+ int err = 0;
s_page = pfn_to_page(src_pfn);
d_page = pfn_to_page(dst_pfn);
if (PageHighMem(s_page)) {
src = kmap_local_page(s_page);
dst = kmap_local_page(d_page);
- zeros_only = do_copy_page(dst, src);
+ *zeros_only = do_copy_page(dst, src);
kunmap_local(dst);
kunmap_local(src);
} else {
@@ -1493,23 +1491,29 @@ static bool copy_data_page(unsigned long dst_pfn, unsigned long src_pfn)
* The page pointed to by src may contain some kernel
* data modified by kmap_atomic()
*/
- zeros_only = safe_copy_page(buffer, s_page);
+ err = safe_copy_page(buffer, s_page, zeros_only);
+ if (err)
+ goto out;
dst = kmap_local_page(d_page);
copy_page(dst, buffer);
kunmap_local(dst);
} else {
- zeros_only = safe_copy_page(page_address(d_page), s_page);
+ err = safe_copy_page(page_address(d_page), s_page,
+ zeros_only);
}
}
- return zeros_only;
+out:
+ return err;
+
}
#else
#define page_is_saveable(zone, pfn) saveable_page(zone, pfn)
-static inline int copy_data_page(unsigned long dst_pfn, unsigned long src_pfn)
+static inline int copy_data_page(unsigned long dst_pfn, unsigned long src_pfn,
+ bool *zeros_only)
{
return safe_copy_page(page_address(pfn_to_page(dst_pfn)),
- pfn_to_page(src_pfn));
+ pfn_to_page(src_pfn), zeros_only);
}
#endif /* CONFIG_HIGHMEM */
@@ -1517,15 +1521,20 @@ static inline int copy_data_page(unsigned long dst_pfn, unsigned long src_pfn)
* Copy data pages will copy all pages into pages pulled from the copy_bm.
* If a page was entirely filled with zeros it will be marked in the zero_bm.
*
- * Returns the number of pages copied.
+ * Sets @copied_pages to the number of pages copied.
+ *
+ * Returns 0 on success, a negative error code on failure.
*/
-static unsigned long copy_data_pages(struct memory_bitmap *copy_bm,
- struct memory_bitmap *orig_bm,
- struct memory_bitmap *zero_bm)
+static int copy_data_pages(struct memory_bitmap *copy_bm,
+ struct memory_bitmap *orig_bm,
+ struct memory_bitmap *zero_bm,
+ unsigned int *copied_pages)
{
- unsigned long copied_pages = 0;
+ unsigned long nr_pages = 0;
struct zone *zone;
unsigned long pfn, copy_pfn;
+ bool zeros_only;
+ int err;
for_each_populated_zone(zone) {
unsigned long max_zone_pfn;
@@ -1543,15 +1552,21 @@ static unsigned long copy_data_pages(struct memory_bitmap *copy_bm,
pfn = memory_bm_next_pfn(orig_bm);
if (unlikely(pfn == BM_END_OF_MAP))
break;
- if (copy_data_page(copy_pfn, pfn)) {
+ err = copy_data_page(copy_pfn, pfn, &zeros_only);
+ if (err)
+ return err;
+
+ if (zeros_only) {
memory_bm_set_bit(zero_bm, pfn);
/* Use this copy_pfn for a page that is not full of zeros */
continue;
}
- copied_pages++;
+ nr_pages++;
copy_pfn = memory_bm_next_pfn(copy_bm);
}
- return copied_pages;
+
+ *copied_pages = nr_pages;
+ return 0;
}
/* Total number of image pages */
@@ -2112,6 +2127,7 @@ static int swsusp_alloc(struct memory_bitmap *copy_bm,
asmlinkage __visible int swsusp_save(void)
{
unsigned int nr_pages, nr_highmem;
+ int err;
pm_deferred_pr_dbg("Creating image\n");
@@ -2133,7 +2149,9 @@ asmlinkage __visible int swsusp_save(void)
* Kill them.
*/
drain_local_pages(NULL);
- nr_copy_pages = copy_data_pages(©_bm, &orig_bm, &zero_bm);
+ err = copy_data_pages(©_bm, &orig_bm, &zero_bm, &nr_copy_pages);
+ if (err)
+ goto err_swsusp_free;
/*
* End of critical section. From now on, we can write to memory,
@@ -2149,6 +2167,10 @@ asmlinkage __visible int swsusp_save(void)
nr_copy_pages, nr_zero_pages);
return 0;
+
+err_swsusp_free:
+ swsusp_free();
+ return err;
}
#ifndef CONFIG_ARCH_HIBERNATION_HEADER
--
2.53.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 4/5] hibernation, KFENCE: explicitly map/unmap KFENCE pages
2026-09-17 6:07 [PATCH 0/5] hibernation: make safe_copy_page more robust and remove debug_pagealloc support Mike Rapoport (Microsoft)
` (2 preceding siblings ...)
2026-09-17 6:07 ` [PATCH 3/5] hibernate: handle potential errors in hibernate_{map,unmap}_page() Mike Rapoport (Microsoft)
@ 2026-09-17 6:07 ` Mike Rapoport (Microsoft)
2026-09-17 6:07 ` [PATCH 5/5] hibernation: make hibernation unavailable when debug_pagealloc is on Mike Rapoport (Microsoft)
4 siblings, 0 replies; 6+ messages in thread
From: Mike Rapoport (Microsoft) @ 2026-09-17 6:07 UTC (permalink / raw)
To: Andrew Morton, Alexander Potapenko, David Hildenbrand,
Marco Elver, Rafael J. Wysocki
Cc: Dmitry Vyukov, Len Brown, Mike Rapoport, Pavel Machek, kasan-dev,
linux-kernel, linux-mm, linux-pm
The pages protected by KFENCE are removed from the direct map.
safe_copy_page() temporarily maps and unmaps them using set_direct_map
APIs, or, when the stars align, even using debug_pagealloc_map_pages().
Neither of these APIs cares whether it is a KFENCE page and both blindly
perform the update of the kernel page table for any non-present page.
Ability to use debug_pagealloc_map_pages() to remap KFENCE pages when both
KFENCE and debug_pagealloc are enabled is an amusing coincidence.
But with increasing appetite for using set_direct_map for hardening
purposes, it becomes too big of a hammer to enable saving any non-present
page in the hibernation image.
Another gotcha is that loongarch that does not have a direct map at all
advertises ARCH_HAS_SET_DIRECT_MAP to allow coexistence of KFENCE and
hibernation.
Extend KFENCE with a bitmap that tracks which pages are protected and
provide kfence_force_mapping() and kfence_restore_mapping() APIs that allow
forced mapping and unmapping of KFENCE pages.
Use these APIs in hibernate_{map,unmap}_pages() for KFENCE pages.
Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
include/linux/kfence.h | 29 +++++++++++++++++++++++++++
kernel/power/snapshot.c | 7 +++++++
mm/kfence/core.c | 52 +++++++++++++++++++++++++++++++++++++++++++++++--
3 files changed, 86 insertions(+), 2 deletions(-)
diff --git a/include/linux/kfence.h b/include/linux/kfence.h
index e5822f6e7f279..33a126cb6d1b7 100644
--- a/include/linux/kfence.h
+++ b/include/linux/kfence.h
@@ -222,6 +222,32 @@ struct kmem_obj_info;
bool __kfence_obj_info(struct kmem_obj_info *kpp, void *object, struct slab *slab);
#endif
+/**
+ * kfence_force_mapping() - make sure a KFENCE page is mapped
+ * @page: page to map
+ *
+ * Check whether @page is protected and map it if needed.
+ *
+ * Requires: is_kfence_address(page_address(page))
+ *
+ * Return:
+ * * false - failed to map @page
+ * * true - @page is mapped
+ */
+bool kfence_force_mapping(struct page *page);
+
+/**
+ * kfence_restore_mapping() - restore mapping of a KFENCE page
+ * @page: page to restore mapping
+ *
+ * Requires: is_kfence_address(page_address(page))
+ *
+ * Return:
+ * * false - failed to restore mapping of the @page
+ * * true - succeeded to restore mapping of the @page
+ */
+bool kfence_restore_mapping(struct page *page);
+
#else /* CONFIG_KFENCE */
#define kfence_sample_interval (0)
@@ -241,6 +267,9 @@ static inline bool __must_check kfence_handle_page_fault(unsigned long addr, boo
return false;
}
+static inline bool kfence_force_mapping(struct page *page) { return true; }
+static inline bool kfence_restore_mapping(struct page *page) { return true; }
+
#ifdef CONFIG_PRINTK
struct kmem_obj_info;
static inline bool __kfence_obj_info(struct kmem_obj_info *kpp, void *object, struct slab *slab)
diff --git a/kernel/power/snapshot.c b/kernel/power/snapshot.c
index 52ef0599c2076..6583b57722e25 100644
--- a/kernel/power/snapshot.c
+++ b/kernel/power/snapshot.c
@@ -31,6 +31,7 @@
#include <linux/compiler.h>
#include <linux/ktime.h>
#include <linux/set_memory.h>
+#include <linux/kfence.h>
#include <linux/uaccess.h>
#include <asm/mmu_context.h>
@@ -81,6 +82,9 @@ static inline int hibernate_restore_unprotect_page(void *page_address) {return 0
static inline int hibernate_map_page(struct page *page)
{
+ if (is_kfence_address(page_address(page)))
+ return kfence_force_mapping(page) ? 0 : -EFAULT;
+
if (IS_ENABLED(CONFIG_ARCH_HAS_SET_DIRECT_MAP)) {
return set_direct_map_default_noflush(page, 1);
} else {
@@ -91,6 +95,9 @@ static inline int hibernate_map_page(struct page *page)
static inline int hibernate_unmap_page(struct page *page)
{
+ if (is_kfence_address(page_address(page)))
+ return kfence_restore_mapping(page) ? 0 : -EFAULT;
+
if (IS_ENABLED(CONFIG_ARCH_HAS_SET_DIRECT_MAP)) {
unsigned long addr = (unsigned long)page_address(page);
int ret = set_direct_map_invalid_noflush(page, 1);
diff --git a/mm/kfence/core.c b/mm/kfence/core.c
index 90925c646c4c2..666e8991d1fed 100644
--- a/mm/kfence/core.c
+++ b/mm/kfence/core.c
@@ -8,6 +8,7 @@
#define pr_fmt(fmt) "kfence: " fmt
#include <linux/atomic.h>
+#include <linux/bitmap.h>
#include <linux/bug.h>
#include <linux/debugfs.h>
#include <linux/hash.h>
@@ -123,6 +124,9 @@ module_param_named(check_on_panic, kfence_check_on_panic, bool, 0444);
char *__kfence_pool __read_mostly;
EXPORT_SYMBOL(__kfence_pool); /* Export for test modules. */
+/* keep track of protected pages */
+static DECLARE_BITMAP(kfence_protected_pages, KFENCE_POOL_SIZE / PAGE_SIZE);
+
/*
* Per-object metadata, with one-to-one mapping of object metadata to
* backing pages (in __kfence_pool).
@@ -249,14 +253,36 @@ static bool alloc_covered_contains(u32 alloc_stack_hash)
return true;
}
+static bool __kfence_protect(unsigned long addr, bool protect)
+{
+ unsigned long page_addr = ALIGN_DOWN(addr, PAGE_SIZE);
+ unsigned long pool_addr = (unsigned long)__kfence_pool;
+ unsigned long index = (page_addr - pool_addr) >> PAGE_SHIFT;
+ bool state;
+
+ assign_bit(index, kfence_protected_pages, protect);
+
+ /*
+ * Reapply protection if the desired state changed while updating
+ * the page table.
+ */
+ do {
+ state = test_bit(index, kfence_protected_pages);
+ if (!kfence_protect_page(page_addr, state))
+ return false;
+ } while (state != test_bit(index, kfence_protected_pages));
+
+ return true;
+}
+
static bool kfence_protect(unsigned long addr)
{
- return !KFENCE_WARN_ON(!kfence_protect_page(ALIGN_DOWN(addr, PAGE_SIZE), true));
+ return !KFENCE_WARN_ON(!__kfence_protect(addr, true));
}
static bool kfence_unprotect(unsigned long addr)
{
- return !KFENCE_WARN_ON(!kfence_protect_page(ALIGN_DOWN(addr, PAGE_SIZE), false));
+ return !KFENCE_WARN_ON(!__kfence_protect(addr, false));
}
static inline unsigned long metadata_to_pageaddr(const struct kfence_metadata *meta)
@@ -1335,3 +1361,25 @@ bool kfence_handle_page_fault(unsigned long addr, bool is_write, struct pt_regs
return kfence_unprotect(addr); /* Unprotect and let access proceed. */
}
+
+bool kfence_force_mapping(struct page *page)
+{
+ unsigned long addr = (unsigned long)page_address(page);
+ unsigned long index = (addr - (unsigned long)__kfence_pool) >> PAGE_SHIFT;
+
+ if (!test_bit(index, kfence_protected_pages))
+ return true;
+
+ return kfence_protect_page(addr, false);
+}
+
+bool kfence_restore_mapping(struct page *page)
+{
+ unsigned long addr = (unsigned long)page_address(page);
+ unsigned long index = (addr - (unsigned long)__kfence_pool) >> PAGE_SHIFT;
+
+ if (!test_bit(index, kfence_protected_pages))
+ return true;
+
+ return kfence_protect_page(addr, true);
+}
--
2.53.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 5/5] hibernation: make hibernation unavailable when debug_pagealloc is on
2026-09-17 6:07 [PATCH 0/5] hibernation: make safe_copy_page more robust and remove debug_pagealloc support Mike Rapoport (Microsoft)
` (3 preceding siblings ...)
2026-09-17 6:07 ` [PATCH 4/5] hibernation, KFENCE: explicitly map/unmap KFENCE pages Mike Rapoport (Microsoft)
@ 2026-09-17 6:07 ` Mike Rapoport (Microsoft)
4 siblings, 0 replies; 6+ messages in thread
From: Mike Rapoport (Microsoft) @ 2026-09-17 6:07 UTC (permalink / raw)
To: Andrew Morton, Alexander Potapenko, David Hildenbrand,
Marco Elver, Rafael J. Wysocki
Cc: Dmitry Vyukov, Len Brown, Mike Rapoport, Pavel Machek, kasan-dev,
linux-kernel, linux-mm, linux-pm
Back in 2008 when commit 8a235efad548a ("Hibernation: Handle
DEBUG_PAGEALLOC on x86") enabled coexistence of hibernation and
DEBUG_PAGEALLOC, the world was simpler, the combination was only supported
on x86 and a non-present page in the direct map meant that the page was
free and debug_pagealloc removed its mapping.
Nowadays there are more architectures with inconsistent views of how the
direct map should be managed, there are more debug mechanisms in MM that
can either drop a page from the direct map (e.g. KFENCE) or require that a
page must be mapped immediately after resume (e.g. init_on_free).
The latter constraint is particularly nasty, because enabling
debug_pagealloc and PAGE_POISON at the same time simply breaks resume:
BUG: unable to handle page fault for address: ffff888000100000
#PF: supervisor write access in kernel mode
#PF: error_code(0x0002) - not-present page
PGD 4f0c067 P4D 4f0c067 PUD 4f0d067 PMD 4f0e067 PTE 800fffffffeff020
Oops: Oops: 0002 [#1] SMP DEBUG_PAGEALLOC NOPTI
CPU: 0 UID: 0 PID: 477 Comm: bash Not tainted 7.3.0-rc3-00090-g4a613f1d9124 #1 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.17.0-debian-1.17.0-1 04/01/2014
RIP: 0010:__kernel_poison_pages+0x45/0x70
Code: 89 fa 48 c1 e6 06 48 01 fe 48 89 d1 48 2b 0d 9a ed 87 01 48 83 c2 40 48 c1 f9 06 48 c1 e1 0c 48 03 0d 97 ed 87 01 48 8d 79 08 <48> 89 01 48 83 e7 f8 48 89 81 f8 0f 00 00 48 29 f9 81 c1 00 10 00
RSP: 0018:ffffc900017dfd78 EFLAGS: 00010086
RAX: aaaaaaaaaaaaaaaa RBX: 0000000000000000 RCX: ffff888000100000
RDX: ffffea0000004040 RSI: ffffea0000004040 RDI: ffff888000100008
RBP: 0000000000000000 R08: ffff888130fcada0 R09: 00000000723e0de4
R10: 0000000096ad9215 R11: 00000000db907bcf R12: 0000000000000005
R13: 0000000000000005 R14: 0000000000000000 R15: 0000000000000000
FS: 00007f8f2243e780(0000) GS:ffff8882e8e13000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: ffff888000100000 CR3: 0000000140679000 CR4: 0000000000750ef0
PKRU: 55555554
Call Trace:
<TASK>
clear_or_poison_free_pages+0xd1/0xe0
hibernation_snapshot+0x548/0x5a0
hibernate.cold+0xcb/0x321
state_store+0xc3/0xd0
kernfs_fop_write_iter+0x163/0x240
vfs_write+0x21f/0x550
ksys_write+0x70/0xf0
do_syscall_64+0xb1/0x590
? __irq_exit_rcu+0x40/0x110
entry_SYSCALL_64_after_hwframe+0x76/0x7e
RIP: 0033:0x7f8f224d0687
Code: 48 89 fa 4c 89 df e8 58 b3 00 00 8b 93 08 03 00 00 59 5e 48 83 f8 fc 74 1a 5b c3 0f 1f 84 00 00 00 00 00 48 8b 44 24 10 0f 05 <5b> c3 0f 1f 80 00 00 00 00 83 e2 39 83 fa 08 75 de e8 23 ff ff ff
RSP: 002b:00007ffe7bbe0910 EFLAGS: 00000202 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 00007f8f2243e780 RCX: 00007f8f224d0687
RDX: 0000000000000005 RSI: 000055e3e53135d0 RDI: 0000000000000001
RBP: 000055e3e53135d0 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000202 R12: 0000000000000005
R13: 00007f8f226295c0 R14: 00007f8f22626e80 R15: 0000000000000000
</TASK>
Modules linked in:
CR2: ffff888000100000
It's possible to build a complex infrastructure involving a new page flag
and/or page_type to allow robust hibernation and restore with
debug_pagealloc enabled.
This infrastructure requires a proper detection whether it is safe to
temporarily remap a page to include it in the hibernation snapshot because
relying on kernel_page_present() is too permissive and may include pages
that were deliberately removed from the direct map for security reasons.
This complexity does not seem justified to deal with an esoteric use case
of hibernating a system that runs with debug_pagealloc enabled.
Make hibernation unavailable when debug_pagealloc is enabled and simplify
hibernate_{map,unmap}_page() that don't need to deal with debug_pagealloc
anymore.
Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
kernel/power/hibernate.c | 1 +
kernel/power/snapshot.c | 31 +++++--------------------------
2 files changed, 6 insertions(+), 26 deletions(-)
diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c
index 281ad49411481..37e41c08f4f36 100644
--- a/kernel/power/hibernate.c
+++ b/kernel/power/hibernate.c
@@ -110,6 +110,7 @@ bool hibernation_available(void)
{
return nohibernate == 0 &&
!security_locked_down(LOCKDOWN_HIBERNATION) &&
+ !debug_pagealloc_enabled() &&
!secretmem_active() && !cxl_mem_active();
}
diff --git a/kernel/power/snapshot.c b/kernel/power/snapshot.c
index 6583b57722e25..baa4fc9e5ad6f 100644
--- a/kernel/power/snapshot.c
+++ b/kernel/power/snapshot.c
@@ -85,12 +85,7 @@ static inline int hibernate_map_page(struct page *page)
if (is_kfence_address(page_address(page)))
return kfence_force_mapping(page) ? 0 : -EFAULT;
- if (IS_ENABLED(CONFIG_ARCH_HAS_SET_DIRECT_MAP)) {
- return set_direct_map_default_noflush(page, 1);
- } else {
- debug_pagealloc_map_pages(page, 1);
- return 0;
- }
+ return 0;
}
static inline int hibernate_unmap_page(struct page *page)
@@ -98,17 +93,6 @@ static inline int hibernate_unmap_page(struct page *page)
if (is_kfence_address(page_address(page)))
return kfence_restore_mapping(page) ? 0 : -EFAULT;
- if (IS_ENABLED(CONFIG_ARCH_HAS_SET_DIRECT_MAP)) {
- unsigned long addr = (unsigned long)page_address(page);
- int ret = set_direct_map_invalid_noflush(page, 1);
-
- if (ret)
- return ret;
-
- flush_tlb_kernel_range(addr, addr + PAGE_SIZE);
- } else {
- debug_pagealloc_unmap_pages(page, 1);
- }
return 0;
}
@@ -1446,10 +1430,10 @@ static inline bool do_copy_page(long *dst, long *src)
/*
* safe_copy_page - Copy a page in a safe way.
*
- * Check if the page we are going to copy is marked as present in the kernel
- * page tables. This always is the case if CONFIG_DEBUG_PAGEALLOC or
- * CONFIG_ARCH_HAS_SET_DIRECT_MAP is not set. In that case kernel_page_present()
- * always returns 'true'.
+ * Page could be not present in the kernel page tables. Try to map it before
+ * copiyng and unmap back as needed.
+ * If the kernel page table update fails, bail out with an error.
+ *
* Sets @zeros_only to true if the page was entirely composed of zeros.
*
* Returns 0 on success, a negative error code on failure.
@@ -1458,11 +1442,6 @@ static int safe_copy_page(void *dst, struct page *s_page, bool *zeros_only)
{
int err;
- if (kernel_page_present(s_page)) {
- *zeros_only = do_copy_page(dst, page_address(s_page));
- return 0;
- }
-
err = hibernate_map_page(s_page);
if (err)
return err;
--
2.53.0
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-09-17 6:07 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-17 6:07 [PATCH 0/5] hibernation: make safe_copy_page more robust and remove debug_pagealloc support Mike Rapoport (Microsoft)
2026-09-17 6:07 ` [PATCH 1/5] hibernation: make swsusp_page helpers static Mike Rapoport (Microsoft)
2026-09-17 6:07 ` [PATCH 2/5] hibernation: ensure secretmem pages don't reach a snapshot Mike Rapoport (Microsoft)
2026-09-17 6:07 ` [PATCH 3/5] hibernate: handle potential errors in hibernate_{map,unmap}_page() Mike Rapoport (Microsoft)
2026-09-17 6:07 ` [PATCH 4/5] hibernation, KFENCE: explicitly map/unmap KFENCE pages Mike Rapoport (Microsoft)
2026-09-17 6:07 ` [PATCH 5/5] hibernation: make hibernation unavailable when debug_pagealloc is on Mike Rapoport (Microsoft)
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®