AI Trajectory


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

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [
    "walk_page_range",
    "walk_page_vma",
    "walk_page_mapping"
  ],
  "KMSANReasoning": "The patch fixes a logic bug in the page table walker (`mm/pagewalk.c`) where a stale `walk-\u003eaction` (e.g., `ACTION_AGAIN`) could escape a lower-level walk function and cause the higher-level walk function to incorrectly repeat the walk over the same range. The fix simply moves the assignment `walk-\u003eaction = ACTION_SUBTREE;` to the beginning of the loop. This change only affects control flow and does not introduce any new memory allocations, uninitialized variables, or data structures copied to user space. Therefore, there is no risk of uninitialized memory use or info-leaks, and KMSAN is not applicable. Standard testing and KASAN are sufficient.",
  "NeedsKMSAN": false,
  "Reasoning": "The patch fixes a race condition in core MM page walking logic (walk_pmd_range, walk_pud_range) where a stale walk-\u003eaction could cause the same range to be walked twice. This is a functional change in core kernel logic that is reachable from userspace (e.g., via /proc/pid/smaps or madvise).",
  "WorthFuzzing": true
}

1/1 2026/08/11 17:10 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 6700d699cf347d4d09312f8be37265a6b731708e\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Tue Aug 11 17:10:46 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/mm/pagewalk.c b/mm/pagewalk.c\nindex 5d87c632a2550..d3bfece319336 100644\n--- a/mm/pagewalk.c\n+++ b/mm/pagewalk.c\n@@ -126,6 +126,7 @@ static int walk_pmd_range(pud_t *pud, unsigned long addr, unsigned long end,\n \tpmd = pmd_offset(pud, addr);\n \tdo {\n again:\n+\t\twalk-\u003eaction = ACTION_SUBTREE;\n \t\tnext = pmd_addr_end(addr, end);\n \t\tif (pmd_none(*pmd)) {\n \t\t\tif (has_install)\n@@ -138,8 +139,6 @@ static int walk_pmd_range(pud_t *pud, unsigned long addr, unsigned long end,\n \t\t\t\tcontinue;\n \t\t}\n \n-\t\twalk-\u003eaction = ACTION_SUBTREE;\n-\n \t\t/*\n \t\t * This implies that each -\u003epmd_entry() handler\n \t\t * needs to know about pmd_trans_huge() pmds\n@@ -196,6 +195,7 @@ static int walk_pud_range(p4d_t *p4d, unsigned long addr, unsigned long end,\n \tpud = pud_offset(p4d, addr);\n \tdo {\n  again:\n+\t\twalk-\u003eaction = ACTION_SUBTREE;\n \t\tnext = pud_addr_end(addr, end);\n \t\tif (pud_none(*pud)) {\n \t\t\tif (has_install)\n@@ -208,8 +208,6 @@ static int walk_pud_range(p4d_t *p4d, unsigned long addr, unsigned long end,\n \t\t\t\tcontinue;\n \t\t}\n \n-\t\twalk-\u003eaction = ACTION_SUBTREE;\n-\n \t\tif (ops-\u003epud_entry)\n \t\t\terr = ops-\u003epud_entry(pud, addr, next, walk);\n \t\tif (err)\ndiff --git a/tools/testing/selftests/mm/.gitignore b/tools/testing/selftests/mm/.gitignore\nindex 9ccd9e1447e66..92f981f97740f 100644\n--- a/tools/testing/selftests/mm/.gitignore\n+++ b/tools/testing/selftests/mm/.gitignore\n@@ -66,3 +66,4 @@ merge\n prctl_thp_disable\n rmap\n folio_split_race_test\n+pagewalk_race_test\ndiff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile\nindex e6df968f0971c..cde9b22f121d4 100644\n--- a/tools/testing/selftests/mm/Makefile\n+++ b/tools/testing/selftests/mm/Makefile\n@@ -105,6 +105,7 @@ TEST_GEN_FILES += guard-regions\n TEST_GEN_FILES += merge\n TEST_GEN_FILES += rmap\n TEST_GEN_FILES += folio_split_race_test\n+TEST_GEN_FILES += pagewalk_race_test\n \n ifneq ($(ARCH),arm64)\n TEST_GEN_FILES += soft-dirty\n@@ -163,6 +164,7 @@ TEST_PROGS += ksft_mlock.sh\n TEST_PROGS += ksft_mmap.sh\n TEST_PROGS += ksft_mremap.sh\n TEST_PROGS += ksft_pagemap.sh\n+TEST_PROGS += ksft_pagewalk.sh\n TEST_PROGS += ksft_pfnmap.sh\n TEST_PROGS += ksft_pkey.sh\n TEST_PROGS += ksft_process_madv.sh\ndiff --git a/tools/testing/selftests/mm/ksft_pagewalk.sh b/tools/testing/selftests/mm/ksft_pagewalk.sh\nnew file mode 100755\nindex 0000000000000..6f6c3ee1c13ef\n--- /dev/null\n+++ b/tools/testing/selftests/mm/ksft_pagewalk.sh\n@@ -0,0 +1,4 @@\n+#!/bin/sh -e\n+# SPDX-License-Identifier: GPL-2.0\n+\n+./run_vmtests.sh -t pagewalk\ndiff --git a/tools/testing/selftests/mm/pagewalk_race_test.c b/tools/testing/selftests/mm/pagewalk_race_test.c\nnew file mode 100644\nindex 0000000000000..42fd6e75e821e\n--- /dev/null\n+++ b/tools/testing/selftests/mm/pagewalk_race_test.c\n@@ -0,0 +1,138 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * Regression test for a stale walk-\u003eaction escaping walk_pmd_range() and\n+ * making walk_pud_range() walk the same range twice.\n+ *\n+ * The mapping is two PMDs inside one PUD. PMD 0 is populated once and left\n+ * alone, PMD 1 is faulted in and dropped again by a second thread. Clearing\n+ * PMD 1 under smaps_pte_range() makes it raise ACTION_AGAIN, and since it is\n+ * the last entry the stale value leaves walk_pmd_range(), so smaps accounts\n+ * PMD 0 twice. A kernel that does not reclaim the emptied page table never\n+ * clears PMD 1 and so never hits the race.\n+ *\n+ * A hit can only come from the kernel counting the same page twice, so the\n+ * test cannot fail spuriously.\n+ */\n+#define _GNU_SOURCE\n+\n+#include \u003cpthread.h\u003e\n+#include \u003cstdatomic.h\u003e\n+#include \u003cstdio.h\u003e\n+#include \u003cstdlib.h\u003e\n+#include \u003cstring.h\u003e\n+#include \u003csys/mman.h\u003e\n+#include \u003cunistd.h\u003e\n+\n+#include \"vm_util.h\"\n+#include \"kselftest.h\"\n+\n+#define NR_PMDS\t\t2\n+#define NR_ROUNDS\t20000\n+/* Cap on how much of PMD 0 to fault in, so that a large PMD stays cheap. */\n+#define POP_MAX\t\t(2 * 1024 * 1024)\n+\n+static char *area;\n+static size_t pmd_size;\n+static atomic_int stop;\n+\n+static void *racer(void *arg)\n+{\n+\tchar *pmd1 = area + pmd_size;\n+\n+\twhile (atomic_load_explicit(\u0026stop, memory_order_acquire) == 0) {\n+\t\t/* madvise() below keeps the compiler from lifting this out. */\n+\t\t*pmd1 = 1;\n+\t\tmadvise(pmd1, pmd_size, MADV_DONTNEED);\n+\t}\n+\treturn NULL;\n+}\n+\n+static unsigned long smaps_rss_kb(void)\n+{\n+\tchar buf[1024];\n+\tchar *entry;\n+\n+\tentry = __get_smap_entry(area, \"Rss:\", buf, sizeof(buf));\n+\tif (!entry)\n+\t\tksft_exit_fail_msg(\"no Rss: entry for the test mapping\\n\");\n+\n+\treturn strtoul(entry, NULL, 10);\n+}\n+\n+int main(void)\n+{\n+\tunsigned long max_rss_kb, rss_kb = 0;\n+\tsize_t size, pop_size, i;\n+\tpthread_t thread;\n+\tchar *raw;\n+\n+\tksft_print_header();\n+\n+\tpmd_size = read_pmd_pagesize();\n+\tif (!pmd_size)\n+\t\tksft_exit_skip(\"Cannot determine PMD size\\n\");\n+\n+\tif (sysconf(_SC_NPROCESSORS_ONLN) \u003c 2)\n+\t\tksft_exit_skip(\"Need at least 2 CPUs to race\\n\");\n+\n+\tsize = NR_PMDS * pmd_size;\n+\n+\t/*\n+\t * Align to the mapping size to stay inside one PUD, then trim the\n+\t * slack so that smaps has exactly one VMA to report.\n+\t */\n+\traw = mmap(NULL, 2 * size, PROT_READ | PROT_WRITE,\n+\t\t   MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);\n+\tif (raw == MAP_FAILED)\n+\t\tksft_exit_fail_msg(\"mmap failed\\n\");\n+\n+\tarea = (char *)(((unsigned long)raw + size - 1) \u0026 ~(size - 1));\n+\tif (area != raw)\n+\t\tmunmap(raw, area - raw);\n+\tif (raw + 2 * size != area + size)\n+\t\tmunmap(area + size, raw + 2 * size - (area + size));\n+\n+\t/* A huge PMD never reaches pte_offset_map_lock(), so keep them out. */\n+\tif (madvise(area, size, MADV_NOHUGEPAGE))\n+\t\tksft_exit_skip(\"MADV_NOHUGEPAGE failed\\n\");\n+\n+\tpop_size = pmd_size \u003c POP_MAX ? pmd_size : POP_MAX;\n+\tmemset(area, 1, pop_size);\n+\n+\tmax_rss_kb = (pop_size \u003e\u003e 10) + 256;\n+\n+\t/* Over the limit before racing means this is not our own mapping. */\n+\trss_kb = smaps_rss_kb();\n+\tif (rss_kb \u003e max_rss_kb)\n+\t\tksft_exit_fail_msg(\"Rss is %lu kB before racing, expected at most %lu kB\\n\",\n+\t\t\t\t   rss_kb, max_rss_kb);\n+\n+\tksft_set_plan(1);\n+\tksft_print_msg(\"racing smaps against MADV_DONTNEED, %d rounds\\n\",\n+\t\t       NR_ROUNDS);\n+\n+\tif (pthread_create(\u0026thread, NULL, racer, NULL))\n+\t\tksft_exit_fail_msg(\"pthread_create failed\\n\");\n+\n+\tfor (i = 0; i \u003c NR_ROUNDS; i++) {\n+\t\trss_kb = smaps_rss_kb();\n+\t\tif (rss_kb \u003e max_rss_kb)\n+\t\t\tbreak;\n+\t}\n+\n+\tatomic_store_explicit(\u0026stop, 1, memory_order_release);\n+\tpthread_join(thread, NULL);\n+\n+\tif (i \u003c NR_ROUNDS) {\n+\t\tksft_print_msg(\"walk ran twice over the same range\\n\");\n+\t\tksft_test_result_fail(\"Rss %lu kB exceeds %lu kB, round %zu\\n\",\n+\t\t\t\t      rss_kb, max_rss_kb, i);\n+\t} else {\n+\t\tksft_test_result_pass(\"Rss within %lu kB over %d rounds\\n\",\n+\t\t\t\t      max_rss_kb, NR_ROUNDS);\n+\t}\n+\n+\tksft_exit(i == NR_ROUNDS);\n+\n+\treturn 0;\n+}\ndiff --git a/tools/testing/selftests/mm/run_vmtests.sh b/tools/testing/selftests/mm/run_vmtests.sh\nindex 8c296dedf0474..d90c6370814f7 100755\n--- a/tools/testing/selftests/mm/run_vmtests.sh\n+++ b/tools/testing/selftests/mm/run_vmtests.sh\n@@ -398,6 +398,8 @@ fi\n \n CATEGORY=\"pagemap\" run_test ./pagemap_ioctl\n \n+CATEGORY=\"pagewalk\" run_test ./pagewalk_race_test\n+\n CATEGORY=\"pfnmap\" run_test ./pfnmap\n \n # COW tests\ndiff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h\nindex ea8fc8fdf0eb0..62292e2417d16 100644\n--- a/tools/testing/selftests/mm/vm_util.h\n+++ b/tools/testing/selftests/mm/vm_util.h\n@@ -88,6 +88,7 @@ bool pagemap_is_populated(int fd, char *start);\n unsigned long pagemap_get_pfn(int fd, char *start);\n void clear_softdirty(void);\n bool check_for_pattern(FILE *fp, const char *pattern, char *buf, size_t len);\n+char *__get_smap_entry(void *addr, const char *pattern, char *buf, size_t len);\n uint64_t read_pmd_pagesize(void);\n unsigned long rss_anon(void);\n bool check_huge_anon(void *addr, int nr_hpages, uint64_t hpage_size);\n"
}

2/1 2026/08/11 17:10 agent patch-evaluator
0m Model:
best-expensive

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [
    "walk_page_range",
    "walk_page_vma",
    "walk_page_mapping"
  ],
  "Reasoning": "The patch fixes a race condition in core MM page walking logic (walk_pmd_range, walk_pud_range) where a stale walk-\u003eaction could cause the same range to be walked twice. This is a functional change in core kernel logic that is reachable from userspace (e.g., via /proc/pid/smaps or madvise).",
  "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 6700d699cf347d4d09312f8be37265a6b731708e
Author: syz-cluster <triage@syzkaller.com>
Date:   Tue Aug 11 17:10:46 2026 +0000

    syz-cluster: applied patch under review

diff --git a/mm/pagewalk.c b/mm/pagewalk.c
index 5d87c632a2550..d3bfece319336 100644
--- a/mm/pagewalk.c
+++ b/mm/pagewalk.c
@@ -126,6 +126,7 @@ static int walk_pmd_range(pud_t *pud, unsigned long addr, unsigned long end,
 	pmd = pmd_offset(pud, addr);
 	do {
 again:
+		walk->action = ACTION_SUBTREE;
 		next = pmd_addr_end(addr, end);
 		if (pmd_none(*pmd)) {
 			if (has_install)
@@ -138,8 +139,6 @@ static int walk_pmd_range(pud_t *pud, unsigned long addr, unsigned long end,
 				continue;
 		}
 
-		walk->action = ACTION_SUBTREE;
-
 		/*
 		 * This implies that each ->pmd_entry() handler
 		 * needs to know about pmd_trans_huge() pmds
@@ -196,6 +195,7 @@ static int walk_pud_range(p4d_t *p4d, unsigned long addr, unsigned long end,
 	pud = pud_offset(p4d, addr);
 	do {
  again:
+		walk->action = ACTION_SUBTREE;
 		next = pud_addr_end(addr, end);
 		if (pud_none(*pud)) {
 			if (has_install)
@@ -208,8 +208,6 @@ static int walk_pud_range(p4d_t *p4d, unsigned long addr, unsigned long end,
 				continue;
 		}
 
-		walk->action = ACTION_SUBTREE;
-
 		if (ops->pud_entry)
 			err = ops->pud_entry(pud, addr, next, walk);
 		if (err)
diff --git a/tools/testing/selftests/mm/.gitignore b/tools/testing/selftests/mm/.gitignore
index 9ccd9e1447e66..92f981f97740f 100644
--- a/tools/testing/selftests/mm/.gitignore
+++ b/tools/testing/selftests/mm/.gitignore
@@ -66,3 +66,4 @@ merge
 prctl_thp_disable
 rmap
 folio_split_race_test
+pagewalk_race_test
diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile
index e6df968f0971c..cde9b22f121d4 100644
--- a/tools/testing/selftests/mm/Makefile
+++ b/tools/testing/selftests/mm/Makefile
@@ -105,6 +105,7 @@ TEST_GEN_FILES += guard-regions
 TEST_GEN_FILES += merge
 TEST_GEN_FILES += rmap
 TEST_GEN_FILES += folio_split_race_test
+TEST_GEN_FILES += pagewalk_race_test
 
 ifneq ($(ARCH),arm64)
 TEST_GEN_FILES += soft-dirty
@@ -163,6 +164,7 @@ TEST_PROGS += ksft_mlock.sh
 TEST_PROGS += ksft_mmap.sh
 TEST_PROGS += ksft_mremap.sh
 TEST_PROGS += ksft_pagemap.sh
+TEST_PROGS += ksft_pagewalk.sh
 TEST_PROGS += ksft_pfnmap.sh
 TEST_PROGS += ksft_pkey.sh
 TEST_PROGS += ksft_process_madv.sh
diff --git a/tools/testing/selftests/mm/ksft_pagewalk.sh b/tools/testing/selftests/mm/ksft_pagewalk.sh
new file mode 100755
index 0000000000000..6f6c3ee1c13ef
--- /dev/null
+++ b/tools/testing/selftests/mm/ksft_pagewalk.sh
@@ -0,0 +1,4 @@
+#!/bin/sh -e
+# SPDX-License-Identifier: GPL-2.0
+
+./run_vmtests.sh -t pagewalk
diff --git a/tools/testing/selftests/mm/pagewalk_race_test.c b/tools/testing/selftests/mm/pagewalk_race_test.c
new file mode 100644
index 0000000000000..42fd6e75e821e
--- /dev/null
+++ b/tools/testing/selftests/mm/pagewalk_race_test.c
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Regression test for a stale walk->action escaping walk_pmd_range() and
+ * making walk_pud_range() walk the same range twice.
+ *
+ * The mapping is two PMDs inside one PUD. PMD 0 is populated once and left
+ * alone, PMD 1 is faulted in and dropped again by a second thread. Clearing
+ * PMD 1 under smaps_pte_range() makes it raise ACTION_AGAIN, and since it is
+ * the last entry the stale value leaves walk_pmd_range(), so smaps accounts
+ * PMD 0 twice. A kernel that does not reclaim the emptied page table never
+ * clears PMD 1 and so never hits the race.
+ *
+ * A hit can only come from the kernel counting the same page twice, so the
+ * test cannot fail spuriously.
+ */
+#define _GNU_SOURCE
+
+#include <pthread.h>
+#include <stdatomic.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "vm_util.h"
+#include "kselftest.h"
+
+#define NR_PMDS		2
+#define NR_ROUNDS	20000
+/* Cap on how much of PMD 0 to fault in, so that a large PMD stays cheap. */
+#define POP_MAX		(2 * 1024 * 1024)
+
+static char *area;
+static size_t pmd_size;
+static atomic_int stop;
+
+static void *racer(void *arg)
+{
+	char *pmd1 = area + pmd_size;
+
+	while (atomic_load_explicit(&stop, memory_order_acquire) == 0) {
+		/* madvise() below keeps the compiler from lifting this out. */
+		*pmd1 = 1;
+		madvise(pmd1, pmd_size, MADV_DONTNEED);
+	}
+	return NULL;
+}
+
+static unsigned long smaps_rss_kb(void)
+{
+	char buf[1024];
+	char *entry;
+
+	entry = __get_smap_entry(area, "Rss:", buf, sizeof(buf));
+	if (!entry)
+		ksft_exit_fail_msg("no Rss: entry for the test mapping\n");
+
+	return strtoul(entry, NULL, 10);
+}
+
+int main(void)
+{
+	unsigned long max_rss_kb, rss_kb = 0;
+	size_t size, pop_size, i;
+	pthread_t thread;
+	char *raw;
+
+	ksft_print_header();
+
+	pmd_size = read_pmd_pagesize();
+	if (!pmd_size)
+		ksft_exit_skip("Cannot determine PMD size\n");
+
+	if (sysconf(_SC_NPROCESSORS_ONLN) < 2)
+		ksft_exit_skip("Need at least 2 CPUs to race\n");
+
+	size = NR_PMDS * pmd_size;
+
+	/*
+	 * Align to the mapping size to stay inside one PUD, then trim the
+	 * slack so that smaps has exactly one VMA to report.
+	 */
+	raw = mmap(NULL, 2 * size, PROT_READ | PROT_WRITE,
+		   MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
+	if (raw == MAP_FAILED)
+		ksft_exit_fail_msg("mmap failed\n");
+
+	area = (char *)(((unsigned long)raw + size - 1) & ~(size - 1));
+	if (area != raw)
+		munmap(raw, area - raw);
+	if (raw + 2 * size != area + size)
+		munmap(area + size, raw + 2 * size - (area + size));
+
+	/* A huge PMD never reaches pte_offset_map_lock(), so keep them out. */
+	if (madvise(area, size, MADV_NOHUGEPAGE))
+		ksft_exit_skip("MADV_NOHUGEPAGE failed\n");
+
+	pop_size = pmd_size < POP_MAX ? pmd_size : POP_MAX;
+	memset(area, 1, pop_size);
+
+	max_rss_kb = (pop_size >> 10) + 256;
+
+	/* Over the limit before racing means this is not our own mapping. */
+	rss_kb = smaps_rss_kb();
+	if (rss_kb > max_rss_kb)
+		ksft_exit_fail_msg("Rss is %lu kB before racing, expected at most %lu kB\n",
+				   rss_kb, max_rss_kb);
+
+	ksft_set_plan(1);
+	ksft_print_msg("racing smaps against MADV_DONTNEED, %d rounds\n",
+		       NR_ROUNDS);
+
+	if (pthread_create(&thread, NULL, racer, NULL))
+		ksft_exit_fail_msg("pthread_create failed\n");
+
+	for (i = 0; i < NR_ROUNDS; i++) {
+		rss_kb = smaps_rss_kb();
+		if (rss_kb > max_rss_kb)
+			break;
+	}
+
+	atomic_store_explicit(&stop, 1, memory_order_release);
+	pthread_join(thread, NULL);
+
+	if (i < NR_ROUNDS) {
+		ksft_print_msg("walk ran twice over the same range\n");
+		ksft_test_result_fail("Rss %lu kB exceeds %lu kB, round %zu\n",
+				      rss_kb, max_rss_kb, i);
+	} else {
+		ksft_test_result_pass("Rss within %lu kB over %d rounds\n",
+				      max_rss_kb, NR_ROUNDS);
+	}
+
+	ksft_exit(i == NR_ROUNDS);
+
+	return 0;
+}
diff --git a/tools/testing/selftests/mm/run_vmtests.sh b/tools/testing/selftests/mm/run_vmtests.sh
index 8c296dedf0474..d90c6370814f7 100755
--- a/tools/testing/selftests/mm/run_vmtests.sh
+++ b/tools/testing/selftests/mm/run_vmtests.sh
@@ -398,6 +398,8 @@ fi
 
 CATEGORY="pagemap" run_test ./pagemap_ioctl
 
+CATEGORY="pagewalk" run_test ./pagewalk_race_test
+
 CATEGORY="pfnmap" run_test ./pfnmap
 
 # COW tests
diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h
index ea8fc8fdf0eb0..62292e2417d16 100644
--- a/tools/testing/selftests/mm/vm_util.h
+++ b/tools/testing/selftests/mm/vm_util.h
@@ -88,6 +88,7 @@ bool pagemap_is_populated(int fd, char *start);
 unsigned long pagemap_get_pfn(int fd, char *start);
 void clear_softdirty(void);
 bool check_for_pattern(FILE *fp, const char *pattern, char *buf, size_t len);
+char *__get_smap_entry(void *addr, const char *pattern, char *buf, size_t len);
 uint64_t read_pmd_pagesize(void);
 unsigned long rss_anon(void);
 bool check_huge_anon(void *addr, int nr_hpages, uint64_t hpage_size);

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

Args:
null

Results:
null

Tokens:
							input: 5088
							output: 37
							thoughts: 226
						

Thoughts:
**Analyzing a Kernel Patch in `mm/pagewalk.c`**

Okay, so I'm looking at this patch that seems to be touching something fundamental within `mm/pagewalk.c`. Specifically, the code's modifying the location of `walk->action = ACTION_SUBTREE;` within `walk_pmd_range` and `walk_pud_range`. Moving it *inside* the `do { again: ... }` loop is pretty telling. My initial thought is this is intended to address some kind of race condition, where the `walk->action` variable might be holding onto a stale value—perhaps `ACTION_AGAIN`—and escaping the function, ultimately leading to a redundant walk of the same memory range by the caller. Classic concurrency issue.

I'll double-check exactly which functions are affected. Looks like the core changes are in the `walk_pmd_range` and `walk_pud_range` functions, both of which I know are static within that file. Right, and the other entry points to this code are the non-static functions like `walk_page_range`, `walk_page_range_novma`, `walk_page_vma`, and `walk_page_mapping`. I should probably familiarize myself with those as well, so I can see what they're calling. Let's fire up `grepper` and get a list of those functions and who's calling them. This'll help me put this fix in the larger context and truly understand its implications.



4/2 2026/08/11 17:10 tool grepper
0m Args:
{
  "Expression": "^(int|void).*walk_page_.*\\(",
  "PathPrefix": "mm/pagewalk.c"
}

Results:
{
  "Output": "mm/pagewalk.c=444=static inline void process_vma_walk_lock(struct vm_area_struct *vma,\n--\nmm/pagewalk.c-471- */\nmm/pagewalk.c:472:int walk_page_range_mm_unsafe(struct mm_struct *mm, unsigned long start,\nmm/pagewalk.c-473-\t\tunsigned long end, const struct mm_walk_ops *ops,\n--\nmm/pagewalk.c=541=static bool check_ops_safe(const struct mm_walk_ops *ops)\n--\nmm/pagewalk.c-594- */\nmm/pagewalk.c:595:int walk_page_range(struct mm_struct *mm, unsigned long start,\nmm/pagewalk.c-596-\t\tunsigned long end, const struct mm_walk_ops *ops,\n--\nmm/pagewalk.c=646=int walk_kernel_page_table_range_lockless(unsigned long start, unsigned long end,\n--\nmm/pagewalk.c-681- */\nmm/pagewalk.c:682:int walk_page_range_debug(struct mm_struct *mm, unsigned long start,\nmm/pagewalk.c-683-\t\t\t  unsigned long end, const struct mm_walk_ops *ops,\n--\nmm/pagewalk.c-719-\nmm/pagewalk.c:720:int walk_page_range_vma_unsafe(struct vm_area_struct *vma, unsigned long start,\nmm/pagewalk.c-721-\t\tunsigned long end, const struct mm_walk_ops *ops, void *private)\n--\nmm/pagewalk.c-739-\nmm/pagewalk.c:740:int walk_page_range_vma(struct vm_area_struct *vma, unsigned long start,\nmm/pagewalk.c-741-\t\t\tunsigned long end, const struct mm_walk_ops *ops,\n--\nmm/pagewalk.c-749-\nmm/pagewalk.c:750:int walk_page_vma(struct vm_area_struct *vma, const struct mm_walk_ops *ops,\nmm/pagewalk.c-751-\t\tvoid *private)\n--\nmm/pagewalk.c-799- */\nmm/pagewalk.c:800:int walk_page_mapping(struct address_space *mapping, pgoff_t first_index,\nmm/pagewalk.c-801-\t\t      pgoff_t nr, const struct mm_walk_ops *ops,\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 6326
							output: 27
							thoughts: 143
						

Thoughts:
**Fuzzing Target Identification & Patch Assessment**

Okay, so I've identified these memory management walk functions: `walk_page_range`, `walk_page_range_mm_unsafe`, `walk_page_range_vma`, `walk_page_range_vma_unsafe`, `walk_page_vma`, and `walk_page_mapping`. They seem like prime targets for fuzzing, given their roles in page table traversal. They're definitely reachable and represent crucial areas of the kernel MM subsystem.

This patch, apparently, addresses a potential race condition or stale value issue with `walk->action`. Specifically, if I understand it correctly, there was a bug where the action to be performed during the walk could be left with incorrect data, which would then cause the walk to repeat, causing an indefinite loop or data corruption. This is a functional change, a core memory management bug fix. This needs thorough testing.

Now, before I dig in too deep, I need to check if these functions, or the code surrounding them, require any specific kernel configurations. My recollection is that `mm/pagewalk.c` is compiled unconditionally, but it's always worth a quick check to rule out any build-time conditionalities.



6/2 2026/08/11 17:10 tool grepper
0m Args:
{
  "Expression": "pagewalk.o",
  "PathPrefix": "mm/Makefile"
}

Results:
{
  "Output": "mm/Makefile=37=mmu-$(CONFIG_MMU)\t:= highmem.o memory.o mincore.o \\\nmm/Makefile-38-\t\t\t   mlock.o mmap.o mmu_gather.o mprotect.o mremap.o \\\nmm/Makefile:39:\t\t\t   msync.o page_vma_mapped.o pagewalk.o \\\nmm/Makefile-40-\t\t\t   pgtable-generic.o rmap.o vmalloc.o vma.o vma_exec.o\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 6896
							output: 37
							thoughts: 95
						

Thoughts:
Okay, here's my take on those thoughts, framed from my expert perspective:

**Page Table Walker Strategy Review**

Alright, so the standard kernel configuration, with `CONFIG_MMU` set, means we're dealing with a compiled environment where the page table walkers are active. That's the baseline, the assumption I work from. Now, I need to zero in on the core mechanisms.

My initial focus needs to be on the primary entry points. I'm thinking: `walk_page_range`, `walk_page_vma`, and `walk_page_mapping`. Those are the usual suspects – the starting points for traversing the page tables under various conditions. They're where I'd expect the general control flow to begin.

Before I dive deeper into those three, it makes sense to go over the `walk_page_range_vma` and `walk_page_range_vma_unsafe`. I need to ensure there are no subtle interactions or potentially problematic edge cases in the range-based walkers. These should play the role I assume and I should be aware if the `_unsafe` function requires further analysis due to potential race conditions.



8/2 2026/08/11 17:11 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "EXPORT_SYMBOL_GPL\\(walk_page_",
  "PathPrefix": "mm/pagewalk.c"
}

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

9/2 2026/08/11 17:11 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 7289
							output: 35
							thoughts: 9
						

Thoughts:
Okay, here's my attempt at summarizing that thought process, keeping in mind I'm speaking to a fellow expert:

**Investigating `EXPORT_SYMBOL`**

Alright, time to dive into this. The initial thought is to simply *check* `EXPORT_SYMBOL`. I'm going to quickly refresh my understanding of how it functions. What are its implications for symbol visibility? What's the context here? Is this related to kernel module development, or are we looking at something else? I need to clarify what problem or specific aspect requires attention within the realm of the `EXPORT_SYMBOL` macro. Is there a suspected issue or is this simply for general analysis? Time to assess what exactly needs checking.



10/2 2026/08/11 17:11 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "EXPORT_SYMBOL.*\\(walk_page_",
  "PathPrefix": "mm/pagewalk.c"
}

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

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

Args:
null

Results:
null

Tokens:
							input: 7495
							output: 29
							thoughts: 20
						

Thoughts:
Here's my thought process, summarized as you requested:

**Navigating the Codebase**

Okay, hold on a second. Before I start down a potentially rabbit hole, let's take a more targeted approach. I'm going to quickly search within `mm/pagewalk.c` for `EXPORT_SYMBOL`. That macro is a key indicator, right? It's a fundamental part of the Linux kernel's symbol export mechanism. Finding where it's used in that specific file—related to page table walking, which is my immediate area of interest—could be the quickest way to zero in on the relevant code, the specific data structures being exposed, or the functions related to pagewalk that are being made available for use by kernel modules. It'll give me a direct line into the implementation details I need, without unnecessary broader exploration of potentially irrelevant code paths. This direct search is more efficient than a vague keyword search, or just randomly looking at the files. It's the most pragmatic first step.



12/2 2026/08/11 17:11 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "EXPORT_SYMBOL",
  "PathPrefix": "mm/pagewalk.c"
}

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

13/2 2026/08/11 17:11 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 7769
							output: 34
							thoughts: 59
						

Thoughts:
**Internal Kernel Data Structures: My Assessment**

Okay, so I'm considering these "things," and while they might not be directly exported for external use, it's pretty clear they're *absolutely* fundamental and used internally by a vast number of kernel subsystems. Think procfs, `madvise`, all those core components – they rely on this.

Right, time to check if they're static. If they are, that would limit their scope significantly. Let's see... yep, definitely non-static. Good. That expands the possibilities and how they might be managed within the kernel.

Next, I need to take a closer look at `walk_page_range`. That function is probably the key to understanding how these things are actually *used* and interacted with on a granular level. That will give me a deeper understanding.



14/2 2026/08/11 17:11 tool grepper
0m Args:
{
  "Expression": "^int walk_page_range\\(",
  "PathPrefix": "mm/pagewalk.c"
}

Results:
{
  "Output": "mm/pagewalk.c=541=static bool check_ops_safe(const struct mm_walk_ops *ops)\n--\nmm/pagewalk.c-594- */\nmm/pagewalk.c:595:int walk_page_range(struct mm_struct *mm, unsigned long start,\nmm/pagewalk.c-596-\t\tunsigned long end, const struct mm_walk_ops *ops,\n"
}

15/2 2026/08/11 17:11 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 8153
							output: 130
							thoughts: 266
						

Thoughts:
**My Assessment of the Patch's Vulnerability**

Okay, so this patch clearly addresses something non-static, which immediately piques my interest. It's fixing a race condition within the core MM page walking functions – specifically, `walk_pmd_range` and `walk_pud_range`. That's a critical area, potentially affecting memory management across the board. The test case involving `/proc/pid/smaps` tells me that this fix likely touches `smaps_pte_range` or a similar function, which itself is called through either `walk_page_vma` or `walk_page_range`.

My focus is sharpening on those key symbols: `walk_page_range`, `walk_page_vma`, and `walk_page_mapping`. Those are the entry points I'm going to want to examine.

No special configurations are needed, thankfully – just a standard MMU, meaning this is broadly applicable.

So, summing this up for myself, I'd say this is definitely "WorthFuzzing." The focus symbols are `walk_page_range`, `walk_page_vma`, and `walk_page_mapping`. There are no specific configurations needed, a win for me. The reasoning? The patch prevents a race condition in fundamental page walking logic. A stale `walk->action` variable could lead to double-walking the same memory range. This is a functional change, reachable from user space by things like `/proc/pid/smaps` or system calls like `madvise`. This combination screams "vulnerability potential".



16/2 2026/08/11 17:11 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [
    "walk_page_range",
    "walk_page_vma",
    "walk_page_mapping"
  ],
  "Reasoning": "The patch fixes a race condition in core MM page walking logic (walk_pmd_range, walk_pud_range) where a stale walk-\u003eaction could cause the same range to be walked twice. This is a functional change in core kernel logic that is reachable from userspace (e.g., via /proc/pid/smaps or madvise).",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [
    "walk_page_range",
    "walk_page_vma",
    "walk_page_mapping"
  ],
  "Reasoning": "The patch fixes a race condition in core MM page walking logic (walk_pmd_range, walk_pud_range) where a stale walk-\u003eaction could cause the same range to be walked twice. This is a functional change in core kernel logic that is reachable from userspace (e.g., via /proc/pid/smaps or madvise).",
  "WorthFuzzing": true
}

17/1 2026/08/11 17:11 action If
0m Args:
{
  "WorthFuzzing": true
}

Results:
null

18/2 2026/08/11 17:11 agent kmsan-evaluator
0m Model:
best-expensive

Args:
null

Results:
{
  "KMSANReasoning": "The patch fixes a logic bug in the page table walker (`mm/pagewalk.c`) where a stale `walk-\u003eaction` (e.g., `ACTION_AGAIN`) could escape a lower-level walk function and cause the higher-level walk function to incorrectly repeat the walk over the same range. The fix simply moves the assignment `walk-\u003eaction = ACTION_SUBTREE;` to the beginning of the loop. This change only affects control flow and does not introduce any new memory allocations, uninitialized variables, or data structures copied to user space. Therefore, there is no risk of uninitialized memory use or info-leaks, and KMSAN is not applicable. Standard testing and KASAN are sufficient.",
  "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 6700d699cf347d4d09312f8be37265a6b731708e
Author: syz-cluster <triage@syzkaller.com>
Date:   Tue Aug 11 17:10:46 2026 +0000

    syz-cluster: applied patch under review

diff --git a/mm/pagewalk.c b/mm/pagewalk.c
index 5d87c632a2550..d3bfece319336 100644
--- a/mm/pagewalk.c
+++ b/mm/pagewalk.c
@@ -126,6 +126,7 @@ static int walk_pmd_range(pud_t *pud, unsigned long addr, unsigned long end,
 	pmd = pmd_offset(pud, addr);
 	do {
 again:
+		walk->action = ACTION_SUBTREE;
 		next = pmd_addr_end(addr, end);
 		if (pmd_none(*pmd)) {
 			if (has_install)
@@ -138,8 +139,6 @@ static int walk_pmd_range(pud_t *pud, unsigned long addr, unsigned long end,
 				continue;
 		}
 
-		walk->action = ACTION_SUBTREE;
-
 		/*
 		 * This implies that each ->pmd_entry() handler
 		 * needs to know about pmd_trans_huge() pmds
@@ -196,6 +195,7 @@ static int walk_pud_range(p4d_t *p4d, unsigned long addr, unsigned long end,
 	pud = pud_offset(p4d, addr);
 	do {
  again:
+		walk->action = ACTION_SUBTREE;
 		next = pud_addr_end(addr, end);
 		if (pud_none(*pud)) {
 			if (has_install)
@@ -208,8 +208,6 @@ static int walk_pud_range(p4d_t *p4d, unsigned long addr, unsigned long end,
 				continue;
 		}
 
-		walk->action = ACTION_SUBTREE;
-
 		if (ops->pud_entry)
 			err = ops->pud_entry(pud, addr, next, walk);
 		if (err)
diff --git a/tools/testing/selftests/mm/.gitignore b/tools/testing/selftests/mm/.gitignore
index 9ccd9e1447e66..92f981f97740f 100644
--- a/tools/testing/selftests/mm/.gitignore
+++ b/tools/testing/selftests/mm/.gitignore
@@ -66,3 +66,4 @@ merge
 prctl_thp_disable
 rmap
 folio_split_race_test
+pagewalk_race_test
diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile
index e6df968f0971c..cde9b22f121d4 100644
--- a/tools/testing/selftests/mm/Makefile
+++ b/tools/testing/selftests/mm/Makefile
@@ -105,6 +105,7 @@ TEST_GEN_FILES += guard-regions
 TEST_GEN_FILES += merge
 TEST_GEN_FILES += rmap
 TEST_GEN_FILES += folio_split_race_test
+TEST_GEN_FILES += pagewalk_race_test
 
 ifneq ($(ARCH),arm64)
 TEST_GEN_FILES += soft-dirty
@@ -163,6 +164,7 @@ TEST_PROGS += ksft_mlock.sh
 TEST_PROGS += ksft_mmap.sh
 TEST_PROGS += ksft_mremap.sh
 TEST_PROGS += ksft_pagemap.sh
+TEST_PROGS += ksft_pagewalk.sh
 TEST_PROGS += ksft_pfnmap.sh
 TEST_PROGS += ksft_pkey.sh
 TEST_PROGS += ksft_process_madv.sh
diff --git a/tools/testing/selftests/mm/ksft_pagewalk.sh b/tools/testing/selftests/mm/ksft_pagewalk.sh
new file mode 100755
index 0000000000000..6f6c3ee1c13ef
--- /dev/null
+++ b/tools/testing/selftests/mm/ksft_pagewalk.sh
@@ -0,0 +1,4 @@
+#!/bin/sh -e
+# SPDX-License-Identifier: GPL-2.0
+
+./run_vmtests.sh -t pagewalk
diff --git a/tools/testing/selftests/mm/pagewalk_race_test.c b/tools/testing/selftests/mm/pagewalk_race_test.c
new file mode 100644
index 0000000000000..42fd6e75e821e
--- /dev/null
+++ b/tools/testing/selftests/mm/pagewalk_race_test.c
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Regression test for a stale walk->action escaping walk_pmd_range() and
+ * making walk_pud_range() walk the same range twice.
+ *
+ * The mapping is two PMDs inside one PUD. PMD 0 is populated once and left
+ * alone, PMD 1 is faulted in and dropped again by a second thread. Clearing
+ * PMD 1 under smaps_pte_range() makes it raise ACTION_AGAIN, and since it is
+ * the last entry the stale value leaves walk_pmd_range(), so smaps accounts
+ * PMD 0 twice. A kernel that does not reclaim the emptied page table never
+ * clears PMD 1 and so never hits the race.
+ *
+ * A hit can only come from the kernel counting the same page twice, so the
+ * test cannot fail spuriously.
+ */
+#define _GNU_SOURCE
+
+#include <pthread.h>
+#include <stdatomic.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "vm_util.h"
+#include "kselftest.h"
+
+#define NR_PMDS		2
+#define NR_ROUNDS	20000
+/* Cap on how much of PMD 0 to fault in, so that a large PMD stays cheap. */
+#define POP_MAX		(2 * 1024 * 1024)
+
+static char *area;
+static size_t pmd_size;
+static atomic_int stop;
+
+static void *racer(void *arg)
+{
+	char *pmd1 = area + pmd_size;
+
+	while (atomic_load_explicit(&stop, memory_order_acquire) == 0) {
+		/* madvise() below keeps the compiler from lifting this out. */
+		*pmd1 = 1;
+		madvise(pmd1, pmd_size, MADV_DONTNEED);
+	}
+	return NULL;
+}
+
+static unsigned long smaps_rss_kb(void)
+{
+	char buf[1024];
+	char *entry;
+
+	entry = __get_smap_entry(area, "Rss:", buf, sizeof(buf));
+	if (!entry)
+		ksft_exit_fail_msg("no Rss: entry for the test mapping\n");
+
+	return strtoul(entry, NULL, 10);
+}
+
+int main(void)
+{
+	unsigned long max_rss_kb, rss_kb = 0;
+	size_t size, pop_size, i;
+	pthread_t thread;
+	char *raw;
+
+	ksft_print_header();
+
+	pmd_size = read_pmd_pagesize();
+	if (!pmd_size)
+		ksft_exit_skip("Cannot determine PMD size\n");
+
+	if (sysconf(_SC_NPROCESSORS_ONLN) < 2)
+		ksft_exit_skip("Need at least 2 CPUs to race\n");
+
+	size = NR_PMDS * pmd_size;
+
+	/*
+	 * Align to the mapping size to stay inside one PUD, then trim the
+	 * slack so that smaps has exactly one VMA to report.
+	 */
+	raw = mmap(NULL, 2 * size, PROT_READ | PROT_WRITE,
+		   MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
+	if (raw == MAP_FAILED)
+		ksft_exit_fail_msg("mmap failed\n");
+
+	area = (char *)(((unsigned long)raw + size - 1) & ~(size - 1));
+	if (area != raw)
+		munmap(raw, area - raw);
+	if (raw + 2 * size != area + size)
+		munmap(area + size, raw + 2 * size - (area + size));
+
+	/* A huge PMD never reaches pte_offset_map_lock(), so keep them out. */
+	if (madvise(area, size, MADV_NOHUGEPAGE))
+		ksft_exit_skip("MADV_NOHUGEPAGE failed\n");
+
+	pop_size = pmd_size < POP_MAX ? pmd_size : POP_MAX;
+	memset(area, 1, pop_size);
+
+	max_rss_kb = (pop_size >> 10) + 256;
+
+	/* Over the limit before racing means this is not our own mapping. */
+	rss_kb = smaps_rss_kb();
+	if (rss_kb > max_rss_kb)
+		ksft_exit_fail_msg("Rss is %lu kB before racing, expected at most %lu kB\n",
+				   rss_kb, max_rss_kb);
+
+	ksft_set_plan(1);
+	ksft_print_msg("racing smaps against MADV_DONTNEED, %d rounds\n",
+		       NR_ROUNDS);
+
+	if (pthread_create(&thread, NULL, racer, NULL))
+		ksft_exit_fail_msg("pthread_create failed\n");
+
+	for (i = 0; i < NR_ROUNDS; i++) {
+		rss_kb = smaps_rss_kb();
+		if (rss_kb > max_rss_kb)
+			break;
+	}
+
+	atomic_store_explicit(&stop, 1, memory_order_release);
+	pthread_join(thread, NULL);
+
+	if (i < NR_ROUNDS) {
+		ksft_print_msg("walk ran twice over the same range\n");
+		ksft_test_result_fail("Rss %lu kB exceeds %lu kB, round %zu\n",
+				      rss_kb, max_rss_kb, i);
+	} else {
+		ksft_test_result_pass("Rss within %lu kB over %d rounds\n",
+				      max_rss_kb, NR_ROUNDS);
+	}
+
+	ksft_exit(i == NR_ROUNDS);
+
+	return 0;
+}
diff --git a/tools/testing/selftests/mm/run_vmtests.sh b/tools/testing/selftests/mm/run_vmtests.sh
index 8c296dedf0474..d90c6370814f7 100755
--- a/tools/testing/selftests/mm/run_vmtests.sh
+++ b/tools/testing/selftests/mm/run_vmtests.sh
@@ -398,6 +398,8 @@ fi
 
 CATEGORY="pagemap" run_test ./pagemap_ioctl
 
+CATEGORY="pagewalk" run_test ./pagewalk_race_test
+
 CATEGORY="pfnmap" run_test ./pfnmap
 
 # COW tests
diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h
index ea8fc8fdf0eb0..62292e2417d16 100644
--- a/tools/testing/selftests/mm/vm_util.h
+++ b/tools/testing/selftests/mm/vm_util.h
@@ -88,6 +88,7 @@ bool pagemap_is_populated(int fd, char *start);
 unsigned long pagemap_get_pfn(int fd, char *start);
 void clear_softdirty(void);
 bool check_for_pattern(FILE *fp, const char *pattern, char *buf, size_t len);
+char *__get_smap_entry(void *addr, const char *pattern, char *buf, size_t len);
 uint64_t read_pmd_pagesize(void);
 unsigned long rss_anon(void);
 bool check_huge_anon(void *addr, int nr_hpages, uint64_t hpage_size);

19/3 2026/08/11 17:11 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 5085
							output: 174
							thoughts: 682
						

Thoughts:
**Analyzing a Page Table Walker Bug Fix**

Okay, so I've been looking at this patch and, after a quick review, I see it modifies `mm/pagewalk.c` and adds a new test. My main concern is, of course, the logic around the page table walking. The gist of it is the movement of `walk->action = ACTION_SUBTREE;` inside the loop in `walk_pmd_range` and `walk_pud_range`. This is designed to address a flaw where a stale `walk->action` value (like `ACTION_AGAIN`) could, in certain situations, cause the same memory range to be walked twice, which is obviously not what we want.

I've examined the code closely, specifically the changes in `mm/pagewalk.c`. It's clear this is a pure control flow bug. The original placement could lead to a situation where the action flag isn't properly reset within the loop, leading to the range being re-walked. Moving the assignment ensures that `ACTION_SUBTREE` is set at the beginning of each iteration.

Now, I need to make sure this doesn't introduce any new vulnerabilities. Let's make sure there isn't any potential for uninitialized memory use or information leaks. After a thorough review, I can confirm that this patch doesn't involve any new memory allocations, structures being copied to user space, or the use of uninitialized variables. We're simply resetting a state variable for correct control flow. The `walk->action` variable, a field within the `struct mm_walk`, is being reset to `ACTION_SUBTREE` at the beginning of each loop.

Given that this fix is purely related to control flow and doesn't introduce any new memory-related operations or potential info-leaks, I'm comfortable setting NeedsKMSAN to false. The standard tests, alongside KASAN, should be sufficient to validate the correctness of this fix. It's a clean, targeted patch that addresses a specific logic issue.



20/3 2026/08/11 17:11 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch fixes a logic bug in the page table walker (`mm/pagewalk.c`) where a stale `walk-\u003eaction` (e.g., `ACTION_AGAIN`) could escape a lower-level walk function and cause the higher-level walk function to incorrectly repeat the walk over the same range. The fix simply moves the assignment `walk-\u003eaction = ACTION_SUBTREE;` to the beginning of the loop. This change only affects control flow and does not introduce any new memory allocations, uninitialized variables, or data structures copied to user space. Therefore, there is no risk of uninitialized memory use or info-leaks, and KMSAN is not applicable. Standard testing and KASAN are sufficient.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch fixes a logic bug in the page table walker (`mm/pagewalk.c`) where a stale `walk-\u003eaction` (e.g., `ACTION_AGAIN`) could escape a lower-level walk function and cause the higher-level walk function to incorrectly repeat the walk over the same range. The fix simply moves the assignment `walk-\u003eaction = ACTION_SUBTREE;` to the beginning of the loop. This change only affects control flow and does not introduce any new memory allocations, uninitialized variables, or data structures copied to user space. Therefore, there is no risk of uninitialized memory use or info-leaks, and KMSAN is not applicable. Standard testing and KASAN are sufficient.",
  "NeedsKMSAN": false
}

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)