| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/13 09:28 | flow | patch-triage |
1mArgs:null Results: {
"EnableConfigs": null,
"FocusSymbols": [
"set_memory_rox",
"set_memory_ro",
"set_memory_rw",
"set_memory_x",
"set_memory_nx",
"text_poke",
"text_poke_copy",
"text_poke_set"
],
"KMSANReasoning": "The patch addresses race conditions in page table collapse (by adding mmap_read_lock/mmap_write_lock on init_mm), fixes a bitwise logic bug when checking page attributes, and updates page table allocation/freeing functions. These changes primarily prevent use-after-free bugs and data races, which are effectively detected by KASAN and standard concurrency/locking detectors (like LOCKDEP). There are no new structures exposed to user space, nor any logic that would introduce or expose uninitialized memory reads. Therefore, a dedicated KMSAN fuzzing session is not required.",
"NeedsKMSAN": false,
"Reasoning": "The patch modifies core x86 memory management code (arch/x86/mm/pat/set_memory.c and arch/x86/kernel/alternative.c) to fix a race condition between text_poke (which uses vmalloc_to_page) and cpa_collapse_large_pages (which is called by set_memory_rox and others). It introduces mmap_read_lock and mmap_write_lock on init_mm to serialize these operations. This is a functional change to core kernel logic that is reachable via BPF, module loading, kprobes, ftrace, etc. It is definitely worth fuzzing.",
"WorthFuzzing": true
} |
| 1/1 | 2026/08/13 09:28 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 321f148bee10eff425638c5e498f93627ad23fb5\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Thu Aug 13 09:28:19 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/arch/x86/kernel/alternative.c b/arch/x86/kernel/alternative.c\nindex 62936a3bde19b..f81d6bc90a6ff 100644\n--- a/arch/x86/kernel/alternative.c\n+++ b/arch/x86/kernel/alternative.c\n@@ -6,6 +6,9 @@\n #include \u003clinux/vmalloc.h\u003e\n #include \u003clinux/memory.h\u003e\n #include \u003clinux/execmem.h\u003e\n+#include \u003clinux/cleanup.h\u003e\n+#include \u003clinux/kgdb.h\u003e\n+#include \u003clinux/mmap_lock.h\u003e\n \n #include \u003casm/text-patching.h\u003e\n #include \u003casm/insn.h\u003e\n@@ -2543,6 +2546,38 @@ static void text_poke_memset(void *dst, const void *src, size_t len)\n \n typedef void text_poke_f(void *dst, const void *src, size_t len);\n \n+static void __poke_vmalloc_pages(struct page **pages, void *addr,\n+\t\t\t\t bool cross_page_boundary)\n+{\n+\tpages[0] = vmalloc_to_page(addr);\n+\tif (cross_page_boundary)\n+\t\tpages[1] = vmalloc_to_page(addr + PAGE_SIZE);\n+}\n+\n+static void poke_vmalloc_pages(struct page **pages, void *addr,\n+\t\t\t bool cross_page_boundary)\n+{\n+\tif (in_dbg_master()) {\n+\t\t/*\n+\t\t * If called from kgdb cannot sleep, but all other CPUs stopped\n+\t\t * anyway so safe to proceed without locks\n+\t\t */\n+\t\t__poke_vmalloc_pages(pages, addr, cross_page_boundary);\n+\t} else {\n+\t\t/*\n+\t\t * execmem ROX ranges are shared between modules and can be\n+\t\t * collapsed to huge PMD entries, and this collapse can happen\n+\t\t * concurrently with a racing set_memory_rox().\n+\t\t *\n+\t\t * Prevent vmalloc_to_page() from racing by acquiring an\n+\t\t * init_mm read lock which pairs with the init_mm write lock in\n+\t\t * cpa_collapse_large_pages().\n+\t\t */\n+\t\tguard(mmap_read_lock)(\u0026init_mm);\n+\t\t__poke_vmalloc_pages(pages, addr, cross_page_boundary);\n+\t}\n+}\n+\n static void *__text_poke(text_poke_f func, void *addr, const void *src, size_t len)\n {\n \tbool cross_page_boundary = offset_in_page(addr) + len \u003e PAGE_SIZE;\n@@ -2560,9 +2595,7 @@ static void *__text_poke(text_poke_f func, void *addr, const void *src, size_t l\n \tBUG_ON(!after_bootmem);\n \n \tif (!core_kernel_text((unsigned long)addr)) {\n-\t\tpages[0] = vmalloc_to_page(addr);\n-\t\tif (cross_page_boundary)\n-\t\t\tpages[1] = vmalloc_to_page(addr + PAGE_SIZE);\n+\t\tpoke_vmalloc_pages(pages, addr, cross_page_boundary);\n \t} else {\n \t\tpages[0] = virt_to_page(addr);\n \t\tWARN_ON(!PageReserved(pages[0]));\ndiff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c\nindex d8d057f444178..430d0b4483718 100644\n--- a/arch/x86/mm/pat/set_memory.c\n+++ b/arch/x86/mm/pat/set_memory.c\n@@ -22,6 +22,7 @@\n #include \u003clinux/cc_platform.h\u003e\n #include \u003clinux/set_memory.h\u003e\n #include \u003clinux/memregion.h\u003e\n+#include \u003clinux/cleanup.h\u003e\n \n #include \u003casm/e820/api.h\u003e\n #include \u003casm/processor.h\u003e\n@@ -49,7 +50,8 @@ struct cpa_data {\n \tunsigned int\tflags;\n \tunsigned int\tforce_split\t\t: 1,\n \t\t\tforce_static_prot\t: 1,\n-\t\t\tforce_flush_all\t\t: 1;\n+\t\t\tforce_flush_all\t\t: 1,\n+\t\t\tinit_mm_read_locked\t: 1;\n \tstruct page\t**pages;\n };\n \n@@ -409,7 +411,7 @@ static void __cpa_flush_tlb(void *data)\n \n static int collapse_large_pages(unsigned long addr, struct list_head *pgtables);\n \n-static void cpa_collapse_large_pages(struct cpa_data *cpa)\n+static void __cpa_collapse_large_pages(struct cpa_data *cpa)\n {\n \tunsigned long start, addr, end;\n \tstruct ptdesc *ptdesc, *tmp;\n@@ -439,10 +441,30 @@ static void cpa_collapse_large_pages(struct cpa_data *cpa)\n \n \tlist_for_each_entry_safe(ptdesc, tmp, \u0026pgtables, pt_list) {\n \t\tlist_del(\u0026ptdesc-\u003ept_list);\n-\t\tpagetable_free(ptdesc);\n+\t\t/*\n+\t\t * Only early alloc'd direct map should not be flagged PG_table\n+\t\t * here and those shouldn't be collapsed. However be abundantly\n+\t\t * cautious and handle the !PG_table case too.\n+\t\t */\n+\t\tif (PageTable((ptdesc_page(ptdesc))))\n+\t\t\tpagetable_dtor_free(ptdesc);\n+\t\telse\n+\t\t\tpagetable_free(ptdesc);\n \t}\n }\n \n+static void cpa_collapse_large_pages(struct cpa_data *cpa)\n+{\n+\t/*\n+\t * Take the mmap write lock on init_mm to:\n+\t * - Avoid a use-after-free if raced by ptdump (which takes its own\n+\t * write lock on init_mm).\n+\t * - Serialise concurrent CPA walkers.\n+\t */\n+\tscoped_guard(mmap_write_lock, \u0026init_mm)\n+\t\t__cpa_collapse_large_pages(cpa);\n+}\n+\n static void cpa_flush(struct cpa_data *cpa, int cache)\n {\n \tunsigned int i;\n@@ -732,7 +754,7 @@ pte_t *lookup_address_in_pgd_attr(pgd_t *pgd, unsigned long address,\n \n \t*level = PG_LEVEL_512G;\n \t*nx |= pgd_flags(*pgd) \u0026 _PAGE_NX;\n-\t*rw \u0026= pgd_flags(*pgd) \u0026 _PAGE_RW;\n+\t*rw \u0026= !!(pgd_flags(*pgd) \u0026 _PAGE_RW);\n \n \tp4d = p4d_offset(pgd, address);\n \tif (p4d_none(*p4d))\n@@ -743,7 +765,7 @@ pte_t *lookup_address_in_pgd_attr(pgd_t *pgd, unsigned long address,\n \n \t*level = PG_LEVEL_1G;\n \t*nx |= p4d_flags(*p4d) \u0026 _PAGE_NX;\n-\t*rw \u0026= p4d_flags(*p4d) \u0026 _PAGE_RW;\n+\t*rw \u0026= !!(p4d_flags(*p4d) \u0026 _PAGE_RW);\n \n \tpud = pud_offset(p4d, address);\n \tif (pud_none(*pud))\n@@ -754,7 +776,7 @@ pte_t *lookup_address_in_pgd_attr(pgd_t *pgd, unsigned long address,\n \n \t*level = PG_LEVEL_2M;\n \t*nx |= pud_flags(*pud) \u0026 _PAGE_NX;\n-\t*rw \u0026= pud_flags(*pud) \u0026 _PAGE_RW;\n+\t*rw \u0026= !!(pud_flags(*pud) \u0026 _PAGE_RW);\n \n \tpmd = pmd_offset(pud, address);\n \tif (pmd_none(*pmd))\n@@ -765,7 +787,7 @@ pte_t *lookup_address_in_pgd_attr(pgd_t *pgd, unsigned long address,\n \n \t*level = PG_LEVEL_4K;\n \t*nx |= pmd_flags(*pmd) \u0026 _PAGE_NX;\n-\t*rw \u0026= pmd_flags(*pmd) \u0026 _PAGE_RW;\n+\t*rw \u0026= !!(pmd_flags(*pmd) \u0026 _PAGE_RW);\n \n \treturn pte_offset_kernel(pmd, address);\n }\n@@ -1120,11 +1142,10 @@ static void split_set_pte(struct cpa_data *cpa, pte_t *pte, unsigned long pfn,\n \n static int\n __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address,\n-\t\t struct ptdesc *ptdesc)\n+\t\t pte_t *pbase)\n {\n \tunsigned long lpaddr, lpinc, ref_pfn, pfn, pfninc = 1;\n-\tstruct page *base = ptdesc_page(ptdesc);\n-\tpte_t *pbase = (pte_t *)page_address(base);\n+\tstruct page *base = virt_to_page(pbase);\n \tunsigned int i, level;\n \tpgprot_t ref_prot;\n \tbool nx, rw;\n@@ -1224,16 +1245,20 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address,\n static int split_large_page(struct cpa_data *cpa, pte_t *kpte,\n \t\t\t unsigned long address)\n {\n-\tstruct ptdesc *ptdesc;\n+\tpte_t *pte;\n \n \tspin_unlock(\u0026cpa_lock);\n-\tptdesc = pagetable_alloc(GFP_KERNEL, 0);\n+\tif (cpa-\u003einit_mm_read_locked)\n+\t\tmmap_read_unlock(\u0026init_mm);\n+\tpte = pte_alloc_one_kernel(\u0026init_mm);\n+\tif (cpa-\u003einit_mm_read_locked)\n+\t\tmmap_read_lock(\u0026init_mm);\n \tspin_lock(\u0026cpa_lock);\n-\tif (!ptdesc)\n+\tif (!pte)\n \t\treturn -ENOMEM;\n \n-\tif (__split_large_page(cpa, kpte, address, ptdesc))\n-\t\tpagetable_free(ptdesc);\n+\tif (__split_large_page(cpa, kpte, address, pte))\n+\t\tpte_free_kernel(\u0026init_mm, pte);\n \n \treturn 0;\n }\n@@ -2121,7 +2146,11 @@ static int change_page_attr_set_clr(unsigned long *addr, int numpages,\n \tcpa.curpage = 0;\n \tcpa.force_split = force_split;\n \n-\tret = __change_page_attr_set_clr(\u0026cpa, 1);\n+\t/* Avoid race with concurrent CPA collapse. */\n+\tcpa.init_mm_read_locked = true;\n+\tscoped_guard(mmap_read_lock, \u0026init_mm)\n+\t\tret = __change_page_attr_set_clr(\u0026cpa, 1);\n+\tcpa.init_mm_read_locked = false;\n \n \t/*\n \t * Check whether we really changed something:\ndiff --git a/include/linux/mmap_lock.h b/include/linux/mmap_lock.h\nindex 04b8f61ece5d8..f4ceb968aeb3d 100644\n--- a/include/linux/mmap_lock.h\n+++ b/include/linux/mmap_lock.h\n@@ -621,6 +621,8 @@ static inline void mmap_read_unlock(struct mm_struct *mm)\n \n DEFINE_GUARD(mmap_read_lock, struct mm_struct *,\n \t mmap_read_lock(_T), mmap_read_unlock(_T))\n+DEFINE_GUARD(mmap_write_lock, struct mm_struct *,\n+\t mmap_write_lock(_T), mmap_write_unlock(_T))\n \n static inline void mmap_read_unlock_non_owner(struct mm_struct *mm)\n {\n"
} |
| 2/1 | 2026/08/13 09:28 | agent | patch-evaluator |
1mModel:best-expensive Args: null Results: {
"EnableConfigs": null,
"FocusSymbols": [
"set_memory_rox",
"set_memory_ro",
"set_memory_rw",
"set_memory_x",
"set_memory_nx",
"text_poke",
"text_poke_copy",
"text_poke_set"
],
"Reasoning": "The patch modifies core x86 memory management code (arch/x86/mm/pat/set_memory.c and arch/x86/kernel/alternative.c) to fix a race condition between text_poke (which uses vmalloc_to_page) and cpa_collapse_large_pages (which is called by set_memory_rox and others). It introduces mmap_read_lock and mmap_write_lock on init_mm to serialize these operations. This is a functional change to core kernel logic that is reachable via BPF, module loading, kprobes, ftrace, etc. It is definitely worth fuzzing.",
"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 321f148bee10eff425638c5e498f93627ad23fb5
Author: syz-cluster <triage@syzkaller.com>
Date: Thu Aug 13 09:28:19 2026 +0000
syz-cluster: applied patch under review
diff --git a/arch/x86/kernel/alternative.c b/arch/x86/kernel/alternative.c
index 62936a3bde19b..f81d6bc90a6ff 100644
--- a/arch/x86/kernel/alternative.c
+++ b/arch/x86/kernel/alternative.c
@@ -6,6 +6,9 @@
#include <linux/vmalloc.h>
#include <linux/memory.h>
#include <linux/execmem.h>
+#include <linux/cleanup.h>
+#include <linux/kgdb.h>
+#include <linux/mmap_lock.h>
#include <asm/text-patching.h>
#include <asm/insn.h>
@@ -2543,6 +2546,38 @@ static void text_poke_memset(void *dst, const void *src, size_t len)
typedef void text_poke_f(void *dst, const void *src, size_t len);
+static void __poke_vmalloc_pages(struct page **pages, void *addr,
+ bool cross_page_boundary)
+{
+ pages[0] = vmalloc_to_page(addr);
+ if (cross_page_boundary)
+ pages[1] = vmalloc_to_page(addr + PAGE_SIZE);
+}
+
+static void poke_vmalloc_pages(struct page **pages, void *addr,
+ bool cross_page_boundary)
+{
+ if (in_dbg_master()) {
+ /*
+ * If called from kgdb cannot sleep, but all other CPUs stopped
+ * anyway so safe to proceed without locks
+ */
+ __poke_vmalloc_pages(pages, addr, cross_page_boundary);
+ } else {
+ /*
+ * execmem ROX ranges are shared between modules and can be
+ * collapsed to huge PMD entries, and this collapse can happen
+ * concurrently with a racing set_memory_rox().
+ *
+ * Prevent vmalloc_to_page() from racing by acquiring an
+ * init_mm read lock which pairs with the init_mm write lock in
+ * cpa_collapse_large_pages().
+ */
+ guard(mmap_read_lock)(&init_mm);
+ __poke_vmalloc_pages(pages, addr, cross_page_boundary);
+ }
+}
+
static void *__text_poke(text_poke_f func, void *addr, const void *src, size_t len)
{
bool cross_page_boundary = offset_in_page(addr) + len > PAGE_SIZE;
@@ -2560,9 +2595,7 @@ static void *__text_poke(text_poke_f func, void *addr, const void *src, size_t l
BUG_ON(!after_bootmem);
if (!core_kernel_text((unsigned long)addr)) {
- pages[0] = vmalloc_to_page(addr);
- if (cross_page_boundary)
- pages[1] = vmalloc_to_page(addr + PAGE_SIZE);
+ poke_vmalloc_pages(pages, addr, cross_page_boundary);
} else {
pages[0] = virt_to_page(addr);
WARN_ON(!PageReserved(pages[0]));
diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c
index d8d057f444178..430d0b4483718 100644
--- a/arch/x86/mm/pat/set_memory.c
+++ b/arch/x86/mm/pat/set_memory.c
@@ -22,6 +22,7 @@
#include <linux/cc_platform.h>
#include <linux/set_memory.h>
#include <linux/memregion.h>
+#include <linux/cleanup.h>
#include <asm/e820/api.h>
#include <asm/processor.h>
@@ -49,7 +50,8 @@ struct cpa_data {
unsigned int flags;
unsigned int force_split : 1,
force_static_prot : 1,
- force_flush_all : 1;
+ force_flush_all : 1,
+ init_mm_read_locked : 1;
struct page **pages;
};
@@ -409,7 +411,7 @@ static void __cpa_flush_tlb(void *data)
static int collapse_large_pages(unsigned long addr, struct list_head *pgtables);
-static void cpa_collapse_large_pages(struct cpa_data *cpa)
+static void __cpa_collapse_large_pages(struct cpa_data *cpa)
{
unsigned long start, addr, end;
struct ptdesc *ptdesc, *tmp;
@@ -439,10 +441,30 @@ static void cpa_collapse_large_pages(struct cpa_data *cpa)
list_for_each_entry_safe(ptdesc, tmp, &pgtables, pt_list) {
list_del(&ptdesc->pt_list);
- pagetable_free(ptdesc);
+ /*
+ * Only early alloc'd direct map should not be flagged PG_table
+ * here and those shouldn't be collapsed. However be abundantly
+ * cautious and handle the !PG_table case too.
+ */
+ if (PageTable((ptdesc_page(ptdesc))))
+ pagetable_dtor_free(ptdesc);
+ else
+ pagetable_free(ptdesc);
}
}
+static void cpa_collapse_large_pages(struct cpa_data *cpa)
+{
+ /*
+ * Take the mmap write lock on init_mm to:
+ * - Avoid a use-after-free if raced by ptdump (which takes its own
+ * write lock on init_mm).
+ * - Serialise concurrent CPA walkers.
+ */
+ scoped_guard(mmap_write_lock, &init_mm)
+ __cpa_collapse_large_pages(cpa);
+}
+
static void cpa_flush(struct cpa_data *cpa, int cache)
{
unsigned int i;
@@ -732,7 +754,7 @@ pte_t *lookup_address_in_pgd_attr(pgd_t *pgd, unsigned long address,
*level = PG_LEVEL_512G;
*nx |= pgd_flags(*pgd) & _PAGE_NX;
- *rw &= pgd_flags(*pgd) & _PAGE_RW;
+ *rw &= !!(pgd_flags(*pgd) & _PAGE_RW);
p4d = p4d_offset(pgd, address);
if (p4d_none(*p4d))
@@ -743,7 +765,7 @@ pte_t *lookup_address_in_pgd_attr(pgd_t *pgd, unsigned long address,
*level = PG_LEVEL_1G;
*nx |= p4d_flags(*p4d) & _PAGE_NX;
- *rw &= p4d_flags(*p4d) & _PAGE_RW;
+ *rw &= !!(p4d_flags(*p4d) & _PAGE_RW);
pud = pud_offset(p4d, address);
if (pud_none(*pud))
@@ -754,7 +776,7 @@ pte_t *lookup_address_in_pgd_attr(pgd_t *pgd, unsigned long address,
*level = PG_LEVEL_2M;
*nx |= pud_flags(*pud) & _PAGE_NX;
- *rw &= pud_flags(*pud) & _PAGE_RW;
+ *rw &= !!(pud_flags(*pud) & _PAGE_RW);
pmd = pmd_offset(pud, address);
if (pmd_none(*pmd))
@@ -765,7 +787,7 @@ pte_t *lookup_address_in_pgd_attr(pgd_t *pgd, unsigned long address,
*level = PG_LEVEL_4K;
*nx |= pmd_flags(*pmd) & _PAGE_NX;
- *rw &= pmd_flags(*pmd) & _PAGE_RW;
+ *rw &= !!(pmd_flags(*pmd) & _PAGE_RW);
return pte_offset_kernel(pmd, address);
}
@@ -1120,11 +1142,10 @@ static void split_set_pte(struct cpa_data *cpa, pte_t *pte, unsigned long pfn,
static int
__split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address,
- struct ptdesc *ptdesc)
+ pte_t *pbase)
{
unsigned long lpaddr, lpinc, ref_pfn, pfn, pfninc = 1;
- struct page *base = ptdesc_page(ptdesc);
- pte_t *pbase = (pte_t *)page_address(base);
+ struct page *base = virt_to_page(pbase);
unsigned int i, level;
pgprot_t ref_prot;
bool nx, rw;
@@ -1224,16 +1245,20 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address,
static int split_large_page(struct cpa_data *cpa, pte_t *kpte,
unsigned long address)
{
- struct ptdesc *ptdesc;
+ pte_t *pte;
spin_unlock(&cpa_lock);
- ptdesc = pagetable_alloc(GFP_KERNEL, 0);
+ if (cpa->init_mm_read_locked)
+ mmap_read_unlock(&init_mm);
+ pte = pte_alloc_one_kernel(&init_mm);
+ if (cpa->init_mm_read_locked)
+ mmap_read_lock(&init_mm);
spin_lock(&cpa_lock);
- if (!ptdesc)
+ if (!pte)
return -ENOMEM;
- if (__split_large_page(cpa, kpte, address, ptdesc))
- pagetable_free(ptdesc);
+ if (__split_large_page(cpa, kpte, address, pte))
+ pte_free_kernel(&init_mm, pte);
return 0;
}
@@ -2121,7 +2146,11 @@ static int change_page_attr_set_clr(unsigned long *addr, int numpages,
cpa.curpage = 0;
cpa.force_split = force_split;
- ret = __change_page_attr_set_clr(&cpa, 1);
+ /* Avoid race with concurrent CPA collapse. */
+ cpa.init_mm_read_locked = true;
+ scoped_guard(mmap_read_lock, &init_mm)
+ ret = __change_page_attr_set_clr(&cpa, 1);
+ cpa.init_mm_read_locked = false;
/*
* Check whether we really changed something:
diff --git a/include/linux/mmap_lock.h b/include/linux/mmap_lock.h
index 04b8f61ece5d8..f4ceb968aeb3d 100644
--- a/include/linux/mmap_lock.h
+++ b/include/linux/mmap_lock.h
@@ -621,6 +621,8 @@ static inline void mmap_read_unlock(struct mm_struct *mm)
DEFINE_GUARD(mmap_read_lock, struct mm_struct *,
mmap_read_lock(_T), mmap_read_unlock(_T))
+DEFINE_GUARD(mmap_write_lock, struct mm_struct *,
+ mmap_write_lock(_T), mmap_write_unlock(_T))
static inline void mmap_read_unlock_non_owner(struct mm_struct *mm)
{
|
| 3/2 | 2026/08/13 09:28 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 4994 output: 41 thoughts: 410 Thoughts: **Analyzing a Critical x86 Memory Management Patch** Okay, so I'm looking at this patch and my initial thought is, this is a pretty significant change. We're talking about modifications to `arch/x86/kernel/alternative.c`, `arch/x86/mm/pat/set_memory.c`, and `include/linux/mmap_lock.h` – that hits some core x86 memory management code. Specifically, the patch is adding `mmap_read_lock` and `mmap_write_lock` around critical sections, targeting things like `init_mm`, `cpa_collapse_large_pages`, `poke_vmalloc_pages`, and `split_large_page`. It's a functional change, dealing with page attribute (CPA) code and the alternative text poking mechanism. Essentially, this patch is designed to fix potential race conditions, specifically when CPA collapse and vmalloc page poking occur concurrently. My next step is to evaluate the reach of this change. `change_page_attr_set_clr` is the core function at the heart of `set_memory_ro`, `set_memory_rw`, `set_memory_x`, `set_memory_nx`, etc. Now, those are used *everywhere*. Think BPF, module loading, kprobes, ftrace... the list goes on. And `text_poke` is used in alternatives, kprobes, ftrace, jump labels, etc. So, this patch impacts fundamental aspects of memory management that are used all over the place. Given the impact on the core x86 memory management, I'm already thinking this is definitely a candidate for fuzzing. I need to figure out some key symbols to target. `change_page_attr_set_clr` seems like a good starting point, but it's a static function. I need to make sure I am focusing on non-static functions like `set_memory_ro`, `set_memory_rw`, `set_memory_x`, `set_memory_nx`, and `set_memory_rox`. Wait a minute, `change_page_attr_set_clr` is static, which means I should look at `arch/x86/mm/pat/set_memory.c` directly to see what exported functions are available there. I need to determine what is externally callable from that file. |
| 4/2 | 2026/08/13 09:28 | tool | grepper |
0mArgs:{
"Expression": "^int set_memory_",
"PathPrefix": "arch/x86/mm/pat/set_memory.c"
}Results: {
"Output": "arch/x86/mm/pat/set_memory.c=2208=int _set_memory_uc(unsigned long addr, int numpages)\n--\narch/x86/mm/pat/set_memory.c-2220-\narch/x86/mm/pat/set_memory.c:2221:int set_memory_uc(unsigned long addr, int numpages)\narch/x86/mm/pat/set_memory.c-2222-{\n--\narch/x86/mm/pat/set_memory.c=2246=int _set_memory_wc(unsigned long addr, int numpages)\n--\narch/x86/mm/pat/set_memory.c-2261-\narch/x86/mm/pat/set_memory.c:2262:int set_memory_wc(unsigned long addr, int numpages)\narch/x86/mm/pat/set_memory.c-2263-{\n--\narch/x86/mm/pat/set_memory.c=2285=int _set_memory_wb(unsigned long addr, int numpages)\n--\narch/x86/mm/pat/set_memory.c-2291-\narch/x86/mm/pat/set_memory.c:2292:int set_memory_wb(unsigned long addr, int numpages)\narch/x86/mm/pat/set_memory.c-2293-{\n--\narch/x86/mm/pat/set_memory.c=2343=EXPORT_SYMBOL_GPL(clear_mce_nospec);\n--\narch/x86/mm/pat/set_memory.c-2345-\narch/x86/mm/pat/set_memory.c:2346:int set_memory_x(unsigned long addr, int numpages)\narch/x86/mm/pat/set_memory.c-2347-{\n--\narch/x86/mm/pat/set_memory.c-2353-\narch/x86/mm/pat/set_memory.c:2354:int set_memory_nx(unsigned long addr, int numpages)\narch/x86/mm/pat/set_memory.c-2355-{\n--\narch/x86/mm/pat/set_memory.c-2361-\narch/x86/mm/pat/set_memory.c:2362:int set_memory_ro(unsigned long addr, int numpages)\narch/x86/mm/pat/set_memory.c-2363-{\n--\narch/x86/mm/pat/set_memory.c-2366-\narch/x86/mm/pat/set_memory.c:2367:int set_memory_rox(unsigned long addr, int numpages)\narch/x86/mm/pat/set_memory.c-2368-{\n--\narch/x86/mm/pat/set_memory.c-2377-\narch/x86/mm/pat/set_memory.c:2378:int set_memory_rw(unsigned long addr, int numpages)\narch/x86/mm/pat/set_memory.c-2379-{\n--\narch/x86/mm/pat/set_memory.c-2382-\narch/x86/mm/pat/set_memory.c:2383:int set_memory_np(unsigned long addr, int numpages)\narch/x86/mm/pat/set_memory.c-2384-{\n--\narch/x86/mm/pat/set_memory.c-2387-\narch/x86/mm/pat/set_memory.c:2388:int set_memory_np_noalias(unsigned long addr, int numpages)\narch/x86/mm/pat/set_memory.c-2389-{\n--\narch/x86/mm/pat/set_memory.c-2394-\narch/x86/mm/pat/set_memory.c:2395:int set_memory_p(unsigned long addr, int numpages)\narch/x86/mm/pat/set_memory.c-2396-{\n--\narch/x86/mm/pat/set_memory.c-2399-\narch/x86/mm/pat/set_memory.c:2400:int set_memory_4k(unsigned long addr, int numpages)\narch/x86/mm/pat/set_memory.c-2401-{\n--\narch/x86/mm/pat/set_memory.c-2406-\narch/x86/mm/pat/set_memory.c:2407:int set_memory_nonglobal(unsigned long addr, int numpages)\narch/x86/mm/pat/set_memory.c-2408-{\n--\narch/x86/mm/pat/set_memory.c-2412-\narch/x86/mm/pat/set_memory.c:2413:int set_memory_global(unsigned long addr, int numpages)\narch/x86/mm/pat/set_memory.c-2414-{\n--\narch/x86/mm/pat/set_memory.c=2509=static int __set_memory_enc_dec(unsigned long addr, int numpages, bool enc)\n--\narch/x86/mm/pat/set_memory.c-2524-\narch/x86/mm/pat/set_memory.c:2525:int set_memory_encrypted(unsigned long addr, int numpages)\narch/x86/mm/pat/set_memory.c-2526-{\n--\narch/x86/mm/pat/set_memory.c=2529=EXPORT_SYMBOL_GPL(set_memory_encrypted);\narch/x86/mm/pat/set_memory.c-2530-\narch/x86/mm/pat/set_memory.c:2531:int set_memory_decrypted(unsigned long addr, int numpages)\narch/x86/mm/pat/set_memory.c-2532-{\n"
} |
| 5/2 | 2026/08/13 09:28 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 7543 output: 38 thoughts: 37 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience, presented in the first person: **Analyzing the __text_poke Modification** Alright, so the patch is tweaking `__text_poke` within `arch/x86/kernel/alternative.c`. My immediate thought is, "Okay, `__text_poke`. That's a low-level function – a text-space write operation. Likely used for patching or modifying instruction streams at runtime, possibly for alternatives or security features." My next move? Definitely need to understand the impact of this. I'm going to quickly identify all the callers of `__text_poke`. It's crucial to understand *how* it's being used and *why* this modification is happening. I need to see the contexts where this is being deployed, what's calling it, and what alternatives it's patching. Is this related to microcode updates, kernel instrumentation, security mitigations (like those against Spectre or Meltdown), or something else entirely? A solid understanding of the call sites will be key to deciphering the intended function of the modified `__text_poke`. That's the first step. |
| 6/2 | 2026/08/13 09:28 | tool | grepper |
0mArgs:{
"Expression": "^void \\*?text_poke",
"PathPrefix": "arch/x86/kernel/alternative.c"
}Results: {
"Output": "arch/x86/kernel/alternative.c=470=static void __apply_relocation(u8 *buf, const u8 * const instr, size_t instrlen, u8 *repl, size_t repl_len)\n--\narch/x86/kernel/alternative.c-523-\narch/x86/kernel/alternative.c:524:void text_poke_apply_relocation(u8 *buf, const u8 * const instr, size_t instrlen, u8 *repl, size_t repl_len)\narch/x86/kernel/alternative.c-525-{\n--\narch/x86/kernel/alternative.c=2581=static void *__text_poke(text_poke_f func, void *addr, const void *src, size_t len)\n--\narch/x86/kernel/alternative.c-2700- */\narch/x86/kernel/alternative.c:2701:void *text_poke(void *addr, const void *opcode, size_t len)\narch/x86/kernel/alternative.c-2702-{\n--\narch/x86/kernel/alternative.c-2721- */\narch/x86/kernel/alternative.c:2722:void *text_poke_kgdb(void *addr, const void *opcode, size_t len)\narch/x86/kernel/alternative.c-2723-{\n--\narch/x86/kernel/alternative.c-2726-\narch/x86/kernel/alternative.c:2727:void *text_poke_copy_locked(void *addr, const void *opcode, size_t len,\narch/x86/kernel/alternative.c-2728-\t\t\t bool core_ok)\n--\narch/x86/kernel/alternative.c-2759- */\narch/x86/kernel/alternative.c:2760:void *text_poke_copy(void *addr, const void *opcode, size_t len)\narch/x86/kernel/alternative.c-2761-{\n--\narch/x86/kernel/alternative.c-2776- */\narch/x86/kernel/alternative.c:2777:void *text_poke_set(void *addr, int c, size_t len)\narch/x86/kernel/alternative.c-2778-{\n"
} |
| 7/2 | 2026/08/13 09:28 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 8477 output: 32 thoughts: 205 Thoughts: **My Initial Assessment of the x86 Memory Protection Code** Okay, so I'm looking at these focus symbols: `text_poke`, `text_poke_copy`, and `text_poke_set`. They all look like good indicators of what's going on here, especially when considering memory manipulation. And then we have these `set_memory_*` functions: `set_memory_ro`, `set_memory_rw`, `set_memory_x`, `set_memory_nx`, and now I see `set_memory_rox`. Wait, hang on... `set_memory_rox` is explicitly referenced in the patch comment, mentioning something about execmem ROX ranges being shared between modules and concurrency issues related to huge PMD entries and `set_memory_rox()`. That's important; it's practically telling me where the pain points are! Right, I need to figure out if there are any specific kernel configurations that are relevant here. Where is the code? It appears to be in `arch/x86/kernel/alternative.c` and `arch/x86/mm/pat/set_memory.c`. Those are core x86 files, so it's probably pretty fundamental. Let me quickly scan the `Makefile` in those directories: `arch/x86/kernel/Makefile` and `arch/x86/mm/pat/Makefile` to see if there are any obvious compile-time dependencies or relevant flags. That should give me a better handle on the context of this code and which features might be enabled or disabled. This is where I can start building a mental map of what this code is supposed to do. |
| 8/2 | 2026/08/13 09:28 | tool | grepper |
0mArgs:{
"Expression": "alternative\\.o",
"PathPrefix": "arch/x86/kernel/Makefile"
}Results: {
"Output": "arch/x86/kernel/Makefile=85=obj-y\t\t\t+= pci-dma.o quirks.o kdebugfs.o\narch/x86/kernel/Makefile:86:obj-y\t\t\t+= alternative.o i8253.o hw_breakpoint.o\narch/x86/kernel/Makefile-87-obj-y\t\t\t+= tsc.o tsc_msr.o io_delay.o rtc.o\n"
} |
| 9/2 | 2026/08/13 09:28 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 9189 output: 36 thoughts: 8 Thoughts: Okay, here's my interpretation of that thought process, assuming I'm deeply familiar with memory management and systems programming: **Deconstructing `set_memory.c` - A Micro-Analysis** Alright, so I'm thinking about `set_memory.c`. Immediately, my mind jumps to the core function it likely contains – the implementation of something like `memset` or a custom memory-setting routine. I'm already anticipating the nuances: the trade-offs between speed and safety, especially when dealing with potentially uninitialized memory or memory that's been allocated. I'm mentally running through the various optimization strategies: byte-by-byte assignments versus using word-sized operations (32-bit, 64-bit, whatever the architecture supports). Is it going to be optimized for a specific alignment of the memory region? That's critical for achieving maximum throughput, and I'll be looking for any hints about that in the code. Then, there's the question of boundary checks. Does it handle cases where the size argument is zero, or where the memory region is partially within the allocated space? A robust implementation *must* guard against those scenarios, particularly with considerations for how other processes might be sharing memory, and what could go wrong if those boundary checks were not enforced. Is it going to be using assembly, or is it going to be written in C, and what trade offs were made? The next thing that I'm considering is the implications on threading. Is it thread-safe? Does it need to be? If it is multi-threaded, then there could be various considerations around ensuring that each thread has its own scope and that the function doesn't write to locations outside of the scope. I'm also wondering about the intended use case. Is this a low-level, performance-critical routine used within the kernel? Or is it a library function used by application code? The answer will heavily influence the design choices and the level of error-checking. The more I learn about the context the better I can provide insights. |
| 10/2 | 2026/08/13 09:28 | tool | grepper |
0mArgs:{
"Expression": "set_memory\\.o",
"PathPrefix": "arch/x86/mm/pat/Makefile"
}Results: {
"Output": "arch/x86/mm/pat/Makefile-2-\narch/x86/mm/pat/Makefile:3:obj-y\t\t\t\t:= set_memory.o memtype.o\narch/x86/mm/pat/Makefile-4-\n"
} |
| 11/2 | 2026/08/13 09:28 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 9734 output: 44 thoughts: 158 Thoughts: **My Initial Assessment** Okay, so the assumption is that everything is consistently compiled on x86, which simplifies things. No need to worry about peculiar configurations here. The next step is to quickly determine if key functions, specifically `text_poke` and `set_memory_rox`, are accessible and exported. I know these functions are often crucial for module manipulation, BPF, kprobes, and ftrace, so accessibility is paramount. Yes, it looks like they are heavily used! That's excellent. Knowing that, I can focus on a set of related functions: `set_memory_rox`, `set_memory_ro`, `set_memory_rw`, `set_memory_x`, `set_memory_nx`, `text_poke`, `text_poke_copy`, and `text_poke_set`. These are all prime candidates for closer inspection. Hold on, `set_memory_rox` seems particularly promising. Its functionality is directly relevant to my area of focus. I should double-check that `set_memory_rox` is definitely exported and available for use. I don't want to waste time down a blind alley! |
| 12/2 | 2026/08/13 09:28 | tool | grepper |
0mError:no matches Args: {
"Expression": "EXPORT_SYMBOL.*set_memory_rox",
"PathPrefix": "arch/x86/mm/pat/set_memory.c"
}Results: {
"error": "no matches"
} |
| 13/2 | 2026/08/13 09:28 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 10196 output: 44 thoughts: 24 Thoughts: **A Quick Diagnostic Check** Okay, hold on a second. I was under the impression `set_memory_rox` was part of the standard set, but now that I'm looking at it, it doesn't seem to be exported. That's unusual, and definitely needs a closer look. Before I dive too deep into debugging, let's just confirm the presence and functionality of its companion function. I need to make sure I'm not missing something obvious, or even worse, misremembering the name altogether! Let's verify `set_memory_ro`. A quick check should give me the answer quickly. If `set_memory_ro` is there, the absence of `set_memory_rox` makes the situation more interesting. If `set_memory_ro` isn't there, well then I've likely got a much more serious problem with my setup. |
| 14/2 | 2026/08/13 09:28 | tool | grepper |
0mError:no matches Args: {
"Expression": "EXPORT_SYMBOL.*set_memory_ro",
"PathPrefix": "arch/x86/mm/pat/set_memory.c"
}Results: {
"error": "no matches"
} |
| 15/2 | 2026/08/13 09:28 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 10466 output: 40 thoughts: 65 Thoughts: **Investigating Potential Code Location** Okay, so I'm trying to track down where this particular function or variable might be defined or handled. It feels like I *should* be looking in `arch/x86/mm/pat/set_memory.c`, but there's a nagging feeling that it might be elsewhere, perhaps even *without* the `_GPL` designation. My immediate thought is, are the relevant definitions being exported in a different file altogether? Let's start by confirming my suspicion. I'll take a look at `set_memory_rox` within the `arch/x86/mm/pat/set_memory.c` file itself. That's the most logical place to start digging. From there, I'll branch out if necessary. |
| 16/2 | 2026/08/13 09:28 | tool | grepper |
0mArgs:{
"Expression": "set_memory_rox",
"PathPrefix": "arch/x86/mm/pat/set_memory.c"
}Results: {
"Output": "arch/x86/mm/pat/set_memory.c=2362=int set_memory_ro(unsigned long addr, int numpages)\n--\narch/x86/mm/pat/set_memory.c-2366-\narch/x86/mm/pat/set_memory.c:2367:int set_memory_rox(unsigned long addr, int numpages)\narch/x86/mm/pat/set_memory.c-2368-{\n"
} |
| 17/2 | 2026/08/13 09:28 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 10872 output: 38 thoughts: 17 Thoughts: Okay, here's my attempt at summarizing the thought process, as requested, geared toward an expert audience, and written in the first person: **Navigating File Structure and Expected Output** Alright, I'm diving into this file... and my first instinct is to see how the data's structured. Specifically, are the results I'm looking for aggregated and dumped at the *end* of the file? That's a common pattern, you know, especially with logging or large data dumps. It saves a lot of processing to stream the important data out at once. It's often cleaner than interleaving results throughout. So, I'm betting that the interesting bits are probably waiting at the bottom. I'll need to confirm, but that's my working hypothesis. |
| 18/2 | 2026/08/13 09:29 | tool | grepper |
0mArgs:{
"Expression": "EXPORT_SYMBOL",
"PathPrefix": "arch/x86/mm/pat/set_memory.c"
}Results: {
"Output": "arch/x86/mm/pat/set_memory.c=349=void clflush_cache_range(void *vaddr, unsigned int size)\n--\narch/x86/mm/pat/set_memory.c-354-}\narch/x86/mm/pat/set_memory.c:355:EXPORT_SYMBOL_GPL(clflush_cache_range);\narch/x86/mm/pat/set_memory.c-356-\n--\narch/x86/mm/pat/set_memory.c=358=void arch_invalidate_pmem(void *addr, size_t size)\n--\narch/x86/mm/pat/set_memory.c-361-}\narch/x86/mm/pat/set_memory.c:362:EXPORT_SYMBOL_GPL(arch_invalidate_pmem);\narch/x86/mm/pat/set_memory.c-363-#endif\n--\narch/x86/mm/pat/set_memory.c=366=bool cpu_cache_has_invalidate_memregion(void)\n--\narch/x86/mm/pat/set_memory.c-369-}\narch/x86/mm/pat/set_memory.c:370:EXPORT_SYMBOL_NS_GPL(cpu_cache_has_invalidate_memregion, \"DEVMEM\");\narch/x86/mm/pat/set_memory.c-371-\narch/x86/mm/pat/set_memory.c=372=int cpu_cache_invalidate_memregion(phys_addr_t start, size_t len)\n--\narch/x86/mm/pat/set_memory.c-378-}\narch/x86/mm/pat/set_memory.c:379:EXPORT_SYMBOL_NS_GPL(cpu_cache_invalidate_memregion, \"DEVMEM\");\narch/x86/mm/pat/set_memory.c-380-#endif\n--\narch/x86/mm/pat/set_memory.c=814=pte_t *lookup_address(unsigned long address, unsigned int *level)\n--\narch/x86/mm/pat/set_memory.c-817-}\narch/x86/mm/pat/set_memory.c:818:EXPORT_SYMBOL_GPL(lookup_address);\narch/x86/mm/pat/set_memory.c-819-\n--\narch/x86/mm/pat/set_memory.c=873=phys_addr_t slow_virt_to_phys(void *__virt_addr)\n--\narch/x86/mm/pat/set_memory.c-904-}\narch/x86/mm/pat/set_memory.c:905:EXPORT_SYMBOL_GPL(slow_virt_to_phys);\narch/x86/mm/pat/set_memory.c-906-\n--\narch/x86/mm/pat/set_memory.c=2221=int set_memory_uc(unsigned long addr, int numpages)\n--\narch/x86/mm/pat/set_memory.c-2243-}\narch/x86/mm/pat/set_memory.c:2244:EXPORT_SYMBOL(set_memory_uc);\narch/x86/mm/pat/set_memory.c-2245-\n--\narch/x86/mm/pat/set_memory.c=2262=int set_memory_wc(unsigned long addr, int numpages)\n--\narch/x86/mm/pat/set_memory.c-2276-}\narch/x86/mm/pat/set_memory.c:2277:EXPORT_SYMBOL(set_memory_wc);\narch/x86/mm/pat/set_memory.c-2278-\n--\narch/x86/mm/pat/set_memory.c=2292=int set_memory_wb(unsigned long addr, int numpages)\n--\narch/x86/mm/pat/set_memory.c-2302-}\narch/x86/mm/pat/set_memory.c:2303:EXPORT_SYMBOL(set_memory_wb);\narch/x86/mm/pat/set_memory.c-2304-\n--\narch/x86/mm/pat/set_memory.c=2307=int set_mce_nospec(unsigned long pfn)\n--\narch/x86/mm/pat/set_memory.c-2333-}\narch/x86/mm/pat/set_memory.c:2334:EXPORT_SYMBOL_GPL(set_mce_nospec);\narch/x86/mm/pat/set_memory.c-2335-\n--\narch/x86/mm/pat/set_memory.c=2337=int clear_mce_nospec(unsigned long pfn)\n--\narch/x86/mm/pat/set_memory.c-2342-}\narch/x86/mm/pat/set_memory.c:2343:EXPORT_SYMBOL_GPL(clear_mce_nospec);\narch/x86/mm/pat/set_memory.c-2344-#endif /* CONFIG_X86_64 */\n--\narch/x86/mm/pat/set_memory.c=2525=int set_memory_encrypted(unsigned long addr, int numpages)\n--\narch/x86/mm/pat/set_memory.c-2528-}\narch/x86/mm/pat/set_memory.c:2529:EXPORT_SYMBOL_GPL(set_memory_encrypted);\narch/x86/mm/pat/set_memory.c-2530-\narch/x86/mm/pat/set_memory.c=2531=int set_memory_decrypted(unsigned long addr, int numpages)\n--\narch/x86/mm/pat/set_memory.c-2534-}\narch/x86/mm/pat/set_memory.c:2535:EXPORT_SYMBOL_GPL(set_memory_decrypted);\narch/x86/mm/pat/set_memory.c-2536-\narch/x86/mm/pat/set_memory.c=2537=int set_pages_uc(struct page *page, int numpages)\n--\narch/x86/mm/pat/set_memory.c-2542-}\narch/x86/mm/pat/set_memory.c:2543:EXPORT_SYMBOL(set_pages_uc);\narch/x86/mm/pat/set_memory.c-2544-\n--\narch/x86/mm/pat/set_memory.c=2591=int set_pages_array_uc(struct page **pages, int numpages)\n--\narch/x86/mm/pat/set_memory.c-2594-}\narch/x86/mm/pat/set_memory.c:2595:EXPORT_SYMBOL(set_pages_array_uc);\narch/x86/mm/pat/set_memory.c-2596-\narch/x86/mm/pat/set_memory.c=2597=int set_pages_array_wc(struct page **pages, int numpages)\n--\narch/x86/mm/pat/set_memory.c-2600-}\narch/x86/mm/pat/set_memory.c:2601:EXPORT_SYMBOL(set_pages_array_wc);\narch/x86/mm/pat/set_memory.c-2602-\narch/x86/mm/pat/set_memory.c=2603=int set_pages_wb(struct page *page, int numpages)\n--\narch/x86/mm/pat/set_memory.c-2608-}\narch/x86/mm/pat/set_memory.c:2609:EXPORT_SYMBOL(set_pages_wb);\narch/x86/mm/pat/set_memory.c-2610-\narch/x86/mm/pat/set_memory.c=2611=int set_pages_array_wb(struct page **pages, int numpages)\n--\narch/x86/mm/pat/set_memory.c-2633-}\narch/x86/mm/pat/set_memory.c:2634:EXPORT_SYMBOL(set_pages_array_wb);\narch/x86/mm/pat/set_memory.c-2635-\n"
} |
| 19/2 | 2026/08/13 09:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 13271 output: 34 thoughts: 118 Thoughts: **Investigating Undocumented Memory Manipulation Functions** Alright, let's see. Those functions – `set_memory_ro`, `set_memory_rox`, `set_memory_x`, `set_memory_nx`, and `set_memory_rw` – aren't in the exported symbol table. My gut feeling is that they're likely internal to the kernel, probably integral to things like the module loader or BPF implementation, and compiled directly into the core. Static linking makes sense in that context. Wait a second... didn't I see something about `set_memory_ro` in `arch/x86/mm/pat/set_memory.c`? Okay, let's double-check. Nope, that's not it. Scratch that. Okay, back to basics. Let's start with `include/linux/set_memory.h` and see what that header file tells us about these functions, if anything. Gotta make sure I haven't missed something obvious. This should give me a better handle on their intended usage and scope, at least. |
| 20/2 | 2026/08/13 09:29 | tool | grepper |
0mArgs:{
"Expression": "set_memory_rox",
"PathPrefix": "include/linux/set_memory.h"
}Results: {
"Output": "include/linux/set_memory.h=14=static inline int __must_check set_memory_nx(unsigned long addr, int numpages) { return 0; }\n--\ninclude/linux/set_memory.h-16-\ninclude/linux/set_memory.h:17:#ifndef set_memory_rox\ninclude/linux/set_memory.h:18:static inline int set_memory_rox(unsigned long addr, int numpages)\ninclude/linux/set_memory.h-19-{\n"
} |
| 21/2 | 2026/08/13 09:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 13790 output: 40 thoughts: 55 Thoughts: **Investigating the Unexported Function `set_memory_rox`** Okay, so I'm looking at this `set_memory_rox` function, and the first thing that jumps out is that it's defined in `arch/x86/mm/pat/set_memory.c`. But, hold on a second... it doesn't look like it's actually *exported*. That's a bit unusual. Normally, I'd expect something like this to be declared in a header if it's meant for broader use within the kernel. Let me double-check that this isn't a simple oversight. I'll take a look at `arch/x86/include/asm/set_memory.h` and see if there's any declaration there. Perhaps it's conditionally included, or defined in a way that doesn't immediately jump out. I need to understand *how* and *why* this function is being called, if at all, given its lack of obvious external linkage. This is pretty standard debugging procedure, need to know what to expect and why. Let's see what the header file reveals... |
| 22/2 | 2026/08/13 09:29 | tool | grepper |
0mArgs:{
"Expression": "set_memory_rox",
"PathPrefix": "arch/x86/include/asm/set_memory.h"
}Results: {
"Output": "arch/x86/include/asm/set_memory.h-8-\narch/x86/include/asm/set_memory.h:9:#define set_memory_rox set_memory_rox\narch/x86/include/asm/set_memory.h:10:int set_memory_rox(unsigned long addr, int numpages);\narch/x86/include/asm/set_memory.h-11-\n"
} |
| 23/2 | 2026/08/13 09:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 14245 output: 38 thoughts: 479 Thoughts: **Fuzzing Plan for X86 Memory Management Race Condition** Alright, let's get down to brass tacks. These functions, the ones involved in memory management, are absolutely normal and accessible. Since they're core to memory management, they're undeniably reachable and exploitable for fuzzing. I'm going to add `set_memory_rox`, `set_memory_ro`, `set_memory_rw`, `set_memory_x`, `set_memory_nx`, `text_poke`, `text_poke_copy`, and `text_poke_set` to my `FocusSymbols` list. Now, let's just make sure there aren't any other functions that would be helpful to include. Ah, `cpa_collapse_large_pages` is static, so it's a non-starter for direct fuzzing. The same goes for `change_page_attr_set_clr`. The crux of the matter is this patch: it's fixing a race condition between `text_poke`, which relies on `vmalloc_to_page`, and `cpa_collapse_large_pages`, which is called by `set_memory_rox` and other similar functions. So, fuzzing these concurrently is the money shot! Let's write a clear rationale: The patch alters essential x86 memory management code, specifically within `arch/x86/mm/pat/set_memory.c` and `arch/x86/kernel/alternative.c`. It addresses a race condition between `text_poke` (using `vmalloc_to_page`) and `cpa_collapse_large_pages` (called by `set_memory_rox` and others). The solution involves introducing `mmap_read_lock` and `mmap_write_lock` on `init_mm` to synchronize these operations. This is a functional change to the core kernel logic, and it is accessible through BPF, module loading, kprobes, ftrace, and other common entry points. It's a prime target for fuzzing. My `FocusSymbols` are: * `set_memory_rox` * `set_memory_ro` * `set_memory_rw` * `set_memory_x` * `set_memory_nx` * `text_poke` * `text_poke_copy` * `text_poke_set` No special configurations are needed, because this is core x86 code. Just to be sure, let me double-check if `text_poke` is exported. |
| 24/2 | 2026/08/13 09:29 | tool | grepper |
0mError:no matches Args: {
"Expression": "EXPORT_SYMBOL.*text_poke",
"PathPrefix": "arch/x86/kernel/alternative.c"
}Results: {
"error": "no matches"
} |
| 25/2 | 2026/08/13 09:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 15346 output: 39 thoughts: 106 Thoughts: **Investigating the Scope of `text_poke`** Okay, so I'm looking at `text_poke`, and initially, it seemed like this function wasn't explicitly exported, meaning it might be internal. But then I realized, hang on, it's actually used by a bunch of critical core code components, specifically within the x86 architecture. I need to be sure. I see `text_poke` being used by `arch/x86/kernel/kprobes/core.c`, which is a key part of the kernel's probing mechanism, and also by `arch/x86/kernel/ftrace.c`, the function tracing code, and `arch/x86/kernel/jump_label.c`, dealing with jump labels. So, my hunch is that it might be defined in a header that is specific to the x86 architecture, hence not explicitly exported. Let me quickly check if `text_poke` is declared in `arch/x86/include/asm/text-patching.h`. That's where I'd expect to find it given its purpose. |
| 26/2 | 2026/08/13 09:29 | tool | grepper |
0mArgs:{
"Expression": "text_poke",
"PathPrefix": "arch/x86/include/asm/text-patching.h"
}Results: {
"Output": "arch/x86/include/asm/text-patching.h-15-\narch/x86/include/asm/text-patching.h:16:extern void text_poke_early(void *addr, const void *opcode, size_t len);\narch/x86/include/asm/text-patching.h-17-\narch/x86/include/asm/text-patching.h:18:extern void text_poke_apply_relocation(u8 *buf, const u8 * const instr, size_t instrlen, u8 *repl, size_t repl_len);\narch/x86/include/asm/text-patching.h-19-\n--\narch/x86/include/asm/text-patching.h-33- */\narch/x86/include/asm/text-patching.h:34:extern void *text_poke(void *addr, const void *opcode, size_t len);\narch/x86/include/asm/text-patching.h:35:extern void smp_text_poke_sync_each_cpu(void);\narch/x86/include/asm/text-patching.h:36:extern void *text_poke_kgdb(void *addr, const void *opcode, size_t len);\narch/x86/include/asm/text-patching.h:37:extern void *text_poke_copy(void *addr, const void *opcode, size_t len);\narch/x86/include/asm/text-patching.h:38:#define text_poke_copy text_poke_copy\narch/x86/include/asm/text-patching.h:39:extern void *text_poke_copy_locked(void *addr, const void *opcode, size_t len, bool core_ok);\narch/x86/include/asm/text-patching.h:40:extern void *text_poke_set(void *addr, int c, size_t len);\narch/x86/include/asm/text-patching.h:41:extern int smp_text_poke_int3_handler(struct pt_regs *regs);\narch/x86/include/asm/text-patching.h:42:extern void smp_text_poke_single(void *addr, const void *opcode, size_t len, const void *emulate);\narch/x86/include/asm/text-patching.h-43-\narch/x86/include/asm/text-patching.h:44:extern void smp_text_poke_batch_add(void *addr, const void *opcode, size_t len, const void *emulate);\narch/x86/include/asm/text-patching.h:45:extern void smp_text_poke_batch_finish(void);\narch/x86/include/asm/text-patching.h-46-\n--\narch/x86/include/asm/text-patching.h=64=static __always_inline int text_opcode_size(u8 opcode)\n--\narch/x86/include/asm/text-patching.h-83-\narch/x86/include/asm/text-patching.h:84:union text_poke_insn {\narch/x86/include/asm/text-patching.h-85-\tu8 text[TEXT_POKE_MAX_OPCODE_SIZE];\n--\narch/x86/include/asm/text-patching.h=93=void __text_gen_insn(void *buf, u8 opcode, const void *addr, const void *dest, int size)\narch/x86/include/asm/text-patching.h-94-{\narch/x86/include/asm/text-patching.h:95:\tunion text_poke_insn *insn = buf;\narch/x86/include/asm/text-patching.h-96-\n--\narch/x86/include/asm/text-patching.h=123=void *text_gen_insn(u8 opcode, const void *addr, const void *dest)\narch/x86/include/asm/text-patching.h-124-{\narch/x86/include/asm/text-patching.h:125:\tstatic union text_poke_insn insn; /* per instance */\narch/x86/include/asm/text-patching.h-126-\t__text_gen_insn(\u0026insn, opcode, addr, dest, text_opcode_size(opcode));\n--\narch/x86/include/asm/text-patching.h=130=extern int after_bootmem;\narch/x86/include/asm/text-patching.h:131:extern __ro_after_init struct mm_struct *text_poke_mm;\narch/x86/include/asm/text-patching.h:132:extern __ro_after_init unsigned long text_poke_mm_addr;\narch/x86/include/asm/text-patching.h-133-\n"
} |
| 27/2 | 2026/08/13 09:29 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 17001 output: 231 thoughts: 33 Thoughts: Okay, here's my summary, taking on the persona you requested: **Making the Connection** Right, so these things – the "thoughts" – they're being exported in the header, which immediately makes them key. That export implies they're being accessed and utilized by other parts of the system, acting as a crucial communication point. Therefore, they function as pretty solid focus symbols, the kind of elements I can really sink my teeth into. Considering their role as communication tools, I'm going to call `set-results` right now. It is a natural next step given this understanding. |
| 28/2 | 2026/08/13 09:29 | tool | set-results |
0mArgs:{
"EnableConfigs": [],
"FocusSymbols": [
"set_memory_rox",
"set_memory_ro",
"set_memory_rw",
"set_memory_x",
"set_memory_nx",
"text_poke",
"text_poke_copy",
"text_poke_set"
],
"Reasoning": "The patch modifies core x86 memory management code (arch/x86/mm/pat/set_memory.c and arch/x86/kernel/alternative.c) to fix a race condition between text_poke (which uses vmalloc_to_page) and cpa_collapse_large_pages (which is called by set_memory_rox and others). It introduces mmap_read_lock and mmap_write_lock on init_mm to serialize these operations. This is a functional change to core kernel logic that is reachable via BPF, module loading, kprobes, ftrace, etc. It is definitely worth fuzzing.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": null,
"FocusSymbols": [
"set_memory_rox",
"set_memory_ro",
"set_memory_rw",
"set_memory_x",
"set_memory_nx",
"text_poke",
"text_poke_copy",
"text_poke_set"
],
"Reasoning": "The patch modifies core x86 memory management code (arch/x86/mm/pat/set_memory.c and arch/x86/kernel/alternative.c) to fix a race condition between text_poke (which uses vmalloc_to_page) and cpa_collapse_large_pages (which is called by set_memory_rox and others). It introduces mmap_read_lock and mmap_write_lock on init_mm to serialize these operations. This is a functional change to core kernel logic that is reachable via BPF, module loading, kprobes, ftrace, etc. It is definitely worth fuzzing.",
"WorthFuzzing": true
} |
| 29/1 | 2026/08/13 09:29 | action | If |
0mArgs:{
"WorthFuzzing": true
}Results: null |
| 30/2 | 2026/08/13 09:29 | agent | kmsan-evaluator |
0mModel:best-expensive Args: null Results: {
"KMSANReasoning": "The patch addresses race conditions in page table collapse (by adding mmap_read_lock/mmap_write_lock on init_mm), fixes a bitwise logic bug when checking page attributes, and updates page table allocation/freeing functions. These changes primarily prevent use-after-free bugs and data races, which are effectively detected by KASAN and standard concurrency/locking detectors (like LOCKDEP). There are no new structures exposed to user space, nor any logic that would introduce or expose uninitialized memory reads. Therefore, a dedicated KMSAN fuzzing session is not required.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
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 321f148bee10eff425638c5e498f93627ad23fb5
Author: syz-cluster <triage@syzkaller.com>
Date: Thu Aug 13 09:28:19 2026 +0000
syz-cluster: applied patch under review
diff --git a/arch/x86/kernel/alternative.c b/arch/x86/kernel/alternative.c
index 62936a3bde19b..f81d6bc90a6ff 100644
--- a/arch/x86/kernel/alternative.c
+++ b/arch/x86/kernel/alternative.c
@@ -6,6 +6,9 @@
#include <linux/vmalloc.h>
#include <linux/memory.h>
#include <linux/execmem.h>
+#include <linux/cleanup.h>
+#include <linux/kgdb.h>
+#include <linux/mmap_lock.h>
#include <asm/text-patching.h>
#include <asm/insn.h>
@@ -2543,6 +2546,38 @@ static void text_poke_memset(void *dst, const void *src, size_t len)
typedef void text_poke_f(void *dst, const void *src, size_t len);
+static void __poke_vmalloc_pages(struct page **pages, void *addr,
+ bool cross_page_boundary)
+{
+ pages[0] = vmalloc_to_page(addr);
+ if (cross_page_boundary)
+ pages[1] = vmalloc_to_page(addr + PAGE_SIZE);
+}
+
+static void poke_vmalloc_pages(struct page **pages, void *addr,
+ bool cross_page_boundary)
+{
+ if (in_dbg_master()) {
+ /*
+ * If called from kgdb cannot sleep, but all other CPUs stopped
+ * anyway so safe to proceed without locks
+ */
+ __poke_vmalloc_pages(pages, addr, cross_page_boundary);
+ } else {
+ /*
+ * execmem ROX ranges are shared between modules and can be
+ * collapsed to huge PMD entries, and this collapse can happen
+ * concurrently with a racing set_memory_rox().
+ *
+ * Prevent vmalloc_to_page() from racing by acquiring an
+ * init_mm read lock which pairs with the init_mm write lock in
+ * cpa_collapse_large_pages().
+ */
+ guard(mmap_read_lock)(&init_mm);
+ __poke_vmalloc_pages(pages, addr, cross_page_boundary);
+ }
+}
+
static void *__text_poke(text_poke_f func, void *addr, const void *src, size_t len)
{
bool cross_page_boundary = offset_in_page(addr) + len > PAGE_SIZE;
@@ -2560,9 +2595,7 @@ static void *__text_poke(text_poke_f func, void *addr, const void *src, size_t l
BUG_ON(!after_bootmem);
if (!core_kernel_text((unsigned long)addr)) {
- pages[0] = vmalloc_to_page(addr);
- if (cross_page_boundary)
- pages[1] = vmalloc_to_page(addr + PAGE_SIZE);
+ poke_vmalloc_pages(pages, addr, cross_page_boundary);
} else {
pages[0] = virt_to_page(addr);
WARN_ON(!PageReserved(pages[0]));
diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c
index d8d057f444178..430d0b4483718 100644
--- a/arch/x86/mm/pat/set_memory.c
+++ b/arch/x86/mm/pat/set_memory.c
@@ -22,6 +22,7 @@
#include <linux/cc_platform.h>
#include <linux/set_memory.h>
#include <linux/memregion.h>
+#include <linux/cleanup.h>
#include <asm/e820/api.h>
#include <asm/processor.h>
@@ -49,7 +50,8 @@ struct cpa_data {
unsigned int flags;
unsigned int force_split : 1,
force_static_prot : 1,
- force_flush_all : 1;
+ force_flush_all : 1,
+ init_mm_read_locked : 1;
struct page **pages;
};
@@ -409,7 +411,7 @@ static void __cpa_flush_tlb(void *data)
static int collapse_large_pages(unsigned long addr, struct list_head *pgtables);
-static void cpa_collapse_large_pages(struct cpa_data *cpa)
+static void __cpa_collapse_large_pages(struct cpa_data *cpa)
{
unsigned long start, addr, end;
struct ptdesc *ptdesc, *tmp;
@@ -439,10 +441,30 @@ static void cpa_collapse_large_pages(struct cpa_data *cpa)
list_for_each_entry_safe(ptdesc, tmp, &pgtables, pt_list) {
list_del(&ptdesc->pt_list);
- pagetable_free(ptdesc);
+ /*
+ * Only early alloc'd direct map should not be flagged PG_table
+ * here and those shouldn't be collapsed. However be abundantly
+ * cautious and handle the !PG_table case too.
+ */
+ if (PageTable((ptdesc_page(ptdesc))))
+ pagetable_dtor_free(ptdesc);
+ else
+ pagetable_free(ptdesc);
}
}
+static void cpa_collapse_large_pages(struct cpa_data *cpa)
+{
+ /*
+ * Take the mmap write lock on init_mm to:
+ * - Avoid a use-after-free if raced by ptdump (which takes its own
+ * write lock on init_mm).
+ * - Serialise concurrent CPA walkers.
+ */
+ scoped_guard(mmap_write_lock, &init_mm)
+ __cpa_collapse_large_pages(cpa);
+}
+
static void cpa_flush(struct cpa_data *cpa, int cache)
{
unsigned int i;
@@ -732,7 +754,7 @@ pte_t *lookup_address_in_pgd_attr(pgd_t *pgd, unsigned long address,
*level = PG_LEVEL_512G;
*nx |= pgd_flags(*pgd) & _PAGE_NX;
- *rw &= pgd_flags(*pgd) & _PAGE_RW;
+ *rw &= !!(pgd_flags(*pgd) & _PAGE_RW);
p4d = p4d_offset(pgd, address);
if (p4d_none(*p4d))
@@ -743,7 +765,7 @@ pte_t *lookup_address_in_pgd_attr(pgd_t *pgd, unsigned long address,
*level = PG_LEVEL_1G;
*nx |= p4d_flags(*p4d) & _PAGE_NX;
- *rw &= p4d_flags(*p4d) & _PAGE_RW;
+ *rw &= !!(p4d_flags(*p4d) & _PAGE_RW);
pud = pud_offset(p4d, address);
if (pud_none(*pud))
@@ -754,7 +776,7 @@ pte_t *lookup_address_in_pgd_attr(pgd_t *pgd, unsigned long address,
*level = PG_LEVEL_2M;
*nx |= pud_flags(*pud) & _PAGE_NX;
- *rw &= pud_flags(*pud) & _PAGE_RW;
+ *rw &= !!(pud_flags(*pud) & _PAGE_RW);
pmd = pmd_offset(pud, address);
if (pmd_none(*pmd))
@@ -765,7 +787,7 @@ pte_t *lookup_address_in_pgd_attr(pgd_t *pgd, unsigned long address,
*level = PG_LEVEL_4K;
*nx |= pmd_flags(*pmd) & _PAGE_NX;
- *rw &= pmd_flags(*pmd) & _PAGE_RW;
+ *rw &= !!(pmd_flags(*pmd) & _PAGE_RW);
return pte_offset_kernel(pmd, address);
}
@@ -1120,11 +1142,10 @@ static void split_set_pte(struct cpa_data *cpa, pte_t *pte, unsigned long pfn,
static int
__split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address,
- struct ptdesc *ptdesc)
+ pte_t *pbase)
{
unsigned long lpaddr, lpinc, ref_pfn, pfn, pfninc = 1;
- struct page *base = ptdesc_page(ptdesc);
- pte_t *pbase = (pte_t *)page_address(base);
+ struct page *base = virt_to_page(pbase);
unsigned int i, level;
pgprot_t ref_prot;
bool nx, rw;
@@ -1224,16 +1245,20 @@ __split_large_page(struct cpa_data *cpa, pte_t *kpte, unsigned long address,
static int split_large_page(struct cpa_data *cpa, pte_t *kpte,
unsigned long address)
{
- struct ptdesc *ptdesc;
+ pte_t *pte;
spin_unlock(&cpa_lock);
- ptdesc = pagetable_alloc(GFP_KERNEL, 0);
+ if (cpa->init_mm_read_locked)
+ mmap_read_unlock(&init_mm);
+ pte = pte_alloc_one_kernel(&init_mm);
+ if (cpa->init_mm_read_locked)
+ mmap_read_lock(&init_mm);
spin_lock(&cpa_lock);
- if (!ptdesc)
+ if (!pte)
return -ENOMEM;
- if (__split_large_page(cpa, kpte, address, ptdesc))
- pagetable_free(ptdesc);
+ if (__split_large_page(cpa, kpte, address, pte))
+ pte_free_kernel(&init_mm, pte);
return 0;
}
@@ -2121,7 +2146,11 @@ static int change_page_attr_set_clr(unsigned long *addr, int numpages,
cpa.curpage = 0;
cpa.force_split = force_split;
- ret = __change_page_attr_set_clr(&cpa, 1);
+ /* Avoid race with concurrent CPA collapse. */
+ cpa.init_mm_read_locked = true;
+ scoped_guard(mmap_read_lock, &init_mm)
+ ret = __change_page_attr_set_clr(&cpa, 1);
+ cpa.init_mm_read_locked = false;
/*
* Check whether we really changed something:
diff --git a/include/linux/mmap_lock.h b/include/linux/mmap_lock.h
index 04b8f61ece5d8..f4ceb968aeb3d 100644
--- a/include/linux/mmap_lock.h
+++ b/include/linux/mmap_lock.h
@@ -621,6 +621,8 @@ static inline void mmap_read_unlock(struct mm_struct *mm)
DEFINE_GUARD(mmap_read_lock, struct mm_struct *,
mmap_read_lock(_T), mmap_read_unlock(_T))
+DEFINE_GUARD(mmap_write_lock, struct mm_struct *,
+ mmap_write_lock(_T), mmap_write_unlock(_T))
static inline void mmap_read_unlock_non_owner(struct mm_struct *mm)
{
|
| 31/3 | 2026/08/13 09:29 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 4991 output: 24 thoughts: 435 Thoughts: **My Analysis of this Patch** Okay, so this patch is touching `arch/x86/kernel/alternative.c` and `arch/x86/mm/pat/set_memory.c`. My initial read tells me it's focused on protecting some critical memory operations, specifically around large page collapse and virtual memory mapping. The introduction of `mmap_read_lock` and `mmap_write_lock` on `init_mm` is definitely a red flag, but a good one. It indicates a concern about race conditions between functions like `vmalloc_to_page` (or, by inference, things like `ptdump` which might be using similar mechanisms) and `cpa_collapse_large_pages`. That makes sense; if those functions are manipulating page tables, you *have* to protect it. The changes to how page tables are freed in `cpa_collapse_large_pages` using `pagetable_dtor_free` if `PageTable` is set also looks good. It seems like they're trying to be more careful about how page table memory is reclaimed after large pages are collapsed. Similarly, switching to `pte_alloc_one_kernel` and `pte_free_kernel` in `split_large_page` indicates a potential optimization or a desire to use the more modern interfaces. I'll need to confirm that that is the case. The most interesting part, though, is the bitwise AND fix in `lookup_address_in_pgd_attr`. The original code, `*rw &= pgd_flags(*pgd) & _PAGE_RW;` seems like it would introduce incorrect behaviour, now they've changed it to `*rw &= !!(pgd_flags(*pgd) & _PAGE_RW);`. This is because `*rw` is a `bool` pointer, isn't it? That bitwise operation needs to be a boolean operation. So, they're ensuring `*rw` is either 0 or 1, which is correct. I have to check the definition of `lookup_address_in_pgd_attr` to make sure I am correct, but this seems like a significant bug fix. My immediate thought is, let's look for potential uninitialized memory risks. But, wait, based on a quick scan, it looks like most of the changes are about locking, page table management, and correcting a bitwise logic error. No new structures are exposed to user space, and I don't see any obvious uninitialized variables. This is a good sign. The bitwise fix is the most interesting; ensuring the correct boolean result is important. Now I need to confirm `*rw` is indeed a `bool` pointer in `lookup_address_in_pgd_attr`. |
| 32/3 | 2026/08/13 09:29 | tool | grepper |
0mArgs:{
"Expression": "lookup_address_in_pgd_attr"
}Results: {
"Output": "arch/x86/include/asm/pgtable_types.h=567=extern pte_t *lookup_address_in_pgd(pgd_t *pgd, unsigned long address,\narch/x86/include/asm/pgtable_types.h-568-\t\t\t\t unsigned int *level);\narch/x86/include/asm/pgtable_types.h:569:pte_t *lookup_address_in_pgd_attr(pgd_t *pgd, unsigned long address,\narch/x86/include/asm/pgtable_types.h-570-\t\t\t\t unsigned int *level, bool *nx, bool *rw);\n--\narch/x86/mm/fault.c=515=show_fault_oops(struct pt_regs *regs, unsigned long error_code, unsigned long address)\n--\narch/x86/mm/fault.c-528-\narch/x86/mm/fault.c:529:\t\tpte = lookup_address_in_pgd_attr(pgd, address, \u0026level, \u0026nx, \u0026rw);\narch/x86/mm/fault.c-530-\n--\narch/x86/mm/pat/set_memory.c=693=static inline pgprot_t verify_rwx(pgprot_t old, pgprot_t new, unsigned long start,\n--\narch/x86/mm/pat/set_memory.c-740- */\narch/x86/mm/pat/set_memory.c:741:pte_t *lookup_address_in_pgd_attr(pgd_t *pgd, unsigned long address,\narch/x86/mm/pat/set_memory.c-742-\t\t\t\t unsigned int *level, bool *nx, bool *rw)\n--\narch/x86/mm/pat/set_memory.c=799=pte_t *lookup_address_in_pgd(pgd_t *pgd, unsigned long address,\n--\narch/x86/mm/pat/set_memory.c-803-\narch/x86/mm/pat/set_memory.c:804:\treturn lookup_address_in_pgd_attr(pgd, address, level, \u0026nx, \u0026rw);\narch/x86/mm/pat/set_memory.c-805-}\n--\narch/x86/mm/pat/set_memory.c=820=static pte_t *_lookup_address_cpa(struct cpa_data *cpa, unsigned long address,\n--\narch/x86/mm/pat/set_memory.c-829-\narch/x86/mm/pat/set_memory.c:830:\treturn lookup_address_in_pgd_attr(pgd, address, level, nx, rw);\narch/x86/mm/pat/set_memory.c-831-}\n"
} |
| 33/3 | 2026/08/13 09:29 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 6779 output: 146 thoughts: 362 Thoughts: **My Assessment of the Proposed Code Changes** Okay, let me break this down. From what I see, we're looking at a patch that primarily concerns itself with concurrency and a sneaky logic bug. My understanding is, `nx` and `rw` are, as I suspected, essentially boolean pointers (`bool *`). The bitwise AND operation `*rw &= pgd_flags(*pgd) & _PAGE_RW;` was the root of the problem. If `*rw` was true (1) and `_PAGE_RW` had its RW bit set (2), the AND operation would unexpectedly clear `*rw` to false (0), even when it should have remained true! The proposed fix, `*rw &= !!(pgd_flags(*pgd) & _PAGE_RW);`, is the right way to coerce the result to either 1 or 0, resolving the issue. This is definitely a logic bug, not an uninitialized memory problem, which is good. The other changes are around locking, specifically to address issues during concurrent page table collapse. These are essential to prevent those nasty use-after-free scenarios and potential race conditions – which are exactly what we want to avoid. None of these changes seem to introduce or expose any new instances of uninitialized memory. KASAN and LOCKDEP are going to be perfect for catching those use-after-free and locking issues, they're tailor-made for it. KMSAN isn't needed here. The patch resolves data races during page table collapse by adding mmap_read_lock/mmap_write_lock, fixes a bitwise logic bug when checking page attributes, and updates page table allocation/freeing functions. The changes prevent use-after-free and data races, which KASAN and standard locking detectors are perfect for. There are no new structures exposed to user space, nor any logic that would introduce or expose uninitialized memory reads. This patch doesn't need KMSAN. |
| 34/3 | 2026/08/13 09:29 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch addresses race conditions in page table collapse (by adding mmap_read_lock/mmap_write_lock on init_mm), fixes a bitwise logic bug when checking page attributes, and updates page table allocation/freeing functions. These changes primarily prevent use-after-free bugs and data races, which are effectively detected by KASAN and standard concurrency/locking detectors (like LOCKDEP). There are no new structures exposed to user space, nor any logic that would introduce or expose uninitialized memory reads. Therefore, a dedicated KMSAN fuzzing session is not required.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch addresses race conditions in page table collapse (by adding mmap_read_lock/mmap_write_lock on init_mm), fixes a bitwise logic bug when checking page attributes, and updates page table allocation/freeing functions. These changes primarily prevent use-after-free bugs and data races, which are effectively detected by KASAN and standard concurrency/locking detectors (like LOCKDEP). There are no new structures exposed to user space, nor any logic that would introduce or expose uninitialized memory reads. Therefore, a dedicated KMSAN fuzzing session is not required.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|