]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
kho: align kho_scratch to MAX_ORDER_NR_PAGES pages
authorMichal Clapinski <mclapinski@google.com>
Fri, 17 Jul 2026 13:40:28 +0000 (15:40 +0200)
committerMike Rapoport (Microsoft) <rppt@kernel.org>
Sat, 25 Jul 2026 12:26:30 +0000 (15:26 +0300)
While booting with KHO, the following crash was observed:

BUG: unable to handle page fault for address: ff19164fffff8328
RIP: 0010:__free_one_page+0x1a1/0x6b0
Call Trace:
 <TASK>
 [<ffffffff913208bf>] free_one_page+0xaf/0x240
 [<ffffffff93973288>] deferred_free_pages+0xa8/0xd0
 [<ffffffff93971b4f>] deferred_init_memmap_chunk+0x10f/0x1b0
 [<ffffffff9396e265>] padata_mt_helper+0x65/0xa0
 [<ffffffff90fac402>] process_scheduled_works+0x202/0x410
 [<ffffffff90fae739>] worker_thread+0x1f9/0x2d0
 [<ffffffff90fb62fd>] kthread+0x27d/0x2f0
 [<ffffffff90fae540>] ? __pfx_worker_thread+0x10/0x10
 [<ffffffff90fb6080>] ? __pfx_kthread+0x10/0x10
 [<ffffffff90efdc55>] ret_from_fork+0x145/0x280
 [<ffffffff90fb6080>] ? __pfx_kthread+0x10/0x10
 [<ffffffff90e2e46a>] ret_from_fork_asm+0x1a/0x30
 </TASK>

deferred_init_memmap_chunk() interleaves initialization of struct pages
with freeing them. This works fine without KHO because free regions
will never be buddy neighbors. However, with KHO, free memory will be split
into (free && scratch) and (free && !scratch), that can be buddy neighbors.

KHO scratch is aligned to CMA_MIN_ALIGNMENT_PAGES pages but buddy looks
at the neighborhood of MAX_ORDER_NR_PAGES pages. These values are
configurable but CMA_MIN_ALIGNMENT_PAGES is always less or equal to
MAX_ORDER_NR_PAGES. In the crashing configuration they were set as
follows:

CMA_MIN_ALIGNMENT_PAGES = 1 << 9
MAX_ORDER_NR_PAGES = 1 << 10

So while freeing one chunk, buddy accessed uninitialized struct pages
from another chunk, tried to merge the blocks and crashed.

To fix this, let's just align KHO scratch to MAX_ORDER_NR_PAGES pages.

Fixes: c6073743d0c7 ("kho: make preserved pages compatible with deferred struct page init")
Signed-off-by: Michal Clapinski <mclapinski@google.com>
Link: https://patch.msgid.link/20260717134028.2880508-1-mclapinski@google.com
[rppt: massaged the changelog]
Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
kernel/liveupdate/kexec_handover.c

index 4834a809985ab7abbcb6d2e9dd6f4a01cf71a711..175c08a6e41e3f6e876a0751a404feeadc8ff747 100644 (file)
 #include "../kexec_internal.h"
 #include "kexec_handover_internal.h"
 
+/*
+ * This is the minimal alignment required by deferred struct page init.
+ * deferred_init_memmap_chunk frees memory to the buddy allocator, which looks
+ * at the neighboring pages (up to MAX_PAGE_ORDER) to merge them.
+ * If KHO scratch is not aligned to that value, buddy can access uninitialized
+ * struct pages, which can cause a crash.
+ */
+#define SCRATCH_ALIGNMENT_BYTES (PAGE_SIZE * MAX_ORDER_NR_PAGES)
+static_assert(SCRATCH_ALIGNMENT_BYTES >= CMA_MIN_ALIGNMENT_BYTES);
+
 /* The magic token for preserved pages */
 #define KHO_PAGE_MAGIC 0x4b484f50U /* ASCII for 'KHOP' */
 
@@ -640,8 +650,8 @@ static void __init scratch_size_update(void)
         * Scratch areas are released as MIGRATE_CMA. Round them up to the right
         * size.
         */
-       scratch_size_lowmem = round_up(scratch_size_lowmem, CMA_MIN_ALIGNMENT_BYTES);
-       scratch_size_global = round_up(scratch_size_global, CMA_MIN_ALIGNMENT_BYTES);
+       scratch_size_lowmem = round_up(scratch_size_lowmem, SCRATCH_ALIGNMENT_BYTES);
+       scratch_size_global = round_up(scratch_size_global, SCRATCH_ALIGNMENT_BYTES);
 }
 
 static phys_addr_t __init scratch_size_node(int nid)
@@ -656,7 +666,7 @@ static phys_addr_t __init scratch_size_node(int nid)
                size = scratch_size_pernode;
        }
 
-       return round_up(size, CMA_MIN_ALIGNMENT_BYTES);
+       return round_up(size, SCRATCH_ALIGNMENT_BYTES);
 }
 
 /**
@@ -692,7 +702,7 @@ static void __init kho_reserve_scratch(void)
         * next kernel
         */
        size = scratch_size_lowmem;
-       addr = memblock_phys_alloc_range(size, CMA_MIN_ALIGNMENT_BYTES, 0,
+       addr = memblock_phys_alloc_range(size, SCRATCH_ALIGNMENT_BYTES, 0,
                                         ARCH_LOW_ADDRESS_LIMIT);
        if (!addr) {
                pr_err("Failed to reserve lowmem scratch buffer\n");
@@ -705,7 +715,7 @@ static void __init kho_reserve_scratch(void)
 
        /* reserve large contiguous area for allocations without nid */
        size = scratch_size_global;
-       addr = memblock_phys_alloc(size, CMA_MIN_ALIGNMENT_BYTES);
+       addr = memblock_phys_alloc(size, SCRATCH_ALIGNMENT_BYTES);
        if (!addr) {
                pr_err("Failed to reserve global scratch buffer\n");
                goto err_free_scratch_areas;
@@ -721,7 +731,7 @@ static void __init kho_reserve_scratch(void)
         */
        for_each_node_state(nid, N_MEMORY) {
                size = scratch_size_node(nid);
-               addr = memblock_alloc_range_nid(size, CMA_MIN_ALIGNMENT_BYTES,
+               addr = memblock_alloc_range_nid(size, SCRATCH_ALIGNMENT_BYTES,
                                                0, MEMBLOCK_ALLOC_ACCESSIBLE,
                                                nid, true);
                if (!addr) {