AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/08/02 10:46 flow patch-triage
3m Args:
null

Results:
{
  "EnableConfigs": [
    "KEXEC_HANDOVER",
    "KEXEC_HANDOVER_DEBUG",
    "LIVEUPDATE",
    "LIVEUPDATE_MEMFD",
    "HUGETLB_PAGE"
  ],
  "FocusSymbols": [
    "kho_radix_add_key",
    "kho_radix_del_key",
    "kho_radix_walk_tree",
    "kho_radix_init_tree",
    "kho_radix_destroy_tree",
    "memblock_alloc_hugetlb",
    "kho_scratch_overlap"
  ],
  "Reasoning": "The patch refactors the KHO (Kexec HandOver) radix tree implementation, renaming page-specific functions to generic key-based functions (e.g., `kho_radix_add_key`, `kho_radix_del_key`). It also introduces `memblock_alloc_hugetlb` for allocating HugeTLB pages during boot, avoiding KHO scratch memory overlaps. The KHO radix tree functions are reachable from userspace via `/dev/liveupdate` ioctls (like `LIVEUPDATE_SESSION_PRESERVE_FD`) when preserving memfd files. Therefore, this functionality is reachable and should be fuzzed.",
  "WorthFuzzing": true
}

1/1 2026/08/02 10:46 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit b1496cd2dd7b426cd90e9449e59173a9fbd22f8d\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Sun Aug 2 10:46:30 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/include/linux/kexec_handover.h b/include/linux/kexec_handover.h\nindex 48a9793c2b76c..46de86dc343e9 100644\n--- a/include/linux/kexec_handover.h\n+++ b/include/linux/kexec_handover.h\n@@ -5,6 +5,7 @@\n #include \u003clinux/err.h\u003e\n #include \u003clinux/errno.h\u003e\n #include \u003clinux/types.h\u003e\n+#include \u003clinux/mm.h\u003e\n #include \u003casm-generic/kexec_handover.h\u003e\n \n struct kho_vmalloc;\n@@ -33,9 +34,20 @@ void kho_remove_subtree(void *blob);\n int kho_retrieve_subtree(const char *name, phys_addr_t *phys, size_t *size);\n \n void kho_memory_init(void);\n+void kho_memory_init_early(void);\n \n void kho_populate(phys_addr_t fdt_phys, u64 fdt_len, phys_addr_t scratch_phys,\n \t\t  u64 scratch_len);\n+\n+bool kho_scratch_overlap(phys_addr_t phys, size_t size);\n+\n+static inline enum migratetype kho_scratch_migratetype(unsigned long pfn,\n+\t\t\t\t\t\t       enum migratetype mt)\n+{\n+\tif (kho_scratch_overlap(PFN_PHYS(pfn), pageblock_nr_pages \u003c\u003c PAGE_SHIFT))\n+\t\treturn MIGRATE_CMA;\n+\treturn mt;\n+}\n #else\n static inline bool kho_is_enabled(void)\n {\n@@ -108,10 +120,23 @@ static inline int kho_retrieve_subtree(const char *name, phys_addr_t *phys,\n \n static inline void kho_memory_init(void) { }\n \n+static inline void kho_memory_init_early(void) { }\n+\n static inline void kho_populate(phys_addr_t fdt_phys, u64 fdt_len,\n \t\t\t\tphys_addr_t scratch_phys, u64 scratch_len)\n {\n }\n+\n+static inline bool kho_scratch_overlap(phys_addr_t phys, size_t size)\n+{\n+\treturn false;\n+}\n+\n+static inline enum migratetype kho_scratch_migratetype(unsigned long pfn,\n+\t\t\t\t\t\t       enum migratetype mt)\n+{\n+\treturn mt;\n+}\n #endif /* CONFIG_KEXEC_HANDOVER */\n \n #endif /* LINUX_KEXEC_HANDOVER_H */\ndiff --git a/include/linux/kho/abi/kexec_handover.h b/include/linux/kho/abi/kexec_handover.h\nindex 5e2eb8519bda6..2f4fb9c639421 100644\n--- a/include/linux/kho/abi/kexec_handover.h\n+++ b/include/linux/kho/abi/kexec_handover.h\n@@ -257,11 +257,8 @@ struct kho_vmalloc {\n  * memory. These constants govern the indexing, sizing, and depth of the tree.\n  */\n enum kho_radix_consts {\n-\t/*\n-\t * The bit position of the order bit (and also the length of the\n-\t * shifted physical address) for an order-0 page.\n-\t */\n-\tKHO_ORDER_0_LOG2 = 64 - PAGE_SHIFT,\n+\t/* Need to store the PFN, plus one bit for order. */\n+\tKHO_RADIX_KEY_WIDTH = 64 - PAGE_SHIFT + 1,\n \n \t/* Size of the table in kho_radix_node, in log2 */\n \tKHO_TABLE_SIZE_LOG2 = const_ilog2(PAGE_SIZE / sizeof(phys_addr_t)),\n@@ -274,7 +271,7 @@ enum kho_radix_consts {\n \t * and 1 bitmap level.\n \t */\n \tKHO_TREE_MAX_DEPTH =\n-\t\tDIV_ROUND_UP(KHO_ORDER_0_LOG2 - KHO_BITMAP_SIZE_LOG2 + 1,\n+\t\tDIV_ROUND_UP(KHO_RADIX_KEY_WIDTH - KHO_BITMAP_SIZE_LOG2,\n \t\t\t     KHO_TABLE_SIZE_LOG2) + 1,\n };\n \ndiff --git a/include/linux/kho_radix_tree.h b/include/linux/kho_radix_tree.h\nindex 84e918b96e53d..fcd69639721be 100644\n--- a/include/linux/kho_radix_tree.h\n+++ b/include/linux/kho_radix_tree.h\n@@ -11,15 +11,19 @@\n /**\n  * DOC: Kexec Handover Radix Tree\n  *\n- * This is a radix tree implementation for tracking physical memory pages\n- * across kexec transitions. It was developed for the KHO mechanism but is\n- * designed for broader use by any subsystem that needs to preserve pages.\n+ * This is a radix tree implementation for tracking numeric keys across kexec\n+ * transitions. It was developed for the KHO preserved memory map but is\n+ * designed for broader use by any subsystem that needs to track keys.\n+ * Conceptually speaking, the data structure is similar to a set. It tracks the\n+ * presence or absence of numeric keys.\n  *\n  * The radix tree is a multi-level tree where leaf nodes are bitmaps\n- * representing individual pages. To allow pages of different sizes (orders)\n+ * representing individual keys.\n+ *\n+ * For the KHO preserved memory map, to allow pages of different sizes (orders)\n  * to be stored efficiently in a single tree, it uses a unique key encoding\n- * scheme. Each key is an unsigned long that combines a page's physical\n- * address and its order.\n+ * scheme. Each key is an unsigned long that combines a page's physical address\n+ * and its order.\n  *\n  * Client code is responsible for allocating the root node of the tree,\n  * initializing the mutex lock, and managing its lifecycle. It must use the\n@@ -34,37 +38,53 @@ struct kho_radix_tree {\n \tstruct mutex lock; /* protects the tree's structure and root pointer */\n };\n \n-typedef int (*kho_radix_tree_walk_callback_t)(phys_addr_t phys,\n-\t\t\t\t\t      unsigned int order);\n+/**\n+ * struct kho_radix_walk_cb - Callbacks for KHO radix tree walk.\n+ * @leaf:      Called on each present key in the radix tree.\n+ * @node:      Called on each node of the radix tree itself. Receives the\n+ *             physical address of the page containing the node.\n+ *\n+ * For each callback, a return value of 0 continues the walk and a non-zero\n+ * return value is directly returned to the caller.\n+ */\n+struct kho_radix_walk_cb {\n+\tint (*leaf)(unsigned long key, void *data);\n+\tint (*node)(phys_addr_t phys, void *data);\n+};\n \n #ifdef CONFIG_KEXEC_HANDOVER\n \n-int kho_radix_add_page(struct kho_radix_tree *tree, unsigned long pfn,\n-\t\t       unsigned int order);\n-\n-void kho_radix_del_page(struct kho_radix_tree *tree, unsigned long pfn,\n-\t\t\tunsigned int order);\n-\n+int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key);\n+void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key);\n int kho_radix_walk_tree(struct kho_radix_tree *tree,\n-\t\t\tkho_radix_tree_walk_callback_t cb);\n+\t\t\tconst struct kho_radix_walk_cb *cb, void *data);\n+int kho_radix_init_tree(struct kho_radix_tree *tree, struct kho_radix_node *root);\n+void kho_radix_destroy_tree(struct kho_radix_tree *tree);\n \n #else  /* #ifdef CONFIG_KEXEC_HANDOVER */\n \n-static inline int kho_radix_add_page(struct kho_radix_tree *tree, long pfn,\n-\t\t\t\t     unsigned int order)\n+static inline int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key)\n {\n \treturn -EOPNOTSUPP;\n }\n \n-static inline void kho_radix_del_page(struct kho_radix_tree *tree,\n-\t\t\t\t      unsigned long pfn, unsigned int order) { }\n+static inline void kho_radix_del_key(struct kho_radix_tree *tree,\n+\t\t\t\t     unsigned long key) { }\n \n static inline int kho_radix_walk_tree(struct kho_radix_tree *tree,\n-\t\t\t\t      kho_radix_tree_walk_callback_t cb)\n+\t\t\t\t      const struct kho_radix_walk_cb *cb, void *data)\n {\n \treturn -EOPNOTSUPP;\n }\n \n+static inline int kho_radix_init_tree(struct kho_radix_tree *tree,\n+\t\t\t\t      struct kho_radix_node *root)\n+{\n+\treturn 0;\n+}\n+\n+static inline void kho_radix_destroy_tree(struct kho_radix_tree *tree) { }\n+\n #endif /* #ifdef CONFIG_KEXEC_HANDOVER */\n \n #endif\t/* _LINUX_KHO_RADIX_TREE_H */\ndiff --git a/include/linux/memblock.h b/include/linux/memblock.h\nindex 5afcd99aa8c14..d62db9e776cf9 100644\n--- a/include/linux/memblock.h\n+++ b/include/linux/memblock.h\n@@ -51,6 +51,7 @@ extern unsigned long long max_possible_pfn;\n  * memory reservations yet, so we get scratch memory from the previous\n  * kernel that we know is good to use. It is the only memory that\n  * allocations may happen from in this phase.\n+ * @MEMBLOCK_RSRV_HUGETLB: memory is reserved for hugetlb pages\n  */\n enum memblock_flags {\n \tMEMBLOCK_NONE\t\t= 0x0,\t/* No special request */\n@@ -61,6 +62,7 @@ enum memblock_flags {\n \tMEMBLOCK_RSRV_NOINIT\t= 0x10,\t/* don't initialize struct pages */\n \tMEMBLOCK_RSRV_KERN\t= 0x20,\t/* memory reserved for kernel use */\n \tMEMBLOCK_KHO_SCRATCH\t= 0x40,\t/* scratch memory for kexec handover */\n+\tMEMBLOCK_RSRV_HUGETLB\t= 0x80, /* memory reserved for hugetlb pages */\n };\n \n /**\n@@ -420,6 +422,7 @@ void *memblock_alloc_try_nid_raw(phys_addr_t size, phys_addr_t align,\n void *memblock_alloc_try_nid(phys_addr_t size, phys_addr_t align,\n \t\t\t     phys_addr_t min_addr, phys_addr_t max_addr,\n \t\t\t     int nid);\n+void *memblock_alloc_hugetlb(phys_addr_t size, int nid, bool exact_nid);\n \n static __always_inline void *memblock_alloc(phys_addr_t size, phys_addr_t align)\n {\n@@ -484,6 +487,7 @@ static inline __init_memblock bool memblock_bottom_up(void)\n phys_addr_t memblock_phys_mem_size(void);\n phys_addr_t memblock_reserved_size(void);\n phys_addr_t memblock_reserved_kern_size(phys_addr_t limit, int nid);\n+phys_addr_t memblock_reserved_hugetlb_size(phys_addr_t limit, int nid);\n unsigned long memblock_estimated_nr_free_pages(void);\n phys_addr_t memblock_start_of_DRAM(void);\n phys_addr_t memblock_end_of_DRAM(void);\n@@ -613,28 +617,9 @@ static inline void memtest_report_meminfo(struct seq_file *m) { }\n #ifdef CONFIG_MEMBLOCK_KHO_SCRATCH\n void memblock_set_kho_scratch_only(void);\n void memblock_clear_kho_scratch_only(void);\n-bool memblock_is_kho_scratch_memory(phys_addr_t addr);\n-\n-static inline enum migratetype kho_scratch_migratetype(unsigned long pfn,\n-\t\t\t\t\t\t       enum migratetype mt)\n-{\n-\tif (memblock_is_kho_scratch_memory(PFN_PHYS(pfn)))\n-\t\treturn MIGRATE_CMA;\n-\treturn mt;\n-}\n #else\n static inline void memblock_set_kho_scratch_only(void) { }\n static inline void memblock_clear_kho_scratch_only(void) { }\n-static inline bool memblock_is_kho_scratch_memory(phys_addr_t addr)\n-{\n-\treturn false;\n-}\n-\n-static inline enum migratetype kho_scratch_migratetype(unsigned long pfn,\n-\t\t\t\t\t\t       enum migratetype mt)\n-{\n-\treturn mt;\n-}\n #endif\n \n #endif /* _LINUX_MEMBLOCK_H */\ndiff --git a/kernel/liveupdate/Makefile b/kernel/liveupdate/Makefile\nindex eec9d3ae07eb4..d9f4694625567 100644\n--- a/kernel/liveupdate/Makefile\n+++ b/kernel/liveupdate/Makefile\n@@ -8,7 +8,6 @@ luo-y :=\t\t\t\t\t\t\t\t\\\n \t\tluo_session.o\n \n obj-$(CONFIG_KEXEC_HANDOVER)\t\t+= kexec_handover.o\n-obj-$(CONFIG_KEXEC_HANDOVER_DEBUG)\t+= kexec_handover_debug.o\n obj-$(CONFIG_KEXEC_HANDOVER_DEBUGFS)\t+= kexec_handover_debugfs.o\n \n obj-$(CONFIG_LIVEUPDATE)\t\t+= luo.o\ndiff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c\nindex 4834a809985ab..861621faa557b 100644\n--- a/kernel/liveupdate/kexec_handover.c\n+++ b/kernel/liveupdate/kexec_handover.c\n@@ -84,8 +84,25 @@ static struct kho_out kho_out = {\n \t},\n };\n \n+struct kho_in {\n+\tphys_addr_t fdt_phys;\n+\tphys_addr_t scratch_phys;\n+\tchar previous_release[__NEW_UTS_LEN + 1];\n+\tu32 kexec_count;\n+\tstruct kho_debugfs dbg;\n+\tstruct kho_radix_tree radix_tree;\n+};\n+\n+static struct kho_in kho_in = {\n+};\n+\n+static const void *kho_get_fdt(void)\n+{\n+\treturn kho_in.fdt_phys ? phys_to_virt(kho_in.fdt_phys) : NULL;\n+}\n+\n /**\n- * kho_radix_encode_key - Encodes a physical address and order into a radix key.\n+ * kho_encode_radix_key - Encodes a physical address and order into a radix key.\n  * @phys: The physical address of the page.\n  * @order: The order of the page.\n  *\n@@ -95,35 +112,38 @@ static struct kho_out kho_out = {\n  *\n  * Return: The encoded unsigned long radix key.\n  */\n-static unsigned long kho_radix_encode_key(phys_addr_t phys, unsigned int order)\n+static unsigned long kho_encode_radix_key(phys_addr_t phys, unsigned int order)\n {\n-\t/* Order bits part */\n-\tunsigned long h = 1UL \u003c\u003c (KHO_ORDER_0_LOG2 - order);\n-\t/* Shifted physical address part */\n-\tunsigned long l = phys \u003e\u003e (PAGE_SHIFT + order);\n+\t/* The physical address is encoded by shifting the PFN by its order. */\n+\tunsigned long shift = PAGE_SHIFT + order;\n+\t/* Order bit goes right before the shifted PFN. */\n+\tunsigned long h = 1UL \u003c\u003c (64 - shift);\n+\t/* Shifted PFN. */\n+\tunsigned long l = phys \u003e\u003e shift;\n \n \treturn h | l;\n }\n \n /**\n- * kho_radix_decode_key - Decodes a radix key back into a physical address and order.\n+ * kho_decode_radix_key - Decodes a radix key back into a physical address and order.\n  * @key: The unsigned long key to decode.\n  * @order: An output parameter, a pointer to an unsigned int where the decoded\n  *         page order will be stored.\n  *\n- * This function reverses the encoding performed by kho_radix_encode_key(),\n+ * This function reverses the encoding performed by kho_encode_radix_key(),\n  * extracting the original physical address and page order from a given key.\n  *\n  * Return: The decoded physical address.\n  */\n-static phys_addr_t kho_radix_decode_key(unsigned long key, unsigned int *order)\n+static phys_addr_t kho_decode_radix_key(unsigned long key, unsigned int *order)\n {\n-\tunsigned int order_bit = fls64(key);\n+\t/* fls64() indexes starting from 1. */\n+\tunsigned int order_bit = fls64(key) - 1;\n \tphys_addr_t phys;\n \n-\t/* order_bit is numbered starting at 1 from fls64 */\n-\t*order = KHO_ORDER_0_LOG2 - order_bit + 1;\n-\t/* The order is discarded by the shift */\n+\t/* order bit goes right before the shifted PFN. */\n+\t*order = 64 - (PAGE_SHIFT + order_bit);\n+\t/* The order bit is discarded by the shift */\n \tphys = key \u003c\u003c (PAGE_SHIFT + *order);\n \n \treturn phys;\n@@ -143,25 +163,47 @@ static unsigned long kho_radix_get_table_index(unsigned long key,\n \treturn (key \u003e\u003e s) % (1 \u003c\u003c KHO_TABLE_SIZE_LOG2);\n }\n \n+static void __ref *kho_radix_alloc_node(void)\n+{\n+\tstruct kho_radix_node *node;\n+\n+\tif (slab_is_available())\n+\t\tnode = (struct kho_radix_node *)get_zeroed_page(GFP_KERNEL);\n+\telse\n+\t\tnode = memblock_alloc(PAGE_SIZE, PAGE_SIZE);\n+\n+\treturn node;\n+}\n+\n+static void __ref kho_radix_free_node(struct kho_radix_node *node)\n+{\n+\tif (slab_is_available())\n+\t\tfree_page((unsigned long)node);\n+\telse\n+\t\tmemblock_free(node, PAGE_SIZE);\n+}\n+\n /**\n- * kho_radix_add_page - Marks a page as preserved in the radix tree.\n+ * kho_radix_add_key - Add a key to the radix tree.\n  * @tree: The KHO radix tree.\n- * @pfn: The page frame number of the page to preserve.\n- * @order: The order of the page.\n+ * @key: The key to add.\n+ *\n+ * This function traverses the radix tree based on the @key provided. It sets the\n+ * corresponding bit in the leaf bitmap to mark the @key as present. If\n+ * intermediate nodes do not exist along the path, they are allocated and added\n+ * to the tree.\n  *\n- * This function traverses the radix tree based on the key derived from @pfn\n- * and @order. It sets the corresponding bit in the leaf bitmap to mark the\n- * page for preservation. If intermediate nodes do not exist along the path,\n- * they are allocated and added to the tree.\n+ * NOTE: Currently only keys of width up to %KHO_RADIX_KEY_WIDTH are supported.\n+ * This limit only exists because current users of the radix tree don't use more\n+ * than that. Changing the maximum width requires changing the tree depth, which\n+ * needs bumping the ABI version.\n  *\n  * Return: 0 on success, or a negative error code on failure.\n  */\n-int kho_radix_add_page(struct kho_radix_tree *tree,\n-\t\t       unsigned long pfn, unsigned int order)\n+int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key)\n {\n \t/* Newly allocated nodes for error cleanup */\n \tstruct kho_radix_node *intermediate_nodes[KHO_TREE_MAX_DEPTH] = { 0 };\n-\tunsigned long key = kho_radix_encode_key(PFN_PHYS(pfn), order);\n \tstruct kho_radix_node *anchor_node = NULL;\n \tstruct kho_radix_node *node = tree-\u003eroot;\n \tstruct kho_radix_node *new_node;\n@@ -172,6 +214,9 @@ int kho_radix_add_page(struct kho_radix_tree *tree,\n \tif (WARN_ON_ONCE(!tree-\u003eroot))\n \t\treturn -EINVAL;\n \n+\tif (unlikely(fls64(key) \u003e KHO_RADIX_KEY_WIDTH))\n+\t\treturn -ERANGE;\n+\n \tmight_sleep();\n \n \tguard(mutex)(\u0026tree-\u003elock);\n@@ -186,7 +231,7 @@ int kho_radix_add_page(struct kho_radix_tree *tree,\n \t\t}\n \n \t\t/* Next node is empty, create a new node for it */\n-\t\tnew_node = (struct kho_radix_node *)get_zeroed_page(GFP_KERNEL);\n+\t\tnew_node = kho_radix_alloc_node();\n \t\tif (!new_node) {\n \t\t\terr = -ENOMEM;\n \t\t\tgoto err_free_nodes;\n@@ -217,29 +262,26 @@ int kho_radix_add_page(struct kho_radix_tree *tree,\n err_free_nodes:\n \tfor (i = KHO_TREE_MAX_DEPTH - 1; i \u003e 0; i--) {\n \t\tif (intermediate_nodes[i])\n-\t\t\tfree_page((unsigned long)intermediate_nodes[i]);\n+\t\t\tkho_radix_free_node(intermediate_nodes[i]);\n \t}\n \tif (anchor_node)\n \t\tanchor_node-\u003etable[anchor_idx] = 0;\n \n \treturn err;\n }\n-EXPORT_SYMBOL_GPL(kho_radix_add_page);\n+EXPORT_SYMBOL_GPL(kho_radix_add_key);\n \n /**\n- * kho_radix_del_page - Removes a page's preservation status from the radix tree.\n+ * kho_radix_del_key - Removes the key from the radix tree.\n  * @tree: The KHO radix tree.\n- * @pfn: The page frame number of the page to unpreserve.\n- * @order: The order of the page.\n+ * @key: The key to remove.\n  *\n  * This function traverses the radix tree and clears the bit corresponding to\n- * the page, effectively removing its \"preserved\" status. It does not free\n- * the tree's intermediate nodes, even if they become empty.\n+ * the @key, effectively removing it from the tree. It does not free the tree's\n+ * intermediate nodes, even if they become empty.\n  */\n-void kho_radix_del_page(struct kho_radix_tree *tree, unsigned long pfn,\n-\t\t\tunsigned int order)\n+void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key)\n {\n-\tunsigned long key = kho_radix_encode_key(PFN_PHYS(pfn), order);\n \tstruct kho_radix_node *node = tree-\u003eroot;\n \tstruct kho_radix_leaf *leaf;\n \tunsigned int i, idx;\n@@ -247,6 +289,10 @@ void kho_radix_del_page(struct kho_radix_tree *tree, unsigned long pfn,\n \tif (WARN_ON_ONCE(!tree-\u003eroot))\n \t\treturn;\n \n+\t/* Keys wider than KHO_RADIX_KEY_WIDTH are not allowed to be added. */\n+\tif (unlikely(fls64(key) \u003e KHO_RADIX_KEY_WIDTH))\n+\t\treturn;\n+\n \tmight_sleep();\n \n \tguard(mutex)(\u0026tree-\u003elock);\n@@ -270,21 +316,85 @@ void kho_radix_del_page(struct kho_radix_tree *tree, unsigned long pfn,\n \tidx = kho_radix_get_bitmap_index(key);\n \t__clear_bit(idx, leaf-\u003ebitmap);\n }\n-EXPORT_SYMBOL_GPL(kho_radix_del_page);\n+EXPORT_SYMBOL_GPL(kho_radix_del_key);\n+\n+static void __kho_radix_destroy_tree(struct kho_radix_node *root,\n+\t\t\t\t     unsigned int level)\n+{\n+\tunsigned long i;\n+\n+\tif (level == 0) {\n+\t\tkho_radix_free_node(root);\n+\t\treturn;\n+\t}\n+\n+\tfor (i = 0; i \u003c PAGE_SIZE / sizeof(phys_addr_t); i++) {\n+\t\tif (root-\u003etable[i])\n+\t\t\t__kho_radix_destroy_tree(phys_to_virt(root-\u003etable[i]),\n+\t\t\t\t\t\t level - 1);\n+\t}\n+\n+\tkho_radix_free_node(root);\n+}\n \n-static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf,\n-\t\t\t       unsigned long key,\n-\t\t\t       kho_radix_tree_walk_callback_t cb)\n+/**\n+ * kho_radix_init_tree - initialize the radix tree.\n+ * @tree:   the tree to initialize.\n+ * @root:   root table of the radix tree.\n+ *\n+ * Initialize the radix tree with the given root node. If root is %NULL, an\n+ * empty root table is allocated. If root is not %NULL, it is the caller's\n+ * responsibility to make sure the root is valid and in the correct format.\n+ *\n+ * Return: 0 on success, -errno on failure.\n+ */\n+int kho_radix_init_tree(struct kho_radix_tree *tree, struct kho_radix_node *root)\n+{\n+\tif (!root)\n+\t\troot = kho_radix_alloc_node();\n+\tif (!root)\n+\t\treturn -ENOMEM;\n+\n+\ttree-\u003eroot = root;\n+\tmutex_init(\u0026tree-\u003elock);\n+\treturn 0;\n+}\n+EXPORT_SYMBOL_GPL(kho_radix_init_tree);\n+\n+/**\n+ * kho_radix_destroy_tree - Destroy the radix tree\n+ * @tree: The radix tree to destroy\n+ *\n+ * Walk @tree and free all its nodes.\n+ */\n+void kho_radix_destroy_tree(struct kho_radix_tree *tree)\n+{\n+\tif (!tree-\u003eroot)\n+\t\treturn;\n+\n+\t__kho_radix_destroy_tree(tree-\u003eroot, KHO_TREE_MAX_DEPTH - 1);\n+\ttree-\u003eroot = NULL;\n+}\n+EXPORT_SYMBOL_GPL(kho_radix_destroy_tree);\n+\n+static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf, unsigned long key,\n+\t\t\t       const struct kho_radix_walk_cb *cb, void *data)\n {\n \tunsigned long *bitmap = (unsigned long *)leaf;\n-\tunsigned int order;\n-\tphys_addr_t phys;\n \tunsigned int i;\n \tint err;\n \n+\tif (cb-\u003enode) {\n+\t\terr = cb-\u003enode(virt_to_phys(leaf), data);\n+\t\tif (err)\n+\t\t\treturn err;\n+\t}\n+\n+\tif (!cb-\u003eleaf)\n+\t\treturn 0;\n+\n \tfor_each_set_bit(i, bitmap, PAGE_SIZE * BITS_PER_BYTE) {\n-\t\tphys = kho_radix_decode_key(key | i, \u0026order);\n-\t\terr = cb(phys, order);\n+\t\terr = cb-\u003eleaf(key | i, data);\n \t\tif (err)\n \t\t\treturn err;\n \t}\n@@ -294,7 +404,7 @@ static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf,\n \n static int __kho_radix_walk_tree(struct kho_radix_node *root,\n \t\t\t\t unsigned int level, unsigned long start,\n-\t\t\t\t kho_radix_tree_walk_callback_t cb)\n+\t\t\t\t const struct kho_radix_walk_cb *cb, void *data)\n {\n \tstruct kho_radix_node *node;\n \tstruct kho_radix_leaf *leaf;\n@@ -302,6 +412,12 @@ static int __kho_radix_walk_tree(struct kho_radix_node *root,\n \tunsigned int shift;\n \tint err;\n \n+\tif (cb-\u003enode) {\n+\t\terr = cb-\u003enode(virt_to_phys(root), data);\n+\t\tif (err)\n+\t\t\treturn err;\n+\t}\n+\n \tfor (i = 0; i \u003c PAGE_SIZE / sizeof(phys_addr_t); i++) {\n \t\tif (!root-\u003etable[i])\n \t\t\tcontinue;\n@@ -318,10 +434,10 @@ static int __kho_radix_walk_tree(struct kho_radix_node *root,\n \t\t\t * node is pointing to the level 0 bitmap.\n \t\t\t */\n \t\t\tleaf = (struct kho_radix_leaf *)node;\n-\t\t\terr = kho_radix_walk_leaf(leaf, key, cb);\n+\t\t\terr = kho_radix_walk_leaf(leaf, key, cb, data);\n \t\t} else {\n \t\t\terr  = __kho_radix_walk_tree(node, level - 1,\n-\t\t\t\t\t\t     key, cb);\n+\t\t\t\t\t\t     key, cb, data);\n \t\t}\n \n \t\tif (err)\n@@ -332,28 +448,27 @@ static int __kho_radix_walk_tree(struct kho_radix_node *root,\n }\n \n /**\n- * kho_radix_walk_tree - Traverses the radix tree and calls a callback for each preserved page.\n+ * kho_radix_walk_tree - Traverses the radix tree and calls a callback for each key.\n  * @tree: A pointer to the KHO radix tree to walk.\n- * @cb: A callback function of type kho_radix_tree_walk_callback_t that will be\n- *      invoked for each preserved page found in the tree. The callback receives\n- *      the physical address and order of the preserved page.\n+ * @cb:   Set of callbacks to be invoked during the tree walk.\n+ * @data: Opaque data pointer passed to each callback in @cb.\n  *\n- * This function walks the radix tree, searching from the specified top level\n- * down to the lowest level (level 0). For each preserved page found, it invokes\n- * the provided callback, passing the page's physical address and order.\n+ * This function walks the radix tree, searching from the top level down to the\n+ * lowest level (level 0), invoking the appropriate callbacks.\n  *\n  * Return: 0 if the walk completed the specified tree, or the non-zero return\n  *         value from the callback that stopped the walk.\n  */\n int kho_radix_walk_tree(struct kho_radix_tree *tree,\n-\t\t\tkho_radix_tree_walk_callback_t cb)\n+\t\t\tconst struct kho_radix_walk_cb *cb, void *data)\n {\n \tif (WARN_ON_ONCE(!tree-\u003eroot))\n \t\treturn -EINVAL;\n \n \tguard(mutex)(\u0026tree-\u003elock);\n \n-\treturn __kho_radix_walk_tree(tree-\u003eroot, KHO_TREE_MAX_DEPTH - 1, 0, cb);\n+\treturn __kho_radix_walk_tree(tree-\u003eroot, KHO_TREE_MAX_DEPTH - 1, 0, cb,\n+\t\t\t\t     data);\n }\n EXPORT_SYMBOL_GPL(kho_radix_walk_tree);\n \n@@ -484,13 +599,16 @@ static struct page *__init kho_get_preserved_page(phys_addr_t phys,\n \treturn pfn_to_page(pfn);\n }\n \n-static int __init kho_preserved_memory_reserve(phys_addr_t phys,\n-\t\t\t\t\t       unsigned int order)\n+static int __init kho_preserved_memory_reserve(unsigned long key, void *data)\n {\n \tunion kho_page_info info;\n \tstruct page *page;\n+\tunsigned int order;\n+\tphys_addr_t phys;\n \tu64 sz;\n \n+\tphys = kho_decode_radix_key(key, \u0026order);\n+\n \tsz = 1 \u003c\u003c (order + PAGE_SHIFT);\n \tpage = kho_get_preserved_page(phys, order);\n \n@@ -504,19 +622,24 @@ static int __init kho_preserved_memory_reserve(phys_addr_t phys,\n \treturn 0;\n }\n \n-/* Returns physical address of the preserved memory map from FDT */\n-static phys_addr_t __init kho_get_mem_map_phys(const void *fdt)\n+/* Returns virtual address of the preserved memory map from FDT */\n+static __init void *kho_get_mem_map(const void *fdt)\n {\n \tconst void *mem_ptr;\n+\tphys_addr_t mem_map_phys;\n \tint len;\n \n \tmem_ptr = fdt_getprop(fdt, 0, KHO_FDT_MEMORY_MAP_PROP_NAME, \u0026len);\n \tif (!mem_ptr || len != sizeof(u64)) {\n \t\tpr_err(\"failed to get preserved memory map\\n\");\n-\t\treturn 0;\n+\t\treturn NULL;\n \t}\n \n-\treturn get_unaligned((const u64 *)mem_ptr);\n+\tmem_map_phys = get_unaligned((const u64 *)mem_ptr);\n+\tif (!mem_map_phys)\n+\t\treturn NULL;\n+\n+\treturn phys_to_virt(mem_map_phys);\n }\n \n /*\n@@ -619,19 +742,24 @@ early_param(\"kho_scratch\", kho_parse_scratch_size);\n static void __init scratch_size_update(void)\n {\n \t/*\n-\t * If fixed sizes are not provided via command line, calculate them\n-\t * now.\n+\t * If fixed sizes are not provided via command line, calculate them now.\n+\t * Remove HugeTLB allocations from it because they never get allocated\n+\t * from scratch.\n \t */\n \tif (scratch_scale) {\n \t\tphys_addr_t size;\n \n \t\tsize = memblock_reserved_kern_size(ARCH_LOW_ADDRESS_LIMIT,\n \t\t\t\t\t\t   NUMA_NO_NODE);\n+\t\tsize -= memblock_reserved_hugetlb_size(ARCH_LOW_ADDRESS_LIMIT,\n+\t\t\t\t\t\t       NUMA_NO_NODE);\n \t\tsize = size * scratch_scale / 100;\n \t\tscratch_size_lowmem = size;\n \n \t\tsize = memblock_reserved_kern_size(MEMBLOCK_ALLOC_ANYWHERE,\n \t\t\t\t\t\t   NUMA_NO_NODE);\n+\t\tsize -= memblock_reserved_hugetlb_size(MEMBLOCK_ALLOC_ANYWHERE,\n+\t\t\t\t\t\t       NUMA_NO_NODE);\n \t\tsize = size * scratch_scale / 100 - scratch_size_lowmem;\n \t\tscratch_size_global = size;\n \t}\n@@ -651,6 +779,9 @@ static phys_addr_t __init scratch_size_node(int nid)\n \tif (scratch_scale) {\n \t\tsize = memblock_reserved_kern_size(MEMBLOCK_ALLOC_ANYWHERE,\n \t\t\t\t\t\t   nid);\n+\t\t/* Do not count HugeTLB pages. */\n+\t\tsize -= memblock_reserved_hugetlb_size(MEMBLOCK_ALLOC_ANYWHERE,\n+\t\t\t\t\t\t       nid);\n \t\tsize = size * scratch_scale / 100;\n \t} else {\n \t\tsize = scratch_size_pernode;\n@@ -659,6 +790,22 @@ static phys_addr_t __init scratch_size_node(int nid)\n \treturn round_up(size, CMA_MIN_ALIGNMENT_BYTES);\n }\n \n+bool kho_scratch_overlap(phys_addr_t phys, size_t size)\n+{\n+\tphys_addr_t scratch_start, scratch_end;\n+\tunsigned int i;\n+\n+\tfor (i = 0; i \u003c kho_scratch_cnt; i++) {\n+\t\tscratch_start = kho_scratch[i].addr;\n+\t\tscratch_end = kho_scratch[i].addr + kho_scratch[i].size;\n+\n+\t\tif (phys \u003c scratch_end \u0026\u0026 (phys + size) \u003e scratch_start)\n+\t\t\treturn true;\n+\t}\n+\n+\treturn false;\n+}\n+\n /**\n  * kho_reserve_scratch - Reserve a contiguous chunk of memory for kexec\n  *\n@@ -746,6 +893,140 @@ static void __init kho_reserve_scratch(void)\n \tkho_enable = false;\n }\n \n+/*\n+ * Look for free blocks of 1G. This is a heuristic chosen to work efficiently\n+ * with large systems with hundreds of gigabytes of memory. It will work poorly\n+ * on smaller systems. The algorithm itself doesn't depend on the actual value,\n+ * so it can be changed to a different heuristic later if needed.\n+ */\n+#define KHO_SCRATCH_EXT_BLKSIZE\t\tSZ_1G\n+#define KHO_SCRATCH_EXT_BLKSHIFT\tconst_ilog2(KHO_SCRATCH_EXT_BLKSIZE)\n+\n+/* Called for the KHO preserved memory radix tree. */\n+static int __init kho_ext_walk_leaf(unsigned long key, void *data)\n+{\n+\tstruct kho_radix_tree *busy_blocks = data;\n+\tphys_addr_t start, end;\n+\tunsigned int order;\n+\tint err;\n+\n+\t/*\n+\t * The key is from the KHO preserved memory radix tree. It is decoded to\n+\t * a physical address of a preservation and its order.\n+\t */\n+\tstart = kho_decode_radix_key(key, \u0026order);\n+\tend = start + (1UL \u003c\u003c (order + PAGE_SHIFT));\n+\n+\twhile (start \u003c end) {\n+\t\terr = kho_radix_add_key(busy_blocks, start \u003e\u003e KHO_SCRATCH_EXT_BLKSHIFT);\n+\t\tif (err)\n+\t\t\treturn err;\n+\n+\t\tstart += (1UL \u003c\u003c KHO_SCRATCH_EXT_BLKSHIFT);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/* Called for the KHO preserved memory radix tree. */\n+static int __init kho_ext_walk_node(phys_addr_t phys, void *data)\n+{\n+\tstruct kho_radix_tree *busy_blocks = data;\n+\n+\treturn kho_radix_add_key(busy_blocks, phys \u003e\u003e KHO_SCRATCH_EXT_BLKSHIFT);\n+}\n+\n+/* Called for the busy block radix tree. */\n+static int __init kho_ext_mark_scratch(unsigned long key, void *data)\n+{\n+\tphys_addr_t *prev_end = data;\n+\tphys_addr_t start = key \u003c\u003c KHO_SCRATCH_EXT_BLKSHIFT;\n+\tint err;\n+\n+\tif (start \u003e *prev_end) {\n+\t\terr = memblock_mark_kho_scratch(*prev_end, start - *prev_end);\n+\t\tif (err)\n+\t\t\treturn err;\n+\t}\n+\n+\t*prev_end = start + (1UL \u003c\u003c KHO_SCRATCH_EXT_BLKSHIFT);\n+\treturn 0;\n+}\n+\n+/*\n+ * kho_extend_scratch - Extend the scratch regions\n+ *\n+ * The KHO preserved memory radix tree mixes both physical address and order\n+ * into a single key. This makes it hard to look for free ranges directly. This\n+ * function first walks the radix tree and digests it down into another radix\n+ * tree, whose keys identify blocks of size KHO_SCRATCH_EXT_BLKSIZE which\n+ * contain preserved memory.\n+ *\n+ * Then it walks the digested radix tree and marks everything that doesn't have\n+ * preserved memory as scratch.\n+ *\n+ * NOTE: This function allocates memory so it should be called when scratch has\n+ * available space.\n+ *\n+ * NOTE: The pages of the KHO preserved memory radix tree tables are not marked\n+ * as preserved in the preserved memory tree. But they are expected to remain\n+ * untouched until the tree is fully parsed. So this function also considers\n+ * them to be \"preserved memory\" and marks their blocks as busy.\n+ *\n+ * NOTE: efi_init()::reserve_regions() removes all regions except\n+ * MEMBLOCK_KHO_SCRATCH. This function adds such regions but they are not KHO\n+ * scratch memory, so they should not be removed. This function should always be\n+ * called after reserve_regions().\n+ */\n+static void __init kho_extend_scratch(void)\n+{\n+\tconst struct kho_radix_walk_cb kho_cb = {\n+\t\t.leaf = kho_ext_walk_leaf,\n+\t\t.node = kho_ext_walk_node,\n+\t};\n+\tconst struct kho_radix_walk_cb ext_cb = {\n+\t\t.leaf = kho_ext_mark_scratch,\n+\t};\n+\tstatic struct lock_class_key busy_radix_class;\n+\tstruct kho_radix_tree busy_blocks;\n+\tphys_addr_t prev_end = 0;\n+\tint err = 0;\n+\n+\terr = kho_radix_init_tree(\u0026busy_blocks, NULL);\n+\tif (err)\n+\t\tgoto print;\n+\n+\t/*\n+\t * The walk of kho_in.radix_tree adds keys to busy_blocks. The walk\n+\t * takes the kho_in radix tree lock and adding the key takes busy_blocks\n+\t * lock. Since both are struct kho_radix_tree and share the same lock\n+\t * class, lockdep gets confused. Set a different class for\n+\t * busy_blocks.lock to make lockdep happy.\n+\t */\n+\tlockdep_set_class(\u0026busy_blocks.lock, \u0026busy_radix_class);\n+\n+\t/* Walk the KHO radix tree to find busy blocks. */\n+\terr = kho_radix_walk_tree(\u0026kho_in.radix_tree, \u0026kho_cb, \u0026busy_blocks);\n+\tif (err)\n+\t\tgoto out;\n+\n+\t/* Walk the busy blocks and mark everything between keys as scratch. */\n+\terr = kho_radix_walk_tree(\u0026busy_blocks, \u0026ext_cb, \u0026prev_end);\n+\tif (err)\n+\t\tgoto out;\n+\n+\t/* Mark everything from last busy block to end of DRAM. */\n+\tif (prev_end \u003c memblock_end_of_DRAM())\n+\t\terr = memblock_mark_kho_scratch(prev_end, memblock_end_of_DRAM() - prev_end);\n+\n+\t/* fallthrough */\n+out:\n+\tkho_radix_destroy_tree(\u0026busy_blocks);\n+print:\n+\tif (err)\n+\t\tpr_err(\"Failed to extend scratch: %pe\\n\", ERR_PTR(err));\n+}\n+\n /**\n  * kho_add_subtree - record the physical address of a sub blob in KHO root tree.\n  * @name: name of the sub tree.\n@@ -856,10 +1137,12 @@ int kho_preserve_folio(struct folio *folio)\n \tconst unsigned long pfn = folio_pfn(folio);\n \tconst unsigned int order = folio_order(folio);\n \n-\tif (WARN_ON(kho_scratch_overlap(pfn \u003c\u003c PAGE_SHIFT, PAGE_SIZE \u003c\u003c order)))\n+\tif (IS_ENABLED(CONFIG_KEXEC_HANDOVER_DEBUG) \u0026\u0026\n+\t    WARN_ON(kho_scratch_overlap(pfn \u003c\u003c PAGE_SHIFT, PAGE_SIZE \u003c\u003c order)))\n \t\treturn -EINVAL;\n \n-\treturn kho_radix_add_page(tree, pfn, order);\n+\treturn kho_radix_add_key(tree, kho_encode_radix_key(PFN_PHYS(pfn),\n+\t\t\t\t\t\t\t    order));\n }\n EXPORT_SYMBOL_GPL(kho_preserve_folio);\n \n@@ -877,7 +1160,7 @@ void kho_unpreserve_folio(struct folio *folio)\n \tconst unsigned long pfn = folio_pfn(folio);\n \tconst unsigned int order = folio_order(folio);\n \n-\tkho_radix_del_page(tree, pfn, order);\n+\tkho_radix_del_key(tree, kho_encode_radix_key(PFN_PHYS(pfn), order));\n }\n EXPORT_SYMBOL_GPL(kho_unpreserve_folio);\n \n@@ -906,7 +1189,8 @@ static void __kho_unpreserve(struct kho_radix_tree *tree,\n \twhile (pfn \u003c end_pfn) {\n \t\torder = __kho_preserve_pages_order(pfn, end_pfn);\n \n-\t\tkho_radix_del_page(tree, pfn, order);\n+\t\tkho_radix_del_key(tree, kho_encode_radix_key(PFN_PHYS(pfn),\n+\t\t\t\t\t\t\t     order));\n \n \t\tpfn += 1 \u003c\u003c order;\n \t}\n@@ -931,7 +1215,8 @@ int kho_preserve_pages(struct page *page, unsigned long nr_pages)\n \tunsigned long failed_pfn = 0;\n \tint err = 0;\n \n-\tif (WARN_ON(kho_scratch_overlap(start_pfn \u003c\u003c PAGE_SHIFT,\n+\tif (IS_ENABLED(CONFIG_KEXEC_HANDOVER_DEBUG) \u0026\u0026\n+\t    WARN_ON(kho_scratch_overlap(start_pfn \u003c\u003c PAGE_SHIFT,\n \t\t\t\t\tnr_pages \u003c\u003c PAGE_SHIFT))) {\n \t\treturn -EINVAL;\n \t}\n@@ -939,7 +1224,8 @@ int kho_preserve_pages(struct page *page, unsigned long nr_pages)\n \twhile (pfn \u003c end_pfn) {\n \t\tunsigned int order = __kho_preserve_pages_order(pfn, end_pfn);\n \n-\t\terr = kho_radix_add_page(tree, pfn, order);\n+\t\terr = kho_radix_add_key(tree, kho_encode_radix_key(PFN_PHYS(pfn),\n+\t\t\t\t\t\t\t\t   order));\n \t\tif (err) {\n \t\t\tfailed_pfn = pfn;\n \t\t\tbreak;\n@@ -1315,22 +1601,6 @@ void kho_restore_free(void *mem)\n }\n EXPORT_SYMBOL_GPL(kho_restore_free);\n \n-struct kho_in {\n-\tphys_addr_t fdt_phys;\n-\tphys_addr_t scratch_phys;\n-\tchar previous_release[__NEW_UTS_LEN + 1];\n-\tu32 kexec_count;\n-\tstruct kho_debugfs dbg;\n-};\n-\n-static struct kho_in kho_in = {\n-};\n-\n-static const void *kho_get_fdt(void)\n-{\n-\treturn kho_in.fdt_phys ? phys_to_virt(kho_in.fdt_phys) : NULL;\n-}\n-\n /**\n  * is_kho_boot - check if current kernel was booted via KHO-enabled\n  * kexec\n@@ -1398,26 +1668,24 @@ int kho_retrieve_subtree(const char *name, phys_addr_t *phys, size_t *size)\n }\n EXPORT_SYMBOL_GPL(kho_retrieve_subtree);\n \n-static int __init kho_mem_retrieve(const void *fdt)\n+static void __init kho_mem_retrieve(void)\n {\n-\tstruct kho_radix_tree tree;\n-\tconst phys_addr_t *mem;\n-\tint len;\n-\n-\t/* Retrieve the KHO radix tree from passed-in FDT. */\n-\tmem = fdt_getprop(fdt, 0, KHO_FDT_MEMORY_MAP_PROP_NAME, \u0026len);\n+\tconst struct kho_radix_walk_cb cb = {\n+\t\t.leaf = kho_preserved_memory_reserve,\n+\t};\n \n-\tif (!mem || len != sizeof(*mem)) {\n-\t\tpr_err(\"failed to get preserved KHO memory tree\\n\");\n-\t\treturn -ENOENT;\n-\t}\n+\tif (kho_radix_walk_tree(\u0026kho_in.radix_tree, \u0026cb, NULL))\n+\t\tgoto err;\n \n-\tif (!*mem)\n-\t\treturn -EINVAL;\n+\treturn;\n \n-\ttree.root = phys_to_virt(*mem);\n-\tmutex_init(\u0026tree.lock);\n-\treturn kho_radix_walk_tree(\u0026tree, kho_preserved_memory_reserve);\n+err:\n+\t/*\n+\t * Failed to initialize preserved memory. Clear FDT and radix so KHO\n+\t * users don't treat it as a KHO boot.\n+\t */\n+\tkho_in.fdt_phys = 0;\n+\tkho_in.radix_tree.root = NULL;\n }\n \n static __init int kho_out_fdt_setup(void)\n@@ -1543,16 +1811,14 @@ static __init int kho_init(void)\n \tif (!kho_enable)\n \t\treturn 0;\n \n-\ttree-\u003eroot = kzalloc(PAGE_SIZE, GFP_KERNEL);\n-\tif (!tree-\u003eroot) {\n-\t\terr = -ENOMEM;\n+\terr = kho_radix_init_tree(tree, NULL);\n+\tif (err)\n \t\tgoto err_free_scratch;\n-\t}\n \n \tkho_out.fdt = kho_alloc_preserve(PAGE_SIZE);\n \tif (IS_ERR(kho_out.fdt)) {\n \t\terr = PTR_ERR(kho_out.fdt);\n-\t\tgoto err_free_kho_radix_tree_root;\n+\t\tgoto err_free_kho_radix_tree;\n \t}\n \n \terr = kho_debugfs_init();\n@@ -1603,9 +1869,8 @@ static __init int kho_init(void)\n \n err_free_fdt:\n \tkho_unpreserve_free(kho_out.fdt);\n-err_free_kho_radix_tree_root:\n-\tkfree(tree-\u003eroot);\n-\ttree-\u003eroot = NULL;\n+err_free_kho_radix_tree:\n+\tkho_radix_destroy_tree(tree);\n err_free_scratch:\n \tkho_out.fdt = NULL;\n \tfor (int i = 0; i \u003c kho_scratch_cnt; i++) {\n@@ -1619,16 +1884,43 @@ static __init int kho_init(void)\n }\n fs_initcall(kho_init);\n \n-void __init kho_memory_init(void)\n+void __init kho_memory_init_early(void)\n {\n-\tif (kho_in.scratch_phys) {\n-\t\tkho_scratch = phys_to_virt(kho_in.scratch_phys);\n+\tconst void *fdt = kho_get_fdt();\n \n-\t\tif (kho_mem_retrieve(kho_get_fdt()))\n-\t\t\tkho_in.fdt_phys = 0;\n-\t} else {\n-\t\tkho_reserve_scratch();\n+\tif (!is_kho_boot())\n+\t\treturn;\n+\n+\t/*\n+\t * kho_scratch_overlap() needs kho_scratch to be initialized. It\n+\t * is used by free_area_init() on KHO boots, so initialize it\n+\t * early.\n+\t */\n+\tkho_scratch = phys_to_virt(kho_in.scratch_phys);\n+\n+\t/*\n+\t * kho_get_mem_map() should always succeed. If it fails, kho_populate()\n+\t * catches that and never sets kho_in.fdt_phys.\n+\t */\n+\tif (kho_radix_init_tree(\u0026kho_in.radix_tree, kho_get_mem_map(fdt))) {\n+\t\t/*\n+\t\t * Failed to initialize preserved memory radix tree. Clear FDT\n+\t\t * and scratch so KHO users don't treat it as a KHO boot.\n+\t\t */\n+\t\tkho_in.fdt_phys = 0;\n+\t\tkho_in.scratch_phys = 0;\n+\t\treturn;\n \t}\n+\n+\tkho_extend_scratch();\n+}\n+\n+void __init kho_memory_init(void)\n+{\n+\tif (kho_in.scratch_phys)\n+\t\tkho_mem_retrieve();\n+\telse\n+\t\tkho_reserve_scratch();\n }\n \n void __init kho_populate(phys_addr_t fdt_phys, u64 fdt_len,\n@@ -1636,9 +1928,8 @@ void __init kho_populate(phys_addr_t fdt_phys, u64 fdt_len,\n {\n \tunsigned int scratch_cnt = scratch_len / sizeof(*kho_scratch);\n \tstruct kho_scratch *scratch = NULL;\n-\tphys_addr_t mem_map_phys;\n-\tvoid *fdt = NULL;\n \tbool populated = false;\n+\tvoid *fdt = NULL;\n \tint err;\n \n \t/* Validate the input FDT */\n@@ -1660,8 +1951,13 @@ void __init kho_populate(phys_addr_t fdt_phys, u64 fdt_len,\n \t\tgoto unmap_fdt;\n \t}\n \n-\tmem_map_phys = kho_get_mem_map_phys(fdt);\n-\tif (!mem_map_phys)\n+\t/*\n+\t * At this point phys_to_virt() doesn't work properly and so\n+\t * kho_get_mem_map() can return a pre-KASLR virtual address. But here we\n+\t * only want to make sure the mem_map is valid so the actual value\n+\t * doesn't matter as long as it isn't NULL.\n+\t */\n+\tif (!kho_get_mem_map(fdt))\n \t\tgoto unmap_fdt;\n \n \tscratch = early_memremap(scratch_phys, scratch_len);\ndiff --git a/kernel/liveupdate/kexec_handover_debug.c b/kernel/liveupdate/kexec_handover_debug.c\ndeleted file mode 100644\nindex 6efb696f54261..0000000000000\n--- a/kernel/liveupdate/kexec_handover_debug.c\n+++ /dev/null\n@@ -1,25 +0,0 @@\n-// SPDX-License-Identifier: GPL-2.0-only\n-/*\n- * kexec_handover_debug.c - kexec handover optional debug functionality\n- * Copyright (C) 2025 Google LLC, Pasha Tatashin \u003cpasha.tatashin@soleen.com\u003e\n- */\n-\n-#define pr_fmt(fmt) \"KHO: \" fmt\n-\n-#include \"kexec_handover_internal.h\"\n-\n-bool kho_scratch_overlap(phys_addr_t phys, size_t size)\n-{\n-\tphys_addr_t scratch_start, scratch_end;\n-\tunsigned int i;\n-\n-\tfor (i = 0; i \u003c kho_scratch_cnt; i++) {\n-\t\tscratch_start = kho_scratch[i].addr;\n-\t\tscratch_end = kho_scratch[i].addr + kho_scratch[i].size;\n-\n-\t\tif (phys \u003c scratch_end \u0026\u0026 (phys + size) \u003e scratch_start)\n-\t\t\treturn true;\n-\t}\n-\n-\treturn false;\n-}\ndiff --git a/kernel/liveupdate/kexec_handover_internal.h b/kernel/liveupdate/kexec_handover_internal.h\nindex 0399ff1077750..805d2a76c3881 100644\n--- a/kernel/liveupdate/kexec_handover_internal.h\n+++ b/kernel/liveupdate/kexec_handover_internal.h\n@@ -41,13 +41,4 @@ static inline void kho_debugfs_blob_remove(struct kho_debugfs *dbg,\n \t\t\t\t\t   void *blob) { }\n #endif /* CONFIG_KEXEC_HANDOVER_DEBUGFS */\n \n-#ifdef CONFIG_KEXEC_HANDOVER_DEBUG\n-bool kho_scratch_overlap(phys_addr_t phys, size_t size);\n-#else\n-static inline bool kho_scratch_overlap(phys_addr_t phys, size_t size)\n-{\n-\treturn false;\n-}\n-#endif /* CONFIG_KEXEC_HANDOVER_DEBUG */\n-\n #endif /* LINUX_KEXEC_HANDOVER_INTERNAL_H */\ndiff --git a/mm/hugetlb.c b/mm/hugetlb.c\nindex 571212b80835e..ab4afc818e8cb 100644\n--- a/mm/hugetlb.c\n+++ b/mm/hugetlb.c\n@@ -3033,29 +3033,21 @@ static __init void *alloc_bootmem(struct hstate *h, int nid, bool node_exact)\n \tif (hugetlb_early_cma(h))\n \t\tm = hugetlb_cma_alloc_bootmem(h, \u0026listnode, node_exact);\n \telse {\n-\t\tif (node_exact)\n-\t\t\tm = memblock_alloc_exact_nid_raw(huge_page_size(h),\n-\t\t\t\thuge_page_size(h), 0,\n-\t\t\t\tMEMBLOCK_ALLOC_ACCESSIBLE, nid);\n-\t\telse {\n-\t\t\tm = memblock_alloc_try_nid_raw(huge_page_size(h),\n-\t\t\t\thuge_page_size(h), 0,\n-\t\t\t\tMEMBLOCK_ALLOC_ACCESSIBLE, nid);\n+\t\tm = memblock_alloc_hugetlb(huge_page_size(h), nid, node_exact);\n+\t\tif (m) {\n+\t\t\tm-\u003eflags = 0;\n+\t\t\tm-\u003ecma = NULL;\n+\n \t\t\t/*\n \t\t\t * For pre-HVO to work correctly, pages need to be on\n \t\t\t * the list for the node they were actually allocated\n \t\t\t * from. That node may be different in the case of\n-\t\t\t * fallback by memblock_alloc_try_nid_raw. So,\n+\t\t\t * fallback by memblock_alloc_hugetlb_bootmem. So,\n \t\t\t * extract the actual node first.\n \t\t\t */\n-\t\t\tif (m)\n+\t\t\tif (!node_exact)\n \t\t\t\tlistnode = early_pfn_to_nid(PHYS_PFN(__pa(m)));\n \t\t}\n-\n-\t\tif (m) {\n-\t\t\tm-\u003eflags = 0;\n-\t\t\tm-\u003ecma = NULL;\n-\t\t}\n \t}\n \n \tif (m) {\ndiff --git a/mm/memblock.c b/mm/memblock.c\nindex 6349c48154f4b..37e06e70c1d00 100644\n--- a/mm/memblock.c\n+++ b/mm/memblock.c\n@@ -19,11 +19,9 @@\n #include \u003clinux/mutex.h\u003e\n #include \u003clinux/string_helpers.h\u003e\n \n-#ifdef CONFIG_KEXEC_HANDOVER\n #include \u003clinux/libfdt.h\u003e\n #include \u003clinux/kexec_handover.h\u003e\n #include \u003clinux/kho/abi/memblock.h\u003e\n-#endif /* CONFIG_KEXEC_HANDOVER */\n \n #include \u003casm/sections.h\u003e\n #include \u003clinux/io.h\u003e\n@@ -1506,6 +1504,32 @@ int __init_memblock memblock_set_node(phys_addr_t base, phys_addr_t size,\n \treturn 0;\n }\n \n+static void memblock_prep_allocation(phys_addr_t start, phys_addr_t size,\n+\t\t\t\t     bool kmemleak_trace)\n+{\n+\t/*\n+\t * Skip kmemleak for those places like kasan_init() and\n+\t * early_pgtable_alloc() due to high volume.\n+\t */\n+\tif (kmemleak_trace)\n+\t\t/*\n+\t\t * Memblock allocated blocks are never reported as\n+\t\t * leaks. This is because many of these blocks are\n+\t\t * only referred via the physical address which is\n+\t\t * not looked up by kmemleak.\n+\t\t */\n+\t\tkmemleak_alloc_phys(start, size, 0);\n+\n+\t/*\n+\t * Some Virtual Machine platforms, such as Intel TDX or AMD SEV-SNP,\n+\t * require memory to be accepted before it can be used by the\n+\t * guest.\n+\t *\n+\t * Accept the memory of the allocated buffer.\n+\t */\n+\taccept_memory(start, size);\n+}\n+\n /**\n  * memblock_alloc_range_nid - allocate boot memory block\n  * @size: size of memory block to be allocated in bytes\n@@ -1580,28 +1604,7 @@ phys_addr_t __init memblock_alloc_range_nid(phys_addr_t size,\n \treturn 0;\n \n done:\n-\t/*\n-\t * Skip kmemleak for those places like kasan_init() and\n-\t * early_pgtable_alloc() due to high volume.\n-\t */\n-\tif (end != MEMBLOCK_ALLOC_NOLEAKTRACE)\n-\t\t/*\n-\t\t * Memblock allocated blocks are never reported as\n-\t\t * leaks. This is because many of these blocks are\n-\t\t * only referred via the physical address which is\n-\t\t * not looked up by kmemleak.\n-\t\t */\n-\t\tkmemleak_alloc_phys(found, size, 0);\n-\n-\t/*\n-\t * Some Virtual Machine platforms, such as Intel TDX or AMD SEV-SNP,\n-\t * require memory to be accepted before it can be used by the\n-\t * guest.\n-\t *\n-\t * Accept the memory of the allocated buffer.\n-\t */\n-\taccept_memory(found, size);\n-\n+\tmemblock_prep_allocation(found, size, end != MEMBLOCK_ALLOC_NOLEAKTRACE);\n \treturn found;\n }\n \n@@ -1756,6 +1759,77 @@ void * __init memblock_alloc_try_nid_raw(\n \t\t\t\t       false);\n }\n \n+/**\n+ * memblock_alloc_hugetlb - allocate boot memory for HugeTLB pages\n+ * @size:      size of the memory to be allocated in bytes\n+ * @nid:       nid of the free memory to find, %NUMA_NO_NODE for any node\n+ * @exact_nid: only allocate from the specified nid. If %false, the specified\n+ *             nid is tried first, and then all nodes are tried as fallback.\n+ *\n+ * HugeTLB pages are always aligned by their size, so the alignment matches\n+ * @size. Since the memory is for userspace, mirrored memory is not used. The\n+ * memory is not zeroed. Does not panic if request cannot be satisfied.\n+ *\n+ * Return:\n+ * Virtual address of allocated memory block on success, %NULL on failure.\n+ */\n+void * __init memblock_alloc_hugetlb(phys_addr_t size, int nid, bool exact_nid)\n+{\n+\tenum memblock_flags flags = choose_memblock_flags();\n+\tphys_addr_t addr, start = 0, end = MEMBLOCK_ALLOC_ACCESSIBLE;\n+\n+\tmemblock_dbg(\"%s: %llu bytes, nid=%d, exact_nid=%d %pS\\n\", __func__,\n+\t\t     (u64)size, nid, exact_nid, (void *)_RET_IP_);\n+\n+\t/* Don't waste mirrored memory on HugeTLB pages. */\n+\tflags \u0026= ~MEMBLOCK_MIRROR;\n+retry:\n+\t/* HugeTLB pages are always aligned by their size. */\n+\taddr = memblock_find_in_range_node(size, size, start, end, nid, flags);\n+\tif (addr)\n+\t\tgoto found;\n+\n+\t/* Try all nodes if allowed. */\n+\tif (numa_valid_node(nid) \u0026\u0026 !exact_nid) {\n+\t\tnid = NUMA_NO_NODE;\n+\t\t/*\n+\t\t * If a previous candidate overlapped with KHO scratch, it would\n+\t\t * update start or end. Now that the search is opening to all\n+\t\t * nodes, reset them.\n+\t\t */\n+\t\tstart = 0;\n+\t\tend = MEMBLOCK_ALLOC_ACCESSIBLE;\n+\n+\t\tgoto retry;\n+\t}\n+\n+\t/* Found nothing... :-( */\n+\treturn NULL;\n+\n+found:\n+\t/*\n+\t * HugeTLB pages can be preserved with KHO and no preserved memory can\n+\t * be in scratch. So retry if found address overlaps with scratch.\n+\t *\n+\t * Scratch areas are normally not very large, so this shouldn't take too\n+\t * many retries.\n+\t */\n+\tif (kho_scratch_overlap(addr, size)) {\n+\t\tif (memblock_bottom_up())\n+\t\t\tstart = addr + size;\n+\t\telse\n+\t\t\tend = addr;\n+\n+\t\tgoto retry;\n+\t}\n+\n+\tif (__memblock_reserve(addr, size, nid, MEMBLOCK_RSRV_KERN | MEMBLOCK_RSRV_HUGETLB))\n+\t\treturn NULL;\n+\n+\tmemblock_prep_allocation(addr, size, true);\n+\treturn phys_to_virt(addr);\n+}\n+\n /**\n  * memblock_alloc_try_nid - allocate boot memory block\n  * @size: size of memory block to be allocated in bytes\n@@ -1825,6 +1899,28 @@ phys_addr_t __init_memblock memblock_reserved_size(void)\n \treturn memblock.reserved.total_size;\n }\n \n+phys_addr_t __init_memblock memblock_reserved_hugetlb_size(phys_addr_t limit, int nid)\n+{\n+\tstruct memblock_region *r;\n+\tphys_addr_t total = 0;\n+\n+\tfor_each_reserved_mem_region(r) {\n+\t\tphys_addr_t size = r-\u003esize;\n+\n+\t\tif (r-\u003ebase \u003e limit)\n+\t\t\tbreak;\n+\n+\t\tif (r-\u003ebase + r-\u003esize \u003e limit)\n+\t\t\tsize = limit - r-\u003ebase;\n+\n+\t\tif (nid == memblock_get_region_node(r) || !numa_valid_node(nid))\n+\t\t\tif (r-\u003eflags \u0026 MEMBLOCK_RSRV_HUGETLB)\n+\t\t\t\ttotal += size;\n+\t}\n+\n+\treturn total;\n+}\n+\n phys_addr_t __init_memblock memblock_reserved_kern_size(phys_addr_t limit, int nid)\n {\n \tstruct memblock_region *r;\n@@ -2511,16 +2607,6 @@ __init void memblock_clear_kho_scratch_only(void)\n {\n \tkho_scratch_only = false;\n }\n-\n-bool __init_memblock memblock_is_kho_scratch_memory(phys_addr_t addr)\n-{\n-\tint i = memblock_search(\u0026memblock.memory, addr);\n-\n-\tif (i == -1)\n-\t\treturn false;\n-\n-\treturn memblock_is_kho_scratch(\u0026memblock.memory.regions[i]);\n-}\n #endif\n \n #ifdef CONFIG_KEXEC_HANDOVER\ndiff --git a/mm/mm_init.c b/mm/mm_init.c\nindex 0f64909e8d20e..dc20d6814e48b 100644\n--- a/mm/mm_init.c\n+++ b/mm/mm_init.c\n@@ -674,15 +674,17 @@ static inline void fixup_hashdist(void)\n static inline void fixup_hashdist(void) {}\n #endif /* CONFIG_NUMA */\n \n-#ifdef CONFIG_ZONE_DEVICE\n+#if defined(CONFIG_ZONE_DEVICE) || defined(CONFIG_DEFERRED_STRUCT_PAGE_INIT)\n static __meminit void pageblock_migratetype_init_range(unsigned long pfn,\n-\t\tunsigned long nr_pages, int migratetype)\n+\t\tunsigned long nr_pages, int migratetype, bool atomic)\n {\n \tconst unsigned long end = pfn + nr_pages;\n \n \tfor (pfn = pageblock_align(pfn); pfn \u003c end; pfn += pageblock_nr_pages) {\n-\t\tinit_pageblock_migratetype(pfn_to_page(pfn), migratetype, false);\n-\t\tif (IS_ALIGNED(pfn, PAGES_PER_SECTION))\n+\t\tenum migratetype mt = kho_scratch_migratetype(pfn, migratetype);\n+\n+\t\tinit_pageblock_migratetype(pfn_to_page(pfn), mt, false);\n+\t\tif (!atomic \u0026\u0026 IS_ALIGNED(pfn, PAGES_PER_SECTION))\n \t\t\tcond_resched();\n \t}\n }\n@@ -932,8 +934,9 @@ void __meminit memmap_init_range(unsigned long size, int nid, unsigned long zone\n \t\t * over the place during system boot.\n \t\t */\n \t\tif (pageblock_aligned(pfn)) {\n-\t\t\tinit_pageblock_migratetype(page, migratetype,\n-\t\t\t\t\tisolate_pageblock);\n+\t\t\tenum migratetype mt = kho_scratch_migratetype(pfn, migratetype);\n+\n+\t\t\tinit_pageblock_migratetype(page, mt, isolate_pageblock);\n \t\t\tcond_resched();\n \t\t}\n \t\tpfn++;\n@@ -943,8 +946,7 @@ void __meminit memmap_init_range(unsigned long size, int nid, unsigned long zone\n static void __init memmap_init_zone_range(struct zone *zone,\n \t\t\t\t\t  unsigned long start_pfn,\n \t\t\t\t\t  unsigned long end_pfn,\n-\t\t\t\t\t  unsigned long *hole_pfn,\n-\t\t\t\t\t  enum migratetype mt)\n+\t\t\t\t\t  unsigned long *hole_pfn)\n {\n \tunsigned long zone_start_pfn = zone-\u003ezone_start_pfn;\n \tunsigned long zone_end_pfn = zone_start_pfn + zone-\u003espanned_pages;\n@@ -957,7 +959,8 @@ static void __init memmap_init_zone_range(struct zone *zone,\n \t\treturn;\n \n \tmemmap_init_range(end_pfn - start_pfn, nid, zone_id, start_pfn,\n-\t\t\t  zone_end_pfn, MEMINIT_EARLY, NULL, mt, false);\n+\t\t\t  zone_end_pfn, MEMINIT_EARLY, NULL, MIGRATE_MOVABLE,\n+\t\t\t  false);\n \n \tif (*hole_pfn \u003c start_pfn)\n \t\tinit_unavailable_range(*hole_pfn, start_pfn, zone_id, nid);\n@@ -973,8 +976,6 @@ static void __init memmap_init(void)\n \n \tfor_each_mem_pfn_range(i, MAX_NUMNODES, \u0026start_pfn, \u0026end_pfn, \u0026nid) {\n \t\tstruct pglist_data *node = NODE_DATA(nid);\n-\t\tenum migratetype mt =\n-\t\t\tkho_scratch_migratetype(start_pfn, MIGRATE_MOVABLE);\n \n \t\tfor (j = 0; j \u003c MAX_NR_ZONES; j++) {\n \t\t\tstruct zone *zone = node-\u003enode_zones + j;\n@@ -983,7 +984,7 @@ static void __init memmap_init(void)\n \t\t\t\tcontinue;\n \n \t\t\tmemmap_init_zone_range(zone, start_pfn, end_pfn,\n-\t\t\t\t\t       \u0026hole_pfn, mt);\n+\t\t\t\t\t       \u0026hole_pfn);\n \t\t\tzone_id = j;\n \t\t}\n \t}\n@@ -1142,7 +1143,7 @@ void __ref memmap_init_zone_device(struct zone *zone,\n \t\t\t\t     compound_nr_pages(pfn, altmap, pgmap));\n \t}\n \n-\tpageblock_migratetype_init_range(start_pfn, nr_pages, MIGRATE_MOVABLE);\n+\tpageblock_migratetype_init_range(start_pfn, nr_pages, MIGRATE_MOVABLE, false);\n \n \tpr_debug(\"%s initialised %lu pages in %ums\\n\", __func__,\n \t\tnr_pages, jiffies_to_msecs(jiffies - start));\n@@ -1973,7 +1974,7 @@ unsigned long __init node_map_pfn_alignment(void)\n \n #ifdef CONFIG_DEFERRED_STRUCT_PAGE_INIT\n static void __init deferred_free_pages(unsigned long pfn,\n-\t\tunsigned long nr_pages, enum migratetype mt)\n+\t\tunsigned long nr_pages)\n {\n \tstruct page *page;\n \tunsigned long i;\n@@ -1981,12 +1982,12 @@ static void __init deferred_free_pages(unsigned long pfn,\n \tif (!nr_pages)\n \t\treturn;\n \n+\tpageblock_migratetype_init_range(pfn, nr_pages, MIGRATE_MOVABLE, true);\n+\n \tpage = pfn_to_page(pfn);\n \n \t/* Free a large naturally-aligned chunk if possible */\n \tif (nr_pages == MAX_ORDER_NR_PAGES \u0026\u0026 IS_MAX_ORDER_ALIGNED(pfn)) {\n-\t\tfor (i = 0; i \u003c nr_pages; i += pageblock_nr_pages)\n-\t\t\tinit_pageblock_migratetype(page + i, mt, false);\n \t\t__free_pages_core(page, MAX_PAGE_ORDER, MEMINIT_EARLY);\n \t\treturn;\n \t}\n@@ -1994,11 +1995,8 @@ static void __init deferred_free_pages(unsigned long pfn,\n \t/* Accept chunks smaller than MAX_PAGE_ORDER upfront */\n \taccept_memory(PFN_PHYS(pfn), nr_pages * PAGE_SIZE);\n \n-\tfor (i = 0; i \u003c nr_pages; i++, page++, pfn++) {\n-\t\tif (pageblock_aligned(pfn))\n-\t\t\tinit_pageblock_migratetype(page, mt, false);\n-\t\t__free_pages_core(page, 0, MEMINIT_EARLY);\n-\t}\n+\tfor (i = 0; i \u003c nr_pages; i++)\n+\t\t__free_pages_core(page + i, 0, MEMINIT_EARLY);\n }\n \n /* Completion tracking for deferred_init_memmap() threads */\n@@ -2054,8 +2052,6 @@ deferred_init_memmap_chunk(unsigned long start_pfn, unsigned long end_pfn,\n \tfor_each_free_mem_range(i, nid, 0, \u0026start, \u0026end, NULL) {\n \t\tunsigned long spfn = PFN_UP(start);\n \t\tunsigned long epfn = PFN_DOWN(end);\n-\t\tenum migratetype mt =\n-\t\t\tkho_scratch_migratetype(spfn, MIGRATE_MOVABLE);\n \n \t\tif (spfn \u003e= end_pfn)\n \t\t\tbreak;\n@@ -2068,7 +2064,7 @@ deferred_init_memmap_chunk(unsigned long start_pfn, unsigned long end_pfn,\n \t\t\tunsigned long chunk_end = min(mo_pfn, epfn);\n \n \t\t\tnr_pages += deferred_init_pages(zone, spfn, chunk_end);\n-\t\t\tdeferred_free_pages(spfn, chunk_end - spfn, mt);\n+\t\t\tdeferred_free_pages(spfn, chunk_end - spfn);\n \n \t\t\tspfn = chunk_end;\n \n@@ -2687,6 +2683,7 @@ void __init __weak mem_init(void)\n \n void __init mm_core_init_early(void)\n {\n+\tkho_memory_init_early();\n \thugetlb_cma_reserve();\n \thugetlb_bootmem_alloc();\n \ndiff --git a/tools/testing/memblock/internal.h b/tools/testing/memblock/internal.h\nindex b6b1d147fd750..e86bb9000b22f 100644\n--- a/tools/testing/memblock/internal.h\n+++ b/tools/testing/memblock/internal.h\n@@ -66,4 +66,9 @@ static inline void init_deferred_page(unsigned long pfn, int nid)\n \n #define __SetPageReserved(p)\t((void)(p))\n \n+static inline bool kho_scratch_overlap(phys_addr_t phys, size_t size)\n+{\n+\treturn false;\n+}\n+\n #endif\ndiff --git a/tools/testing/memblock/linux/kexec_handover.h b/tools/testing/memblock/linux/kexec_handover.h\nnew file mode 100644\nindex 0000000000000..bdfd9445b1fd3\n--- /dev/null\n+++ b/tools/testing/memblock/linux/kexec_handover.h\n@@ -0,0 +1,9 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+#ifndef LINUX_KEXEC_HANDOVER_H\n+#define LINUX_KEXEC_HANDOVER_H\n+\n+/*\n+ * Header stub to avoid test build breakage; we don't need to actually implement\n+ * any KHO functions as they are not used in the tests.\n+ */\n+#endif /* LINUX_KEXEC_HANDOVER_H */\ndiff --git a/tools/testing/memblock/linux/kho/abi/memblock.h b/tools/testing/memblock/linux/kho/abi/memblock.h\nnew file mode 100644\nindex 0000000000000..71cf6982a2b98\n--- /dev/null\n+++ b/tools/testing/memblock/linux/kho/abi/memblock.h\n@@ -0,0 +1,10 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+\n+#ifndef _LINUX_KHO_ABI_MEMBLOCK_H\n+#define _LINUX_KHO_ABI_MEMBLOCK_H\n+\n+/*\n+ * Header stub to avoid test build breakage; we don't need to actually define\n+ * any ABI as they are not used in the tests.\n+ */\n+#endif /* _LINUX_KHO_ABI_MEMBLOCK_H */\ndiff --git a/tools/testing/memblock/linux/libfdt.h b/tools/testing/memblock/linux/libfdt.h\nnew file mode 100644\nindex 0000000000000..6137b788fe8de\n--- /dev/null\n+++ b/tools/testing/memblock/linux/libfdt.h\n@@ -0,0 +1,9 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+#ifndef _INCLUDE_LIBFDT_H_\n+#define _INCLUDE_LIBFDT_H_\n+\n+/*\n+ * Header stub to avoid test build breakage; we don't need to actually implement\n+ * any FDT functions as they are not used in the tests.\n+ */\n+#endif /* _INCLUDE_LIBFDT_H_ */\n"
}

2/1 2026/08/02 10:46 agent patch-evaluator
3m Model:
best-expensive

Args:
null

Results:
{
  "EnableConfigs": [
    "KEXEC_HANDOVER",
    "KEXEC_HANDOVER_DEBUG",
    "LIVEUPDATE",
    "LIVEUPDATE_MEMFD",
    "HUGETLB_PAGE"
  ],
  "FocusSymbols": [
    "kho_radix_add_key",
    "kho_radix_del_key",
    "kho_radix_walk_tree",
    "kho_radix_init_tree",
    "kho_radix_destroy_tree",
    "memblock_alloc_hugetlb",
    "kho_scratch_overlap"
  ],
  "Reasoning": "The patch refactors the KHO (Kexec HandOver) radix tree implementation, renaming page-specific functions to generic key-based functions (e.g., `kho_radix_add_key`, `kho_radix_del_key`). It also introduces `memblock_alloc_hugetlb` for allocating HugeTLB pages during boot, avoiding KHO scratch memory overlaps. The KHO radix tree functions are reachable from userspace via `/dev/liveupdate` ioctls (like `LIVEUPDATE_SESSION_PRESERVE_FD`) when preserving memfd files. Therefore, this functionality is reachable and should be fuzzed.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
  even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).
- Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed,
  ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU.
- Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove)
  that are executed only during PCI hot-unplug or sysfs driver unbind operations.

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
   Prefer non-static, non-inlined API entrypoint functions over internal static helper functions
   (which are inlined by the compiler and do not have distinct symbol addresses).
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs
   needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs
   output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit b1496cd2dd7b426cd90e9449e59173a9fbd22f8d
Author: syz-cluster <triage@syzkaller.com>
Date:   Sun Aug 2 10:46:30 2026 +0000

    syz-cluster: applied patch under review

diff --git a/include/linux/kexec_handover.h b/include/linux/kexec_handover.h
index 48a9793c2b76c..46de86dc343e9 100644
--- a/include/linux/kexec_handover.h
+++ b/include/linux/kexec_handover.h
@@ -5,6 +5,7 @@
 #include <linux/err.h>
 #include <linux/errno.h>
 #include <linux/types.h>
+#include <linux/mm.h>
 #include <asm-generic/kexec_handover.h>
 
 struct kho_vmalloc;
@@ -33,9 +34,20 @@ void kho_remove_subtree(void *blob);
 int kho_retrieve_subtree(const char *name, phys_addr_t *phys, size_t *size);
 
 void kho_memory_init(void);
+void kho_memory_init_early(void);
 
 void kho_populate(phys_addr_t fdt_phys, u64 fdt_len, phys_addr_t scratch_phys,
 		  u64 scratch_len);
+
+bool kho_scratch_overlap(phys_addr_t phys, size_t size);
+
+static inline enum migratetype kho_scratch_migratetype(unsigned long pfn,
+						       enum migratetype mt)
+{
+	if (kho_scratch_overlap(PFN_PHYS(pfn), pageblock_nr_pages << PAGE_SHIFT))
+		return MIGRATE_CMA;
+	return mt;
+}
 #else
 static inline bool kho_is_enabled(void)
 {
@@ -108,10 +120,23 @@ static inline int kho_retrieve_subtree(const char *name, phys_addr_t *phys,
 
 static inline void kho_memory_init(void) { }
 
+static inline void kho_memory_init_early(void) { }
+
 static inline void kho_populate(phys_addr_t fdt_phys, u64 fdt_len,
 				phys_addr_t scratch_phys, u64 scratch_len)
 {
 }
+
+static inline bool kho_scratch_overlap(phys_addr_t phys, size_t size)
+{
+	return false;
+}
+
+static inline enum migratetype kho_scratch_migratetype(unsigned long pfn,
+						       enum migratetype mt)
+{
+	return mt;
+}
 #endif /* CONFIG_KEXEC_HANDOVER */
 
 #endif /* LINUX_KEXEC_HANDOVER_H */
diff --git a/include/linux/kho/abi/kexec_handover.h b/include/linux/kho/abi/kexec_handover.h
index 5e2eb8519bda6..2f4fb9c639421 100644
--- a/include/linux/kho/abi/kexec_handover.h
+++ b/include/linux/kho/abi/kexec_handover.h
@@ -257,11 +257,8 @@ struct kho_vmalloc {
  * memory. These constants govern the indexing, sizing, and depth of the tree.
  */
 enum kho_radix_consts {
-	/*
-	 * The bit position of the order bit (and also the length of the
-	 * shifted physical address) for an order-0 page.
-	 */
-	KHO_ORDER_0_LOG2 = 64 - PAGE_SHIFT,
+	/* Need to store the PFN, plus one bit for order. */
+	KHO_RADIX_KEY_WIDTH = 64 - PAGE_SHIFT + 1,
 
 	/* Size of the table in kho_radix_node, in log2 */
 	KHO_TABLE_SIZE_LOG2 = const_ilog2(PAGE_SIZE / sizeof(phys_addr_t)),
@@ -274,7 +271,7 @@ enum kho_radix_consts {
 	 * and 1 bitmap level.
 	 */
 	KHO_TREE_MAX_DEPTH =
-		DIV_ROUND_UP(KHO_ORDER_0_LOG2 - KHO_BITMAP_SIZE_LOG2 + 1,
+		DIV_ROUND_UP(KHO_RADIX_KEY_WIDTH - KHO_BITMAP_SIZE_LOG2,
 			     KHO_TABLE_SIZE_LOG2) + 1,
 };
 
diff --git a/include/linux/kho_radix_tree.h b/include/linux/kho_radix_tree.h
index 84e918b96e53d..fcd69639721be 100644
--- a/include/linux/kho_radix_tree.h
+++ b/include/linux/kho_radix_tree.h
@@ -11,15 +11,19 @@
 /**
  * DOC: Kexec Handover Radix Tree
  *
- * This is a radix tree implementation for tracking physical memory pages
- * across kexec transitions. It was developed for the KHO mechanism but is
- * designed for broader use by any subsystem that needs to preserve pages.
+ * This is a radix tree implementation for tracking numeric keys across kexec
+ * transitions. It was developed for the KHO preserved memory map but is
+ * designed for broader use by any subsystem that needs to track keys.
+ * Conceptually speaking, the data structure is similar to a set. It tracks the
+ * presence or absence of numeric keys.
  *
  * The radix tree is a multi-level tree where leaf nodes are bitmaps
- * representing individual pages. To allow pages of different sizes (orders)
+ * representing individual keys.
+ *
+ * For the KHO preserved memory map, to allow pages of different sizes (orders)
  * to be stored efficiently in a single tree, it uses a unique key encoding
- * scheme. Each key is an unsigned long that combines a page's physical
- * address and its order.
+ * scheme. Each key is an unsigned long that combines a page's physical address
+ * and its order.
  *
  * Client code is responsible for allocating the root node of the tree,
  * initializing the mutex lock, and managing its lifecycle. It must use the
@@ -34,37 +38,53 @@ struct kho_radix_tree {
 	struct mutex lock; /* protects the tree's structure and root pointer */
 };
 
-typedef int (*kho_radix_tree_walk_callback_t)(phys_addr_t phys,
-					      unsigned int order);
+/**
+ * struct kho_radix_walk_cb - Callbacks for KHO radix tree walk.
+ * @leaf:      Called on each present key in the radix tree.
+ * @node:      Called on each node of the radix tree itself. Receives the
+ *             physical address of the page containing the node.
+ *
+ * For each callback, a return value of 0 continues the walk and a non-zero
+ * return value is directly returned to the caller.
+ */
+struct kho_radix_walk_cb {
+	int (*leaf)(unsigned long key, void *data);
+	int (*node)(phys_addr_t phys, void *data);
+};
 
 #ifdef CONFIG_KEXEC_HANDOVER
 
-int kho_radix_add_page(struct kho_radix_tree *tree, unsigned long pfn,
-		       unsigned int order);
-
-void kho_radix_del_page(struct kho_radix_tree *tree, unsigned long pfn,
-			unsigned int order);
-
+int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key);
+void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key);
 int kho_radix_walk_tree(struct kho_radix_tree *tree,
-			kho_radix_tree_walk_callback_t cb);
+			const struct kho_radix_walk_cb *cb, void *data);
+int kho_radix_init_tree(struct kho_radix_tree *tree, struct kho_radix_node *root);
+void kho_radix_destroy_tree(struct kho_radix_tree *tree);
 
 #else  /* #ifdef CONFIG_KEXEC_HANDOVER */
 
-static inline int kho_radix_add_page(struct kho_radix_tree *tree, long pfn,
-				     unsigned int order)
+static inline int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key)
 {
 	return -EOPNOTSUPP;
 }
 
-static inline void kho_radix_del_page(struct kho_radix_tree *tree,
-				      unsigned long pfn, unsigned int order) { }
+static inline void kho_radix_del_key(struct kho_radix_tree *tree,
+				     unsigned long key) { }
 
 static inline int kho_radix_walk_tree(struct kho_radix_tree *tree,
-				      kho_radix_tree_walk_callback_t cb)
+				      const struct kho_radix_walk_cb *cb, void *data)
 {
 	return -EOPNOTSUPP;
 }
 
+static inline int kho_radix_init_tree(struct kho_radix_tree *tree,
+				      struct kho_radix_node *root)
+{
+	return 0;
+}
+
+static inline void kho_radix_destroy_tree(struct kho_radix_tree *tree) { }
+
 #endif /* #ifdef CONFIG_KEXEC_HANDOVER */
 
 #endif	/* _LINUX_KHO_RADIX_TREE_H */
diff --git a/include/linux/memblock.h b/include/linux/memblock.h
index 5afcd99aa8c14..d62db9e776cf9 100644
--- a/include/linux/memblock.h
+++ b/include/linux/memblock.h
@@ -51,6 +51,7 @@ extern unsigned long long max_possible_pfn;
  * memory reservations yet, so we get scratch memory from the previous
  * kernel that we know is good to use. It is the only memory that
  * allocations may happen from in this phase.
+ * @MEMBLOCK_RSRV_HUGETLB: memory is reserved for hugetlb pages
  */
 enum memblock_flags {
 	MEMBLOCK_NONE		= 0x0,	/* No special request */
@@ -61,6 +62,7 @@ enum memblock_flags {
 	MEMBLOCK_RSRV_NOINIT	= 0x10,	/* don't initialize struct pages */
 	MEMBLOCK_RSRV_KERN	= 0x20,	/* memory reserved for kernel use */
 	MEMBLOCK_KHO_SCRATCH	= 0x40,	/* scratch memory for kexec handover */
+	MEMBLOCK_RSRV_HUGETLB	= 0x80, /* memory reserved for hugetlb pages */
 };
 
 /**
@@ -420,6 +422,7 @@ void *memblock_alloc_try_nid_raw(phys_addr_t size, phys_addr_t align,
 void *memblock_alloc_try_nid(phys_addr_t size, phys_addr_t align,
 			     phys_addr_t min_addr, phys_addr_t max_addr,
 			     int nid);
+void *memblock_alloc_hugetlb(phys_addr_t size, int nid, bool exact_nid);
 
 static __always_inline void *memblock_alloc(phys_addr_t size, phys_addr_t align)
 {
@@ -484,6 +487,7 @@ static inline __init_memblock bool memblock_bottom_up(void)
 phys_addr_t memblock_phys_mem_size(void);
 phys_addr_t memblock_reserved_size(void);
 phys_addr_t memblock_reserved_kern_size(phys_addr_t limit, int nid);
+phys_addr_t memblock_reserved_hugetlb_size(phys_addr_t limit, int nid);
 unsigned long memblock_estimated_nr_free_pages(void);
 phys_addr_t memblock_start_of_DRAM(void);
 phys_addr_t memblock_end_of_DRAM(void);
@@ -613,28 +617,9 @@ static inline void memtest_report_meminfo(struct seq_file *m) { }
 #ifdef CONFIG_MEMBLOCK_KHO_SCRATCH
 void memblock_set_kho_scratch_only(void);
 void memblock_clear_kho_scratch_only(void);
-bool memblock_is_kho_scratch_memory(phys_addr_t addr);
-
-static inline enum migratetype kho_scratch_migratetype(unsigned long pfn,
-						       enum migratetype mt)
-{
-	if (memblock_is_kho_scratch_memory(PFN_PHYS(pfn)))
-		return MIGRATE_CMA;
-	return mt;
-}
 #else
 static inline void memblock_set_kho_scratch_only(void) { }
 static inline void memblock_clear_kho_scratch_only(void) { }
-static inline bool memblock_is_kho_scratch_memory(phys_addr_t addr)
-{
-	return false;
-}
-
-static inline enum migratetype kho_scratch_migratetype(unsigned long pfn,
-						       enum migratetype mt)
-{
-	return mt;
-}
 #endif
 
 #endif /* _LINUX_MEMBLOCK_H */
diff --git a/kernel/liveupdate/Makefile b/kernel/liveupdate/Makefile
index eec9d3ae07eb4..d9f4694625567 100644
--- a/kernel/liveupdate/Makefile
+++ b/kernel/liveupdate/Makefile
@@ -8,7 +8,6 @@ luo-y :=								\
 		luo_session.o
 
 obj-$(CONFIG_KEXEC_HANDOVER)		+= kexec_handover.o
-obj-$(CONFIG_KEXEC_HANDOVER_DEBUG)	+= kexec_handover_debug.o
 obj-$(CONFIG_KEXEC_HANDOVER_DEBUGFS)	+= kexec_handover_debugfs.o
 
 obj-$(CONFIG_LIVEUPDATE)		+= luo.o
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 4834a809985ab..861621faa557b 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -84,8 +84,25 @@ static struct kho_out kho_out = {
 	},
 };
 
+struct kho_in {
+	phys_addr_t fdt_phys;
+	phys_addr_t scratch_phys;
+	char previous_release[__NEW_UTS_LEN + 1];
+	u32 kexec_count;
+	struct kho_debugfs dbg;
+	struct kho_radix_tree radix_tree;
+};
+
+static struct kho_in kho_in = {
+};
+
+static const void *kho_get_fdt(void)
+{
+	return kho_in.fdt_phys ? phys_to_virt(kho_in.fdt_phys) : NULL;
+}
+
 /**
- * kho_radix_encode_key - Encodes a physical address and order into a radix key.
+ * kho_encode_radix_key - Encodes a physical address and order into a radix key.
  * @phys: The physical address of the page.
  * @order: The order of the page.
  *
@@ -95,35 +112,38 @@ static struct kho_out kho_out = {
  *
  * Return: The encoded unsigned long radix key.
  */
-static unsigned long kho_radix_encode_key(phys_addr_t phys, unsigned int order)
+static unsigned long kho_encode_radix_key(phys_addr_t phys, unsigned int order)
 {
-	/* Order bits part */
-	unsigned long h = 1UL << (KHO_ORDER_0_LOG2 - order);
-	/* Shifted physical address part */
-	unsigned long l = phys >> (PAGE_SHIFT + order);
+	/* The physical address is encoded by shifting the PFN by its order. */
+	unsigned long shift = PAGE_SHIFT + order;
+	/* Order bit goes right before the shifted PFN. */
+	unsigned long h = 1UL << (64 - shift);
+	/* Shifted PFN. */
+	unsigned long l = phys >> shift;
 
 	return h | l;
 }
 
 /**
- * kho_radix_decode_key - Decodes a radix key back into a physical address and order.
+ * kho_decode_radix_key - Decodes a radix key back into a physical address and order.
  * @key: The unsigned long key to decode.
  * @order: An output parameter, a pointer to an unsigned int where the decoded
  *         page order will be stored.
  *
- * This function reverses the encoding performed by kho_radix_encode_key(),
+ * This function reverses the encoding performed by kho_encode_radix_key(),
  * extracting the original physical address and page order from a given key.
  *
  * Return: The decoded physical address.
  */
-static phys_addr_t kho_radix_decode_key(unsigned long key, unsigned int *order)
+static phys_addr_t kho_decode_radix_key(unsigned long key, unsigned int *order)
 {
-	unsigned int order_bit = fls64(key);
+	/* fls64() indexes starting from 1. */
+	unsigned int order_bit = fls64(key) - 1;
 	phys_addr_t phys;
 
-	/* order_bit is numbered starting at 1 from fls64 */
-	*order = KHO_ORDER_0_LOG2 - order_bit + 1;
-	/* The order is discarded by the shift */
+	/* order bit goes right before the shifted PFN. */
+	*order = 64 - (PAGE_SHIFT + order_bit);
+	/* The order bit is discarded by the shift */
 	phys = key << (PAGE_SHIFT + *order);
 
 	return phys;
@@ -143,25 +163,47 @@ static unsigned long kho_radix_get_table_index(unsigned long key,
 	return (key >> s) % (1 << KHO_TABLE_SIZE_LOG2);
 }
 
+static void __ref *kho_radix_alloc_node(void)
+{
+	struct kho_radix_node *node;
+
+	if (slab_is_available())
+		node = (struct kho_radix_node *)get_zeroed_page(GFP_KERNEL);
+	else
+		node = memblock_alloc(PAGE_SIZE, PAGE_SIZE);
+
+	return node;
+}
+
+static void __ref kho_radix_free_node(struct kho_radix_node *node)
+{
+	if (slab_is_available())
+		free_page((unsigned long)node);
+	else
+		memblock_free(node, PAGE_SIZE);
+}
+
 /**
- * kho_radix_add_page - Marks a page as preserved in the radix tree.
+ * kho_radix_add_key - Add a key to the radix tree.
  * @tree: The KHO radix tree.
- * @pfn: The page frame number of the page to preserve.
- * @order: The order of the page.
+ * @key: The key to add.
+ *
+ * This function traverses the radix tree based on the @key provided. It sets the
+ * corresponding bit in the leaf bitmap to mark the @key as present. If
+ * intermediate nodes do not exist along the path, they are allocated and added
+ * to the tree.
  *
- * This function traverses the radix tree based on the key derived from @pfn
- * and @order. It sets the corresponding bit in the leaf bitmap to mark the
- * page for preservation. If intermediate nodes do not exist along the path,
- * they are allocated and added to the tree.
+ * NOTE: Currently only keys of width up to %KHO_RADIX_KEY_WIDTH are supported.
+ * This limit only exists because current users of the radix tree don't use more
+ * than that. Changing the maximum width requires changing the tree depth, which
+ * needs bumping the ABI version.
  *
  * Return: 0 on success, or a negative error code on failure.
  */
-int kho_radix_add_page(struct kho_radix_tree *tree,
-		       unsigned long pfn, unsigned int order)
+int kho_radix_add_key(struct kho_radix_tree *tree, unsigned long key)
 {
 	/* Newly allocated nodes for error cleanup */
 	struct kho_radix_node *intermediate_nodes[KHO_TREE_MAX_DEPTH] = { 0 };
-	unsigned long key = kho_radix_encode_key(PFN_PHYS(pfn), order);
 	struct kho_radix_node *anchor_node = NULL;
 	struct kho_radix_node *node = tree->root;
 	struct kho_radix_node *new_node;
@@ -172,6 +214,9 @@ int kho_radix_add_page(struct kho_radix_tree *tree,
 	if (WARN_ON_ONCE(!tree->root))
 		return -EINVAL;
 
+	if (unlikely(fls64(key) > KHO_RADIX_KEY_WIDTH))
+		return -ERANGE;
+
 	might_sleep();
 
 	guard(mutex)(&tree->lock);
@@ -186,7 +231,7 @@ int kho_radix_add_page(struct kho_radix_tree *tree,
 		}
 
 		/* Next node is empty, create a new node for it */
-		new_node = (struct kho_radix_node *)get_zeroed_page(GFP_KERNEL);
+		new_node = kho_radix_alloc_node();
 		if (!new_node) {
 			err = -ENOMEM;
 			goto err_free_nodes;
@@ -217,29 +262,26 @@ int kho_radix_add_page(struct kho_radix_tree *tree,
 err_free_nodes:
 	for (i = KHO_TREE_MAX_DEPTH - 1; i > 0; i--) {
 		if (intermediate_nodes[i])
-			free_page((unsigned long)intermediate_nodes[i]);
+			kho_radix_free_node(intermediate_nodes[i]);
 	}
 	if (anchor_node)
 		anchor_node->table[anchor_idx] = 0;
 
 	return err;
 }
-EXPORT_SYMBOL_GPL(kho_radix_add_page);
+EXPORT_SYMBOL_GPL(kho_radix_add_key);
 
 /**
- * kho_radix_del_page - Removes a page's preservation status from the radix tree.
+ * kho_radix_del_key - Removes the key from the radix tree.
  * @tree: The KHO radix tree.
- * @pfn: The page frame number of the page to unpreserve.
- * @order: The order of the page.
+ * @key: The key to remove.
  *
  * This function traverses the radix tree and clears the bit corresponding to
- * the page, effectively removing its "preserved" status. It does not free
- * the tree's intermediate nodes, even if they become empty.
+ * the @key, effectively removing it from the tree. It does not free the tree's
+ * intermediate nodes, even if they become empty.
  */
-void kho_radix_del_page(struct kho_radix_tree *tree, unsigned long pfn,
-			unsigned int order)
+void kho_radix_del_key(struct kho_radix_tree *tree, unsigned long key)
 {
-	unsigned long key = kho_radix_encode_key(PFN_PHYS(pfn), order);
 	struct kho_radix_node *node = tree->root;
 	struct kho_radix_leaf *leaf;
 	unsigned int i, idx;
@@ -247,6 +289,10 @@ void kho_radix_del_page(struct kho_radix_tree *tree, unsigned long pfn,
 	if (WARN_ON_ONCE(!tree->root))
 		return;
 
+	/* Keys wider than KHO_RADIX_KEY_WIDTH are not allowed to be added. */
+	if (unlikely(fls64(key) > KHO_RADIX_KEY_WIDTH))
+		return;
+
 	might_sleep();
 
 	guard(mutex)(&tree->lock);
@@ -270,21 +316,85 @@ void kho_radix_del_page(struct kho_radix_tree *tree, unsigned long pfn,
 	idx = kho_radix_get_bitmap_index(key);
 	__clear_bit(idx, leaf->bitmap);
 }
-EXPORT_SYMBOL_GPL(kho_radix_del_page);
+EXPORT_SYMBOL_GPL(kho_radix_del_key);
+
+static void __kho_radix_destroy_tree(struct kho_radix_node *root,
+				     unsigned int level)
+{
+	unsigned long i;
+
+	if (level == 0) {
+		kho_radix_free_node(root);
+		return;
+	}
+
+	for (i = 0; i < PAGE_SIZE / sizeof(phys_addr_t); i++) {
+		if (root->table[i])
+			__kho_radix_destroy_tree(phys_to_virt(root->table[i]),
+						 level - 1);
+	}
+
+	kho_radix_free_node(root);
+}
 
-static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf,
-			       unsigned long key,
-			       kho_radix_tree_walk_callback_t cb)
+/**
+ * kho_radix_init_tree - initialize the radix tree.
+ * @tree:   the tree to initialize.
+ * @root:   root table of the radix tree.
+ *
+ * Initialize the radix tree with the given root node. If root is %NULL, an
+ * empty root table is allocated. If root is not %NULL, it is the caller's
+ * responsibility to make sure the root is valid and in the correct format.
+ *
+ * Return: 0 on success, -errno on failure.
+ */
+int kho_radix_init_tree(struct kho_radix_tree *tree, struct kho_radix_node *root)
+{
+	if (!root)
+		root = kho_radix_alloc_node();
+	if (!root)
+		return -ENOMEM;
+
+	tree->root = root;
+	mutex_init(&tree->lock);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(kho_radix_init_tree);
+
+/**
+ * kho_radix_destroy_tree - Destroy the radix tree
+ * @tree: The radix tree to destroy
+ *
+ * Walk @tree and free all its nodes.
+ */
+void kho_radix_destroy_tree(struct kho_radix_tree *tree)
+{
+	if (!tree->root)
+		return;
+
+	__kho_radix_destroy_tree(tree->root, KHO_TREE_MAX_DEPTH - 1);
+	tree->root = NULL;
+}
+EXPORT_SYMBOL_GPL(kho_radix_destroy_tree);
+
+static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf, unsigned long key,
+			       const struct kho_radix_walk_cb *cb, void *data)
 {
 	unsigned long *bitmap = (unsigned long *)leaf;
-	unsigned int order;
-	phys_addr_t phys;
 	unsigned int i;
 	int err;
 
+	if (cb->node) {
+		err = cb->node(virt_to_phys(leaf), data);
+		if (err)
+			return err;
+	}
+
+	if (!cb->leaf)
+		return 0;
+
 	for_each_set_bit(i, bitmap, PAGE_SIZE * BITS_PER_BYTE) {
-		phys = kho_radix_decode_key(key | i, &order);
-		err = cb(phys, order);
+		err = cb->leaf(key | i, data);
 		if (err)
 			return err;
 	}
@@ -294,7 +404,7 @@ static int kho_radix_walk_leaf(struct kho_radix_leaf *leaf,
 
 static int __kho_radix_walk_tree(struct kho_radix_node *root,
 				 unsigned int level, unsigned long start,
-				 kho_radix_tree_walk_callback_t cb)
+				 const struct kho_radix_walk_cb *cb, void *data)
 {
 	struct kho_radix_node *node;
 	struct kho_radix_leaf *leaf;
@@ -302,6 +412,12 @@ static int __kho_radix_walk_tree(struct kho_radix_node *root,
 	unsigned int shift;
 	int err;
 
+	if (cb->node) {
+		err = cb->node(virt_to_phys(root), data);
+		if (err)
+			return err;
+	}
+
 	for (i = 0; i < PAGE_SIZE / sizeof(phys_addr_t); i++) {
 		if (!root->table[i])
 			continue;
@@ -318,10 +434,10 @@ static int __kho_radix_walk_tree(struct kho_radix_node *root,
 			 * node is pointing to the level 0 bitmap.
 			 */
 			leaf = (struct kho_radix_leaf *)node;
-			err = kho_radix_walk_leaf(leaf, key, cb);
+			err = kho_radix_walk_leaf(leaf, key, cb, data);
 		} else {
 			err  = __kho_radix_walk_tree(node, level - 1,
-						     key, cb);
+						     key, cb, data);
 		}
 
 		if (err)
@@ -332,28 +448,27 @@ static int __kho_radix_walk_tree(struct kho_radix_node *root,
 }
 
 /**
- * kho_radix_walk_tree - Traverses the radix tree and calls a callback for each preserved page.
+ * kho_radix_walk_tree - Traverses the radix tree and calls a callback for each key.
  * @tree: A pointer to the KHO radix tree to walk.
- * @cb: A callback function of type kho_radix_tree_walk_callback_t that will be
- *      invoked for each preserved page found in the tree. The callback receives
- *      the physical address and order of the preserved page.
+ * @cb:   Set of callbacks to be invoked during the tree walk.
+ * @data: Opaque data pointer passed to each callback in @cb.
  *
- * This function walks the radix tree, searching from the specified top level
- * down to the lowest level (level 0). For each preserved page found, it invokes
- * the provided callback, passing the page's physical address and order.
+ * This function walks the radix tree, searching from the top level down to the
+ * lowest level (level 0), invoking the appropriate callbacks.
  *
  * Return: 0 if the walk completed the specified tree, or the non-zero return
  *         value from the callback that stopped the walk.
  */
 int kho_radix_walk_tree(struct kho_radix_tree *tree,
-			kho_radix_tree_walk_callback_t cb)
+			const struct kho_radix_walk_cb *cb, void *data)
 {
 	if (WARN_ON_ONCE(!tree->root))
 		return -EINVAL;
 
 	guard(mutex)(&tree->lock);
 
-	return __kho_radix_walk_tree(tree->root, KHO_TREE_MAX_DEPTH - 1, 0, cb);
+	return __kho_radix_walk_tree(tree->root, KHO_TREE_MAX_DEPTH - 1, 0, cb,
+				     data);
 }
 EXPORT_SYMBOL_GPL(kho_radix_walk_tree);
 
@@ -484,13 +599,16 @@ static struct page *__init kho_get_preserved_page(phys_addr_t phys,
 	return pfn_to_page(pfn);
 }
 
-static int __init kho_preserved_memory_reserve(phys_addr_t phys,
-					       unsigned int order)
+static int __init kho_preserved_memory_reserve(unsigned long key, void *data)
 {
 	union kho_page_info info;
 	struct page *page;
+	unsigned int order;
+	phys_addr_t phys;
 	u64 sz;
 
+	phys = kho_decode_radix_key(key, &order);
+
 	sz = 1 << (order + PAGE_SHIFT);
 	page = kho_get_preserved_page(phys, order);
 
@@ -504,19 +622,24 @@ static int __init kho_preserved_memory_reserve(phys_addr_t phys,
 	return 0;
 }
 
-/* Returns physical address of the preserved memory map from FDT */
-static phys_addr_t __init kho_get_mem_map_phys(const void *fdt)
+/* Returns virtual address of the preserved memory map from FDT */
+static __init void *kho_get_mem_map(const void *fdt)
 {
 	const void *mem_ptr;
+	phys_addr_t mem_map_phys;
 	int len;
 
 	mem_ptr = fdt_getprop(fdt, 0, KHO_FDT_MEMORY_MAP_PROP_NAME, &len);
 	if (!mem_ptr || len != sizeof(u64)) {
 		pr_err("failed to get preserved memory map\n");
-		return 0;
+		return NULL;
 	}
 
-	return get_unaligned((const u64 *)mem_ptr);
+	mem_map_phys = get_unaligned((const u64 *)mem_ptr);
+	if (!mem_map_phys)
+		return NULL;
+
+	return phys_to_virt(mem_map_phys);
 }
 
 /*
@@ -619,19 +742,24 @@ early_param("kho_scratch", kho_parse_scratch_size);
 static void __init scratch_size_update(void)
 {
 	/*
-	 * If fixed sizes are not provided via command line, calculate them
-	 * now.
+	 * If fixed sizes are not provided via command line, calculate them now.
+	 * Remove HugeTLB allocations from it because they never get allocated
+	 * from scratch.
 	 */
 	if (scratch_scale) {
 		phys_addr_t size;
 
 		size = memblock_reserved_kern_size(ARCH_LOW_ADDRESS_LIMIT,
 						   NUMA_NO_NODE);
+		size -= memblock_reserved_hugetlb_size(ARCH_LOW_ADDRESS_LIMIT,
+						       NUMA_NO_NODE);
 		size = size * scratch_scale / 100;
 		scratch_size_lowmem = size;
 
 		size = memblock_reserved_kern_size(MEMBLOCK_ALLOC_ANYWHERE,
 						   NUMA_NO_NODE);
+		size -= memblock_reserved_hugetlb_size(MEMBLOCK_ALLOC_ANYWHERE,
+						       NUMA_NO_NODE);
 		size = size * scratch_scale / 100 - scratch_size_lowmem;
 		scratch_size_global = size;
 	}
@@ -651,6 +779,9 @@ static phys_addr_t __init scratch_size_node(int nid)
 	if (scratch_scale) {
 		size = memblock_reserved_kern_size(MEMBLOCK_ALLOC_ANYWHERE,
 						   nid);
+		/* Do not count HugeTLB pages. */
+		size -= memblock_reserved_hugetlb_size(MEMBLOCK_ALLOC_ANYWHERE,
+						       nid);
 		size = size * scratch_scale / 100;
 	} else {
 		size = scratch_size_pernode;
@@ -659,6 +790,22 @@ static phys_addr_t __init scratch_size_node(int nid)
 	return round_up(size, CMA_MIN_ALIGNMENT_BYTES);
 }
 
+bool kho_scratch_overlap(phys_addr_t phys, size_t size)
+{
+	phys_addr_t scratch_start, scratch_end;
+	unsigned int i;
+
+	for (i = 0; i < kho_scratch_cnt; i++) {
+		scratch_start = kho_scratch[i].addr;
+		scratch_end = kho_scratch[i].addr + kho_scratch[i].size;
+
+		if (phys < scratch_end && (phys + size) > scratch_start)
+			return true;
+	}
+
+	return false;
+}
+
 /**
  * kho_reserve_scratch - Reserve a contiguous chunk of memory for kexec
  *
@@ -746,6 +893,140 @@ static void __init kho_reserve_scratch(void)
 	kho_enable = false;
 }
 
+/*
+ * Look for free blocks of 1G. This is a heuristic chosen to work efficiently
+ * with large systems with hundreds of gigabytes of memory. It will work poorly
+ * on smaller systems. The algorithm itself doesn't depend on the actual value,
+ * so it can be changed to a different heuristic later if needed.
+ */
+#define KHO_SCRATCH_EXT_BLKSIZE		SZ_1G
+#define KHO_SCRATCH_EXT_BLKSHIFT	const_ilog2(KHO_SCRATCH_EXT_BLKSIZE)
+
+/* Called for the KHO preserved memory radix tree. */
+static int __init kho_ext_walk_leaf(unsigned long key, void *data)
+{
+	struct kho_radix_tree *busy_blocks = data;
+	phys_addr_t start, end;
+	unsigned int order;
+	int err;
+
+	/*
+	 * The key is from the KHO preserved memory radix tree. It is decoded to
+	 * a physical address of a preservation and its order.
+	 */
+	start = kho_decode_radix_key(key, &order);
+	end = start + (1UL << (order + PAGE_SHIFT));
+
+	while (start < end) {
+		err = kho_radix_add_key(busy_blocks, start >> KHO_SCRATCH_EXT_BLKSHIFT);
+		if (err)
+			return err;
+
+		start += (1UL << KHO_SCRATCH_EXT_BLKSHIFT);
+	}
+
+	return 0;
+}
+
+/* Called for the KHO preserved memory radix tree. */
+static int __init kho_ext_walk_node(phys_addr_t phys, void *data)
+{
+	struct kho_radix_tree *busy_blocks = data;
+
+	return kho_radix_add_key(busy_blocks, phys >> KHO_SCRATCH_EXT_BLKSHIFT);
+}
+
+/* Called for the busy block radix tree. */
+static int __init kho_ext_mark_scratch(unsigned long key, void *data)
+{
+	phys_addr_t *prev_end = data;
+	phys_addr_t start = key << KHO_SCRATCH_EXT_BLKSHIFT;
+	int err;
+
+	if (start > *prev_end) {
+		err = memblock_mark_kho_scratch(*prev_end, start - *prev_end);
+		if (err)
+			return err;
+	}
+
+	*prev_end = start + (1UL << KHO_SCRATCH_EXT_BLKSHIFT);
+	return 0;
+}
+
+/*
+ * kho_extend_scratch - Extend the scratch regions
+ *
+ * The KHO preserved memory radix tree mixes both physical address and order
+ * into a single key. This makes it hard to look for free ranges directly. This
+ * function first walks the radix tree and digests it down into another radix
+ * tree, whose keys identify blocks of size KHO_SCRATCH_EXT_BLKSIZE which
+ * contain preserved memory.
+ *
+ * Then it walks the digested radix tree and marks everything that doesn't have
+ * preserved memory as scratch.
+ *
+ * NOTE: This function allocates memory so it should be called when scratch has
+ * available space.
+ *
+ * NOTE: The pages of the KHO preserved memory radix tree tables are not marked
+ * as preserved in the preserved memory tree. But they are expected to remain
+ * untouched until the tree is fully parsed. So this function also considers
+ * them to be "preserved memory" and marks their blocks as busy.
+ *
+ * NOTE: efi_init()::reserve_regions() removes all regions except
+ * MEMBLOCK_KHO_SCRATCH. This function adds such regions but they are not KHO
+ * scratch memory, so they should not be removed. This function should always be
+ * called after reserve_regions().
+ */
+static void __init kho_extend_scratch(void)
+{
+	const struct kho_radix_walk_cb kho_cb = {
+		.leaf = kho_ext_walk_leaf,
+		.node = kho_ext_walk_node,
+	};
+	const struct kho_radix_walk_cb ext_cb = {
+		.leaf = kho_ext_mark_scratch,
+	};
+	static struct lock_class_key busy_radix_class;
+	struct kho_radix_tree busy_blocks;
+	phys_addr_t prev_end = 0;
+	int err = 0;
+
+	err = kho_radix_init_tree(&busy_blocks, NULL);
+	if (err)
+		goto print;
+
+	/*
+	 * The walk of kho_in.radix_tree adds keys to busy_blocks. The walk
+	 * takes the kho_in radix tree lock and adding the key takes busy_blocks
+	 * lock. Since both are struct kho_radix_tree and share the same lock
+	 * class, lockdep gets confused. Set a different class for
+	 * busy_blocks.lock to make lockdep happy.
+	 */
+	lockdep_set_class(&busy_blocks.lock, &busy_radix_class);
+
+	/* Walk the KHO radix tree to find busy blocks. */
+	err = kho_radix_walk_tree(&kho_in.radix_tree, &kho_cb, &busy_blocks);
+	if (err)
+		goto out;
+
+	/* Walk the busy blocks and mark everything between keys as scratch. */
+	err = kho_radix_walk_tree(&busy_blocks, &ext_cb, &prev_end);
+	if (err)
+		goto out;
+
+	/* Mark everything from last busy block to end of DRAM. */
+	if (prev_end < memblock_end_of_DRAM())
+		err = memblock_mark_kho_scratch(prev_end, memblock_end_of_DRAM() - prev_end);
+
+	/* fallthrough */
+out:
+	kho_radix_destroy_tree(&busy_blocks);
+print:
+	if (err)
+		pr_err("Failed to extend scratch: %pe\n", ERR_PTR(err));
+}
+
 /**
  * kho_add_subtree - record the physical address of a sub blob in KHO root tree.
  * @name: name of the sub tree.
@@ -856,10 +1137,12 @@ int kho_preserve_folio(struct folio *folio)
 	const unsigned long pfn = folio_pfn(folio);
 	const unsigned int order = folio_order(folio);
 
-	if (WARN_ON(kho_scratch_overlap(pfn << PAGE_SHIFT, PAGE_SIZE << order)))
+	if (IS_ENABLED(CONFIG_KEXEC_HANDOVER_DEBUG) &&
+	    WARN_ON(kho_scratch_overlap(pfn << PAGE_SHIFT, PAGE_SIZE << order)))
 		return -EINVAL;
 
-	return kho_radix_add_page(tree, pfn, order);
+	return kho_radix_add_key(tree, kho_encode_radix_key(PFN_PHYS(pfn),
+							    order));
 }
 EXPORT_SYMBOL_GPL(kho_preserve_folio);
 
@@ -877,7 +1160,7 @@ void kho_unpreserve_folio(struct folio *folio)
 	const unsigned long pfn = folio_pfn(folio);
 	const unsigned int order = folio_order(folio);
 
-	kho_radix_del_page(tree, pfn, order);
+	kho_radix_del_key(tree, kho_encode_radix_key(PFN_PHYS(pfn), order));
 }
 EXPORT_SYMBOL_GPL(kho_unpreserve_folio);
 
@@ -906,7 +1189,8 @@ static void __kho_unpreserve(struct kho_radix_tree *tree,
 	while (pfn < end_pfn) {
 		order = __kho_preserve_pages_order(pfn, end_pfn);
 
-		kho_radix_del_page(tree, pfn, order);
+		kho_radix_del_key(tree, kho_encode_radix_key(PFN_PHYS(pfn),
+							     order));
 
 		pfn += 1 << order;
 	}
@@ -931,7 +1215,8 @@ int kho_preserve_pages(struct page *page, unsigned long nr_pages)
 	unsigned long failed_pfn = 0;
 	int err = 0;
 
-	if (WARN_ON(kho_scratch_overlap(start_pfn << PAGE_SHIFT,
+	if (IS_ENABLED(CONFIG_KEXEC_HANDOVER_DEBUG) &&
+	    WARN_ON(kho_scratch_overlap(start_pfn << PAGE_SHIFT,
 					nr_pages << PAGE_SHIFT))) {
 		return -EINVAL;
 	}
@@ -939,7 +1224,8 @@ int kho_preserve_pages(struct page *page, unsigned long nr_pages)
 	while (pfn < end_pfn) {
 		unsigned int order = __kho_preserve_pages_order(pfn, end_pfn);
 
-		err = kho_radix_add_page(tree, pfn, order);
+		err = kho_radix_add_key(tree, kho_encode_radix_key(PFN_PHYS(pfn),
+								   order));
 		if (err) {
 			failed_pfn = pfn;
 			break;
@@ -1315,22 +1601,6 @@ void kho_restore_free(void *mem)
 }
 EXPORT_SYMBOL_GPL(kho_restore_free);
 
-struct kho_in {
-	phys_addr_t fdt_phys;
-	phys_addr_t scratch_phys;
-	char previous_release[__NEW_UTS_LEN + 1];
-	u32 kexec_count;
-	struct kho_debugfs dbg;
-};
-
-static struct kho_in kho_in = {
-};
-
-static const void *kho_get_fdt(void)
-{
-	return kho_in.fdt_phys ? phys_to_virt(kho_in.fdt_phys) : NULL;
-}
-
 /**
  * is_kho_boot - check if current kernel was booted via KHO-enabled
  * kexec
@@ -1398,26 +1668,24 @@ int kho_retrieve_subtree(const char *name, phys_addr_t *phys, size_t *size)
 }
 EXPORT_SYMBOL_GPL(kho_retrieve_subtree);
 
-static int __init kho_mem_retrieve(const void *fdt)
+static void __init kho_mem_retrieve(void)
 {
-	struct kho_radix_tree tree;
-	const phys_addr_t *mem;
-	int len;
-
-	/* Retrieve the KHO radix tree from passed-in FDT. */
-	mem = fdt_getprop(fdt, 0, KHO_FDT_MEMORY_MAP_PROP_NAME, &len);
+	const struct kho_radix_walk_cb cb = {
+		.leaf = kho_preserved_memory_reserve,
+	};
 
-	if (!mem || len != sizeof(*mem)) {
-		pr_err("failed to get preserved KHO memory tree\n");
-		return -ENOENT;
-	}
+	if (kho_radix_walk_tree(&kho_in.radix_tree, &cb, NULL))
+		goto err;
 
-	if (!*mem)
-		return -EINVAL;
+	return;
 
-	tree.root = phys_to_virt(*mem);
-	mutex_init(&tree.lock);
-	return kho_radix_walk_tree(&tree, kho_preserved_memory_reserve);
+err:
+	/*
+	 * Failed to initialize preserved memory. Clear FDT and radix so KHO
+	 * users don't treat it as a KHO boot.
+	 */
+	kho_in.fdt_phys = 0;
+	kho_in.radix_tree.root = NULL;
 }
 
 static __init int kho_out_fdt_setup(void)
@@ -1543,16 +1811,14 @@ static __init int kho_init(void)
 	if (!kho_enable)
 		return 0;
 
-	tree->root = kzalloc(PAGE_SIZE, GFP_KERNEL);
-	if (!tree->root) {
-		err = -ENOMEM;
+	err = kho_radix_init_tree(tree, NULL);
+	if (err)
 		goto err_free_scratch;
-	}
 
 	kho_out.fdt = kho_alloc_preserve(PAGE_SIZE);
 	if (IS_ERR(kho_out.fdt)) {
 		err = PTR_ERR(kho_out.fdt);
-		goto err_free_kho_radix_tree_root;
+		goto err_free_kho_radix_tree;
 	}
 
 	err = kho_debugfs_init();
@@ -1603,9 +1869,8 @@ static __init int kho_init(void)
 
 err_free_fdt:
 	kho_unpreserve_free(kho_out.fdt);
-err_free_kho_radix_tree_root:
-	kfree(tree->root);
-	tree->root = NULL;
+err_free_kho_radix_tree:
+	kho_radix_destroy_tree(tree);
 err_free_scratch:
 	kho_out.fdt = NULL;
 	for (int i = 0; i < kho_scratch_cnt; i++) {
@@ -1619,16 +1884,43 @@ static __init int kho_init(void)
 }
 fs_initcall(kho_init);
 
-void __init kho_memory_init(void)
+void __init kho_memory_init_early(void)
 {
-	if (kho_in.scratch_phys) {
-		kho_scratch = phys_to_virt(kho_in.scratch_phys);
+	const void *fdt = kho_get_fdt();
 
-		if (kho_mem_retrieve(kho_get_fdt()))
-			kho_in.fdt_phys = 0;
-	} else {
-		kho_reserve_scratch();
+	if (!is_kho_boot())
+		return;
+
+	/*
+	 * kho_scratch_overlap() needs kho_scratch to be initialized. It
+	 * is used by free_area_init() on KHO boots, so initialize it
+	 * early.
+	 */
+	kho_scratch = phys_to_virt(kho_in.scratch_phys);
+
+	/*
+	 * kho_get_mem_map() should always succeed. If it fails, kho_populate()
+	 * catches that and never sets kho_in.fdt_phys.
+	 */
+	if (kho_radix_init_tree(&kho_in.radix_tree, kho_get_mem_map(fdt))) {
+		/*
+		 * Failed to initialize preserved memory radix tree. Clear FDT
+		 * and scratch so KHO users don't treat it as a KHO boot.
+		 */
+		kho_in.fdt_phys = 0;
+		kho_in.scratch_phys = 0;
+		return;
 	}
+
+	kho_extend_scratch();
+}
+
+void __init kho_memory_init(void)
+{
+	if (kho_in.scratch_phys)
+		kho_mem_retrieve();
+	else
+		kho_reserve_scratch();
 }
 
 void __init kho_populate(phys_addr_t fdt_phys, u64 fdt_len,
@@ -1636,9 +1928,8 @@ void __init kho_populate(phys_addr_t fdt_phys, u64 fdt_len,
 {
 	unsigned int scratch_cnt = scratch_len / sizeof(*kho_scratch);
 	struct kho_scratch *scratch = NULL;
-	phys_addr_t mem_map_phys;
-	void *fdt = NULL;
 	bool populated = false;
+	void *fdt = NULL;
 	int err;
 
 	/* Validate the input FDT */
@@ -1660,8 +1951,13 @@ void __init kho_populate(phys_addr_t fdt_phys, u64 fdt_len,
 		goto unmap_fdt;
 	}
 
-	mem_map_phys = kho_get_mem_map_phys(fdt);
-	if (!mem_map_phys)
+	/*
+	 * At this point phys_to_virt() doesn't work properly and so
+	 * kho_get_mem_map() can return a pre-KASLR virtual address. But here we
+	 * only want to make sure the mem_map is valid so the actual value
+	 * doesn't matter as long as it isn't NULL.
+	 */
+	if (!kho_get_mem_map(fdt))
 		goto unmap_fdt;
 
 	scratch = early_memremap(scratch_phys, scratch_len);
diff --git a/kernel/liveupdate/kexec_handover_debug.c b/kernel/liveupdate/kexec_handover_debug.c
deleted file mode 100644
index 6efb696f54261..0000000000000
--- a/kernel/liveupdate/kexec_handover_debug.c
+++ /dev/null
@@ -1,25 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * kexec_handover_debug.c - kexec handover optional debug functionality
- * Copyright (C) 2025 Google LLC, Pasha Tatashin <pasha.tatashin@soleen.com>
- */
-
-#define pr_fmt(fmt) "KHO: " fmt
-
-#include "kexec_handover_internal.h"
-
-bool kho_scratch_overlap(phys_addr_t phys, size_t size)
-{
-	phys_addr_t scratch_start, scratch_end;
-	unsigned int i;
-
-	for (i = 0; i < kho_scratch_cnt; i++) {
-		scratch_start = kho_scratch[i].addr;
-		scratch_end = kho_scratch[i].addr + kho_scratch[i].size;
-
-		if (phys < scratch_end && (phys + size) > scratch_start)
-			return true;
-	}
-
-	return false;
-}
diff --git a/kernel/liveupdate/kexec_handover_internal.h b/kernel/liveupdate/kexec_handover_internal.h
index 0399ff1077750..805d2a76c3881 100644
--- a/kernel/liveupdate/kexec_handover_internal.h
+++ b/kernel/liveupdate/kexec_handover_internal.h
@@ -41,13 +41,4 @@ static inline void kho_debugfs_blob_remove(struct kho_debugfs *dbg,
 					   void *blob) { }
 #endif /* CONFIG_KEXEC_HANDOVER_DEBUGFS */
 
-#ifdef CONFIG_KEXEC_HANDOVER_DEBUG
-bool kho_scratch_overlap(phys_addr_t phys, size_t size);
-#else
-static inline bool kho_scratch_overlap(phys_addr_t phys, size_t size)
-{
-	return false;
-}
-#endif /* CONFIG_KEXEC_HANDOVER_DEBUG */
-
 #endif /* LINUX_KEXEC_HANDOVER_INTERNAL_H */
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 571212b80835e..ab4afc818e8cb 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -3033,29 +3033,21 @@ static __init void *alloc_bootmem(struct hstate *h, int nid, bool node_exact)
 	if (hugetlb_early_cma(h))
 		m = hugetlb_cma_alloc_bootmem(h, &listnode, node_exact);
 	else {
-		if (node_exact)
-			m = memblock_alloc_exact_nid_raw(huge_page_size(h),
-				huge_page_size(h), 0,
-				MEMBLOCK_ALLOC_ACCESSIBLE, nid);
-		else {
-			m = memblock_alloc_try_nid_raw(huge_page_size(h),
-				huge_page_size(h), 0,
-				MEMBLOCK_ALLOC_ACCESSIBLE, nid);
+		m = memblock_alloc_hugetlb(huge_page_size(h), nid, node_exact);
+		if (m) {
+			m->flags = 0;
+			m->cma = NULL;
+
 			/*
 			 * For pre-HVO to work correctly, pages need to be on
 			 * the list for the node they were actually allocated
 			 * from. That node may be different in the case of
-			 * fallback by memblock_alloc_try_nid_raw. So,
+			 * fallback by memblock_alloc_hugetlb_bootmem. So,
 			 * extract the actual node first.
 			 */
-			if (m)
+			if (!node_exact)
 				listnode = early_pfn_to_nid(PHYS_PFN(__pa(m)));
 		}
-
-		if (m) {
-			m->flags = 0;
-			m->cma = NULL;
-		}
 	}
 
 	if (m) {
diff --git a/mm/memblock.c b/mm/memblock.c
index 6349c48154f4b..37e06e70c1d00 100644
--- a/mm/memblock.c
+++ b/mm/memblock.c
@@ -19,11 +19,9 @@
 #include <linux/mutex.h>
 #include <linux/string_helpers.h>
 
-#ifdef CONFIG_KEXEC_HANDOVER
 #include <linux/libfdt.h>
 #include <linux/kexec_handover.h>
 #include <linux/kho/abi/memblock.h>
-#endif /* CONFIG_KEXEC_HANDOVER */
 
 #include <asm/sections.h>
 #include <linux/io.h>
@@ -1506,6 +1504,32 @@ int __init_memblock memblock_set_node(phys_addr_t base, phys_addr_t size,
 	return 0;
 }
 
+static void memblock_prep_allocation(phys_addr_t start, phys_addr_t size,
+				     bool kmemleak_trace)
+{
+	/*
+	 * Skip kmemleak for those places like kasan_init() and
+	 * early_pgtable_alloc() due to high volume.
+	 */
+	if (kmemleak_trace)
+		/*
+		 * Memblock allocated blocks are never reported as
+		 * leaks. This is because many of these blocks are
+		 * only referred via the physical address which is
+		 * not looked up by kmemleak.
+		 */
+		kmemleak_alloc_phys(start, size, 0);
+
+	/*
+	 * Some Virtual Machine platforms, such as Intel TDX or AMD SEV-SNP,
+	 * require memory to be accepted before it can be used by the
+	 * guest.
+	 *
+	 * Accept the memory of the allocated buffer.
+	 */
+	accept_memory(start, size);
+}
+
 /**
  * memblock_alloc_range_nid - allocate boot memory block
  * @size: size of memory block to be allocated in bytes
@@ -1580,28 +1604,7 @@ phys_addr_t __init memblock_alloc_range_nid(phys_addr_t size,
 	return 0;
 
 done:
-	/*
-	 * Skip kmemleak for those places like kasan_init() and
-	 * early_pgtable_alloc() due to high volume.
-	 */
-	if (end != MEMBLOCK_ALLOC_NOLEAKTRACE)
-		/*
-		 * Memblock allocated blocks are never reported as
-		 * leaks. This is because many of these blocks are
-		 * only referred via the physical address which is
-		 * not looked up by kmemleak.
-		 */
-		kmemleak_alloc_phys(found, size, 0);
-
-	/*
-	 * Some Virtual Machine platforms, such as Intel TDX or AMD SEV-SNP,
-	 * require memory to be accepted before it can be used by the
-	 * guest.
-	 *
-	 * Accept the memory of the allocated buffer.
-	 */
-	accept_memory(found, size);
-
+	memblock_prep_allocation(found, size, end != MEMBLOCK_ALLOC_NOLEAKTRACE);
 	return found;
 }
 
@@ -1756,6 +1759,77 @@ void * __init memblock_alloc_try_nid_raw(
 				       false);
 }
 
+/**
+ * memblock_alloc_hugetlb - allocate boot memory for HugeTLB pages
+ * @size:      size of the memory to be allocated in bytes
+ * @nid:       nid of the free memory to find, %NUMA_NO_NODE for any node
+ * @exact_nid: only allocate from the specified nid. If %false, the specified
+ *             nid is tried first, and then all nodes are tried as fallback.
+ *
+ * HugeTLB pages are always aligned by their size, so the alignment matches
+ * @size. Since the memory is for userspace, mirrored memory is not used. The
+ * memory is not zeroed. Does not panic if request cannot be satisfied.
+ *
+ * Return:
+ * Virtual address of allocated memory block on success, %NULL on failure.
+ */
+void * __init memblock_alloc_hugetlb(phys_addr_t size, int nid, bool exact_nid)
+{
+	enum memblock_flags flags = choose_memblock_flags();
+	phys_addr_t addr, start = 0, end = MEMBLOCK_ALLOC_ACCESSIBLE;
+
+	memblock_dbg("%s: %llu bytes, nid=%d, exact_nid=%d %pS\n", __func__,
+		     (u64)size, nid, exact_nid, (void *)_RET_IP_);
+
+	/* Don't waste mirrored memory on HugeTLB pages. */
+	flags &= ~MEMBLOCK_MIRROR;
+retry:
+	/* HugeTLB pages are always aligned by their size. */
+	addr = memblock_find_in_range_node(size, size, start, end, nid, flags);
+	if (addr)
+		goto found;
+
+	/* Try all nodes if allowed. */
+	if (numa_valid_node(nid) && !exact_nid) {
+		nid = NUMA_NO_NODE;
+		/*
+		 * If a previous candidate overlapped with KHO scratch, it would
+		 * update start or end. Now that the search is opening to all
+		 * nodes, reset them.
+		 */
+		start = 0;
+		end = MEMBLOCK_ALLOC_ACCESSIBLE;
+
+		goto retry;
+	}
+
+	/* Found nothing... :-( */
+	return NULL;
+
+found:
+	/*
+	 * HugeTLB pages can be preserved with KHO and no preserved memory can
+	 * be in scratch. So retry if found address overlaps with scratch.
+	 *
+	 * Scratch areas are normally not very large, so this shouldn't take too
+	 * many retries.
+	 */
+	if (kho_scratch_overlap(addr, size)) {
+		if (memblock_bottom_up())
+			start = addr + size;
+		else
+			end = addr;
+
+		goto retry;
+	}
+
+	if (__memblock_reserve(addr, size, nid, MEMBLOCK_RSRV_KERN | MEMBLOCK_RSRV_HUGETLB))
+		return NULL;
+
+	memblock_prep_allocation(addr, size, true);
+	return phys_to_virt(addr);
+}
+
 /**
  * memblock_alloc_try_nid - allocate boot memory block
  * @size: size of memory block to be allocated in bytes
@@ -1825,6 +1899,28 @@ phys_addr_t __init_memblock memblock_reserved_size(void)
 	return memblock.reserved.total_size;
 }
 
+phys_addr_t __init_memblock memblock_reserved_hugetlb_size(phys_addr_t limit, int nid)
+{
+	struct memblock_region *r;
+	phys_addr_t total = 0;
+
+	for_each_reserved_mem_region(r) {
+		phys_addr_t size = r->size;
+
+		if (r->base > limit)
+			break;
+
+		if (r->base + r->size > limit)
+			size = limit - r->base;
+
+		if (nid == memblock_get_region_node(r) || !numa_valid_node(nid))
+			if (r->flags & MEMBLOCK_RSRV_HUGETLB)
+				total += size;
+	}
+
+	return total;
+}
+
 phys_addr_t __init_memblock memblock_reserved_kern_size(phys_addr_t limit, int nid)
 {
 	struct memblock_region *r;
@@ -2511,16 +2607,6 @@ __init void memblock_clear_kho_scratch_only(void)
 {
 	kho_scratch_only = false;
 }
-
-bool __init_memblock memblock_is_kho_scratch_memory(phys_addr_t addr)
-{
-	int i = memblock_search(&memblock.memory, addr);
-
-	if (i == -1)
-		return false;
-
-	return memblock_is_kho_scratch(&memblock.memory.regions[i]);
-}
 #endif
 
 #ifdef CONFIG_KEXEC_HANDOVER
diff --git a/mm/mm_init.c b/mm/mm_init.c
index 0f64909e8d20e..dc20d6814e48b 100644
--- a/mm/mm_init.c
+++ b/mm/mm_init.c
@@ -674,15 +674,17 @@ static inline void fixup_hashdist(void)
 static inline void fixup_hashdist(void) {}
 #endif /* CONFIG_NUMA */
 
-#ifdef CONFIG_ZONE_DEVICE
+#if defined(CONFIG_ZONE_DEVICE) || defined(CONFIG_DEFERRED_STRUCT_PAGE_INIT)
 static __meminit void pageblock_migratetype_init_range(unsigned long pfn,
-		unsigned long nr_pages, int migratetype)
+		unsigned long nr_pages, int migratetype, bool atomic)
 {
 	const unsigned long end = pfn + nr_pages;
 
 	for (pfn = pageblock_align(pfn); pfn < end; pfn += pageblock_nr_pages) {
-		init_pageblock_migratetype(pfn_to_page(pfn), migratetype, false);
-		if (IS_ALIGNED(pfn, PAGES_PER_SECTION))
+		enum migratetype mt = kho_scratch_migratetype(pfn, migratetype);
+
+		init_pageblock_migratetype(pfn_to_page(pfn), mt, false);
+		if (!atomic && IS_ALIGNED(pfn, PAGES_PER_SECTION))
 			cond_resched();
 	}
 }
@@ -932,8 +934,9 @@ void __meminit memmap_init_range(unsigned long size, int nid, unsigned long zone
 		 * over the place during system boot.
 		 */
 		if (pageblock_aligned(pfn)) {
-			init_pageblock_migratetype(page, migratetype,
-					isolate_pageblock);
+			enum migratetype mt = kho_scratch_migratetype(pfn, migratetype);
+
+			init_pageblock_migratetype(page, mt, isolate_pageblock);
 			cond_resched();
 		}
 		pfn++;
@@ -943,8 +946,7 @@ void __meminit memmap_init_range(unsigned long size, int nid, unsigned long zone
 static void __init memmap_init_zone_range(struct zone *zone,
 					  unsigned long start_pfn,
 					  unsigned long end_pfn,
-					  unsigned long *hole_pfn,
-					  enum migratetype mt)
+					  unsigned long *hole_pfn)
 {
 	unsigned long zone_start_pfn = zone->zone_start_pfn;
 	unsigned long zone_end_pfn = zone_start_pfn + zone->spanned_pages;
@@ -957,7 +959,8 @@ static void __init memmap_init_zone_range(struct zone *zone,
 		return;
 
 	memmap_init_range(end_pfn - start_pfn, nid, zone_id, start_pfn,
-			  zone_end_pfn, MEMINIT_EARLY, NULL, mt, false);
+			  zone_end_pfn, MEMINIT_EARLY, NULL, MIGRATE_MOVABLE,
+			  false);
 
 	if (*hole_pfn < start_pfn)
 		init_unavailable_range(*hole_pfn, start_pfn, zone_id, nid);
@@ -973,8 +976,6 @@ static void __init memmap_init(void)
 
 	for_each_mem_pfn_range(i, MAX_NUMNODES, &start_pfn, &end_pfn, &nid) {
 		struct pglist_data *node = NODE_DATA(nid);
-		enum migratetype mt =
-			kho_scratch_migratetype(start_pfn, MIGRATE_MOVABLE);
 
 		for (j = 0; j < MAX_NR_ZONES; j++) {
 			struct zone *zone = node->node_zones + j;
@@ -983,7 +984,7 @@ static void __init memmap_init(void)
 				continue;
 
 			memmap_init_zone_range(zone, start_pfn, end_pfn,
-					       &hole_pfn, mt);
+					       &hole_pfn);
 			zone_id = j;
 		}
 	}
@@ -1142,7 +1143,7 @@ void __ref memmap_init_zone_device(struct zone *zone,
 				     compound_nr_pages(pfn, altmap, pgmap));
 	}
 
-	pageblock_migratetype_init_range(start_pfn, nr_pages, MIGRATE_MOVABLE);
+	pageblock_migratetype_init_range(start_pfn, nr_pages, MIGRATE_MOVABLE, false);
 
 	pr_debug("%s initialised %lu pages in %ums\n", __func__,
 		nr_pages, jiffies_to_msecs(jiffies - start));
@@ -1973,7 +1974,7 @@ unsigned long __init node_map_pfn_alignment(void)
 
 #ifdef CONFIG_DEFERRED_STRUCT_PAGE_INIT
 static void __init deferred_free_pages(unsigned long pfn,
-		unsigned long nr_pages, enum migratetype mt)
+		unsigned long nr_pages)
 {
 	struct page *page;
 	unsigned long i;
@@ -1981,12 +1982,12 @@ static void __init deferred_free_pages(unsigned long pfn,
 	if (!nr_pages)
 		return;
 
+	pageblock_migratetype_init_range(pfn, nr_pages, MIGRATE_MOVABLE, true);
+
 	page = pfn_to_page(pfn);
 
 	/* Free a large naturally-aligned chunk if possible */
 	if (nr_pages == MAX_ORDER_NR_PAGES && IS_MAX_ORDER_ALIGNED(pfn)) {
-		for (i = 0; i < nr_pages; i += pageblock_nr_pages)
-			init_pageblock_migratetype(page + i, mt, false);
 		__free_pages_core(page, MAX_PAGE_ORDER, MEMINIT_EARLY);
 		return;
 	}
@@ -1994,11 +1995,8 @@ static void __init deferred_free_pages(unsigned long pfn,
 	/* Accept chunks smaller than MAX_PAGE_ORDER upfront */
 	accept_memory(PFN_PHYS(pfn), nr_pages * PAGE_SIZE);
 
-	for (i = 0; i < nr_pages; i++, page++, pfn++) {
-		if (pageblock_aligned(pfn))
-			init_pageblock_migratetype(page, mt, false);
-		__free_pages_core(page, 0, MEMINIT_EARLY);
-	}
+	for (i = 0; i < nr_pages; i++)
+		__free_pages_core(page + i, 0, MEMINIT_EARLY);
 }
 
 /* Completion tracking for deferred_init_memmap() threads */
@@ -2054,8 +2052,6 @@ deferred_init_memmap_chunk(unsigned long start_pfn, unsigned long end_pfn,
 	for_each_free_mem_range(i, nid, 0, &start, &end, NULL) {
 		unsigned long spfn = PFN_UP(start);
 		unsigned long epfn = PFN_DOWN(end);
-		enum migratetype mt =
-			kho_scratch_migratetype(spfn, MIGRATE_MOVABLE);
 
 		if (spfn >= end_pfn)
 			break;
@@ -2068,7 +2064,7 @@ deferred_init_memmap_chunk(unsigned long start_pfn, unsigned long end_pfn,
 			unsigned long chunk_end = min(mo_pfn, epfn);
 
 			nr_pages += deferred_init_pages(zone, spfn, chunk_end);
-			deferred_free_pages(spfn, chunk_end - spfn, mt);
+			deferred_free_pages(spfn, chunk_end - spfn);
 
 			spfn = chunk_end;
 
@@ -2687,6 +2683,7 @@ void __init __weak mem_init(void)
 
 void __init mm_core_init_early(void)
 {
+	kho_memory_init_early();
 	hugetlb_cma_reserve();
 	hugetlb_bootmem_alloc();
 
diff --git a/tools/testing/memblock/internal.h b/tools/testing/memblock/internal.h
index b6b1d147fd750..e86bb9000b22f 100644
--- a/tools/testing/memblock/internal.h
+++ b/tools/testing/memblock/internal.h
@@ -66,4 +66,9 @@ static inline void init_deferred_page(unsigned long pfn, int nid)
 
 #define __SetPageReserved(p)	((void)(p))
 
+static inline bool kho_scratch_overlap(phys_addr_t phys, size_t size)
+{
+	return false;
+}
+
 #endif
diff --git a/tools/testing/memblock/linux/kexec_handover.h b/tools/testing/memblock/linux/kexec_handover.h
new file mode 100644
index 0000000000000..bdfd9445b1fd3
--- /dev/null
+++ b/tools/testing/memblock/linux/kexec_handover.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef LINUX_KEXEC_HANDOVER_H
+#define LINUX_KEXEC_HANDOVER_H
+
+/*
+ * Header stub to avoid test build breakage; we don't need to actually implement
+ * any KHO functions as they are not used in the tests.
+ */
+#endif /* LINUX_KEXEC_HANDOVER_H */
diff --git a/tools/testing/memblock/linux/kho/abi/memblock.h b/tools/testing/memblock/linux/kho/abi/memblock.h
new file mode 100644
index 0000000000000..71cf6982a2b98
--- /dev/null
+++ b/tools/testing/memblock/linux/kho/abi/memblock.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef _LINUX_KHO_ABI_MEMBLOCK_H
+#define _LINUX_KHO_ABI_MEMBLOCK_H
+
+/*
+ * Header stub to avoid test build breakage; we don't need to actually define
+ * any ABI as they are not used in the tests.
+ */
+#endif /* _LINUX_KHO_ABI_MEMBLOCK_H */
diff --git a/tools/testing/memblock/linux/libfdt.h b/tools/testing/memblock/linux/libfdt.h
new file mode 100644
index 0000000000000..6137b788fe8de
--- /dev/null
+++ b/tools/testing/memblock/linux/libfdt.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _INCLUDE_LIBFDT_H_
+#define _INCLUDE_LIBFDT_H_
+
+/*
+ * Header stub to avoid test build breakage; we don't need to actually implement
+ * any FDT functions as they are not used in the tests.
+ */
+#endif /* _INCLUDE_LIBFDT_H_ */

3/2 2026/08/02 10:46 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 22090
							output: 19
							thoughts: 237
						

Thoughts:
**Analyzing the Kexec Handover Patch Series**

Okay, so I'm looking at this patch series targeting `kexec_handover` functionality in the kernel. Seems like a fairly extensive modification, touching quite a few areas. I see changes across several key include files: `kexec_handover.h`, the ABI-specific header, `kho_radix_tree.h`, and `memblock.h`. That's already telling me they are making foundational changes.

Diving deeper, I'm noting the changes in `kernel/liveupdate/Makefile` and `kexec_handover.c`. Interesting, they're removing `kexec_handover_debug.c`, which likely means the debug functionality is either being refactored, integrated elsewhere, or deemed unnecessary. Good to keep an eye on how they are handling debugging and observability here.

I'm also seeing impacts on memory management: `mm/hugetlb.c`, `mm/memblock.c`, and `mm/mm_init.c` are all getting touched. This hints that the handover process is going to interact significantly with how memory is allocated, initialized, and managed, especially during the live update phase. That's a critical area.

Finally, some changes in the `tools/testing/memblock` directory with stubs. These stubs are likely for testing, which is great to see. Always good to ensure proper testing for such low level kernel modifications.

Now, hold on. `CONFIG_KEXEC_HANDOVER`... that's the main configuration option controlling this whole feature, right? Let me make sure I understand the dependencies. I need to figure out what other configurations are required or impacted by this change. I'm going to quickly use my `default_api:grepper` to run a search for `CONFIG_KEXEC_HANDOVER` to establish the configuration dependencies. This will help determine how widespread this change is and where this feature is reachable from.



4/2 2026/08/02 10:46 tool grepper
0m Args:
{
  "Expression": "KEXEC_HANDOVER"
}

Results:
{
  "Output": "Documentation/admin-guide/mm/kho.rst=14=Prerequisites\n--\nDocumentation/admin-guide/mm/kho.rst-16-\nDocumentation/admin-guide/mm/kho.rst:17:KHO is available when the kernel is compiled with ``CONFIG_KEXEC_HANDOVER``\nDocumentation/admin-guide/mm/kho.rst-18-set to y. Every KHO producer may have its own config option that you\n--\nDocumentation/admin-guide/mm/kho.rst=87=These debugfs interfaces are available when the kernel is compiled with\nDocumentation/admin-guide/mm/kho.rst:88:``CONFIG_KEXEC_HANDOVER_DEBUGFS`` enabled.\nDocumentation/admin-guide/mm/kho.rst-89-\n--\narch/arm64/Kconfig=1654=config ARCH_DEFAULT_KEXEC_IMAGE_VERIFY_SIG\n--\narch/arm64/Kconfig-1656-\narch/arm64/Kconfig:1657:config ARCH_SUPPORTS_KEXEC_HANDOVER\narch/arm64/Kconfig-1658-\tdef_bool y\n--\narch/x86/Kconfig=1974=config ARCH_SUPPORTS_KEXEC_JUMP\n--\narch/x86/Kconfig-1976-\narch/x86/Kconfig:1977:config ARCH_SUPPORTS_KEXEC_HANDOVER\narch/x86/Kconfig-1978-\tdef_bool X86_64\n--\narch/x86/boot/compressed/kaslr.c=770=static bool process_kho_entries(unsigned long minimum, unsigned long image_size)\n--\narch/x86/boot/compressed/kaslr.c-776-\narch/x86/boot/compressed/kaslr.c:777:\tif (!IS_ENABLED(CONFIG_KEXEC_HANDOVER))\narch/x86/boot/compressed/kaslr.c-778-\t\treturn false;\n--\narch/x86/kernel/kexec-bzimage64.c=274=static void setup_kho(const struct kimage *image, struct boot_params *params,\n--\narch/x86/kernel/kexec-bzimage64.c-280-\narch/x86/kernel/kexec-bzimage64.c:281:\tif (!IS_ENABLED(CONFIG_KEXEC_HANDOVER))\narch/x86/kernel/kexec-bzimage64.c-282-\t\treturn;\n--\narch/x86/kernel/kexec-bzimage64.c=301=setup_boot_parameters(struct kimage *image, struct boot_params *params,\n--\narch/x86/kernel/kexec-bzimage64.c-390-\narch/x86/kernel/kexec-bzimage64.c:391:\tif (IS_ENABLED(CONFIG_KEXEC_HANDOVER)) {\narch/x86/kernel/kexec-bzimage64.c-392-\t\t/* Setup space to store preservation metadata */\n--\narch/x86/kernel/kexec-bzimage64.c=475=static void *bzImage64_load(struct kimage *image, char *kernel,\n--\narch/x86/kernel/kexec-bzimage64.c-581-\narch/x86/kernel/kexec-bzimage64.c:582:\tif (IS_ENABLED(CONFIG_KEXEC_HANDOVER))\narch/x86/kernel/kexec-bzimage64.c-583-\t\tkbuf.bufsz += sizeof(struct setup_data) +\n--\narch/x86/kernel/setup.c=457=static void __init add_kho(u64 phys_addr, u32 data_len)\n--\narch/x86/kernel/setup.c-462-\narch/x86/kernel/setup.c:463:\tif (!IS_ENABLED(CONFIG_KEXEC_HANDOVER)) {\narch/x86/kernel/setup.c:464:\t\tpr_warn(\"Passed KHO data, but CONFIG_KEXEC_HANDOVER not set. Ignoring.\\n\");\narch/x86/kernel/setup.c-465-\t\treturn;\n--\ndrivers/of/fdt.c=928=static void __init early_init_dt_check_kho(void)\n--\ndrivers/of/fdt.c-932-\ndrivers/of/fdt.c:933:\tif (!IS_ENABLED(CONFIG_KEXEC_HANDOVER) || (long)node \u003c 0)\ndrivers/of/fdt.c-934-\t\treturn;\n--\ndrivers/of/kexec.c=258=static int kho_add_chosen(const struct kimage *image, void *fdt, int chosen_node)\n--\ndrivers/of/kexec.c-260-\tint ret = 0;\ndrivers/of/kexec.c:261:#ifdef CONFIG_KEXEC_HANDOVER\ndrivers/of/kexec.c-262-\tphys_addr_t fdt_mem = 0;\n--\ndrivers/of/kexec.c-290-\ndrivers/of/kexec.c:291:#endif /* CONFIG_KEXEC_HANDOVER */\ndrivers/of/kexec.c-292-\treturn ret;\n--\ninclude/asm-generic/kexec_handover.h-1-/* SPDX-License-Identifier: GPL-2.0 */\ninclude/asm-generic/kexec_handover.h:2:#ifndef __ASM_GENERIC_KEXEC_HANDOVER_H\ninclude/asm-generic/kexec_handover.h:3:#define __ASM_GENERIC_KEXEC_HANDOVER_H\ninclude/asm-generic/kexec_handover.h-4-\n--\ninclude/asm-generic/kexec_handover.h=7=struct kho_scratch {\n--\ninclude/asm-generic/kexec_handover.h-11-\ninclude/asm-generic/kexec_handover.h:12:#endif /* __ASM_GENERIC_KEXEC_HANDOVER_H */\n--\ninclude/linux/kexec_handover.h-1-/* SPDX-License-Identifier: GPL-2.0 */\ninclude/linux/kexec_handover.h:2:#ifndef LINUX_KEXEC_HANDOVER_H\ninclude/linux/kexec_handover.h:3:#define LINUX_KEXEC_HANDOVER_H\ninclude/linux/kexec_handover.h-4-\n--\ninclude/linux/kexec_handover.h=14=struct page;\ninclude/linux/kexec_handover.h-15-\ninclude/linux/kexec_handover.h:16:#ifdef CONFIG_KEXEC_HANDOVER\ninclude/linux/kexec_handover.h-17-bool kho_is_enabled(void);\n--\ninclude/linux/kexec_handover.h=135=static inline enum migratetype kho_scratch_migratetype(unsigned long pfn,\n--\ninclude/linux/kexec_handover.h-139-}\ninclude/linux/kexec_handover.h:140:#endif /* CONFIG_KEXEC_HANDOVER */\ninclude/linux/kexec_handover.h-141-\ninclude/linux/kexec_handover.h:142:#endif /* LINUX_KEXEC_HANDOVER_H */\n--\ninclude/linux/kho/abi/kexec_handover.h-9-\ninclude/linux/kho/abi/kexec_handover.h:10:#ifndef _LINUX_KHO_ABI_KEXEC_HANDOVER_H\ninclude/linux/kho/abi/kexec_handover.h:11:#define _LINUX_KHO_ABI_KEXEC_HANDOVER_H\ninclude/linux/kho/abi/kexec_handover.h-12-\n--\ninclude/linux/kho/abi/kexec_handover.h=282=struct kho_radix_leaf {\n--\ninclude/linux/kho/abi/kexec_handover.h-285-\ninclude/linux/kho/abi/kexec_handover.h:286:#endif\t/* _LINUX_KHO_ABI_KEXEC_HANDOVER_H */\n--\ninclude/linux/kho_radix_tree.h=50=struct kho_radix_walk_cb {\n--\ninclude/linux/kho_radix_tree.h-54-\ninclude/linux/kho_radix_tree.h:55:#ifdef CONFIG_KEXEC_HANDOVER\ninclude/linux/kho_radix_tree.h-56-\n--\ninclude/linux/kho_radix_tree.h=62=void kho_radix_destroy_tree(struct kho_radix_tree *tree);\ninclude/linux/kho_radix_tree.h-63-\ninclude/linux/kho_radix_tree.h:64:#else  /* #ifdef CONFIG_KEXEC_HANDOVER */\ninclude/linux/kho_radix_tree.h-65-\n--\ninclude/linux/kho_radix_tree.h=86=static inline void kho_radix_destroy_tree(struct kho_radix_tree *tree) { }\ninclude/linux/kho_radix_tree.h-87-\ninclude/linux/kho_radix_tree.h:88:#endif /* #ifdef CONFIG_KEXEC_HANDOVER */\ninclude/linux/kho_radix_tree.h-89-\n--\nkernel/kexec_internal.h=43=struct kexec_buf;\nkernel/kexec_internal.h-44-\nkernel/kexec_internal.h:45:#ifdef CONFIG_KEXEC_HANDOVER\nkernel/kexec_internal.h-46-int kho_locate_mem_hole(struct kexec_buf *kbuf,\n--\nkernel/kexec_internal.h=56=static inline int kho_fill_kimage(struct kimage *image) { return 0; }\nkernel/kexec_internal.h:57:#endif /* CONFIG_KEXEC_HANDOVER */\nkernel/kexec_internal.h-58-#endif /* LINUX_KEXEC_INTERNAL_H */\n--\nkernel/liveupdate/Kconfig=3=menu \"Live Update and Kexec HandOver\"\nkernel/liveupdate/Kconfig-4-\nkernel/liveupdate/Kconfig:5:config KEXEC_HANDOVER\nkernel/liveupdate/Kconfig-6-\tbool \"kexec handover\"\nkernel/liveupdate/Kconfig:7:\tdepends on ARCH_SUPPORTS_KEXEC_HANDOVER \u0026\u0026 ARCH_SUPPORTS_KEXEC_FILE\nkernel/liveupdate/Kconfig-8-\tselect MEMBLOCK_KHO_SCRATCH\n--\nkernel/liveupdate/Kconfig-17-\nkernel/liveupdate/Kconfig:18:config KEXEC_HANDOVER_DEBUG\nkernel/liveupdate/Kconfig-19-\tbool \"Enable Kexec Handover debug checks\"\nkernel/liveupdate/Kconfig:20:\tdepends on KEXEC_HANDOVER\nkernel/liveupdate/Kconfig-21-\thelp\n--\nkernel/liveupdate/Kconfig-26-\nkernel/liveupdate/Kconfig:27:config KEXEC_HANDOVER_DEBUGFS\nkernel/liveupdate/Kconfig-28-\tbool \"kexec handover debugfs interface\"\nkernel/liveupdate/Kconfig:29:\tdefault KEXEC_HANDOVER\nkernel/liveupdate/Kconfig:30:\tdepends on KEXEC_HANDOVER\nkernel/liveupdate/Kconfig-31-\tselect DEBUG_FS\n--\nkernel/liveupdate/Kconfig-37-\nkernel/liveupdate/Kconfig:38:config KEXEC_HANDOVER_ENABLE_DEFAULT\nkernel/liveupdate/Kconfig-39-\tbool \"Enable kexec handover by default\"\nkernel/liveupdate/Kconfig:40:\tdepends on KEXEC_HANDOVER\nkernel/liveupdate/Kconfig-41-\thelp\n--\nkernel/liveupdate/Kconfig=52=config LIVEUPDATE\nkernel/liveupdate/Kconfig-53-\tbool \"Live Update Orchestrator\"\nkernel/liveupdate/Kconfig:54:\tdepends on KEXEC_HANDOVER\nkernel/liveupdate/Kconfig-55-\thelp\n--\nkernel/liveupdate/Makefile=3=luo-y :=\t\t\t\t\t\t\t\t\\\n--\nkernel/liveupdate/Makefile-9-\nkernel/liveupdate/Makefile:10:obj-$(CONFIG_KEXEC_HANDOVER)\t\t+= kexec_handover.o\nkernel/liveupdate/Makefile:11:obj-$(CONFIG_KEXEC_HANDOVER_DEBUGFS)\t+= kexec_handover_debugfs.o\nkernel/liveupdate/Makefile-12-\n--\nkernel/liveupdate/kexec_handover.c=56=static_assert(sizeof(union kho_page_info) == sizeof(((struct page *)0)-\u003eprivate));\nkernel/liveupdate/kexec_handover.c-57-\nkernel/liveupdate/kexec_handover.c:58:static bool kho_enable __ro_after_init = IS_ENABLED(CONFIG_KEXEC_HANDOVER_ENABLE_DEFAULT);\nkernel/liveupdate/kexec_handover.c-59-\n--\nkernel/liveupdate/kexec_handover.c=981=static void __init kho_extend_scratch(void)\n--\nkernel/liveupdate/kexec_handover.c-1041- * ``/sys/kernel/debug/kho/out/sub_fdts/@name`` when kernel is configured with\nkernel/liveupdate/kexec_handover.c:1042: * CONFIG_KEXEC_HANDOVER_DEBUGFS\nkernel/liveupdate/kexec_handover.c-1043- *\n--\nkernel/liveupdate/kexec_handover.c=1134=int kho_preserve_folio(struct folio *folio)\n--\nkernel/liveupdate/kexec_handover.c-1139-\nkernel/liveupdate/kexec_handover.c:1140:\tif (IS_ENABLED(CONFIG_KEXEC_HANDOVER_DEBUG) \u0026\u0026\nkernel/liveupdate/kexec_handover.c-1141-\t    WARN_ON(kho_scratch_overlap(pfn \u003c\u003c PAGE_SHIFT, PAGE_SIZE \u003c\u003c order)))\n--\nkernel/liveupdate/kexec_handover.c=1209=int kho_preserve_pages(struct page *page, unsigned long nr_pages)\n--\nkernel/liveupdate/kexec_handover.c-1217-\nkernel/liveupdate/kexec_handover.c:1218:\tif (IS_ENABLED(CONFIG_KEXEC_HANDOVER_DEBUG) \u0026\u0026\nkernel/liveupdate/kexec_handover.c-1219-\t    WARN_ON(kho_scratch_overlap(start_pfn \u003c\u003c PAGE_SHIFT,\n--\nkernel/liveupdate/kexec_handover_internal.h-1-/* SPDX-License-Identifier: GPL-2.0 */\nkernel/liveupdate/kexec_handover_internal.h:2:#ifndef LINUX_KEXEC_HANDOVER_INTERNAL_H\nkernel/liveupdate/kexec_handover_internal.h:3:#define LINUX_KEXEC_HANDOVER_INTERNAL_H\nkernel/liveupdate/kexec_handover_internal.h-4-\n--\nkernel/liveupdate/kexec_handover_internal.h-8-\nkernel/liveupdate/kexec_handover_internal.h:9:#ifdef CONFIG_KEXEC_HANDOVER_DEBUGFS\nkernel/liveupdate/kexec_handover_internal.h-10-#include \u003clinux/debugfs.h\u003e\n--\nkernel/liveupdate/kexec_handover_internal.h=23=extern unsigned int kho_scratch_cnt;\nkernel/liveupdate/kexec_handover_internal.h-24-\nkernel/liveupdate/kexec_handover_internal.h:25:#ifdef CONFIG_KEXEC_HANDOVER_DEBUGFS\nkernel/liveupdate/kexec_handover_internal.h-26-int kho_debugfs_init(void);\n--\nkernel/liveupdate/kexec_handover_internal.h=40=static inline void kho_debugfs_blob_remove(struct kho_debugfs *dbg,\nkernel/liveupdate/kexec_handover_internal.h-41-\t\t\t\t\t   void *blob) { }\nkernel/liveupdate/kexec_handover_internal.h:42:#endif /* CONFIG_KEXEC_HANDOVER_DEBUGFS */\nkernel/liveupdate/kexec_handover_internal.h-43-\nkernel/liveupdate/kexec_handover_internal.h:44:#endif /* LINUX_KEXEC_HANDOVER_INTERNAL_H */\n--\nlib/Kconfig.debug=3399=config TEST_OBJPOOL\n--\nlib/Kconfig.debug-3409-\nlib/Kconfig.debug:3410:config TEST_KEXEC_HANDOVER\nlib/Kconfig.debug-3411-\tbool \"Test for Kexec HandOver\"\nlib/Kconfig.debug-3412-\tdefault n\nlib/Kconfig.debug:3413:\tdepends on KEXEC_HANDOVER\nlib/Kconfig.debug-3414-\thelp\n--\nlib/Makefile=106=obj-$(CONFIG_TEST_OBJPOOL) += test_objpool.o\nlib/Makefile:107:obj-$(CONFIG_TEST_KEXEC_HANDOVER) += test_kho.o\nlib/Makefile-108-\n--\nmm/memblock.c=2606=__init void memblock_clear_kho_scratch_only(void)\n--\nmm/memblock.c-2611-\nmm/memblock.c:2612:#ifdef CONFIG_KEXEC_HANDOVER\nmm/memblock.c-2613-\n--\nmm/memblock.c=2794=static bool __init reserve_mem_kho_revive(const char *name, phys_addr_t size,\n--\nmm/memblock.c-2798-}\nmm/memblock.c:2799:#endif /* CONFIG_KEXEC_HANDOVER */\nmm/memblock.c-2800-\n--\ntools/testing/memblock/linux/kexec_handover.h-1-/* SPDX-License-Identifier: GPL-2.0 */\ntools/testing/memblock/linux/kexec_handover.h:2:#ifndef LINUX_KEXEC_HANDOVER_H\ntools/testing/memblock/linux/kexec_handover.h:3:#define LINUX_KEXEC_HANDOVER_H\ntools/testing/memblock/linux/kexec_handover.h-4-\n--\ntools/testing/memblock/linux/kexec_handover.h-8- */\ntools/testing/memblock/linux/kexec_handover.h:9:#endif /* LINUX_KEXEC_HANDOVER_H */\n--\ntools/testing/selftests/kho/vmtest.sh=60=CONFIG_BLK_DEV_INITRD=y\ntools/testing/selftests/kho/vmtest.sh:61:CONFIG_KEXEC_HANDOVER=y\ntools/testing/selftests/kho/vmtest.sh:62:CONFIG_KEXEC_HANDOVER_DEBUG=y\ntools/testing/selftests/kho/vmtest.sh:63:CONFIG_KEXEC_HANDOVER_DEBUGFS=y\ntools/testing/selftests/kho/vmtest.sh:64:CONFIG_TEST_KEXEC_HANDOVER=y\ntools/testing/selftests/kho/vmtest.sh-65-CONFIG_DEBUG_KERNEL=y\n--\ntools/testing/selftests/liveupdate/config=2=CONFIG_KEXEC_FILE=y\ntools/testing/selftests/liveupdate/config:3:CONFIG_KEXEC_HANDOVER=y\ntools/testing/selftests/liveupdate/config:4:CONFIG_KEXEC_HANDOVER_ENABLE_DEFAULT=y\ntools/testing/selftests/liveupdate/config:5:CONFIG_KEXEC_HANDOVER_DEBUGFS=y\ntools/testing/selftests/liveupdate/config:6:CONFIG_KEXEC_HANDOVER_DEBUG=y\ntools/testing/selftests/liveupdate/config-7-CONFIG_LIVEUPDATE=y\n"
}

5/2 2026/08/02 10:46 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 27737
							output: 19
							thoughts: 374
						

Thoughts:
**Analyzing the KEXEC_HANDOVER Patch**

Okay, so this patch is touching `kexec_handover.c`, `memblock.c`, and `hugetlb.c`. It's introducing several new functions: `kho_radix_add_key`, `kho_radix_del_key`, `kho_radix_walk_tree`, `kho_radix_init_tree`, and `kho_radix_destroy_tree`. Also, it's adding `memblock_alloc_hugetlb`, moving `kho_scratch_overlap`, and modifying `mm/hugetlb.c` to use `memblock_alloc_hugetlb`.  It looks like `mm/mm_init.c` is being modified to call `kho_memory_init_early()`.

Right, `KEXEC_HANDOVER` - that's enabled by `CONFIG_KEXEC_HANDOVER`. Interesting.  Could this be a fuzzing target?  Yes, it's a kernel feature, so it should be.

Let me zero in on `memblock_alloc_hugetlb` in `mm/memblock.c`. It's called from `alloc_bootmem` in `mm/hugetlb.c`.  Okay, that's definitely reachable during boot if HugeTLB is being used. Wait, so `memblock_alloc_hugetlb` is reachable at boot, not exactly ideal for post-boot fuzzing.

But what about `kho_radix_add_key`? Is *that* reachable after boot? Let me look at where `kho_preserve_folio` is called to figure that out.



6/2 2026/08/02 10:46 tool grepper
0m Args:
{
  "Expression": "kho_preserve_folio"
}

Results:
{
  "Output": "include/linux/kexec_handover.h=18=bool is_kho_boot(void);\ninclude/linux/kexec_handover.h-19-\ninclude/linux/kexec_handover.h:20:int kho_preserve_folio(struct folio *folio);\ninclude/linux/kexec_handover.h-21-void kho_unpreserve_folio(struct folio *folio);\n--\ninclude/linux/kexec_handover.h=57=static inline bool is_kho_boot(void)\n--\ninclude/linux/kexec_handover.h-61-\ninclude/linux/kexec_handover.h:62:static inline int kho_preserve_folio(struct folio *folio)\ninclude/linux/kexec_handover.h-63-{\n--\nkernel/liveupdate/kexec_handover.c=1123=EXPORT_SYMBOL_GPL(kho_remove_subtree);\n--\nkernel/liveupdate/kexec_handover.c-1125-/**\nkernel/liveupdate/kexec_handover.c:1126: * kho_preserve_folio - preserve a folio across kexec.\nkernel/liveupdate/kexec_handover.c-1127- * @folio: folio to preserve.\n--\nkernel/liveupdate/kexec_handover.c-1133- */\nkernel/liveupdate/kexec_handover.c:1134:int kho_preserve_folio(struct folio *folio)\nkernel/liveupdate/kexec_handover.c-1135-{\n--\nkernel/liveupdate/kexec_handover.c-1146-}\nkernel/liveupdate/kexec_handover.c:1147:EXPORT_SYMBOL_GPL(kho_preserve_folio);\nkernel/liveupdate/kexec_handover.c-1148-\n--\nkernel/liveupdate/kexec_handover.c-1153- * Instructs KHO to unpreserve a folio that was preserved by\nkernel/liveupdate/kexec_handover.c:1154: * kho_preserve_folio() before. The provided @folio (pfn and order)\nkernel/liveupdate/kexec_handover.c-1155- * must exactly match a previously preserved folio.\n--\nkernel/liveupdate/kexec_handover.c=1532=void *kho_alloc_preserve(size_t size)\n--\nkernel/liveupdate/kexec_handover.c-1547-\nkernel/liveupdate/kexec_handover.c:1548:\tret = kho_preserve_folio(folio);\nkernel/liveupdate/kexec_handover.c-1549-\tif (ret) {\n--\nlib/test_kho.c=54=static int kho_test_preserve_data(struct kho_test_state *state)\n--\nlib/test_kho.c-75-\t\tfolios_info[i] = virt_to_phys(folio_address(folio)) | order;\nlib/test_kho.c:76:\t\terr = kho_preserve_folio(folio);\nlib/test_kho.c-77-\t\tif (err)\n--\nlib/test_kho.c=124=static int kho_test_preserve(struct kho_test_state *state)\n--\nlib/test_kho.c-133-\nlib/test_kho.c:134:\terr = kho_preserve_folio(state-\u003efdt);\nlib/test_kho.c-135-\tif (err)\n--\nmm/memfd_luo.c=87=static int memfd_luo_preserve_folios(struct file *file,\n--\nmm/memfd_luo.c-151-\nmm/memfd_luo.c:152:\t\terr = kho_preserve_folio(folio);\nmm/memfd_luo.c-153-\t\tif (err)\n"
}

7/2 2026/08/02 10:46 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 29463
							output: 19
							thoughts: 31
						

Thoughts:
Here's my analysis of the situation:

**Investigating the Interaction Between memfd_luo.c and kho_preserve_folio**

Okay, so `memfd_luo.c` is calling `kho_preserve_folio`. This suggests a crucial point of interaction, likely related to memory management or folio preservation within the context of memfd (memory file descriptor) operations. My initial thought is to dive straight into `memfd_luo.c`. I need to understand *why* this call is being made. What's the context? What data is being preserved, and *why* does it require special handling via `kho_preserve_folio`? Is this about ensuring data integrity during some operation, preventing premature freeing, or perhaps protecting a folio from being reclaimed prematurely by the kernel? Let's check `memfd_luo.c` to find out.



8/2 2026/08/02 10:46 tool grepper
0m Args:
{
  "Expression": "memfd_luo"
}

Results:
{
  "Output": "Documentation/mm/memfd_preservation.rst=4=Memfd Preservation via LUO\n--\nDocumentation/mm/memfd_preservation.rst-6-\nDocumentation/mm/memfd_preservation.rst:7:.. kernel-doc:: mm/memfd_luo.c\nDocumentation/mm/memfd_preservation.rst-8-   :doc: Memfd Preservation via LUO\n--\nMAINTAINERS=15055=F:\tlib/tests/liveupdate.c\nMAINTAINERS:15056:F:\tmm/memfd_luo.c\nMAINTAINERS-15057-F:\ttools/testing/selftests/liveupdate/\n--\ninclude/linux/kho/abi/memfd.h-22- *\ninclude/linux/kho/abi/memfd.h:23: * The state is serialized into a packed structure `struct memfd_luo_ser`\ninclude/linux/kho/abi/memfd.h-24- * which is handed over to the next kernel via the KHO mechanism.\n--\ninclude/linux/kho/abi/memfd.h-47-/**\ninclude/linux/kho/abi/memfd.h:48: * struct memfd_luo_folio_ser - Serialized state of a single folio.\ninclude/linux/kho/abi/memfd.h-49- * @pfn:       The page frame number of the folio.\n--\ninclude/linux/kho/abi/memfd.h-52- */\ninclude/linux/kho/abi/memfd.h:53:struct memfd_luo_folio_ser {\ninclude/linux/kho/abi/memfd.h-54-\tu64 pfn:52;\n--\ninclude/linux/kho/abi/memfd.h-70-/**\ninclude/linux/kho/abi/memfd.h:71: * struct memfd_luo_ser - Main serialization structure for a memfd.\ninclude/linux/kho/abi/memfd.h-72- * @pos:       The file's current position (f_pos).\n--\ninclude/linux/kho/abi/memfd.h-78- * @folios:    KHO vmalloc descriptor pointing to the array of\ninclude/linux/kho/abi/memfd.h:79: *             struct memfd_luo_folio_ser.\ninclude/linux/kho/abi/memfd.h-80- */\ninclude/linux/kho/abi/memfd.h:81:struct memfd_luo_ser {\ninclude/linux/kho/abi/memfd.h-82-\tu64 pos;\n--\nmm/Makefile=102=obj-$(CONFIG_PAGE_COUNTER) += page_counter.o\nmm/Makefile:103:obj-$(CONFIG_LIVEUPDATE_MEMFD) += memfd_luo.o\nmm/Makefile-104-obj-$(CONFIG_MEMCG_V1) += memcontrol-v1.o\n--\nmm/memfd_luo.c-86-\nmm/memfd_luo.c:87:static int memfd_luo_preserve_folios(struct file *file,\nmm/memfd_luo.c-88-\t\t\t\t     struct kho_vmalloc *kho_vmalloc,\nmm/memfd_luo.c:89:\t\t\t\t     struct memfd_luo_folio_ser **out_folios_ser,\nmm/memfd_luo.c-90-\t\t\t\t     u64 *nr_foliosp)\n--\nmm/memfd_luo.c-92-\tstruct inode *inode = file_inode(file);\nmm/memfd_luo.c:93:\tstruct memfd_luo_folio_ser *folios_ser;\nmm/memfd_luo.c-94-\tunsigned int max_folios;\n--\nmm/memfd_luo.c-148-\tfor (i = 0; i \u003c nr_folios; i++) {\nmm/memfd_luo.c:149:\t\tstruct memfd_luo_folio_ser *pfolio = \u0026folios_ser[i];\nmm/memfd_luo.c-150-\t\tstruct folio *folio = folios[i];\n--\nmm/memfd_luo.c-230-\nmm/memfd_luo.c:231:static void memfd_luo_unpreserve_folios(struct kho_vmalloc *kho_vmalloc,\nmm/memfd_luo.c:232:\t\t\t\t\tstruct memfd_luo_folio_ser *folios_ser,\nmm/memfd_luo.c-233-\t\t\t\t\tu64 nr_folios)\n--\nmm/memfd_luo.c-242-\tfor (i = 0; i \u003c nr_folios; i++) {\nmm/memfd_luo.c:243:\t\tconst struct memfd_luo_folio_ser *pfolio = \u0026folios_ser[i];\nmm/memfd_luo.c-244-\t\tstruct folio *folio;\n--\nmm/memfd_luo.c-257-\nmm/memfd_luo.c:258:static int memfd_luo_preserve(struct liveupdate_file_op_args *args)\nmm/memfd_luo.c-259-{\nmm/memfd_luo.c-260-\tstruct inode *inode = file_inode(args-\u003efile);\nmm/memfd_luo.c:261:\tstruct memfd_luo_folio_ser *folios_ser;\nmm/memfd_luo.c:262:\tstruct memfd_luo_ser *ser;\nmm/memfd_luo.c-263-\tu64 nr_folios, inode_size;\n--\nmm/memfd_luo.c-302-\nmm/memfd_luo.c:303:\terr = memfd_luo_preserve_folios(args-\u003efile, \u0026ser-\u003efolios,\nmm/memfd_luo.c-304-\t\t\t\t\t\u0026folios_ser, \u0026nr_folios);\n--\nmm/memfd_luo.c-323-\nmm/memfd_luo.c:324:static int memfd_luo_freeze(struct liveupdate_file_op_args *args)\nmm/memfd_luo.c-325-{\nmm/memfd_luo.c:326:\tstruct memfd_luo_ser *ser;\nmm/memfd_luo.c-327-\n--\nmm/memfd_luo.c-341-\nmm/memfd_luo.c:342:static void memfd_luo_unpreserve(struct liveupdate_file_op_args *args)\nmm/memfd_luo.c-343-{\nmm/memfd_luo.c-344-\tstruct inode *inode = file_inode(args-\u003efile);\nmm/memfd_luo.c:345:\tstruct memfd_luo_ser *ser;\nmm/memfd_luo.c-346-\n--\nmm/memfd_luo.c-354-\nmm/memfd_luo.c:355:\tmemfd_luo_unpreserve_folios(\u0026ser-\u003efolios, args-\u003eprivate_data,\nmm/memfd_luo.c-356-\t\t\t\t    ser-\u003enr_folios);\n--\nmm/memfd_luo.c-361-\nmm/memfd_luo.c:362:static void memfd_luo_discard_folios(const struct memfd_luo_folio_ser *folios_ser,\nmm/memfd_luo.c-363-\t\t\t\t     u64 nr_folios)\n--\nmm/memfd_luo.c-367-\tfor (i = 0; i \u003c nr_folios; i++) {\nmm/memfd_luo.c:368:\t\tconst struct memfd_luo_folio_ser *pfolio = \u0026folios_ser[i];\nmm/memfd_luo.c-369-\t\tstruct folio *folio;\n--\nmm/memfd_luo.c-386-\nmm/memfd_luo.c:387:static void memfd_luo_finish(struct liveupdate_file_op_args *args)\nmm/memfd_luo.c-388-{\nmm/memfd_luo.c:389:\tstruct memfd_luo_folio_ser *folios_ser;\nmm/memfd_luo.c:390:\tstruct memfd_luo_ser *ser;\nmm/memfd_luo.c-391-\n--\nmm/memfd_luo.c-408-\nmm/memfd_luo.c:409:\t\tmemfd_luo_discard_folios(folios_ser, ser-\u003enr_folios);\nmm/memfd_luo.c-410-\t\tvfree(folios_ser);\n--\nmm/memfd_luo.c-416-\nmm/memfd_luo.c:417:static int memfd_luo_retrieve_folios(struct file *file,\nmm/memfd_luo.c:418:\t\t\t\t     struct memfd_luo_folio_ser *folios_ser,\nmm/memfd_luo.c-419-\t\t\t\t     u64 nr_folios)\n--\nmm/memfd_luo.c-428-\tfor (i = 0; i \u003c nr_folios; i++) {\nmm/memfd_luo.c:429:\t\tconst struct memfd_luo_folio_ser *pfolio = \u0026folios_ser[i];\nmm/memfd_luo.c-430-\t\tphys_addr_t phys;\n--\nmm/memfd_luo.c-500-\tfor (long j = i + 1; j \u003c nr_folios; j++) {\nmm/memfd_luo.c:501:\t\tconst struct memfd_luo_folio_ser *pfolio = \u0026folios_ser[j];\nmm/memfd_luo.c-502-\t\tphys_addr_t phys;\n--\nmm/memfd_luo.c-517-\nmm/memfd_luo.c:518:static int memfd_luo_retrieve(struct liveupdate_file_op_args *args)\nmm/memfd_luo.c-519-{\nmm/memfd_luo.c:520:\tstruct memfd_luo_folio_ser *folios_ser;\nmm/memfd_luo.c:521:\tstruct memfd_luo_ser *ser;\nmm/memfd_luo.c-522-\tstruct file *file;\n--\nmm/memfd_luo.c-561-\nmm/memfd_luo.c:562:\t\terr = memfd_luo_retrieve_folios(file, folios_ser, ser-\u003enr_folios);\nmm/memfd_luo.c-563-\t\tvfree(folios_ser);\n--\nmm/memfd_luo.c-579-\nmm/memfd_luo.c:580:static bool memfd_luo_can_preserve(struct liveupdate_file_handler *handler,\nmm/memfd_luo.c-581-\t\t\t\t   struct file *file)\n--\nmm/memfd_luo.c-587-\nmm/memfd_luo.c:588:static unsigned long memfd_luo_get_id(struct file *file)\nmm/memfd_luo.c-589-{\n--\nmm/memfd_luo.c-592-\nmm/memfd_luo.c:593:static const struct liveupdate_file_ops memfd_luo_file_ops = {\nmm/memfd_luo.c:594:\t.freeze = memfd_luo_freeze,\nmm/memfd_luo.c:595:\t.finish = memfd_luo_finish,\nmm/memfd_luo.c:596:\t.retrieve = memfd_luo_retrieve,\nmm/memfd_luo.c:597:\t.preserve = memfd_luo_preserve,\nmm/memfd_luo.c:598:\t.unpreserve = memfd_luo_unpreserve,\nmm/memfd_luo.c:599:\t.can_preserve = memfd_luo_can_preserve,\nmm/memfd_luo.c:600:\t.get_id = memfd_luo_get_id,\nmm/memfd_luo.c-601-\t.owner = THIS_MODULE,\n--\nmm/memfd_luo.c-603-\nmm/memfd_luo.c:604:static struct liveupdate_file_handler memfd_luo_handler = {\nmm/memfd_luo.c:605:\t.ops = \u0026memfd_luo_file_ops,\nmm/memfd_luo.c-606-\t.compatible = MEMFD_LUO_FH_COMPATIBLE,\n--\nmm/memfd_luo.c-608-\nmm/memfd_luo.c:609:static int __init memfd_luo_init(void)\nmm/memfd_luo.c-610-{\nmm/memfd_luo.c:611:\tint err = liveupdate_register_file_handler(\u0026memfd_luo_handler);\nmm/memfd_luo.c-612-\n--\nmm/memfd_luo.c-621-}\nmm/memfd_luo.c:622:late_initcall(memfd_luo_init);\n"
}

9/2 2026/08/02 10:47 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 33137
							output: 22
							thoughts: 263
						

Thoughts:
**My Analysis of the New Radix Tree Functions**

Okay, so this patch introduces a handful of new functions: `kho_radix_add_key`, `kho_radix_del_key`, `kho_radix_walk_tree`, `kho_radix_init_tree`, and `kho_radix_destroy_tree`. They're exported symbols, so that immediately suggests they're designed for external use, which is good to know. Now, looking at the call sites, I see that `kho_preserve_folio` calls `kho_radix_add_key`, and `kho_unpreserve_folio` calls `kho_radix_del_key`.

Ah, and `kho_preserve_folio` is itself exported and used by `memfd_luo.c`. That's interesting. Diving a little deeper, `memfd_luo.c` is part of `CONFIG_LIVEUPDATE_MEMFD`, which in turn depends on `CONFIG_LIVEUPDATE` and ultimately `CONFIG_KEXEC_HANDOVER`. That gives me a bit of a roadmap. My thought is that fuzzing `memfd_luo` would inevitably trigger `kho_preserve_folio` and then `kho_radix_add_key`. A valid approach for testing for potential vulnerabilities, or at least understanding the new code.

Now, wait a second, `memfd_luo` is a file handler for liveupdate... Right, so, the question that comes to mind is can I fuzz that directly? It's likely through some of the liveupdate syscalls or ioctls, right? I'll need to check the kernel source – probably `kernel/liveupdate/luo.c` or something similar – to figure out *how* `memfd_luo` gets triggered. That's the key to getting a test harness together.



10/2 2026/08/02 10:47 tool grepper
0m Args:
{
  "Expression": "liveupdate_register_file_handler"
}

Results:
{
  "Output": "include/linux/liveupdate.h=232=int liveupdate_reboot(void);\ninclude/linux/liveupdate.h-233-\ninclude/linux/liveupdate.h:234:int liveupdate_register_file_handler(struct liveupdate_file_handler *fh);\ninclude/linux/liveupdate.h-235-void liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh);\n--\ninclude/linux/liveupdate.h=254=static inline int liveupdate_reboot(void)\n--\ninclude/linux/liveupdate.h-258-\ninclude/linux/liveupdate.h:259:static inline int liveupdate_register_file_handler(struct liveupdate_file_handler *fh)\ninclude/linux/liveupdate.h-260-{\n--\nkernel/liveupdate/luo_file.c=855=void luo_file_set_destroy(struct luo_file_set *file_set)\n--\nkernel/liveupdate/luo_file.c-862-/**\nkernel/liveupdate/luo_file.c:863: * liveupdate_register_file_handler - Register a file handler with LUO.\nkernel/liveupdate/luo_file.c-864- * @fh: Pointer to a caller-allocated \u0026struct liveupdate_file_handler.\n--\nkernel/liveupdate/luo_file.c-873- */\nkernel/liveupdate/luo_file.c:874:int liveupdate_register_file_handler(struct liveupdate_file_handler *fh)\nkernel/liveupdate/luo_file.c-875-{\n--\nkernel/liveupdate/luo_file.c-917- * Unregisters the file handler from the liveupdate core. This function\nkernel/liveupdate/luo_file.c:918: * reverses the operations of liveupdate_register_file_handler().\nkernel/liveupdate/luo_file.c-919- */\n--\nmm/memfd_luo.c=609=static int __init memfd_luo_init(void)\nmm/memfd_luo.c-610-{\nmm/memfd_luo.c:611:\tint err = liveupdate_register_file_handler(\u0026memfd_luo_handler);\nmm/memfd_luo.c-612-\n"
}

11/2 2026/08/02 10:47 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 34418
							output: 19
							thoughts: 41
						

Thoughts:
Here's my take on the thought process:

**Deeper Dive into Kernel Module Dependencies**

Okay, hold on a second. I just realized something significant. This `kho_radix_add_key` function, the one I'm working with, isn't just internal. It's also utilized by `kho_preserve_pages`, and *that* function is exported. This changes the game quite a bit, making it more crucial to understand its implications.

Alright, so the next logical step is to trace the dependency chain. Let's dig into who, or rather, which other parts of the system, are calling `kho_preserve_pages`. This is going to be key to understanding the ripple effect of any changes or potential vulnerabilities related to `kho_radix_add_key`. I need to see the bigger picture now; understanding all call sites is critical.



12/2 2026/08/02 10:47 tool grepper
0m Args:
{
  "Expression": "kho_preserve_pages"
}

Results:
{
  "Output": "include/linux/kexec_handover.h=21=void kho_unpreserve_folio(struct folio *folio);\ninclude/linux/kexec_handover.h:22:int kho_preserve_pages(struct page *page, unsigned long nr_pages);\ninclude/linux/kexec_handover.h-23-void kho_unpreserve_pages(struct page *page, unsigned long nr_pages);\n--\ninclude/linux/kexec_handover.h=67=static inline void kho_unpreserve_folio(struct folio *folio) { }\ninclude/linux/kexec_handover.h-68-\ninclude/linux/kexec_handover.h:69:static inline int kho_preserve_pages(struct page *page, unsigned int nr_pages)\ninclude/linux/kexec_handover.h-70-{\n--\nkernel/liveupdate/kexec_handover.c=545=EXPORT_SYMBOL_GPL(kho_restore_folio);\n--\nkernel/liveupdate/kexec_handover.c-552- * Restore a contiguous list of order 0 pages that was preserved with\nkernel/liveupdate/kexec_handover.c:553: * kho_preserve_pages().\nkernel/liveupdate/kexec_handover.c-554- *\n--\nkernel/liveupdate/kexec_handover.c=1165=EXPORT_SYMBOL_GPL(kho_unpreserve_folio);\nkernel/liveupdate/kexec_handover.c-1166-\nkernel/liveupdate/kexec_handover.c:1167:static unsigned int __kho_preserve_pages_order(unsigned long start_pfn,\nkernel/liveupdate/kexec_handover.c-1168-\t\t\t\t\t       unsigned long end_pfn)\n--\nkernel/liveupdate/kexec_handover.c=1184=static void __kho_unpreserve(struct kho_radix_tree *tree,\n--\nkernel/liveupdate/kexec_handover.c-1189-\twhile (pfn \u003c end_pfn) {\nkernel/liveupdate/kexec_handover.c:1190:\t\torder = __kho_preserve_pages_order(pfn, end_pfn);\nkernel/liveupdate/kexec_handover.c-1191-\n--\nkernel/liveupdate/kexec_handover.c-1199-/**\nkernel/liveupdate/kexec_handover.c:1200: * kho_preserve_pages - preserve contiguous pages across kexec\nkernel/liveupdate/kexec_handover.c-1201- * @page: first page in the list.\n--\nkernel/liveupdate/kexec_handover.c-1208- */\nkernel/liveupdate/kexec_handover.c:1209:int kho_preserve_pages(struct page *page, unsigned long nr_pages)\nkernel/liveupdate/kexec_handover.c-1210-{\n--\nkernel/liveupdate/kexec_handover.c-1224-\twhile (pfn \u003c end_pfn) {\nkernel/liveupdate/kexec_handover.c:1225:\t\tunsigned int order = __kho_preserve_pages_order(pfn, end_pfn);\nkernel/liveupdate/kexec_handover.c-1226-\n--\nkernel/liveupdate/kexec_handover.c-1241-}\nkernel/liveupdate/kexec_handover.c:1242:EXPORT_SYMBOL_GPL(kho_preserve_pages);\nkernel/liveupdate/kexec_handover.c-1243-\n--\nkernel/liveupdate/kexec_handover.c-1250- * This must be called with the same @page and @nr_pages as the corresponding\nkernel/liveupdate/kexec_handover.c:1251: * kho_preserve_pages() call. Unpreserving arbitrary sub-ranges of larger\nkernel/liveupdate/kexec_handover.c-1252- * preserved blocks is not supported.\n--\nkernel/liveupdate/kexec_handover.c=1295=static struct kho_vmalloc_chunk *new_vmalloc_chunk(struct kho_vmalloc_chunk *cur)\n--\nkernel/liveupdate/kexec_handover.c-1303-\nkernel/liveupdate/kexec_handover.c:1304:\terr = kho_preserve_pages(virt_to_page(chunk), 1);\nkernel/liveupdate/kexec_handover.c-1305-\tif (err)\n--\nkernel/liveupdate/kexec_handover.c=1345=int kho_preserve_vmalloc(void *ptr, struct kho_vmalloc *preservation)\n--\nkernel/liveupdate/kexec_handover.c-1370-\nkernel/liveupdate/kexec_handover.c:1371:\t\terr = kho_preserve_pages(vm-\u003epages[i], nr_contig_pages);\nkernel/liveupdate/kexec_handover.c-1372-\t\tif (err)\n--\nmm/memblock.c=2614=static int __init reserved_mem_preserve(void)\n--\nmm/memblock.c-2623-\nmm/memblock.c:2624:\t\terr = kho_preserve_pages(page, nr_pages);\nmm/memblock.c-2625-\t\tif (err)\n--\nmm/memblock.c=2643=static int __init prepare_kho_fdt(void)\n--\nmm/memblock.c-2655-\tfdt = page_to_virt(fdt_page);\nmm/memblock.c:2656:\terr = kho_preserve_pages(fdt_page, 1);\nmm/memblock.c-2657-\tif (err)\n"
}

13/2 2026/08/02 10:47 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 36118
							output: 19
							thoughts: 38
						

Thoughts:
Here's my analysis of the provided information, as if these were my internal thoughts:

**Dissecting Memory Preservation in the Linux Kernel**

Okay, so `kho_preserve_pages` is the function we need to focus on. It's called by `reserved_mem_preserve`, which, as the name suggests, deals with memory reservation. My immediate thought is, let's trace this back. `mm/memblock.c` is the location, so I'll jump there and examine `reserved_mem_preserve` directly. I need to understand how *it* invokes `kho_preserve_pages`. What are the arguments being passed? What's the context? Is this the only path to the preservation? Knowing this is vital to truly understand the core logic of `kho_preserve_pages`.



14/2 2026/08/02 10:47 tool grepper
0m Args:
{
  "Expression": "reserved_mem_preserve"
}

Results:
{
  "Output": "mm/memblock.c=2606=__init void memblock_clear_kho_scratch_only(void)\n--\nmm/memblock.c-2613-\nmm/memblock.c:2614:static int __init reserved_mem_preserve(void)\nmm/memblock.c-2615-{\n--\nmm/memblock.c=2643=static int __init prepare_kho_fdt(void)\n--\nmm/memblock.c-2683-\nmm/memblock.c:2684:\terr = reserved_mem_preserve();\nmm/memblock.c-2685-\tif (err)\n"
}

15/2 2026/08/02 10:47 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 36524
							output: 20
							thoughts: 13
						

Thoughts:
Here's my thought process, as if I were analyzing that question:

**Deconstructing `prepare_kho_fdt`**

Okay, so the prompt is asking me about the calling context of the function `prepare_kho_fdt`. This means someone needs to understand where it's used within the codebase. As an expert, I'm already thinking about several things:

*   **Codebase Structure:** I immediately consider the overall project architecture. Is this a standalone script, a library, part of a larger application? Knowing the project's organization will help me narrow down where to look. I'm thinking about typical code patterns and whether this is likely a utility function, a core component, or something specific to a particular module.
*   **Purpose of the Function:** What's the function *doing*? Understanding its role gives me clues. Does the name suggest data preparation, a specific algorithm, file manipulation, or something else entirely? The "kho" part is a signal... does that point to a file format, data type, or library that's relevant to the function? If I can guess what it might do, I can make some educated guesses.
*   **Search Strategies:** My first move will be a quick grep or a similar code search. I'll need to look for calls to the function name, its arguments, or associated variable names to pinpoint the source code where it is invoked. I can also look for usages of related libraries or modules that help constrain the search space.
*   **Dependencies and Imports:** I'll also be mentally mapping out potential dependencies. Are there any import statements that might reveal the module or package the function is part of? This information could guide my search for the function's definition and its call sites.
*   **Documentation (if available):** Of course, if there's any documentation (code comments, API documentation, etc.) that would be the best place to start. A well-documented function often explicitly states its usage and context.
*   **Version Control History:** If I'm working with a version control system (and I probably am!), the commit history can be extremely helpful. Examining the changesets related to this function could reveal when it was introduced, why, and potentially how it's used.

My immediate next step will be to dive into the codebase using a search tool. The answer should come from the code itself.



16/2 2026/08/02 10:47 tool grepper
0m Args:
{
  "Expression": "prepare_kho_fdt"
}

Results:
{
  "Output": "mm/memblock.c=2614=static int __init reserved_mem_preserve(void)\n--\nmm/memblock.c-2642-\nmm/memblock.c:2643:static int __init prepare_kho_fdt(void)\nmm/memblock.c-2644-{\n--\nmm/memblock.c=2702=static int __init reserve_mem_init(void)\n--\nmm/memblock.c-2708-\nmm/memblock.c:2709:\terr = prepare_kho_fdt();\nmm/memblock.c-2710-\tif (err)\n"
}

17/2 2026/08/02 10:47 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 37233
							output: 19
							thoughts: 12
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience, presented in the first person:

**Initial Thoughts on `reserve_mem_init`**

Alright, so the initial thought that pops into my head is, "Is `reserve_mem_init` actually registered as an `initcall`?" It's a fundamental question. We're talking about memory reservation, which is pretty crucial during the early boot process. I'm immediately trying to recall where this function typically fits within the system's initialization sequence. Is it happening early on, before the full memory management infrastructure is up and running? That would make perfect sense, implying an `initcall` status. It allows for critical regions of memory to be locked down before general allocation starts. Understanding the calling context is important. Knowing if it's an `initcall` is a key part of deducing where it sits in the broader boot process. I need to quickly recall the typical initialization order to confirm my suspicion.



18/2 2026/08/02 10:47 tool grepper
0m Args:
{
  "Expression": "reserve_mem_init"
}

Results:
{
  "Output": "mm/memblock.c=2643=static int __init prepare_kho_fdt(void)\n--\nmm/memblock.c-2701-\nmm/memblock.c:2702:static int __init reserve_mem_init(void)\nmm/memblock.c-2703-{\n--\nmm/memblock.c-2713-}\nmm/memblock.c:2714:late_initcall(reserve_mem_init);\nmm/memblock.c-2715-\n"
}

19/2 2026/08/02 10:47 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 37615
							output: 16
							thoughts: 266
						

Thoughts:
**Analyzing the KHO Patch for Potential Vulnerabilities**

Okay, so this `reserve_mem_init` function is indeed a `late_initcall`. That means `kho_preserve_pages` will be executed during boot if the KHO feature is enabled. Hmm, but the patch also introduces `memblock_alloc_hugetlb`. I need to look into that. Let's see... `memblock_alloc_hugetlb` is called from `alloc_bootmem` in `mm/hugetlb.c`. Right, that also happens during boot. Okay, so nothing there that looks like a direct attack surface, at first glance.

Now, is there any user-facing interface, something that's fuzzable? The patch touches `kho_radix_add_key`, `kho_radix_del_key`, `kho_radix_walk_tree`, `kho_radix_init_tree`, and `kho_radix_destroy_tree`. Those are used by `kho_preserve_folio` and `kho_preserve_pages`. Ah, that’s interesting, `memfd_luo` uses `kho_preserve_folio`. Can userspace trigger `memfd_luo`?

Yes! `memfd_luo` registers a file handler via `liveupdate_register_file_handler`. That's a strong indicator. That means userspace can definitely reach it through the `liveupdate` system calls or ioctls. I need to dive deeper into the `liveupdate` syscalls now. This is where the vulnerability most likely lies.



20/2 2026/08/02 10:47 tool grepper
0m Args:
{
  "Expression": "liveupdate"
}

Results:
{
  "Output": "\nFull output is too long, showing 500 out of 1157 lines.\nUse more precise expression if possible.\n\nDocumentation/admin-guide/kernel-parameters.txt=95=Kernel parameters\n--\nDocumentation/admin-guide/kernel-parameters.txt-3520-\nDocumentation/admin-guide/kernel-parameters.txt:3521:\tliveupdate=\t[KNL,EARLY]\nDocumentation/admin-guide/kernel-parameters.txt-3522-\t\t\tFormat: \u003cbool\u003e\n--\nDocumentation/core-api/index.rst=136=Documents that don't fit elsewhere or which have yet to be categorized.\n--\nDocumentation/core-api/index.rst-141-   librs\nDocumentation/core-api/index.rst:142:   liveupdate\nDocumentation/core-api/index.rst-143-   netlink\n--\nDocumentation/core-api/kho/index.rst=80=Public API\n--\nDocumentation/core-api/kho/index.rst-82-\nDocumentation/core-api/kho/index.rst:83:.. kernel-doc:: kernel/liveupdate/kexec_handover.c\nDocumentation/core-api/kho/index.rst-84-  :export:\n--\nDocumentation/core-api/kho/index.rst=86=KHO Serialization Blocks API\n--\nDocumentation/core-api/kho/index.rst-88-\nDocumentation/core-api/kho/index.rst:89:.. kernel-doc:: kernel/liveupdate/kho_block.c\nDocumentation/core-api/kho/index.rst-90-  :doc: KHO Serialization Blocks\n--\nDocumentation/core-api/kho/index.rst-93-\nDocumentation/core-api/kho/index.rst:94:.. kernel-doc:: kernel/liveupdate/kho_block.c\nDocumentation/core-api/kho/index.rst-95-  :internal:\n--\nDocumentation/core-api/liveupdate.rst=4=Live Update Orchestrator\n--\nDocumentation/core-api/liveupdate.rst-7-\nDocumentation/core-api/liveupdate.rst:8:.. kernel-doc:: kernel/liveupdate/luo_core.c\nDocumentation/core-api/liveupdate.rst-9-   :doc: Live Update Orchestrator (LUO)\n--\nDocumentation/core-api/liveupdate.rst=11=LUO Sessions\nDocumentation/core-api/liveupdate.rst-12-============\nDocumentation/core-api/liveupdate.rst:13:.. kernel-doc:: kernel/liveupdate/luo_session.c\nDocumentation/core-api/liveupdate.rst-14-   :doc: LUO Sessions\n--\nDocumentation/core-api/liveupdate.rst=16=LUO Preserving File Descriptors\nDocumentation/core-api/liveupdate.rst-17-===============================\nDocumentation/core-api/liveupdate.rst:18:.. kernel-doc:: kernel/liveupdate/luo_file.c\nDocumentation/core-api/liveupdate.rst-19-   :doc: LUO File Descriptors\n--\nDocumentation/core-api/liveupdate.rst=21=LUO File Lifecycle Bound Global Data\nDocumentation/core-api/liveupdate.rst-22-====================================\nDocumentation/core-api/liveupdate.rst:23:.. kernel-doc:: kernel/liveupdate/luo_flb.c\nDocumentation/core-api/liveupdate.rst-24-   :doc: LUO File Lifecycle Bound Global Data\n--\nDocumentation/core-api/liveupdate.rst=38=Public API\nDocumentation/core-api/liveupdate.rst-39-==========\nDocumentation/core-api/liveupdate.rst:40:.. kernel-doc:: include/linux/liveupdate.h\nDocumentation/core-api/liveupdate.rst-41-\n--\nDocumentation/core-api/liveupdate.rst-44-\nDocumentation/core-api/liveupdate.rst:45:.. kernel-doc:: kernel/liveupdate/luo_core.c\nDocumentation/core-api/liveupdate.rst-46-   :export:\nDocumentation/core-api/liveupdate.rst-47-\nDocumentation/core-api/liveupdate.rst:48:.. kernel-doc:: kernel/liveupdate/luo_flb.c\nDocumentation/core-api/liveupdate.rst-49-   :export:\nDocumentation/core-api/liveupdate.rst-50-\nDocumentation/core-api/liveupdate.rst:51:.. kernel-doc:: kernel/liveupdate/luo_file.c\nDocumentation/core-api/liveupdate.rst-52-   :export:\n--\nDocumentation/core-api/liveupdate.rst=54=Internal API\nDocumentation/core-api/liveupdate.rst-55-============\nDocumentation/core-api/liveupdate.rst:56:.. kernel-doc:: kernel/liveupdate/luo_core.c\nDocumentation/core-api/liveupdate.rst-57-   :internal:\nDocumentation/core-api/liveupdate.rst-58-\nDocumentation/core-api/liveupdate.rst:59:.. kernel-doc:: kernel/liveupdate/luo_flb.c\nDocumentation/core-api/liveupdate.rst-60-   :internal:\nDocumentation/core-api/liveupdate.rst-61-\nDocumentation/core-api/liveupdate.rst:62:.. kernel-doc:: kernel/liveupdate/luo_session.c\nDocumentation/core-api/liveupdate.rst-63-   :internal:\nDocumentation/core-api/liveupdate.rst-64-\nDocumentation/core-api/liveupdate.rst:65:.. kernel-doc:: kernel/liveupdate/luo_file.c\nDocumentation/core-api/liveupdate.rst-66-   :internal:\n--\nDocumentation/core-api/liveupdate.rst=68=See Also\n--\nDocumentation/core-api/liveupdate.rst-70-\nDocumentation/core-api/liveupdate.rst:71:- :doc:`Live Update uAPI \u003c/userspace-api/liveupdate\u003e`\nDocumentation/core-api/liveupdate.rst-72-- :doc:`/core-api/kho/index`\n--\nDocumentation/filesystems/xfs/xfs-online-fsck-design.rst=3395=zero when online fsck is not running.\nDocumentation/filesystems/xfs/xfs-online-fsck-design.rst-3396-\nDocumentation/filesystems/xfs/xfs-online-fsck-design.rst:3397:.. _liveupdate:\nDocumentation/filesystems/xfs/xfs-online-fsck-design.rst-3398-\n--\nDocumentation/filesystems/xfs/xfs-online-fsck-design.rst=3633=It combines a :ref:`coordinated inode scanner \u003ciscan\u003e`, :ref:`live update hooks\nDocumentation/filesystems/xfs/xfs-online-fsck-design.rst:3634:\u003cliveupdate\u003e`, and an :ref:`in-memory rmap btree \u003cxfbtree\u003e` to complete the\nDocumentation/filesystems/xfs/xfs-online-fsck-design.rst-3635-scan for reverse mapping records.\n--\nDocumentation/filesystems/xfs/xfs-online-fsck-design.rst=4403=Directory rebuilding uses a :ref:`coordinated inode scan \u003ciscan\u003e` and\nDocumentation/filesystems/xfs/xfs-online-fsck-design.rst:4404:a :ref:`directory entry live update hook \u003cliveupdate\u003e` as follows:\nDocumentation/filesystems/xfs/xfs-online-fsck-design.rst-4405-\n--\nDocumentation/mm/memfd_preservation.rst=19=See Also\n--\nDocumentation/mm/memfd_preservation.rst-21-\nDocumentation/mm/memfd_preservation.rst:22:- :doc:`/core-api/liveupdate`\nDocumentation/mm/memfd_preservation.rst-23-- :doc:`/core-api/kho/index`\n--\nDocumentation/userspace-api/index.rst=58=Everything else\n--\nDocumentation/userspace-api/index.rst-64-   ELF\nDocumentation/userspace-api/index.rst:65:   liveupdate\nDocumentation/userspace-api/index.rst-66-   netlink/index\n--\nDocumentation/userspace-api/ioctl/ioctl-number.rst=73=Code  Seq#    Include File                                             Comments\n--\nDocumentation/userspace-api/ioctl/ioctl-number.rst-384-                                                                       \u003cmailto:linux-hyperv@vger.kernel.org\u003e\nDocumentation/userspace-api/ioctl/ioctl-number.rst:385:0xBA  00-0F  uapi/linux/liveupdate.h                                   Pasha Tatashin\nDocumentation/userspace-api/ioctl/ioctl-number.rst-386-                                                                       \u003cmailto:pasha.tatashin@soleen.com\u003e\n--\nDocumentation/userspace-api/liveupdate.rst=8=ioctl interface\nDocumentation/userspace-api/liveupdate.rst-9-===============\nDocumentation/userspace-api/liveupdate.rst:10:.. kernel-doc:: kernel/liveupdate/luo_core.c\nDocumentation/userspace-api/liveupdate.rst-11-   :doc: LUO ioctl Interface\n--\nDocumentation/userspace-api/liveupdate.rst=13=ioctl uAPI\nDocumentation/userspace-api/liveupdate.rst-14-===========\nDocumentation/userspace-api/liveupdate.rst:15:.. kernel-doc:: include/uapi/linux/liveupdate.h\nDocumentation/userspace-api/liveupdate.rst-16-\n--\nDocumentation/userspace-api/liveupdate.rst=31=and push file descriptors with a name into the store via\n--\nDocumentation/userspace-api/liveupdate.rst-33-\nDocumentation/userspace-api/liveupdate.rst:34:Services may also create their own LUO sessions (via ``/dev/liveupdate``) and\nDocumentation/userspace-api/liveupdate.rst-35-push the resulting session fds into their file descriptor store like any other\n--\nDocumentation/userspace-api/liveupdate.rst=48=See Also\n--\nDocumentation/userspace-api/liveupdate.rst-50-\nDocumentation/userspace-api/liveupdate.rst:51:- :doc:`Live Update Orchestrator \u003c/core-api/liveupdate\u003e`\n--\nMAINTAINERS=14001=S:\tMaintained\nMAINTAINERS:14002:T:\tgit git://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git\nMAINTAINERS-14003-F:\tDocumentation/admin-guide/kdump/\n--\nMAINTAINERS=14320=W:\thttp://kernel.org/pub/linux/utils/kernel/kexec/\nMAINTAINERS:14321:T:\tgit git://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git\nMAINTAINERS-14322-F:\tinclude/linux/kexec.h\n--\nMAINTAINERS=14333=S:\tMaintained\nMAINTAINERS:14334:T:\tgit git://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git\nMAINTAINERS-14335-F:\tDocumentation/admin-guide/mm/kho.rst\n--\nMAINTAINERS=14340=F:\tinclude/linux/kho_block.h\nMAINTAINERS:14341:F:\tkernel/liveupdate/kexec_handover*\nMAINTAINERS-14342-F:\tlib/test_kho.c\n--\nMAINTAINERS=15046=S:\tMaintained\nMAINTAINERS:15047:T:\tgit git://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git\nMAINTAINERS:15048:F:\tDocumentation/core-api/liveupdate.rst\nMAINTAINERS-15049-F:\tDocumentation/mm/memfd_preservation.rst\nMAINTAINERS:15050:F:\tDocumentation/userspace-api/liveupdate.rst\nMAINTAINERS-15051-F:\tinclude/linux/kho/abi/\nMAINTAINERS:15052:F:\tinclude/linux/liveupdate.h\nMAINTAINERS:15053:F:\tinclude/uapi/linux/liveupdate.h\nMAINTAINERS:15054:F:\tkernel/liveupdate/\nMAINTAINERS:15055:F:\tlib/tests/liveupdate.c\nMAINTAINERS-15056-F:\tmm/memfd_luo.c\nMAINTAINERS:15057:F:\ttools/testing/selftests/liveupdate/\nMAINTAINERS-15058-\n--\ninclude/linux/kho/abi/luo.h-32- *     The central ABI structure that contains the overall state of the LUO.\ninclude/linux/kho/abi/luo.h:33: *     It includes the compatibility string, the liveupdate-number, and pointers\ninclude/linux/kho/abi/luo.h-34- *     to sessions and FLBs.\n--\ninclude/linux/kho/abi/luo.h-61-#include \u003clinux/kho/abi/block.h\u003e\ninclude/linux/kho/abi/luo.h:62:#include \u003cuapi/linux/liveupdate.h\u003e\ninclude/linux/kho/abi/luo.h-63-\n--\ninclude/linux/kho/abi/luo.h-73- * @compatible:     Compatibility string identifying the LUO ABI version.\ninclude/linux/kho/abi/luo.h:74: * @liveupdate_num: A counter tracking the number of successful live updates.\ninclude/linux/kho/abi/luo.h-75- * @sessions_pa:    Physical address of the first session block header.\n--\ninclude/linux/kho/abi/luo.h=80=struct luo_ser {\ninclude/linux/kho/abi/luo.h-81-\tchar compatible[LUO_ABI_COMPAT_LEN];\ninclude/linux/kho/abi/luo.h:82:\tu64 liveupdate_num;\ninclude/linux/kho/abi/luo.h-83-\tu64 sessions_pa;\n--\ninclude/linux/kho/abi/luo.h=152=struct luo_flb_header_ser {\n--\ninclude/linux/kho/abi/luo.h-159- * @name:    The unique compatibility string of the FLB object, used to find the\ninclude/linux/kho/abi/luo.h:160: *           corresponding \u0026struct liveupdate_flb handler in the new kernel.\ninclude/linux/kho/abi/luo.h-161- * @data:    The opaque u64 handle returned by the FLB's .preserve() operation\n--\ninclude/linux/kho/abi/luo.h=174=struct luo_flb_ser {\n--\ninclude/linux/kho/abi/luo.h-181-#ifdef CONFIG_LIVEUPDATE_TEST\ninclude/linux/kho/abi/luo.h:182:#define LIVEUPDATE_TEST_FLB_COMPATIBLE(i)\t\"liveupdate-test-flb-v\" #i\ninclude/linux/kho/abi/luo.h-183-#endif\n--\ninclude/linux/liveupdate.h-17-#include \u003clinux/types.h\u003e\ninclude/linux/liveupdate.h:18:#include \u003cuapi/linux/liveupdate.h\u003e\ninclude/linux/liveupdate.h-19-\ninclude/linux/liveupdate.h:20:struct liveupdate_file_handler;\ninclude/linux/liveupdate.h:21:struct liveupdate_flb;\ninclude/linux/liveupdate.h:22:struct liveupdate_session;\ninclude/linux/liveupdate.h-23-struct file;\n--\ninclude/linux/liveupdate.h-25-/**\ninclude/linux/liveupdate.h:26: * struct liveupdate_file_op_args - Arguments for file operation callbacks.\ninclude/linux/liveupdate.h-27- * @handler:          The file handler being called.\n--\ninclude/linux/liveupdate.h-45- */\ninclude/linux/liveupdate.h:46:struct liveupdate_file_op_args {\ninclude/linux/liveupdate.h:47:\tstruct liveupdate_file_handler *handler;\ninclude/linux/liveupdate.h-48-\tint retrieve_status;\n--\ninclude/linux/liveupdate.h-54-/**\ninclude/linux/liveupdate.h:55: * struct liveupdate_file_ops - Callbacks for live-updatable files.\ninclude/linux/liveupdate.h-56- * @can_preserve: Required. Lightweight check to see if this handler is\n--\ninclude/linux/liveupdate.h-71- * All operations (except can_preserve) receive a pointer to a\ninclude/linux/liveupdate.h:72: * 'struct liveupdate_file_op_args' containing the necessary context.\ninclude/linux/liveupdate.h-73- */\ninclude/linux/liveupdate.h:74:struct liveupdate_file_ops {\ninclude/linux/liveupdate.h:75:\tbool (*can_preserve)(struct liveupdate_file_handler *handler,\ninclude/linux/liveupdate.h-76-\t\t\t     struct file *file);\ninclude/linux/liveupdate.h:77:\tint (*preserve)(struct liveupdate_file_op_args *args);\ninclude/linux/liveupdate.h:78:\tvoid (*unpreserve)(struct liveupdate_file_op_args *args);\ninclude/linux/liveupdate.h:79:\tint (*freeze)(struct liveupdate_file_op_args *args);\ninclude/linux/liveupdate.h:80:\tvoid (*unfreeze)(struct liveupdate_file_op_args *args);\ninclude/linux/liveupdate.h:81:\tint (*retrieve)(struct liveupdate_file_op_args *args);\ninclude/linux/liveupdate.h:82:\tbool (*can_finish)(struct liveupdate_file_op_args *args);\ninclude/linux/liveupdate.h:83:\tvoid (*finish)(struct liveupdate_file_op_args *args);\ninclude/linux/liveupdate.h-84-\tunsigned long (*get_id)(struct file *file);\n--\ninclude/linux/liveupdate.h-88-/**\ninclude/linux/liveupdate.h:89: * struct liveupdate_file_handler - Represents a handler for a live-updatable file type.\ninclude/linux/liveupdate.h-90- * @ops:                Callback functions\n--\ninclude/linux/liveupdate.h-100- */\ninclude/linux/liveupdate.h:101:struct liveupdate_file_handler {\ninclude/linux/liveupdate.h:102:\tconst struct liveupdate_file_ops *ops;\ninclude/linux/liveupdate.h-103-\tconst char compatible[LIVEUPDATE_HNDL_COMPAT_LENGTH];\n--\ninclude/linux/liveupdate.h-116-/**\ninclude/linux/liveupdate.h:117: * struct liveupdate_flb_op_args - Arguments for FLB operation callbacks.\ninclude/linux/liveupdate.h-118- * @flb:       The global FLB instance for which this call is performed.\n--\ninclude/linux/liveupdate.h-127- */\ninclude/linux/liveupdate.h:128:struct liveupdate_flb_op_args {\ninclude/linux/liveupdate.h:129:\tstruct liveupdate_flb *flb;\ninclude/linux/liveupdate.h-130-\tu64 data;\n--\ninclude/linux/liveupdate.h-134-/**\ninclude/linux/liveupdate.h:135: * struct liveupdate_flb_ops - Callbacks for global File-Lifecycle-Bound data.\ninclude/linux/liveupdate.h-136- * @preserve:        Called when the first file using this FLB is preserved.\n--\ninclude/linux/liveupdate.h-156- */\ninclude/linux/liveupdate.h:157:struct liveupdate_flb_ops {\ninclude/linux/liveupdate.h:158:\tint (*preserve)(struct liveupdate_flb_op_args *argp);\ninclude/linux/liveupdate.h:159:\tvoid (*unpreserve)(struct liveupdate_flb_op_args *argp);\ninclude/linux/liveupdate.h:160:\tint (*retrieve)(struct liveupdate_flb_op_args *argp);\ninclude/linux/liveupdate.h:161:\tvoid (*finish)(struct liveupdate_flb_op_args *argp);\ninclude/linux/liveupdate.h-162-\tstruct module *owner;\n--\ninclude/linux/liveupdate.h=197=struct luo_flb_private {\n--\ninclude/linux/liveupdate.h-205-/**\ninclude/linux/liveupdate.h:206: * struct liveupdate_flb - A global definition for a shared data object.\ninclude/linux/liveupdate.h-207- * @ops:         Callback functions\n--\ninclude/linux/liveupdate.h-210- *               supports. This is matched against the compatible string\ninclude/linux/liveupdate.h:211: *               associated with individual \u0026struct liveupdate_flb\ninclude/linux/liveupdate.h-212- *               instances.\n--\ninclude/linux/liveupdate.h-217- */\ninclude/linux/liveupdate.h:218:struct liveupdate_flb {\ninclude/linux/liveupdate.h:219:\tconst struct liveupdate_flb_ops *ops;\ninclude/linux/liveupdate.h-220-\tconst char compatible[LIVEUPDATE_FLB_COMPAT_LENGTH];\n--\ninclude/linux/liveupdate.h-228-/* Return true if live update orchestrator is enabled */\ninclude/linux/liveupdate.h:229:bool liveupdate_enabled(void);\ninclude/linux/liveupdate.h-230-\ninclude/linux/liveupdate.h-231-/* Called during kexec to tell LUO that entered into reboot */\ninclude/linux/liveupdate.h:232:int liveupdate_reboot(void);\ninclude/linux/liveupdate.h-233-\ninclude/linux/liveupdate.h:234:int liveupdate_register_file_handler(struct liveupdate_file_handler *fh);\ninclude/linux/liveupdate.h:235:void liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh);\ninclude/linux/liveupdate.h-236-\ninclude/linux/liveupdate.h:237:int liveupdate_register_flb(struct liveupdate_file_handler *fh,\ninclude/linux/liveupdate.h:238:\t\t\t    struct liveupdate_flb *flb);\ninclude/linux/liveupdate.h:239:void liveupdate_unregister_flb(struct liveupdate_file_handler *fh,\ninclude/linux/liveupdate.h:240:\t\t\t       struct liveupdate_flb *flb);\ninclude/linux/liveupdate.h-241-\ninclude/linux/liveupdate.h:242:int liveupdate_flb_get_incoming(struct liveupdate_flb *flb, void **objp);\ninclude/linux/liveupdate.h:243:void liveupdate_flb_put_incoming(struct liveupdate_flb *flb);\ninclude/linux/liveupdate.h-244-\ninclude/linux/liveupdate.h:245:int liveupdate_flb_get_outgoing(struct liveupdate_flb *flb, void **objp);\ninclude/linux/liveupdate.h-246-\n--\ninclude/linux/liveupdate.h-248-\ninclude/linux/liveupdate.h:249:static inline bool liveupdate_enabled(void)\ninclude/linux/liveupdate.h-250-{\n--\ninclude/linux/liveupdate.h-253-\ninclude/linux/liveupdate.h:254:static inline int liveupdate_reboot(void)\ninclude/linux/liveupdate.h-255-{\n--\ninclude/linux/liveupdate.h-258-\ninclude/linux/liveupdate.h:259:static inline int liveupdate_register_file_handler(struct liveupdate_file_handler *fh)\ninclude/linux/liveupdate.h-260-{\n--\ninclude/linux/liveupdate.h-263-\ninclude/linux/liveupdate.h:264:static inline void liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh)\ninclude/linux/liveupdate.h-265-{\n--\ninclude/linux/liveupdate.h-267-\ninclude/linux/liveupdate.h:268:static inline int liveupdate_register_flb(struct liveupdate_file_handler *fh,\ninclude/linux/liveupdate.h:269:\t\t\t\t\t  struct liveupdate_flb *flb)\ninclude/linux/liveupdate.h-270-{\n--\ninclude/linux/liveupdate.h-273-\ninclude/linux/liveupdate.h:274:static inline void liveupdate_unregister_flb(struct liveupdate_file_handler *fh,\ninclude/linux/liveupdate.h:275:\t\t\t\t\t     struct liveupdate_flb *flb)\ninclude/linux/liveupdate.h-276-{\n--\ninclude/linux/liveupdate.h-278-\ninclude/linux/liveupdate.h:279:static inline int liveupdate_flb_get_incoming(struct liveupdate_flb *flb,\ninclude/linux/liveupdate.h-280-\t\t\t\t\t      void **objp)\n--\ninclude/linux/liveupdate.h-284-\ninclude/linux/liveupdate.h:285:static inline void liveupdate_flb_put_incoming(struct liveupdate_flb *flb)\ninclude/linux/liveupdate.h-286-{\n--\ninclude/linux/liveupdate.h-288-\ninclude/linux/liveupdate.h:289:static inline int liveupdate_flb_get_outgoing(struct liveupdate_flb *flb,\ninclude/linux/liveupdate.h-290-\t\t\t\t\t      void **objp)\n--\ninclude/uapi/linux/liveupdate.h-3-/*\ninclude/uapi/linux/liveupdate.h:4: * Userspace interface for /dev/liveupdate\ninclude/uapi/linux/liveupdate.h-5- * Live Update Orchestrator\n--\ninclude/uapi/linux/liveupdate.h-48-\ninclude/uapi/linux/liveupdate.h:49:/* The /dev/liveupdate ioctl commands */\ninclude/uapi/linux/liveupdate.h-50-enum {\n--\ninclude/uapi/linux/liveupdate.h=57=enum {\n--\ninclude/uapi/linux/liveupdate.h-65-/**\ninclude/uapi/linux/liveupdate.h:66: * struct liveupdate_ioctl_create_session - ioctl(LIVEUPDATE_IOCTL_CREATE_SESSION)\ninclude/uapi/linux/liveupdate.h:67: * @size:\tInput; sizeof(struct liveupdate_ioctl_create_session)\ninclude/uapi/linux/liveupdate.h-68- * @fd:\t\tOutput; The new file descriptor for the created session.\n--\ninclude/uapi/linux/liveupdate.h-73- * Creates a new live update session for managing preserved resources.\ninclude/uapi/linux/liveupdate.h:74: * This ioctl can only be called on the main /dev/liveupdate device.\ninclude/uapi/linux/liveupdate.h-75- *\n--\ninclude/uapi/linux/liveupdate.h-77- */\ninclude/uapi/linux/liveupdate.h:78:struct liveupdate_ioctl_create_session {\ninclude/uapi/linux/liveupdate.h-79-\t__u32\t\tsize;\n--\ninclude/uapi/linux/liveupdate.h-87-/**\ninclude/uapi/linux/liveupdate.h:88: * struct liveupdate_ioctl_retrieve_session - ioctl(LIVEUPDATE_IOCTL_RETRIEVE_SESSION)\ninclude/uapi/linux/liveupdate.h:89: * @size:    Input; sizeof(struct liveupdate_ioctl_retrieve_session)\ninclude/uapi/linux/liveupdate.h-90- * @fd:      Output; The new file descriptor for the retrieved session.\n--\ninclude/uapi/linux/liveupdate.h-109- *\ninclude/uapi/linux/liveupdate.h:110: * This ioctl can only be called on the main /dev/liveupdate device when the\ninclude/uapi/linux/liveupdate.h-111- * system is in the LIVEUPDATE_STATE_UPDATED state.\ninclude/uapi/linux/liveupdate.h-112- */\ninclude/uapi/linux/liveupdate.h:113:struct liveupdate_ioctl_retrieve_session {\ninclude/uapi/linux/liveupdate.h-114-\t__u32\t\tsize;\n--\ninclude/uapi/linux/liveupdate.h-124-/**\ninclude/uapi/linux/liveupdate.h:125: * struct liveupdate_session_preserve_fd - ioctl(LIVEUPDATE_SESSION_PRESERVE_FD)\ninclude/uapi/linux/liveupdate.h:126: * @size:  Input; sizeof(struct liveupdate_session_preserve_fd)\ninclude/uapi/linux/liveupdate.h-127- * @fd:    Input; The user-space file descriptor to be preserved.\n--\ninclude/uapi/linux/liveupdate.h-147- */\ninclude/uapi/linux/liveupdate.h:148:struct liveupdate_session_preserve_fd {\ninclude/uapi/linux/liveupdate.h-149-\t__u32\t\tsize;\n--\ninclude/uapi/linux/liveupdate.h-157-/**\ninclude/uapi/linux/liveupdate.h:158: * struct liveupdate_session_retrieve_fd - ioctl(LIVEUPDATE_SESSION_RETRIEVE_FD)\ninclude/uapi/linux/liveupdate.h:159: * @size:  Input; sizeof(struct liveupdate_session_retrieve_fd)\ninclude/uapi/linux/liveupdate.h-160- * @fd:    Output; The new file descriptor representing the fully restored\n--\ninclude/uapi/linux/liveupdate.h-177- */\ninclude/uapi/linux/liveupdate.h:178:struct liveupdate_session_retrieve_fd {\ninclude/uapi/linux/liveupdate.h-179-\t__u32\t\tsize;\n--\ninclude/uapi/linux/liveupdate.h-187-/**\ninclude/uapi/linux/liveupdate.h:188: * struct liveupdate_session_finish - ioctl(LIVEUPDATE_SESSION_FINISH)\ninclude/uapi/linux/liveupdate.h:189: * @size:     Input; sizeof(struct liveupdate_session_finish)\ninclude/uapi/linux/liveupdate.h-190- * @reserved: Input; Must be zero. Reserved for future use.\n--\ninclude/uapi/linux/liveupdate.h-210- */\ninclude/uapi/linux/liveupdate.h:211:struct liveupdate_session_finish {\ninclude/uapi/linux/liveupdate.h-212-\t__u32\t\tsize;\n--\ninclude/uapi/linux/liveupdate.h-219-/**\ninclude/uapi/linux/liveupdate.h:220: * struct liveupdate_session_get_name - ioctl(LIVEUPDATE_SESSION_GET_NAME)\ninclude/uapi/linux/liveupdate.h:221: * @size:  Input; sizeof(struct liveupdate_session_get_name)\ninclude/uapi/linux/liveupdate.h-222- * @reserved: Input; Must be zero. Reserved for future use.\n--\ninclude/uapi/linux/liveupdate.h-229- */\ninclude/uapi/linux/liveupdate.h:230:struct liveupdate_session_get_name {\ninclude/uapi/linux/liveupdate.h-231-\t__u32\t\tsize;\n--\ninit/Kconfig=2255=source \"kernel/Kconfig.kexec\"\ninit/Kconfig-2256-\ninit/Kconfig:2257:source \"kernel/liveupdate/Kconfig\"\ninit/Kconfig-2258-\n--\nkernel/Makefile=56=obj-y += livepatch/\nkernel/Makefile:57:obj-y += liveupdate/\nkernel/Makefile-58-obj-y += dma/\n--\nkernel/kexec_core.c-17-#include \u003clinux/list.h\u003e\nkernel/kexec_core.c:18:#include \u003clinux/liveupdate.h\u003e\nkernel/kexec_core.c-19-#include \u003clinux/highmem.h\u003e\n--\nkernel/kexec_core.c=1138=int kernel_kexec(void)\n--\nkernel/kexec_core.c-1149-\tif (!kexec_image-\u003epreserve_context) {\nkernel/kexec_core.c:1150:\t\terror = liveupdate_reboot();\nkernel/kexec_core.c-1151-\t\tif (error)\n--\nkernel/liveupdate/luo_core.c-40- * .. note::\nkernel/liveupdate/luo_core.c:41: *     To enable LUO, boot the kernel with the ``liveupdate=on`` command line\nkernel/liveupdate/luo_core.c-42- *     parameter.\n--\nkernel/liveupdate/luo_core.c-56-#include \u003clinux/kobject.h\u003e\nkernel/liveupdate/luo_core.c:57:#include \u003clinux/liveupdate.h\u003e\nkernel/liveupdate/luo_core.c-58-#include \u003clinux/miscdevice.h\u003e\n--\nkernel/liveupdate/luo_core.c=67=static struct {\n--\nkernel/liveupdate/luo_core.c-69-\tstruct luo_ser *luo_ser_out;\nkernel/liveupdate/luo_core.c:70:\tu64 liveupdate_num;\nkernel/liveupdate/luo_core.c-71-} luo_global;\n--\nkernel/liveupdate/luo_core.c=76=DECLARE_RWSEM(luo_register_rwlock);\nkernel/liveupdate/luo_core.c-77-\nkernel/liveupdate/luo_core.c:78:static int __init early_liveupdate_param(char *buf)\nkernel/liveupdate/luo_core.c-79-{\n--\nkernel/liveupdate/luo_core.c-81-}\nkernel/liveupdate/luo_core.c:82:early_param(\"liveupdate\", early_liveupdate_param);\nkernel/liveupdate/luo_core.c-83-\nkernel/liveupdate/luo_core.c=84=static int __init luo_early_startup(void)\n--\nkernel/liveupdate/luo_core.c-91-\tif (!kho_is_enabled()) {\nkernel/liveupdate/luo_core.c:92:\t\tif (liveupdate_enabled())\nkernel/liveupdate/luo_core.c:93:\t\t\tpr_warn(\"Disabling liveupdate because KHO is disabled\\n\");\nkernel/liveupdate/luo_core.c-94-\t\tluo_global.enabled = false;\n--\nkernel/liveupdate/luo_core.c-120-\nkernel/liveupdate/luo_core.c:121:\tluo_global.liveupdate_num = luo_ser-\u003eliveupdate_num;\nkernel/liveupdate/luo_core.c:122:\tpr_info(\"Retrieved live update data, liveupdate number: %lld\\n\",\nkernel/liveupdate/luo_core.c:123:\t\tluo_global.liveupdate_num);\nkernel/liveupdate/luo_core.c-124-\n--\nkernel/liveupdate/luo_core.c-137-\nkernel/liveupdate/luo_core.c:138:static int __init liveupdate_early_init(void)\nkernel/liveupdate/luo_core.c-139-{\n--\nkernel/liveupdate/luo_core.c-150-}\nkernel/liveupdate/luo_core.c:151:early_initcall(liveupdate_early_init);\nkernel/liveupdate/luo_core.c-152-\n--\nkernel/liveupdate/luo_core.c=154=static int __init luo_state_setup(void)\n--\nkernel/liveupdate/luo_core.c-165-\tstrscpy(luo_ser-\u003ecompatible, LUO_ABI_COMPATIBLE, sizeof(luo_ser-\u003ecompatible));\nkernel/liveupdate/luo_core.c:166:\tluo_ser-\u003eliveupdate_num = luo_global.liveupdate_num + 1;\nkernel/liveupdate/luo_core.c-167-\n--\nkernel/liveupdate/luo_core.c-190- * late initcall because it initializes the outgoing tree that is needed only\nkernel/liveupdate/luo_core.c:191: * once userspace starts using /dev/liveupdate.\nkernel/liveupdate/luo_core.c-192- */\nkernel/liveupdate/luo_core.c=193=static int __init luo_late_startup(void)\n--\nkernel/liveupdate/luo_core.c-196-\nkernel/liveupdate/luo_core.c:197:\tif (!liveupdate_enabled())\nkernel/liveupdate/luo_core.c-198-\t\treturn 0;\n--\nkernel/liveupdate/luo_core.c=206=late_initcall(luo_late_startup);\n--\nkernel/liveupdate/luo_core.c-210-/**\nkernel/liveupdate/luo_core.c:211: * liveupdate_reboot() - Kernel reboot notifier for live update final\nkernel/liveupdate/luo_core.c-212- * serialization.\n--\nkernel/liveupdate/luo_core.c-219- */\nkernel/liveupdate/luo_core.c:220:int liveupdate_reboot(void)\nkernel/liveupdate/luo_core.c-221-{\n--\nkernel/liveupdate/luo_core.c-223-\nkernel/liveupdate/luo_core.c:224:\tif (!liveupdate_enabled())\nkernel/liveupdate/luo_core.c-225-\t\treturn 0;\n--\nkernel/liveupdate/luo_core.c-236-/**\nkernel/liveupdate/luo_core.c:237: * liveupdate_enabled - Check if the live update feature is enabled.\nkernel/liveupdate/luo_core.c-238- *\nkernel/liveupdate/luo_core.c-239- * This function returns the state of the live update feature flag, which\n\n"
}

21/2 2026/08/02 10:47 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 48085
							output: 22
							thoughts: 598
						

Thoughts:
**Fuzzing Target Identification: Liveupdate and KHO Radix Tree**

Okay, so I'm looking at this `liveupdate` feature, enabled by either `liveupdate=on` or `CONFIG_LIVEUPDATE`. It exposes `/dev/liveupdate` with associated ioctls. The core of this patch seems to be modifications to the KHO radix tree implementation and `memblock_alloc_hugetlb`.  I immediately see a potential fuzzing target here. `memblock_alloc_hugetlb` gets called during the boot process, which means that any bugs introduced here can effect the init process. The KHO radix tree implementation is accessible through the `/dev/liveupdate` ioctls, specifically, the `LIVEUPDATE_SESSION_PRESERVE_FD` path, which calls `memfd_luo_preserve`, then `kho_preserve_folio`, and finally `kho_radix_add_key`. This definitely screams "fuzz me!"

Let's break down the key functions. The patch adds `kho_radix_add_key`, `kho_radix_del_key`, `kho_radix_walk_tree`, `kho_radix_init_tree`, `kho_radix_destroy_tree`, and `memblock_alloc_hugetlb`, and `kho_scratch_overlap`.

Now, the interesting bit: `kho_radix_add_key` and `kho_radix_del_key` are exported symbols. `memblock_alloc_hugetlb` is an `__init` function; Syzkaller doesn't directly target those, but it's executed during boot. I'm going to follow the `memblock_alloc_hugetlb` call path to see how it can be accessed after boot. Ah hah!  It's called from `alloc_bootmem` which is used in `mm/hugetlb.c`. Syzkaller won't directly fuzz boot-time code. However, `kho_radix_add_key` and `kho_radix_del_key` are accessible after boot through `kho_preserve_folio` and `kho_unpreserve_folio`. That's the real target.

So, I'm going to focus my fuzzing efforts on the following functions: `kho_radix_add_key`, `kho_radix_del_key`, `kho_radix_walk_tree`, `kho_radix_init_tree`, and `kho_radix_destroy_tree`.

Now I need to see if `kho_radix_init_tree` and `kho_radix_destroy_tree` are accessible after boot. I see that `kho_radix_init_tree` is called from `kho_init` during `fs_initcall` *and* `kho_memory_init_early` at boot time, and `kho_extend_scratch` (at boot time). Wait, is it called anywhere else? I need to double-check that. I need to make sure I am fuzzing code that is reachable after boot.



22/2 2026/08/02 10:48 tool grepper
0m Args:
{
  "Expression": "kho_radix_init_tree"
}

Results:
{
  "Output": "include/linux/kho_radix_tree.h=59=int kho_radix_walk_tree(struct kho_radix_tree *tree,\ninclude/linux/kho_radix_tree.h-60-\t\t\tconst struct kho_radix_walk_cb *cb, void *data);\ninclude/linux/kho_radix_tree.h:61:int kho_radix_init_tree(struct kho_radix_tree *tree, struct kho_radix_node *root);\ninclude/linux/kho_radix_tree.h-62-void kho_radix_destroy_tree(struct kho_radix_tree *tree);\n--\ninclude/linux/kho_radix_tree.h=74=static inline int kho_radix_walk_tree(struct kho_radix_tree *tree,\n--\ninclude/linux/kho_radix_tree.h-79-\ninclude/linux/kho_radix_tree.h:80:static inline int kho_radix_init_tree(struct kho_radix_tree *tree,\ninclude/linux/kho_radix_tree.h-81-\t\t\t\t      struct kho_radix_node *root)\n--\nkernel/liveupdate/kexec_handover.c=321=static void __kho_radix_destroy_tree(struct kho_radix_node *root,\n--\nkernel/liveupdate/kexec_handover.c-340-/**\nkernel/liveupdate/kexec_handover.c:341: * kho_radix_init_tree - initialize the radix tree.\nkernel/liveupdate/kexec_handover.c-342- * @tree:   the tree to initialize.\n--\nkernel/liveupdate/kexec_handover.c-350- */\nkernel/liveupdate/kexec_handover.c:351:int kho_radix_init_tree(struct kho_radix_tree *tree, struct kho_radix_node *root)\nkernel/liveupdate/kexec_handover.c-352-{\n--\nkernel/liveupdate/kexec_handover.c-361-}\nkernel/liveupdate/kexec_handover.c:362:EXPORT_SYMBOL_GPL(kho_radix_init_tree);\nkernel/liveupdate/kexec_handover.c-363-\n--\nkernel/liveupdate/kexec_handover.c=981=static void __init kho_extend_scratch(void)\n--\nkernel/liveupdate/kexec_handover.c-994-\nkernel/liveupdate/kexec_handover.c:995:\terr = kho_radix_init_tree(\u0026busy_blocks, NULL);\nkernel/liveupdate/kexec_handover.c-996-\tif (err)\n--\nkernel/liveupdate/kexec_handover.c=1805=static __init int kho_init(void)\n--\nkernel/liveupdate/kexec_handover.c-1813-\nkernel/liveupdate/kexec_handover.c:1814:\terr = kho_radix_init_tree(tree, NULL);\nkernel/liveupdate/kexec_handover.c-1815-\tif (err)\n--\nkernel/liveupdate/kexec_handover.c=1887=void __init kho_memory_init_early(void)\n--\nkernel/liveupdate/kexec_handover.c-1904-\t */\nkernel/liveupdate/kexec_handover.c:1905:\tif (kho_radix_init_tree(\u0026kho_in.radix_tree, kho_get_mem_map(fdt))) {\nkernel/liveupdate/kexec_handover.c-1906-\t\t/*\n"
}

23/2 2026/08/02 10:48 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 50306
							output: 19
							thoughts: 81
						

Thoughts:
Here's my take on the thought process:

**Diving into Radix Tree Initialization and Folio Preservation**

Okay, so I'm looking at `kho_radix_init_tree` and it's hitting me that this is largely a boot-time operation. Seems pretty straightforward there. But then my mind jumps to `kho_radix_add_key` and `kho_radix_del_key` – I recall these being called from somewhere a bit more dynamic: `kho_preserve_folio` and `kho_unpreserve_folio`. That's where things get interesting. I need to understand how folio preservation and unpreservation interact with the radix tree. Let's dig into those `kho_preserve_folio` and `kho_unpreserve_folio` functions next. They are clearly the key to understanding how this whole mechanism functions.



24/2 2026/08/02 10:48 tool grepper
0m Args:
{
  "Expression": "kho_preserve_folio"
}

Results:
{
  "Output": "include/linux/kexec_handover.h=18=bool is_kho_boot(void);\ninclude/linux/kexec_handover.h-19-\ninclude/linux/kexec_handover.h:20:int kho_preserve_folio(struct folio *folio);\ninclude/linux/kexec_handover.h-21-void kho_unpreserve_folio(struct folio *folio);\n--\ninclude/linux/kexec_handover.h=57=static inline bool is_kho_boot(void)\n--\ninclude/linux/kexec_handover.h-61-\ninclude/linux/kexec_handover.h:62:static inline int kho_preserve_folio(struct folio *folio)\ninclude/linux/kexec_handover.h-63-{\n--\nkernel/liveupdate/kexec_handover.c=1123=EXPORT_SYMBOL_GPL(kho_remove_subtree);\n--\nkernel/liveupdate/kexec_handover.c-1125-/**\nkernel/liveupdate/kexec_handover.c:1126: * kho_preserve_folio - preserve a folio across kexec.\nkernel/liveupdate/kexec_handover.c-1127- * @folio: folio to preserve.\n--\nkernel/liveupdate/kexec_handover.c-1133- */\nkernel/liveupdate/kexec_handover.c:1134:int kho_preserve_folio(struct folio *folio)\nkernel/liveupdate/kexec_handover.c-1135-{\n--\nkernel/liveupdate/kexec_handover.c-1146-}\nkernel/liveupdate/kexec_handover.c:1147:EXPORT_SYMBOL_GPL(kho_preserve_folio);\nkernel/liveupdate/kexec_handover.c-1148-\n--\nkernel/liveupdate/kexec_handover.c-1153- * Instructs KHO to unpreserve a folio that was preserved by\nkernel/liveupdate/kexec_handover.c:1154: * kho_preserve_folio() before. The provided @folio (pfn and order)\nkernel/liveupdate/kexec_handover.c-1155- * must exactly match a previously preserved folio.\n--\nkernel/liveupdate/kexec_handover.c=1532=void *kho_alloc_preserve(size_t size)\n--\nkernel/liveupdate/kexec_handover.c-1547-\nkernel/liveupdate/kexec_handover.c:1548:\tret = kho_preserve_folio(folio);\nkernel/liveupdate/kexec_handover.c-1549-\tif (ret) {\n--\nlib/test_kho.c=54=static int kho_test_preserve_data(struct kho_test_state *state)\n--\nlib/test_kho.c-75-\t\tfolios_info[i] = virt_to_phys(folio_address(folio)) | order;\nlib/test_kho.c:76:\t\terr = kho_preserve_folio(folio);\nlib/test_kho.c-77-\t\tif (err)\n--\nlib/test_kho.c=124=static int kho_test_preserve(struct kho_test_state *state)\n--\nlib/test_kho.c-133-\nlib/test_kho.c:134:\terr = kho_preserve_folio(state-\u003efdt);\nlib/test_kho.c-135-\tif (err)\n--\nmm/memfd_luo.c=87=static int memfd_luo_preserve_folios(struct file *file,\n--\nmm/memfd_luo.c-151-\nmm/memfd_luo.c:152:\t\terr = kho_preserve_folio(folio);\nmm/memfd_luo.c-153-\t\tif (err)\n"
}

25/2 2026/08/02 10:48 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 51562
							output: 20
							thoughts: 108
						

Thoughts:
**Tracing the Preservation Logic**

Okay, let's break this down. It looks like my initial entry point for folio preservation is `kho_preserve_folio`. I see that it's being reached through a chain: `memfd_luo_preserve_folios` calls `kho_preserve_folio`, and `memfd_luo_preserve` calls `memfd_luo_preserve_folios`. Now, `memfd_luo_preserve` is familiar; it's the `.preserve` callback associated with the `memfd_luo_file_ops` structure. That tells me this is all tied to the Live Update Object (LUO) and memory file descriptors. The real question is how we're actually *using* that `.preserve` callback. Where is it being called? I know this goes into `kernel/liveupdate/luo_file.c`, which makes me think it must be in `luo_file_preserve` or some similarly-named function. Let's dig into `luo_file_preserve` and see what it is actually doing.



26/2 2026/08/02 10:48 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "luo_file_preserve"
}

Results:
{
  "error": "no matches"
}

27/2 2026/08/02 10:48 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 51937
							output: 16
							thoughts: 21
						

Thoughts:
Okay, let's dive into this. Here's how I'd approach this task, as if these were my internal thoughts:

**Investigating `.preserve` in the Live Update Process**

Right, so we're looking at something related to live updates and file preservation, and my gut feeling says the place to start is `kernel/liveupdate/luo_file.c`. That file is the most likely location for code handling the low-level details of updating files during a live update, and that's precisely where I'd expect to find logic around what gets preserved and how. I'm thinking that's the canonical place for the `.preserve` functionality. I'll need to confirm that that's the case by taking a look at the content. I'll search for the relevant code within that file to see how `.preserve` is handled, and determine what the role of the file extension is in the overall live update process.



28/2 2026/08/02 10:48 tool grepper
0m Args:
{
  "Expression": "\\.preserve"
}

Results:
{
  "Output": "arch/s390/boot/Makefile=42=obj-all := $(obj-y) piggy.o syms.o\narch/s390/boot/Makefile-43-\narch/s390/boot/Makefile:44:targets\t:= bzImage section_cmp.boot.data section_cmp.boot.preserved.data $(obj-y)\narch/s390/boot/Makefile-45-targets\t+= vmlinux.lds vmlinux vmlinux.bin vmlinux.bin.gz vmlinux.bin.bz2\n--\narch/s390/boot/Makefile=66=endef\narch/s390/boot/Makefile-67-\narch/s390/boot/Makefile:68:$(obj)/bzImage: $(obj)/vmlinux $(obj)/section_cmp.boot.data $(obj)/section_cmp.boot.preserved.data FORCE\narch/s390/boot/Makefile-69-\t$(call if_changed,objcopy)\n--\narch/s390/boot/startup.c=216=static void copy_bootdata(void)\n--\narch/s390/boot/startup.c-221-\tif (__boot_data_preserved_end - __boot_data_preserved_start != vmlinux.bootdata_preserved_size)\narch/s390/boot/startup.c:222:\t\tboot_panic(\".boot.preserved.data section size mismatch\\n\");\narch/s390/boot/startup.c-223-\tmemcpy((void *)vmlinux.bootdata_preserved_off, __boot_data_preserved_start, vmlinux.bootdata_preserved_size);\n--\narch/s390/include/asm/sections.h-19-/*\narch/s390/include/asm/sections.h:20: * .boot.preserved.data is similar to .boot.data, but it is not part of the\narch/s390/include/asm/sections.h-21- * .init section and thus will be preserved for later use in the decompressed\n--\narch/s390/include/asm/sections.h-23- */\narch/s390/include/asm/sections.h:24:#define __bootdata_preserved(var) __section(\".boot.preserved.data.\" #var) var\narch/s390/include/asm/sections.h-25-\n--\narch/s390/include/asm/vmlinux.lds.h-22-/*\narch/s390/include/asm/vmlinux.lds.h:23: * .boot.preserved.data is similar to .boot.data, but it is not part of the\narch/s390/include/asm/vmlinux.lds.h-24- * .init section and thus will be preserved for later use in the decompressed\n--\narch/s390/include/asm/vmlinux.lds.h-28-\t. = ALIGN(PAGE_SIZE);\t\t\t\t\t\t\\\narch/s390/include/asm/vmlinux.lds.h:29:\t.boot.preserved.data : {\t\t\t\t\t\\\narch/s390/include/asm/vmlinux.lds.h-30-\t\t__boot_data_preserved_start = .;\t\t\t\\\narch/s390/include/asm/vmlinux.lds.h:31:\t\t*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.boot.preserved.data*))) \\\narch/s390/include/asm/vmlinux.lds.h-32-\t\t__boot_data_preserved_end = .;\t\t\t\t\\\n--\ndrivers/mailbox/pcc.c=268=static int pcc_mbox_error_check_and_clear(struct pcc_chan_info *pchan)\n--\ndrivers/mailbox/pcc.c-277-\tif (val \u0026 pchan-\u003eerror.status_mask) {\ndrivers/mailbox/pcc.c:278:\t\tval \u0026= pchan-\u003eerror.preserve_mask;\ndrivers/mailbox/pcc.c-279-\t\tpcc_chan_reg_write(\u0026pchan-\u003eerror, val);\n--\nfs/ocfs2/ioctl.c=853=long ocfs2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)\n--\nfs/ocfs2/ioctl.c-917-\t\tnew_path = (const char __user *)(unsigned long)args.new_path;\nfs/ocfs2/ioctl.c:918:\t\tpreserve = (args.preserve != 0);\nfs/ocfs2/ioctl.c-919-\n--\nfs/ocfs2/ioctl.c=965=long ocfs2_compat_ioctl(struct file *file, unsigned cmd, unsigned long arg)\n--\nfs/ocfs2/ioctl.c-984-\t\t\treturn -EFAULT;\nfs/ocfs2/ioctl.c:985:\t\tpreserve = (args.preserve != 0);\nfs/ocfs2/ioctl.c-986-\n--\ninclude/linux/kho/abi/luo.h=152=struct luo_flb_header_ser {\n--\ninclude/linux/kho/abi/luo.h-160- *           corresponding \u0026struct liveupdate_flb handler in the new kernel.\ninclude/linux/kho/abi/luo.h:161: * @data:    The opaque u64 handle returned by the FLB's .preserve() operation\ninclude/linux/kho/abi/luo.h-162- *           in the old kernel. This handle encapsulates the entire state needed\n--\ninclude/linux/liveupdate.h=23=struct file;\n--\ninclude/linux/liveupdate.h-38- * @private_data:     Private data for the file used to hold runtime state that\ninclude/linux/liveupdate.h:39: *                    is not preserved. Set by the handler's .preserve()\ninclude/linux/liveupdate.h-40- *                    callback, and must be freed in the handler's\n--\ninclude/linux/liveupdate.h=101=struct liveupdate_file_handler {\n--\ninclude/linux/liveupdate.h-118- * @flb:       The global FLB instance for which this call is performed.\ninclude/linux/liveupdate.h:119: * @data:      For .preserve():    [OUT] The callback sets this field.\ninclude/linux/liveupdate.h:120: *             For .unpreserve():  [IN]  The handle from .preserve().\ninclude/linux/liveupdate.h:121: *             For .retrieve():    [IN]  The handle from .preserve().\ninclude/linux/liveupdate.h:122: * @obj:       For .preserve():    [OUT] Sets this to the live object.\ninclude/linux/liveupdate.h-123- *             For .retrieve():    [OUT] Sets this to the live object.\n--\ninclude/linux/liveupdate.h=157=struct liveupdate_flb_ops {\n--\ninclude/linux/liveupdate.h-169- *             first/last file.\ninclude/linux/liveupdate.h:170: * @data:      The opaque u64 handle returned by .preserve() or passed to\ninclude/linux/liveupdate.h-171- *             .retrieve().\ninclude/linux/liveupdate.h:172: * @obj:       The live kernel object returned by .preserve() or .retrieve().\ninclude/linux/liveupdate.h-173- * @lock:      A mutex that protects all fields within this structure, providing\n--\nkernel/liveupdate/luo_file.c-30- *     actual reboot transition.\nkernel/liveupdate/luo_file.c:31: *   - unpreserve(): Cleans up any resources allocated by .preserve(), called\nkernel/liveupdate/luo_file.c-32- *     if the preservation process is aborted before the reboot (i.e. session is\n--\nkernel/liveupdate/luo_file.c-47- *    via an ioctl. For each file, luo_preserve_file() finds a compatible\nkernel/liveupdate/luo_file.c:48: *    handler, calls its .preserve() operation, and creates an internal \u0026struct\nkernel/liveupdate/luo_file.c-49- *    luo_file to track the live state.\n--\nkernel/liveupdate/luo_file.c=119=static DEFINE_XARRAY(luo_preserved_files);\n--\nkernel/liveupdate/luo_file.c-132- * @private_data:  Pointer to the private data for the file used to hold runtime\nkernel/liveupdate/luo_file.c:133: *                 state that is not preserved. Set by the handler's .preserve()\nkernel/liveupdate/luo_file.c-134- *                 callback, and must be freed in the handler's .unpreserve()\n--\nkernel/liveupdate/luo_file.c-151- * to link a 'struct file' to its corresponding handler, a user-provided token,\nkernel/liveupdate/luo_file.c:152: * and the serialized state handle returned by the handler's .preserve()\nkernel/liveupdate/luo_file.c-153- * operation.\n--\nkernel/liveupdate/luo_file.c=178=static bool luo_token_is_used(struct luo_file_set *file_set, u64 token)\n--\nkernel/liveupdate/luo_file.c-209- *    compatible with the given @fd.\nkernel/liveupdate/luo_file.c:210: * 4. Calls the handler's .preserve() operation, which saves the file's state\nkernel/liveupdate/luo_file.c-211- *    and returns an opaque private data handle.\n--\nkernel/liveupdate/luo_file.c-227- *         -ENOMEM on memory allocation failure.\nkernel/liveupdate/luo_file.c:228: *         Other erros might be returned by .preserve().\nkernel/liveupdate/luo_file.c-229- */\n--\nkernel/liveupdate/luo_flb.c-28- *\nkernel/liveupdate/luo_flb.c:29: *     - .preserve(): Called for the first file. Saves global state.\nkernel/liveupdate/luo_flb.c-30- *     - .unpreserve(): Called for the last file (if aborted pre-reboot).\n--\nkernel/liveupdate/luo_flb.c=207=void liveupdate_flb_put_incoming(struct liveupdate_flb *flb)\n--\nkernel/liveupdate/luo_flb.c-239- * handler. It increments the reference count for each FLB. If the count becomes\nkernel/liveupdate/luo_flb.c:240: * 1, it triggers the FLB's .preserve() callback to save the global state.\nkernel/liveupdate/luo_flb.c-241- *\nkernel/liveupdate/luo_flb.c:242: * This operation is atomic. If any FLB's .preserve() op fails, it will roll\nkernel/liveupdate/luo_flb.c-243- * back by calling .unpreserve() on any FLBs that were successfully preserved\n--\nkernel/liveupdate/luo_flb.c=503=int liveupdate_flb_get_incoming(struct liveupdate_flb *flb, void **objp)\n--\nkernel/liveupdate/luo_flb.c-533- * This function assumes the object has already been created by the FLB's\nkernel/liveupdate/luo_flb.c:534: * .preserve() callback, which is triggered when the first dependent file\nkernel/liveupdate/luo_flb.c-535- * is preserved.\n--\nlib/tests/liveupdate.c=79=static const struct liveupdate_flb_ops test_flb_ops = {\nlib/tests/liveupdate.c:80:\t.preserve\t= test_flb_preserve,\nlib/tests/liveupdate.c-81-\t.unpreserve\t= test_flb_unpreserve,\n--\nmm/memfd_luo.c=593=static const struct liveupdate_file_ops memfd_luo_file_ops = {\n--\nmm/memfd_luo.c-596-\t.retrieve = memfd_luo_retrieve,\nmm/memfd_luo.c:597:\t.preserve = memfd_luo_preserve,\nmm/memfd_luo.c-598-\t.unpreserve = memfd_luo_unpreserve,\n"
}

29/2 2026/08/02 10:48 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 55327
							output: 20
							thoughts: 62
						

Thoughts:
Here's my summary, as if I were thinking it:

**Tracing the `memfd_luo_preserve` Function**

Alright, so I need to track down how `memfd_luo_preserve` is being used. From what I'm seeing, the function call originates in `kernel/liveupdate/luo_file.c`, through a function named `luo_preserve_file`, or something similar to it. This suggests that the live update functionality is using `memfd_luo_preserve` to manage some sort of file preservation. My next move is clear: I need to scrutinize that `luo_preserve_file` function (or whichever variation is in play) to understand the context of this `memfd_luo_preserve` invocation. I'll need to see what information is being passed to it and how it's being used within the file preservation logic.



30/2 2026/08/02 10:48 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "luo_file_preserve"
}

Results:
{
  "error": "no matches"
}

31/2 2026/08/02 10:48 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 55616
							output: 32
							thoughts: 21
						

Thoughts:
Here's my thought process, as it stands:

**Locating Potential Preservation Logic**

Okay, I need to investigate how file preservation is handled during a Live Update operation. My initial instinct is to go straight to `kernel/liveupdate/luo_file.c`. That's where I'd expect to find the core logic related to file manipulation during a live update, and `preserve` is the keyword to search for. I'm assuming that the developers would have used a clear, identifiable function or variable name related to preserving files – this is standard practice.



32/2 2026/08/02 10:48 tool grepper
0m Args:
{
  "Expression": "preserve",
  "PathPrefix": "kernel/liveupdate/luo_file.c"
}

Results:
{
  "Output": "kernel/liveupdate/luo_file.c-10- *\nkernel/liveupdate/luo_file.c:11: * LUO provides the infrastructure to preserve specific, stateful file\nkernel/liveupdate/luo_file.c-12- * descriptors across a kexec-based live update. The primary goal is to allow\n--\nkernel/liveupdate/luo_file.c-16- * The framework is built around a callback-based handler model and a well-\nkernel/liveupdate/luo_file.c:17: * defined lifecycle for each preserved file.\nkernel/liveupdate/luo_file.c-18- *\n--\nkernel/liveupdate/luo_file.c-24- *\nkernel/liveupdate/luo_file.c:25: *   - can_preserve(): A lightweight check to determine if the handler is\nkernel/liveupdate/luo_file.c-26- *     compatible with a given 'struct file'.\nkernel/liveupdate/luo_file.c:27: *   - preserve(): The heavyweight operation that saves the file's state and\nkernel/liveupdate/luo_file.c-28- *     returns an opaque u64 handle. This is typically performed while the\n--\nkernel/liveupdate/luo_file.c-30- *     actual reboot transition.\nkernel/liveupdate/luo_file.c:31: *   - unpreserve(): Cleans up any resources allocated by .preserve(), called\nkernel/liveupdate/luo_file.c-32- *     if the preservation process is aborted before the reboot (i.e. session is\n--\nkernel/liveupdate/luo_file.c-38- *     is aborted after the freeze phase.\nkernel/liveupdate/luo_file.c:39: *   - retrieve(): Reconstructs the file in the new kernel from the preserved\nkernel/liveupdate/luo_file.c-40- *     handle.\n--\nkernel/liveupdate/luo_file.c-45- *\nkernel/liveupdate/luo_file.c:46: * 1. Preserve (Normal Operation): A userspace agent preserves files one by one\nkernel/liveupdate/luo_file.c:47: *    via an ioctl. For each file, luo_preserve_file() finds a compatible\nkernel/liveupdate/luo_file.c:48: *    handler, calls its .preserve() operation, and creates an internal \u0026struct\nkernel/liveupdate/luo_file.c-49- *    luo_file to track the live state.\n--\nkernel/liveupdate/luo_file.c-51- * 2. Freeze (Pre-Reboot): Just before the kexec, luo_file_freeze() is called.\nkernel/liveupdate/luo_file.c:52: *    It iterates through all preserved files, calls their respective .freeze()\nkernel/liveupdate/luo_file.c-53- *    operation, and serializes their final metadata (compatible string, token,\n--\nkernel/liveupdate/luo_file.c-77- *    descriptor), the session's release handler calls\nkernel/liveupdate/luo_file.c:78: *    luo_file_unpreserve_files(). This invokes the .unpreserve() callback on\nkernel/liveupdate/luo_file.c:79: *    all preserved files, ensuring all allocated resources are cleaned up and\nkernel/liveupdate/luo_file.c-80- *    returning the system to a clean state.\n--\nkernel/liveupdate/luo_file.c=116=static LIST_HEAD(luo_file_handler_list);\nkernel/liveupdate/luo_file.c-117-\nkernel/liveupdate/luo_file.c:118:/* Keep track of files being preserved by LUO */\nkernel/liveupdate/luo_file.c:119:static DEFINE_XARRAY(luo_preserved_files);\nkernel/liveupdate/luo_file.c-120-\nkernel/liveupdate/luo_file.c-121-/**\nkernel/liveupdate/luo_file.c:122: * struct luo_file - Represents a single preserved file instance.\nkernel/liveupdate/luo_file.c-123- * @fh:            Pointer to the \u0026struct liveupdate_file_handler that manages\nkernel/liveupdate/luo_file.c-124- *                 this type of file.\nkernel/liveupdate/luo_file.c:125: * @file:          Pointer to the kernel's \u0026struct file that is being preserved.\nkernel/liveupdate/luo_file.c-126- *                 This is NULL in the new kernel until the file is successfully\n--\nkernel/liveupdate/luo_file.c-132- * @private_data:  Pointer to the private data for the file used to hold runtime\nkernel/liveupdate/luo_file.c:133: *                 state that is not preserved. Set by the handler's .preserve()\nkernel/liveupdate/luo_file.c:134: *                 callback, and must be freed in the handler's .unpreserve()\nkernel/liveupdate/luo_file.c-135- *                 callback.\n--\nkernel/liveupdate/luo_file.c-145- * @list:          The list_head linking this instance into its parent\nkernel/liveupdate/luo_file.c:146: *                 file_set's list of preserved files.\nkernel/liveupdate/luo_file.c-147- * @token:         The user-provided unique token used to identify this file.\n--\nkernel/liveupdate/luo_file.c-149- * This structure is the core in-kernel representation of a single file being\nkernel/liveupdate/luo_file.c:150: * managed through a live update. An instance is created by luo_preserve_file()\nkernel/liveupdate/luo_file.c-151- * to link a 'struct file' to its corresponding handler, a user-provided token,\nkernel/liveupdate/luo_file.c:152: * and the serialized state handle returned by the handler's .preserve()\nkernel/liveupdate/luo_file.c-153- * operation.\n--\nkernel/liveupdate/luo_file.c=178=static bool luo_token_is_used(struct luo_file_set *file_set, u64 token)\n--\nkernel/liveupdate/luo_file.c-190-/**\nkernel/liveupdate/luo_file.c:191: * luo_preserve_file - Initiate the preservation of a file descriptor.\nkernel/liveupdate/luo_file.c:192: * @file_set: The file_set to which the preserved file will be added.\nkernel/liveupdate/luo_file.c-193- * @token:    A unique, user-provided identifier for the file.\nkernel/liveupdate/luo_file.c:194: * @fd:       The file descriptor to be preserved.\nkernel/liveupdate/luo_file.c-195- *\n--\nkernel/liveupdate/luo_file.c-198- * a co-owner of the file. This reference is held until the file is either\nkernel/liveupdate/luo_file.c:199: * unpreserved or successfully finished in the next kernel, preventing the file\nkernel/liveupdate/luo_file.c-200- * from being prematurely destroyed.\n--\nkernel/liveupdate/luo_file.c-207- *    (allocates if needed).\nkernel/liveupdate/luo_file.c:208: * 3. Iterates through registered handlers, calling can_preserve() to find one\nkernel/liveupdate/luo_file.c-209- *    compatible with the given @fd.\nkernel/liveupdate/luo_file.c:210: * 4. Calls the handler's .preserve() operation, which saves the file's state\nkernel/liveupdate/luo_file.c-211- *    and returns an opaque private data handle.\n--\nkernel/liveupdate/luo_file.c-214- * On success, LUO takes a reference to the 'struct file' and considers it\nkernel/liveupdate/luo_file.c:215: * under its management until it is unpreserved or finished.\nkernel/liveupdate/luo_file.c-216- *\n--\nkernel/liveupdate/luo_file.c-222- *         -EEXIST if the token is already used.\nkernel/liveupdate/luo_file.c:223: *         -EBUSY if the file descriptor is already preserved by another session.\nkernel/liveupdate/luo_file.c-224- *         -EBADF if the file descriptor is invalid.\n--\nkernel/liveupdate/luo_file.c-227- *         -ENOMEM on memory allocation failure.\nkernel/liveupdate/luo_file.c:228: *         Other erros might be returned by .preserve().\nkernel/liveupdate/luo_file.c-229- */\nkernel/liveupdate/luo_file.c:230:int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)\nkernel/liveupdate/luo_file.c-231-{\n--\nkernel/liveupdate/luo_file.c-253-\tlist_private_for_each_entry(fh, \u0026luo_file_handler_list, list) {\nkernel/liveupdate/luo_file.c:254:\t\tif (fh-\u003eops-\u003ecan_preserve(fh, file)) {\nkernel/liveupdate/luo_file.c-255-\t\t\tif (try_module_get(fh-\u003eops-\u003eowner))\n--\nkernel/liveupdate/luo_file.c-265-\nkernel/liveupdate/luo_file.c:266:\terr = xa_insert(\u0026luo_preserved_files, luo_get_id(fh, file),\nkernel/liveupdate/luo_file.c-267-\t\t\tfile, GFP_KERNEL);\n--\nkernel/liveupdate/luo_file.c-270-\nkernel/liveupdate/luo_file.c:271:\terr = luo_flb_file_preserve(fh);\nkernel/liveupdate/luo_file.c-272-\tif (err)\n--\nkernel/liveupdate/luo_file.c-277-\t\terr = -ENOMEM;\nkernel/liveupdate/luo_file.c:278:\t\tgoto err_flb_unpreserve;\nkernel/liveupdate/luo_file.c-279-\t}\n--\nkernel/liveupdate/luo_file.c-287-\targs.file = file;\nkernel/liveupdate/luo_file.c:288:\terr = fh-\u003eops-\u003epreserve(\u0026args);\nkernel/liveupdate/luo_file.c-289-\tif (err)\n--\nkernel/liveupdate/luo_file.c-300-\tkfree(luo_file);\nkernel/liveupdate/luo_file.c:301:err_flb_unpreserve:\nkernel/liveupdate/luo_file.c:302:\tluo_flb_file_unpreserve(fh);\nkernel/liveupdate/luo_file.c-303-err_erase_xa:\nkernel/liveupdate/luo_file.c:304:\txa_erase(\u0026luo_preserved_files, luo_get_id(fh, file));\nkernel/liveupdate/luo_file.c-305-err_module_put:\n--\nkernel/liveupdate/luo_file.c-315-/**\nkernel/liveupdate/luo_file.c:316: * luo_file_unpreserve_files - Unpreserves all files from a file_set.\nkernel/liveupdate/luo_file.c-317- * @file_set: The files to be cleaned up.\n--\nkernel/liveupdate/luo_file.c-322- * For each file, it performs the following cleanup actions:\nkernel/liveupdate/luo_file.c:323: *   1. Calls the handler's .unpreserve() callback to allow the handler to\nkernel/liveupdate/luo_file.c-324- *      release any resources it allocated.\n--\nkernel/liveupdate/luo_file.c-326- *   3. Releases the reference to the 'struct file' that was taken by\nkernel/liveupdate/luo_file.c:327: *      luo_preserve_file() via fput(), returning ownership.\nkernel/liveupdate/luo_file.c-328- *   4. Frees the memory associated with the internal 'struct luo_file'.\nkernel/liveupdate/luo_file.c-329- *\nkernel/liveupdate/luo_file.c:330: * After all individual files are unpreserved, it frees the contiguous memory\nkernel/liveupdate/luo_file.c-331- * block that was allocated to hold their serialization data.\nkernel/liveupdate/luo_file.c-332- */\nkernel/liveupdate/luo_file.c:333:void luo_file_unpreserve_files(struct luo_file_set *file_set)\nkernel/liveupdate/luo_file.c-334-{\n--\nkernel/liveupdate/luo_file.c-346-\t\targs.private_data = luo_file-\u003eprivate_data;\nkernel/liveupdate/luo_file.c:347:\t\tluo_file-\u003efh-\u003eops-\u003eunpreserve(\u0026args);\nkernel/liveupdate/luo_file.c:348:\t\tluo_flb_file_unpreserve(luo_file-\u003efh);\nkernel/liveupdate/luo_file.c-349-\nkernel/liveupdate/luo_file.c:350:\t\txa_erase(\u0026luo_preserved_files,\nkernel/liveupdate/luo_file.c-351-\t\t\t luo_get_id(luo_file-\u003efh, luo_file-\u003efile));\n--\nkernel/liveupdate/luo_file.c=406=static void __luo_file_unfreeze(struct luo_file_set *file_set,\n--\nkernel/liveupdate/luo_file.c-422-/**\nkernel/liveupdate/luo_file.c:423: * luo_file_freeze - Freezes all preserved files and serializes their metadata.\nkernel/liveupdate/luo_file.c-424- * @file_set:     The file_set whose files are to be frozen.\n--\nkernel/liveupdate/luo_file.c-428- * kernel transitions to the new image via kexec. Its purpose is to perform the\nkernel/liveupdate/luo_file.c:429: * final preparation and serialization of all preserved files in the file_set.\nkernel/liveupdate/luo_file.c-430- *\nkernel/liveupdate/luo_file.c:431: * It iterates through each preserved file in FIFO order (the order of\nkernel/liveupdate/luo_file.c-432- * preservation) and performs two main actions:\n--\nkernel/liveupdate/luo_file.c=517=void luo_file_unfreeze(struct luo_file_set *file_set,\n--\nkernel/liveupdate/luo_file.c-527-/**\nkernel/liveupdate/luo_file.c:528: * luo_retrieve_file - Restores a preserved file from a file_set by its token.\nkernel/liveupdate/luo_file.c-529- * @file_set: The file_set from which to retrieve the file.\n--\nkernel/liveupdate/luo_file.c=552=int luo_retrieve_file(struct luo_file_set *file_set, u64 token,\n--\nkernel/liveupdate/luo_file.c-601-\nkernel/liveupdate/luo_file.c:602:\tWARN_ON(xa_insert(\u0026luo_preserved_files,\nkernel/liveupdate/luo_file.c-603-\t\t\t  luo_get_id(luo_file-\u003efh, luo_file-\u003efile),\n--\nkernel/liveupdate/luo_file.c=632=static void luo_file_finish_one(struct luo_file_set *file_set,\n--\nkernel/liveupdate/luo_file.c-653- * the new kernel. It should be called after all necessary files have been\nkernel/liveupdate/luo_file.c:654: * retrieved and the userspace agent is ready to release the preserved state.\nkernel/liveupdate/luo_file.c-655- *\n--\nkernel/liveupdate/luo_file.c=680=int luo_file_finish(struct luo_file_set *file_set)\n--\nkernel/liveupdate/luo_file.c-701-\t\tif (luo_file-\u003efile) {\nkernel/liveupdate/luo_file.c:702:\t\t\txa_erase(\u0026luo_preserved_files,\nkernel/liveupdate/luo_file.c-703-\t\t\t\t luo_get_id(luo_file-\u003efh, luo_file-\u003efile));\n--\nkernel/liveupdate/luo_file.c=719=static int luo_file_deserialize_one(struct luo_file_set *file_set,\n--\nkernel/liveupdate/luo_file.c-759-/**\nkernel/liveupdate/luo_file.c:760: * luo_file_deserialize - Reconstructs the list of preserved files in the new kernel.\nkernel/liveupdate/luo_file.c-761- * @file_set:     The incoming file_set to fill with deserialized data.\n--\nkernel/liveupdate/luo_file.c=784=int luo_file_deserialize(struct luo_file_set *file_set,\n--\nkernel/liveupdate/luo_file.c-806-\t *\nkernel/liveupdate/luo_file.c:807:\t * A partial failure leaves the preserved state inconsistent.\nkernel/liveupdate/luo_file.c-808-\t * Implementing a safe \"undo\" to unwind complex dependencies (sessions,\n--\nkernel/liveupdate/luo_file.c=874=int liveupdate_register_file_handler(struct liveupdate_file_handler *fh)\n--\nkernel/liveupdate/luo_file.c-882-\t/* Sanity check that all required callbacks are set */\nkernel/liveupdate/luo_file.c:883:\tif (!fh-\u003eops-\u003epreserve || !fh-\u003eops-\u003eunpreserve || !fh-\u003eops-\u003eretrieve ||\nkernel/liveupdate/luo_file.c:884:\t    !fh-\u003eops-\u003efinish || !fh-\u003eops-\u003ecan_preserve) {\nkernel/liveupdate/luo_file.c-885-\t\treturn -EINVAL;\n"
}

33/2 2026/08/02 10:48 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 60555
							output: 20
							thoughts: 29
						

Thoughts:
Here's my analysis of the situation:

**Investigating the `luo_preserve_file` Function**

Okay, so I see that `luo_preserve_file` is the primary function responsible for invoking the `.preserve` method. This makes sense; it's the gateway for file preservation within this system. Now, the critical question is: what is calling `luo_preserve_file`? Understanding the call stack, the upstream code that's triggering this function, is vital. Knowing the callers will expose the contexts in which this preservation mechanism is being employed, the workflows it's a part of, and, potentially, any dependencies it has. Tracing back from `luo_preserve_file` to its parent functions will provide key insights into its usage and overall architecture. I need to figure out who's telling `luo_preserve_file` to preserve these files and why.



34/2 2026/08/02 10:48 tool grepper
0m Args:
{
  "Expression": "luo_preserve_file"
}

Results:
{
  "Output": "kernel/liveupdate/luo_file.c-46- * 1. Preserve (Normal Operation): A userspace agent preserves files one by one\nkernel/liveupdate/luo_file.c:47: *    via an ioctl. For each file, luo_preserve_file() finds a compatible\nkernel/liveupdate/luo_file.c-48- *    handler, calls its .preserve() operation, and creates an internal \u0026struct\n--\nkernel/liveupdate/luo_file.c=119=static DEFINE_XARRAY(luo_preserved_files);\n--\nkernel/liveupdate/luo_file.c-149- * This structure is the core in-kernel representation of a single file being\nkernel/liveupdate/luo_file.c:150: * managed through a live update. An instance is created by luo_preserve_file()\nkernel/liveupdate/luo_file.c-151- * to link a 'struct file' to its corresponding handler, a user-provided token,\n--\nkernel/liveupdate/luo_file.c=178=static bool luo_token_is_used(struct luo_file_set *file_set, u64 token)\n--\nkernel/liveupdate/luo_file.c-190-/**\nkernel/liveupdate/luo_file.c:191: * luo_preserve_file - Initiate the preservation of a file descriptor.\nkernel/liveupdate/luo_file.c-192- * @file_set: The file_set to which the preserved file will be added.\n--\nkernel/liveupdate/luo_file.c-229- */\nkernel/liveupdate/luo_file.c:230:int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)\nkernel/liveupdate/luo_file.c-231-{\n--\nkernel/liveupdate/luo_file.c-326- *   3. Releases the reference to the 'struct file' that was taken by\nkernel/liveupdate/luo_file.c:327: *      luo_preserve_file() via fput(), returning ownership.\nkernel/liveupdate/luo_file.c-328- *   4. Frees the memory associated with the internal 'struct luo_file'.\n--\nkernel/liveupdate/luo_flb.c=207=void liveupdate_flb_put_incoming(struct liveupdate_flb *flb)\n--\nkernel/liveupdate/luo_flb.c-245- *\nkernel/liveupdate/luo_flb.c:246: * Context: Called from luo_preserve_file()\nkernel/liveupdate/luo_flb.c-247- * Return: 0 on success, or a negative errno on failure.\n--\nkernel/liveupdate/luo_internal.h=85=int luo_session_deserialize(void);\nkernel/liveupdate/luo_internal.h-86-\nkernel/liveupdate/luo_internal.h:87:int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd);\nkernel/liveupdate/luo_internal.h-88-void luo_file_unpreserve_files(struct luo_file_set *file_set);\n--\nkernel/liveupdate/luo_session.c=264=static int luo_session_preserve_fd(struct luo_session *session,\n--\nkernel/liveupdate/luo_session.c-270-\tguard(mutex)(\u0026session-\u003emutex);\nkernel/liveupdate/luo_session.c:271:\terr = luo_preserve_file(\u0026session-\u003efile_set, argp-\u003etoken, argp-\u003efd);\nkernel/liveupdate/luo_session.c-272-\tif (err)\n"
}

35/2 2026/08/02 10:49 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 61756
							output: 20
							thoughts: 824
						

Thoughts:
**Analyzing the Liveupdate Patch**

Okay, so I'm digging into this liveupdate patch, and here's the breakdown of my thought process. It looks like `luo_session_preserve_fd` calls `luo_preserve_file`, which gets triggered via `luo_session_ioctl` when the `LIVEUPDATE_SESSION_PRESERVE_FD` ioctl is called. That's how it's exposed to userspace – accessible through the `/dev/liveupdate` device. This whole thing hinges on having `CONFIG_LIVEUPDATE` and `CONFIG_KEXEC_HANDOVER` enabled, and, importantly for preservation, now `CONFIG_LIVEUPDATE_MEMFD` to get the memfd handler.

The patch itself seems to be making changes in the `kho_radix_*` family of functions: `kho_radix_add_key`, `kho_radix_del_key`, `kho_radix_walk_tree`, `kho_radix_init_tree`, and `kho_radix_destroy_tree`. These functions are core to how `kho_preserve_folio` and `kho_unpreserve_folio` work, so they're the obvious focus. Wait, a closer look, and `kho_radix_add_key` is the new name for `kho_radix_add_page`. Okay, good to know.

I'm expanding my focus based on direct or indirect function calls. Now, let's nail down the critical symbols that are affected directly and indirectly:

-   `kho_radix_add_key`
-   `kho_radix_del_key`
-   `kho_radix_walk_tree`
-   `kho_radix_init_tree`
-   `kho_radix_destroy_tree`
-   `kho_preserve_folio`
-   `kho_unpreserve_folio`
-   `kho_preserve_pages`
-   `kho_unpreserve_pages`
-   `memblock_alloc_hugetlb`
-   `kho_scratch_overlap`

Hold on, I see the patch modifies the `kho_preserve_folio` to use `kho_radix_add_key` now instead of `kho_radix_add_page`. `kho_preserve_pages` is now calling `kho_radix_add_key` too. So my focus shrinks a bit further.

So, the key functions that are modified are these:

-   `kho_radix_add_key`
-   `kho_radix_del_key`
-   `kho_radix_walk_tree`
-   `kho_radix_init_tree`
-   `kho_radix_destroy_tree`
-   `memblock_alloc_hugetlb`
-   `kho_scratch_overlap`

Now, let's see about reachability. `memblock_alloc_hugetlb` is used in `mm/hugetlb.c` during boot, specifically via `alloc_bootmem`. Syzkaller typically doesn't directly hit boot-time code, but it's not impossible, so it's a possibility. However, `kho_radix_add_key` is definitely directly reachable through those `/dev/liveupdate` ioctls, so that's the primary avenue for fuzzing.

Finally, let me re-confirm the dependencies again. Aside from `CONFIG_KEXEC_HANDOVER` and `CONFIG_LIVEUPDATE`, we also need `CONFIG_LIVEUPDATE_MEMFD` to get the memfd handler to be registered, and therefore for `luo_preserve_file` to actually do something useful. That gives us a file handler to preserve. Good, so we are sure we can actually *do* something. Now, I need to check to make sure the CONFIG option is available.



36/2 2026/08/02 10:49 tool grepper
0m Args:
{
  "Expression": "config LIVEUPDATE_MEMFD"
}

Results:
{
  "Output": "kernel/liveupdate/Kconfig=52=config LIVEUPDATE\n--\nkernel/liveupdate/Kconfig-72-\nkernel/liveupdate/Kconfig:73:config LIVEUPDATE_MEMFD\nkernel/liveupdate/Kconfig-74-\tbool \"Live update support for memfd\"\n"
}

37/2 2026/08/02 10:49 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 63532
							output: 31
							thoughts: 65
						

Thoughts:
Okay, let's see, I'm thinking about this live update situation and I'm pretty sure `CONFIG_LIVEUPDATE_MEMFD` is defined, yes. Now, what else might be relevant here? Right, the huge page stuff. I need to make sure I'm not missing anything when it comes to memory allocation during a live update. Specifically, I'm recalling `CONFIG_HUGETLB_PAGE` being used with `memblock_alloc_hugetlb`, which is a common pattern for reserving memory. Now, to be thorough, I should look at the implementation of `alloc_bootmem` in relation to huge pages. Let's dig into `mm/hugetlb.c` to confirm my thinking and see if `alloc_bootmem` is utilized. I want to make sure I understand the full scope of memory management implications.



38/2 2026/08/02 10:49 tool grepper
0m Args:
{
  "Expression": "alloc_bootmem",
  "PathPrefix": "mm/hugetlb.c"
}

Results:
{
  "Output": "mm/hugetlb.c=2855=struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma,\n--\nmm/hugetlb.c-3027-\nmm/hugetlb.c:3028:static __init void *alloc_bootmem(struct hstate *h, int nid, bool node_exact)\nmm/hugetlb.c-3029-{\n--\nmm/hugetlb.c-3033-\tif (hugetlb_early_cma(h))\nmm/hugetlb.c:3034:\t\tm = hugetlb_cma_alloc_bootmem(h, \u0026listnode, node_exact);\nmm/hugetlb.c-3035-\telse {\n--\nmm/hugetlb.c-3069-\nmm/hugetlb.c:3070:int alloc_bootmem_huge_page(struct hstate *h, int nid)\nmm/hugetlb.c:3071:\t__attribute__ ((weak, alias(\"__alloc_bootmem_huge_page\")));\nmm/hugetlb.c:3072:int __alloc_bootmem_huge_page(struct hstate *h, int nid)\nmm/hugetlb.c-3073-{\n--\nmm/hugetlb.c-3078-\tif (nid != NUMA_NO_NODE) {\nmm/hugetlb.c:3079:\t\tm = alloc_bootmem(h, node, true);\nmm/hugetlb.c-3080-\t\tif (!m)\n--\nmm/hugetlb.c-3087-\t\t\t\t    \u0026hugetlb_bootmem_nodes) {\nmm/hugetlb.c:3088:\t\tm = alloc_bootmem(h, node, false);\nmm/hugetlb.c-3089-\t\tif (!m)\n--\nmm/hugetlb.c=3364=static void __init hugetlb_hstate_alloc_pages_onenode(struct hstate *h, int nid)\n--\nmm/hugetlb.c-3371-\t\tif (hstate_is_gigantic(h)) {\nmm/hugetlb.c:3372:\t\t\tif (!alloc_bootmem_huge_page(h, nid))\nmm/hugetlb.c-3373-\t\t\t\tbreak;\n--\nmm/hugetlb.c=3465=static unsigned long __init hugetlb_gigantic_pages_alloc_boot(struct hstate *h)\n--\nmm/hugetlb.c-3469-\tfor (i = 0; i \u003c h-\u003emax_huge_pages; ++i) {\nmm/hugetlb.c:3470:\t\tif (!alloc_bootmem_huge_page(h, NUMA_NO_NODE))\nmm/hugetlb.c-3471-\t\t\tbreak;\n"
}

39/2 2026/08/02 10:49 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 64542
							output: 24
							thoughts: 243
						

Thoughts:
**Analyzing Kernel Configuration Dependencies**

Okay, so I'm looking at this patch and immediately, `alloc_bootmem` jumps out at me. That tells me we're dealing with potentially huge, contiguous memory allocations early in the boot process. Therefore, `CONFIG_HUGETLB_PAGE` is definitely a key factor to consider here. It's directly related to the allocation of large memory pages.

Now, what about `CONFIG_KEXEC_HANDOVER_DEBUG`? The patch removes `kexec_handover_debug.c` and relocates `kho_scratch_overlap` into `kexec_handover.c`. It also uses `IS_ENABLED(CONFIG_KEXEC_HANDOVER_DEBUG)` now for `kho_preserve_folio` and `kho_preserve_pages` instead of the traditional `#ifdef`. So, that debug config is clearly important. It affects how memory is handled during the handover process.

Alright, let's consolidate these configuration variables. I need to make sure I have a clear picture. The relevant configurations are:

*   `KEXEC_HANDOVER`
*   `KEXEC_HANDOVER_DEBUG`
*   `LIVEUPDATE`
*   `LIVEUPDATE_MEMFD`
*   `HUGETLB_PAGE`

However, hang on a second... `KEXEC_HANDOVER` itself is dependent on `ARCH_SUPPORTS_KEXEC_HANDOVER` and `ARCH_SUPPORTS_KEXEC_FILE`. Those architecture-specific prerequisites need to be verified. So, I need to dig into `ARCH_SUPPORTS_KEXEC_HANDOVER` next.



40/2 2026/08/02 10:49 tool grepper
0m Args:
{
  "Expression": "ARCH_SUPPORTS_KEXEC_HANDOVER"
}

Results:
{
  "Output": "arch/arm64/Kconfig=1654=config ARCH_DEFAULT_KEXEC_IMAGE_VERIFY_SIG\n--\narch/arm64/Kconfig-1656-\narch/arm64/Kconfig:1657:config ARCH_SUPPORTS_KEXEC_HANDOVER\narch/arm64/Kconfig-1658-\tdef_bool y\n--\narch/x86/Kconfig=1974=config ARCH_SUPPORTS_KEXEC_JUMP\n--\narch/x86/Kconfig-1976-\narch/x86/Kconfig:1977:config ARCH_SUPPORTS_KEXEC_HANDOVER\narch/x86/Kconfig-1978-\tdef_bool X86_64\n--\nkernel/liveupdate/Kconfig=5=config KEXEC_HANDOVER\nkernel/liveupdate/Kconfig-6-\tbool \"kexec handover\"\nkernel/liveupdate/Kconfig:7:\tdepends on ARCH_SUPPORTS_KEXEC_HANDOVER \u0026\u0026 ARCH_SUPPORTS_KEXEC_FILE\nkernel/liveupdate/Kconfig-8-\tselect MEMBLOCK_KHO_SCRATCH\n"
}

41/2 2026/08/02 10:49 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 65449
							output: 278
							thoughts: 700
						

Thoughts:
**Fuzzing Target Identification: Kexec Handover Radix Tree**

Okay, so this patch is interesting. It touches the Kexec Handover (KHO) radix tree implementation, refactoring it to use generic key-based functions. The fact that Syzkaller already runs on both x86_64 and arm64, and KHO is present on both, gives me some confidence this is a viable target.

The core idea is the introduction of functions like `kho_radix_add_key` and `kho_radix_del_key` to replace the previous page-specific methods. Critically, these radix tree functions are accessible from userspace through `/dev/liveupdate` ioctls – specifically, calls like `LIVEUPDATE_SESSION_PRESERVE_FD` when preserving memfd files. That's a good sign, because it means we have a direct path to trigger and fuzz this logic.

I need to focus my efforts on these symbols:

*   `kho_radix_add_key`
*   `kho_radix_del_key`
*   `kho_radix_walk_tree`
*   `kho_radix_init_tree`
*   `kho_radix_destroy_tree`
*   `memblock_alloc_hugetlb`
*   `kho_scratch_overlap`

And of course, I have to ensure the right configs are set: `KEXEC_HANDOVER`, `KEXEC_HANDOVER_DEBUG`, `LIVEUPDATE`, `LIVEUPDATE_MEMFD`, and `HUGETLB_PAGE`. Let's double check... ah, it looks like `CONFIG_MEMBLOCK_KHO_SCRATCH` is also important, as it is selected by `CONFIG_KEXEC_HANDOVER`, and then `CONFIG_KEXEC_FILE` is needed too.

Hmm, a minor wrinkle: `kho_radix_init_tree` and `kho_radix_destroy_tree` are also used in `kho_extend_scratch`, which is an `__init` function. But they are exported symbols, and called from `kho_init`, which is a `fs_initcall`. The concern is Syzkaller may not hit them as often, but it's important to include them to cover everything.

Just to be thorough, I should verify that `memblock_alloc_hugetlb` is indeed reachable. Right, it's called through `alloc_bootmem` in `mm/hugetlb.c`. And `kho_scratch_overlap` is called from `memblock_alloc_hugetlb` and `kho_preserve_folio`, so that's covered.

I need to use the `set-results` tool to make sure that the fuzzer targets these functions.



42/2 2026/08/02 10:49 tool set-results
0m Args:
{
  "EnableConfigs": [
    "KEXEC_HANDOVER",
    "KEXEC_HANDOVER_DEBUG",
    "LIVEUPDATE",
    "LIVEUPDATE_MEMFD",
    "HUGETLB_PAGE"
  ],
  "FocusSymbols": [
    "kho_radix_add_key",
    "kho_radix_del_key",
    "kho_radix_walk_tree",
    "kho_radix_init_tree",
    "kho_radix_destroy_tree",
    "memblock_alloc_hugetlb",
    "kho_scratch_overlap"
  ],
  "Reasoning": "The patch refactors the KHO (Kexec HandOver) radix tree implementation, renaming page-specific functions to generic key-based functions (e.g., `kho_radix_add_key`, `kho_radix_del_key`). It also introduces `memblock_alloc_hugetlb` for allocating HugeTLB pages during boot, avoiding KHO scratch memory overlaps. The KHO radix tree functions are reachable from userspace via `/dev/liveupdate` ioctls (like `LIVEUPDATE_SESSION_PRESERVE_FD`) when preserving memfd files. Therefore, this functionality is reachable and should be fuzzed.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "KEXEC_HANDOVER",
    "KEXEC_HANDOVER_DEBUG",
    "LIVEUPDATE",
    "LIVEUPDATE_MEMFD",
    "HUGETLB_PAGE"
  ],
  "FocusSymbols": [
    "kho_radix_add_key",
    "kho_radix_del_key",
    "kho_radix_walk_tree",
    "kho_radix_init_tree",
    "kho_radix_destroy_tree",
    "memblock_alloc_hugetlb",
    "kho_scratch_overlap"
  ],
  "Reasoning": "The patch refactors the KHO (Kexec HandOver) radix tree implementation, renaming page-specific functions to generic key-based functions (e.g., `kho_radix_add_key`, `kho_radix_del_key`). It also introduces `memblock_alloc_hugetlb` for allocating HugeTLB pages during boot, avoiding KHO scratch memory overlaps. The KHO radix tree functions are reachable from userspace via `/dev/liveupdate` ioctls (like `LIVEUPDATE_SESSION_PRESERVE_FD`) when preserving memfd files. Therefore, this functionality is reachable and should be fuzzed.",
  "WorthFuzzing": true
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)