| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/07/07 20:20 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": [
"PAGE_OWNER"
],
"FocusSymbols": [
"page_owner_write",
"page_owner_open",
"page_owner_release",
"print_page_owner",
"read_page_owner"
],
"Reasoning": "The patch introduces a new write handler (`page_owner_write`) for the `/sys/kernel/debug/page_owner` debugfs file to support filtering. It also modifies the read handler (`read_page_owner`) and output formatting (`print_page_owner`) to apply the filters. These changes involve parsing user input and managing per-fd state, which are functional changes reachable from userspace and should be fuzzed.",
"WorthFuzzing": true
} |
| 1/1 | 2026/07/07 20:20 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 6c82fdfbaa7e61d07023d2e72a87c4a61396a9e5\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Tue Jul 7 20:20:02 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/mm/page_owner.rst b/Documentation/mm/page_owner.rst\nindex 6b12f3b007ec91..383e59c42743bd 100644\n--- a/Documentation/mm/page_owner.rst\n+++ b/Documentation/mm/page_owner.rst\n@@ -65,7 +65,14 @@ un-tracking state.\n Usage\n =====\n \n-1) Build user-space helper::\n+1) Build user-space helpers::\n+\n+To filter page_owner output:\n+\n+\tcd tools/mm\n+\tmake page_owner_filter\n+\n+To sort and analyze page_owner output:\n \n \tcd tools/mm\n \tmake page_owner_sort\n@@ -74,7 +81,11 @@ Usage\n \n 3) Do the job that you want to debug.\n \n-4) Analyze information from page owner::\n+4) (Optional) Filter page_owner output::\n+\n+\t./page_owner_filter -m handle -n 0,1,2 \u003e filtered_page_owner.txt\n+\n+5) Analyze information from page owner::\n \n \tcat /sys/kernel/debug/page_owner_stacks/show_stacks \u003e stacks.txt\n \tcat stacks.txt\n@@ -263,3 +274,65 @@ STANDARD FORMAT SPECIFIERS\n \tf\t\tfree\t\twhether the page has been released or not\n \tst\t\tstacktrace\tstack trace of the page allocation\n \tator\t\tallocator\tmemory allocator for pages\n+\n+Filtering page_owner output\n+============================\n+\n+page_owner supports filtering output at the kernel level before reading,\n+which reduces the amount of data that needs to be processed in userspace.\n+\n+The page_owner_filter tool provides a convenient interface for this filtering\n+capability. It supports two types of filters:\n+\n+1. **print_mode filter**: Control what information is printed for each page\n+\t- ``stack``: Print full stack traces (default, compatible with existing usage)\n+\t- ``handle``: Print only stack handle numbers (much faster, smaller output)\n+\t- ``stack_handle``: Print both stack traces and handle numbers\n+\n+\tThe ``handle`` mode uses numeric identifiers instead of full stack traces.\n+\tThe mapping from handles to actual stack traces can be obtained via the\n+\tshow_stacks_handles interface.\n+\n+2. **NUMA node filter**: Filter pages by NUMA node ID\n+\t- Supports single node: ``-n 0``\n+\t- Multiple nodes: ``-n 0,1,2``\n+\t- Ranges: ``-n 0-3``\n+\t- Mixed format: ``-n 0,2-3,5``\n+\n+Usage examples::\n+\n+\t# Filter by print mode\n+\t./page_owner_filter -m handle\n+\t./page_owner_filter -m stack_handle\n+\n+\t# Filter by NUMA node\n+\t./page_owner_filter -n 0\n+\t./page_owner_filter -n 0-3\n+\n+\t# Combined filters\n+\t./page_owner_filter -m stack -n 0,1,2\n+\t./page_owner_filter -m handle -n 0,2-3\n+\n+\t# Save to file\n+\t./page_owner_filter -m handle -o filtered_output.txt\n+\n+The handle mode is particularly useful for monitoring and performance-critical\n+scenarios as it dramatically reduces output size. Testing shows handle mode can\n+reduce output size by ~66% (84MB vs 244MB) and improve read performance by ~4.4x\n+compared to full stack output.\n+\n+The NUMA node filter is useful for NUMA-aware memory allocation analysis and debugging.\n+\n+Behind the scenes, page_owner_filter opens /sys/kernel/debug/page_owner and\n+writes filter commands before reading the filtered output. The filtering uses\n+per-file-descriptor state, allowing each open() to have independent filter settings.\n+\n+Each file descriptor maintains its own filter state, so you can have multiple\n+independent filtering operations running concurrently. For example, in different\n+terminals you can run different filters simultaneously::\n+\n+\t# Terminal 1: Filter node 0\n+\t./page_owner_filter -n 0 \u003e node0_output.txt\n+\n+\t# Terminal 2: Filter node 1 (runs concurrently)\n+\t./page_owner_filter -n 1 \u003e node1_output.txt\ndiff --git a/mm/page_owner.c b/mm/page_owner.c\nindex 8178e0be557f87..f1d4c205aff979 100644\n--- a/mm/page_owner.c\n+++ b/mm/page_owner.c\n@@ -54,6 +54,24 @@ struct stack_print_ctx {\n \tu8 flags;\n };\n \n+enum page_owner_print_mode {\n+\tPAGE_OWNER_PRINT_STACK,\n+\tPAGE_OWNER_PRINT_HANDLE,\n+\tPAGE_OWNER_PRINT_STACK_HANDLE,\n+};\n+\n+static const char * const page_owner_print_mode_strings[] = {\n+\t[PAGE_OWNER_PRINT_STACK]\t= \"stack\",\n+\t[PAGE_OWNER_PRINT_HANDLE]\t= \"handle\",\n+\t[PAGE_OWNER_PRINT_STACK_HANDLE]\t= \"stack_handle\",\n+};\n+\n+struct page_owner_filter_state {\n+\tenum page_owner_print_mode print_mode;\n+\tnodemask_t nid_filter;\n+\tbool nid_filter_enabled;\n+};\n+\n static bool page_owner_enabled __initdata;\n DEFINE_STATIC_KEY_FALSE(page_owner_inited);\n \n@@ -547,16 +565,20 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,\n static ssize_t\n print_page_owner(char __user *buf, size_t count, unsigned long pfn,\n \t\tstruct page *page, struct page_owner *page_owner,\n-\t\tdepot_stack_handle_t handle)\n+\t\tdepot_stack_handle_t handle,\n+\t\tstruct page_owner_filter_state *state)\n {\n \tint ret, pageblock_mt, page_mt;\n \tchar *kbuf;\n+\tenum page_owner_print_mode print_mode;\n \n \tcount = min_t(size_t, count, PAGE_SIZE);\n \tkbuf = kmalloc(count, GFP_KERNEL);\n \tif (!kbuf)\n \t\treturn -ENOMEM;\n \n+\tprint_mode = state-\u003eprint_mode;\n+\n \tret = scnprintf(kbuf, count,\n \t\t\t\"Page allocated via order %u, mask %#x(%pGg), pid %d, tgid %d (%s), ts %llu ns\\n\",\n \t\t\tpage_owner-\u003eorder, page_owner-\u003egfp_mask,\n@@ -575,9 +597,18 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,\n \t\t\tmigratetype_names[pageblock_mt],\n \t\t\t\u0026page-\u003eflags);\n \n-\tret += stack_depot_snprint(handle, kbuf + ret, count - ret, 0);\n-\tif (ret \u003e= count)\n-\t\tgoto err;\n+\tif (print_mode != PAGE_OWNER_PRINT_HANDLE) {\n+\t\tret += stack_depot_snprint(handle, kbuf + ret, count - ret, 0);\n+\t\tif (ret \u003e= count)\n+\t\t\tgoto err;\n+\t}\n+\n+\tif (print_mode != PAGE_OWNER_PRINT_STACK) {\n+\t\tret += scnprintf(kbuf + ret, count - ret, \"handle: %u\\n\",\n+\t\t\t\t handle);\n+\t\tif (ret \u003e= count)\n+\t\t\tgoto err;\n+\t}\n \n \tif (page_owner-\u003elast_migrate_reason != -1) {\n \t\tret += scnprintf(kbuf + ret, count - ret,\n@@ -664,6 +695,7 @@ read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos)\n \tstruct page_ext *page_ext;\n \tstruct page_owner *page_owner;\n \tdepot_stack_handle_t handle;\n+\tstruct page_owner_filter_state *state = file-\u003eprivate_data;\n \n \tif (!static_branch_unlikely(\u0026page_owner_inited))\n \t\treturn -EINVAL;\n@@ -740,15 +772,31 @@ read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos)\n \t\tif (!handle)\n \t\t\tgoto ext_put_continue;\n \n+\t\tif (state-\u003enid_filter_enabled) {\n+\t\t\tint nid;\n+\t\t\tmemdesc_flags_t page_flags = READ_ONCE(page-\u003eflags);\n+\n+\t\t\t/*\n+\t\t\t * Bypass PF_POISONED_CHECK() in page_to_nid() to avoid\n+\t\t\t * VM_BUG_ON when accessing poisoned pages.\n+\t\t\t */\n+\t\t\tif (page_flags.f == PAGE_POISON_PATTERN)\n+\t\t\t\tgoto ext_put_continue;\n+\t\t\tnid = memdesc_nid(page_flags);\n+\t\t\tif (!node_isset(nid, state-\u003enid_filter))\n+\t\t\t\tgoto ext_put_continue;\n+\t\t}\n+\n \t\t/* Record the next PFN to read in the file offset */\n \t\t*ppos = pfn + 1;\n \n \t\tpage_owner_tmp = *page_owner;\n \t\tpage_ext_put(page_ext);\n \t\treturn print_page_owner(buf, count, pfn, page,\n-\t\t\t\t\u0026page_owner_tmp, handle);\n+\t\t\t\t\u0026page_owner_tmp, handle, state);\n ext_put_continue:\n \t\tpage_ext_put(page_ext);\n+\t\tcond_resched();\n \t}\n \n \treturn 0;\n@@ -847,7 +895,113 @@ static void init_early_allocated_pages(void)\n \t\tinit_pages_in_zone(zone);\n }\n \n+static int page_owner_open(struct inode *inode, struct file *file)\n+{\n+\tstruct page_owner_filter_state *state;\n+\n+\tstate = kzalloc_obj(*state);\n+\tif (!state)\n+\t\treturn -ENOMEM;\n+\n+\tstate-\u003eprint_mode = PAGE_OWNER_PRINT_STACK;\n+\tnodes_clear(state-\u003enid_filter);\n+\tstate-\u003enid_filter_enabled = false;\n+\tfile-\u003eprivate_data = state;\n+\treturn 0;\n+}\n+\n+static int page_owner_release(struct inode *inode, struct file *file)\n+{\n+\tkfree(file-\u003eprivate_data);\n+\treturn 0;\n+}\n+\n+static ssize_t page_owner_write(struct file *file,\n+\t\t\t\t const char __user *buf,\n+\t\t\t\t size_t count, loff_t *ppos)\n+{\n+\tchar *kbuf;\n+\tchar *orig;\n+\tchar *token;\n+\tint ret;\n+\tstruct page_owner_filter_state *state = file-\u003eprivate_data;\n+\tenum page_owner_print_mode new_print_mode;\n+\tnodemask_t new_nid_filter;\n+\tbool new_nid_filter_enabled;\n+\n+\t/*\n+\t * Maximum input length for filter commands:\n+\t * - 32: print_mode command max length is 17 (\"mode=stack_handle\")\n+\t * with sufficient buffer\n+\t * - 6 * MAX_NUMNODES: worst case for nid list\n+\t * Worst case per node: \",NNNNN\" (comma + 5-digit node number) = 6 bytes\n+\t */\n+\tif (count \u003e 32 + 6 * MAX_NUMNODES)\n+\t\treturn -EINVAL;\n+\n+\tkbuf = memdup_user_nul(buf, count);\n+\tif (IS_ERR(kbuf))\n+\t\treturn PTR_ERR(kbuf);\n+\n+\torig = kbuf;\n+\n+\tnew_print_mode = state-\u003eprint_mode;\n+\tnew_nid_filter = state-\u003enid_filter;\n+\tnew_nid_filter_enabled = state-\u003enid_filter_enabled;\n+\n+\twhile ((token = strsep(\u0026kbuf, \" \\t\\n\")) != NULL) {\n+\t\tif (*token == '\\0')\n+\t\t\tcontinue;\n+\n+\t\tif (!strncmp(token, \"mode=\", 5)) {\n+\t\t\tret = sysfs_match_string(page_owner_print_mode_strings,\n+\t\t\t\t\t\ttoken + 5);\n+\t\t\tif (ret \u003c 0)\n+\t\t\t\tgoto out_free;\n+\t\t\tnew_print_mode = ret;\n+\t\t} else if (!strncmp(token, \"nid=\", 4)) {\n+\t\t\tret = nodelist_parse(token + 4, new_nid_filter);\n+\t\t\tif (ret \u003c 0)\n+\t\t\t\tgoto out_free;\n+\n+\t\t\tif (nodes_empty(new_nid_filter)) {\n+\t\t\t\tret = -EINVAL;\n+\t\t\t\tgoto out_free;\n+\t\t\t}\n+\n+\t\t\t/*\n+\t\t\t * We want to filter memory allocations by numa nodes, so make sure\n+\t\t\t * that the specified nodes have memory.\n+\t\t\t */\n+\t\t\tif (!nodes_subset(new_nid_filter, node_states[N_MEMORY])) {\n+\t\t\t\tret = -EINVAL;\n+\t\t\t\tgoto out_free;\n+\t\t\t}\n+\n+\t\t\tnew_nid_filter_enabled = true;\n+\t\t} else {\n+\t\t\tret = -EINVAL;\n+\t\t\tgoto out_free;\n+\t\t}\n+\t}\n+\n+\t/* Commit all filter changes */\n+\tstate-\u003eprint_mode = new_print_mode;\n+\tstate-\u003enid_filter = new_nid_filter;\n+\tstate-\u003enid_filter_enabled = new_nid_filter_enabled;\n+\n+\tret = count;\n+\n+out_free:\n+\tkfree(orig);\n+\treturn ret;\n+}\n+\n static const struct file_operations page_owner_fops = {\n+\t.owner\t\t= THIS_MODULE,\n+\t.open\t\t= page_owner_open,\n+\t.release\t= page_owner_release,\n+\t.write\t\t= page_owner_write,\n \t.read\t\t= read_page_owner,\n \t.llseek\t\t= lseek_page_owner,\n };\n@@ -980,7 +1134,7 @@ static int __init pageowner_init(void)\n \t\treturn 0;\n \t}\n \n-\tdebugfs_create_file(\"page_owner\", 0400, NULL, NULL, \u0026page_owner_fops);\n+\tdebugfs_create_file(\"page_owner\", 0600, NULL, NULL, \u0026page_owner_fops);\n \tdir = debugfs_create_dir(\"page_owner_stacks\", NULL);\n \tdebugfs_create_file(\"show_stacks\", 0400, dir,\n \t\t\t (void *)(STACK_PRINT_FLAG_STACK |\ndiff --git a/tools/mm/Makefile b/tools/mm/Makefile\nindex f5725b5c23aa26..858186a6eefdbd 100644\n--- a/tools/mm/Makefile\n+++ b/tools/mm/Makefile\n@@ -3,7 +3,7 @@\n #\n include ../scripts/Makefile.include\n \n-BUILD_TARGETS=page-types slabinfo page_owner_sort thp_swap_allocator_test\n+BUILD_TARGETS=page-types slabinfo page_owner_sort page_owner_filter thp_swap_allocator_test\n INSTALL_TARGETS = $(BUILD_TARGETS) thpmaps\n \n LIB_DIR = ../lib/api\n@@ -23,7 +23,7 @@ $(LIBS):\n \t$(CC) $(CFLAGS) -o $@ $\u003c $(LDFLAGS)\n \n clean:\n-\t$(RM) page-types slabinfo page_owner_sort thp_swap_allocator_test\n+\t$(RM) page-types slabinfo page_owner_sort page_owner_filter thp_swap_allocator_test\n \tmake -C $(LIB_DIR) clean\n \n sbindir ?= /usr/sbin\ndiff --git a/tools/mm/page_owner_filter.c b/tools/mm/page_owner_filter.c\nnew file mode 100644\nindex 00000000000000..1d1f0a38678a2f\n--- /dev/null\n+++ b/tools/mm/page_owner_filter.c\n@@ -0,0 +1,310 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * User-space helper to filter page_owner output per-fd\n+ *\n+ * Example use:\n+ * ./page_owner_filter -m handle\n+ * ./page_owner_filter -m stack_handle\n+ * ./page_owner_filter -n 0,1,2\n+ *\n+ * See Documentation/mm/page_owner.rst\n+ */\n+\n+#include \u003cstdio.h\u003e\n+#include \u003cstdlib.h\u003e\n+#include \u003cstring.h\u003e\n+#include \u003cunistd.h\u003e\n+#include \u003cfcntl.h\u003e\n+#include \u003cerrno.h\u003e\n+#include \u003cctype.h\u003e\n+#include \u003cgetopt.h\u003e\n+#include \u003csignal.h\u003e\n+\n+#define MAX_CMD_LEN\t512\n+\n+static void usage(const char *prog)\n+{\n+\tfprintf(stderr, \"Usage: %s [OPTIONS]\\n\", prog);\n+\tfprintf(stderr, \"\\nOptions:\\n\");\n+\tfprintf(stderr, \" -m, --mode MODE : print_mode (stack, handle, or stack_handle)\\n\");\n+\tfprintf(stderr, \" -n, --nid NID_LIST : NUMA node IDs (comma-separated or ranges)\\n\");\n+\tfprintf(stderr, \" -o, --output FILE : output file (default: stdout)\\n\");\n+\tfprintf(stderr, \" -h, --help : show this help message\\n\");\n+\tfprintf(stderr, \"\\nExamples:\\n\");\n+\tfprintf(stderr, \" %s -m stack\\n\", prog);\n+\tfprintf(stderr, \" %s -m handle\\n\", prog);\n+\tfprintf(stderr, \" %s -m stack_handle\\n\", prog);\n+\tfprintf(stderr, \" %s -m stack -o output.txt\\n\", prog);\n+\tfprintf(stderr, \" %s -n 0,1,2\\n\", prog);\n+\tfprintf(stderr, \" %s -m stack -n 0\\n\", prog);\n+}\n+\n+static int validate_mode(const char *mode)\n+{\n+\tif (strcmp(mode, \"stack\") == 0 ||\n+\t strcmp(mode, \"handle\") == 0 ||\n+\t strcmp(mode, \"stack_handle\") == 0)\n+\t\treturn 0;\n+\n+\tfprintf(stderr, \"Error: Invalid mode '%s'\\n\", mode);\n+\tfprintf(stderr, \"Valid modes: stack, handle, stack_handle\\n\");\n+\treturn -1;\n+}\n+\n+static int validate_nid_list(const char *nid_list)\n+{\n+\tconst char *p;\n+\tint i = 0;\n+\tint has_digit = 0;\n+\tint in_range = 0;\n+\tint prev_num = 0;\n+\tint curr_num = 0;\n+\n+\tif (!nid_list || strlen(nid_list) == 0)\n+\t\treturn 0;\n+\n+\tfor (p = nid_list; *p; p++) {\n+\t\tif (*p == ',') {\n+\t\t\tif (!has_digit) {\n+\t\t\t\tfprintf(stderr, \"Error: Invalid nid_list format\\n\");\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t\tif (in_range \u0026\u0026 prev_num \u003e curr_num) {\n+\t\t\t\tfprintf(stderr,\n+\t\t\t\t\t\"Error: Invalid range %d-%d (start must be \u003c= end)\\n\",\n+\t\t\t\t\tprev_num, curr_num);\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t\ti = 0;\n+\t\t\thas_digit = 0;\n+\t\t\tin_range = 0;\n+\t\t\tprev_num = 0;\n+\t\t\tcurr_num = 0;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (*p == '-') {\n+\t\t\tif (!has_digit) {\n+\t\t\t\tfprintf(stderr,\n+\t\t\t\t\t\"Error: Invalid nid_list format \");\n+\t\t\t\tfprintf(stderr,\n+\t\t\t\t\t\"(dash without preceding number)\\n\");\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t\tif (in_range) {\n+\t\t\t\tfprintf(stderr, \"Error: Multiple dashes in nid_list\\n\");\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t\tprev_num = curr_num;\n+\t\t\tcurr_num = 0;\n+\t\t\ti = 0;\n+\t\t\thas_digit = 0;\n+\t\t\tin_range = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (!isdigit((unsigned char)*p)) {\n+\t\t\tfprintf(stderr, \"Error: Invalid character '%c' in nid_list\\n\", *p);\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tif (i \u003e 5) {\n+\t\t\tfprintf(stderr, \"Error: NID too long (max 65536)\\n\");\n+\t\t\treturn -1;\n+\t\t}\n+\t\tcurr_num = curr_num * 10 + (*p - '0');\n+\t\ti++;\n+\t\thas_digit = 1;\n+\t}\n+\n+\tif (!has_digit) {\n+\t\tfprintf(stderr, \"Error: Invalid nid_list format\\n\");\n+\t\treturn -1;\n+\t}\n+\n+\tif (in_range \u0026\u0026 prev_num \u003e curr_num) {\n+\t\tfprintf(stderr,\n+\t\t\t\"Error: Invalid range %d-%d (start must be \u003c= end)\\n\",\n+\t\t\tprev_num, curr_num);\n+\t\treturn -1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int main(int argc, char *argv[])\n+{\n+\tconst char *output_file = NULL;\n+\tchar filter_cmd[MAX_CMD_LEN];\n+\tFILE *output = NULL;\n+\tint fd = -1;\n+\tssize_t ret;\n+\tchar buf[4096];\n+\tint opt;\n+\tsize_t cmd_len = 0;\n+\n+\tsignal(SIGPIPE, SIG_IGN);\n+\n+\tstatic struct option long_options[] = {\n+\t\t{\"mode\",\trequired_argument, 0, 'm'},\n+\t\t{\"nid\",\t\trequired_argument, 0, 'n'},\n+\t\t{\"output\",\trequired_argument, 0, 'o'},\n+\t\t{\"help\",\tno_argument,\t 0, 'h'},\n+\t\t{0, 0, 0, 0}\n+\t};\n+\n+\tfilter_cmd[0] = '\\0';\n+\n+\tif (argc \u003e 1) {\n+\t\tfor (int i = 1; i \u003c argc; i++) {\n+\t\t\tif (strcmp(argv[i], \"-h\") == 0 || strcmp(argv[i], \"--help\") == 0) {\n+\t\t\t\tusage(argv[0]);\n+\t\t\t\treturn 0;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\t/* Check if page_owner exists and is readable */\n+\tif (access(\"/sys/kernel/debug/page_owner\", F_OK) != 0) {\n+\t\tif (errno == ENOENT)\n+\t\t\tfprintf(stderr, \"Error: /sys/kernel/debug/page_owner does not exist\\n\");\n+\t\telse\n+\t\t\tperror(\"Error accessing /sys/kernel/debug/page_owner\");\n+\t\tfprintf(stderr, \"Make sure page_owner is enabled in kernel\\n\");\n+\t\treturn 1;\n+\t}\n+\n+\twhile ((opt = getopt_long(argc, argv, \"m:n:o:h\", long_options, NULL)) != -1) {\n+\t\tint len;\n+\n+\t\tswitch (opt) {\n+\t\tcase 'm': {\n+\t\t\tconst char *mode = optarg;\n+\n+\t\t\tif (validate_mode(mode) \u003c 0)\n+\t\t\t\treturn 1;\n+\t\t\tlen = snprintf(filter_cmd + cmd_len, MAX_CMD_LEN - cmd_len,\n+\t\t\t\t \"%smode=%s\", cmd_len \u003e 0 ? \" \" : \"\", mode);\n+\t\t\tif (len \u003c 0 || cmd_len + len \u003e= MAX_CMD_LEN) {\n+\t\t\t\tfprintf(stderr, \"Error: Command too long\\n\");\n+\t\t\t\treturn 1;\n+\t\t\t}\n+\t\t\tcmd_len += len;\n+\t\t\tbreak;\n+\t\t}\n+\t\tcase 'n': {\n+\t\t\tconst char *nid_list = optarg;\n+\n+\t\t\tif (validate_nid_list(nid_list) \u003c 0)\n+\t\t\t\treturn 1;\n+\t\t\tlen = snprintf(filter_cmd + cmd_len, MAX_CMD_LEN - cmd_len,\n+\t\t\t\t \"%snid=%s\", cmd_len \u003e 0 ? \" \" : \"\", nid_list);\n+\t\t\tif (len \u003c 0 || cmd_len + len \u003e= MAX_CMD_LEN) {\n+\t\t\t\tfprintf(stderr, \"Error: Command too long\\n\");\n+\t\t\t\treturn 1;\n+\t\t\t}\n+\t\t\tcmd_len += len;\n+\t\t\tbreak;\n+\t\t}\n+\t\tcase 'o':\n+\t\t\toutput_file = optarg;\n+\t\t\tbreak;\n+\t\tcase 'h':\n+\t\t\t/* Already handled above */\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tusage(argv[0]);\n+\t\t\treturn 1;\n+\t\t}\n+\t}\n+\n+\t/* At least one filter must be specified */\n+\tif (cmd_len == 0) {\n+\t\tfprintf(stderr, \"Error: At least one filter (-m or -n) must be specified\\n\\n\");\n+\t\tusage(argv[0]);\n+\t\treturn 1;\n+\t}\n+\n+\t/* Open page_owner for read-write - this will fail if kernel doesn't support write */\n+\tfd = open(\"/sys/kernel/debug/page_owner\", O_RDWR);\n+\tif (fd \u003c 0) {\n+\t\tif (errno == EACCES || errno == EPERM) {\n+\t\t\tfprintf(stderr, \"Error: /sys/kernel/debug/page_owner \");\n+\t\t\tfprintf(stderr, \"does not support write access\\n\");\n+\t\t\tfprintf(stderr, \"This kernel does not support \");\n+\t\t\tfprintf(stderr, \"per-fd filtering.\\n\");\n+\t\t\tfprintf(stderr, \"Please ensure you have a kernel with \");\n+\t\t\tfprintf(stderr, \"per-fd filtering support.\\n\");\n+\t\t} else {\n+\t\t\tperror(\"Error opening /sys/kernel/debug/page_owner\");\n+\t\t}\n+\t\treturn 1;\n+\t}\n+\n+\tif (output_file) {\n+\t\toutput = fopen(output_file, \"w\");\n+\t\tif (!output) {\n+\t\t\tperror(\"open output file\");\n+\t\t\tclose(fd);\n+\t\t\treturn 1;\n+\t\t}\n+\t} else {\n+\t\toutput = stdout;\n+\t}\n+\n+\tret = write(fd, filter_cmd, strlen(filter_cmd));\n+\n+\tif (ret \u003c 0) {\n+\t\tif (errno == EINVAL) {\n+\t\t\tfprintf(stderr, \"Error: Kernel rejected the filter command.\\n\");\n+\t\t\tfprintf(stderr, \"Possible causes:\\n\");\n+\t\t\tfprintf(stderr, \" - Kernel does not support per-fd filtering\\n\");\n+\t\t\tfprintf(stderr, \" - NUMA node has no memory\\n\");\n+\t\t\tfprintf(stderr, \" - Unknown reason\\n\");\n+\t\t} else {\n+\t\t\tperror(\"write filter command\");\n+\t\t}\n+\t\tgoto out;\n+\t}\n+\n+\tif ((size_t)ret != strlen(filter_cmd))\n+\t\tfprintf(stderr, \"Warning: Partial write (%zd/%zu)\\n\", ret, strlen(filter_cmd));\n+\n+\t/* Read and display filtered output */\n+\tret = 0;\n+\twhile ((ret = read(fd, buf, sizeof(buf))) \u003e 0) {\n+\t\tsize_t written = fwrite(buf, 1, ret, output);\n+\n+\t\tif (written != (size_t)ret) {\n+\t\t\tif (errno == EPIPE) {\n+\t\t\t\t/* Pipe closed, treat as success */\n+\t\t\t\tret = 0;\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t\tperror(\"write output\");\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n+\t}\n+\n+\tif (ret \u003c 0) {\n+\t\tperror(\"read page_owner\");\n+\t\tgoto out;\n+\t}\n+\n+\tif (fflush(output)) {\n+\t\tif (errno == EPIPE) {\n+\t\t\t/* Pipe closed, treat as success */\n+\t\t\tret = 0;\n+\t\t} else {\n+\t\t\tperror(\"flush output\");\n+\t\t\tret = -1;\n+\t\t}\n+\t}\n+\n+out:\n+\tclose(fd);\n+\tif (output != stdout)\n+\t\tfclose(output);\n+\treturn ret \u003c 0 ? 1 : 0;\n+}\n"
} |
| 2/1 | 2026/07/07 20:20 | agent | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: {
"EnableConfigs": [
"PAGE_OWNER"
],
"FocusSymbols": [
"page_owner_write",
"page_owner_open",
"page_owner_release",
"print_page_owner",
"read_page_owner"
],
"Reasoning": "The patch introduces a new write handler (`page_owner_write`) for the `/sys/kernel/debug/page_owner` debugfs file to support filtering. It also modifies the read handler (`read_page_owner`) and output formatting (`print_page_owner`) to apply the filters. These changes involve parsing user input and managing per-fd state, which are functional changes reachable from userspace and should be fuzzed.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). 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. 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. Do not list too generic configs (we already have them enabled). Only list those that specifically cover the modified code. 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 6c82fdfbaa7e61d07023d2e72a87c4a61396a9e5
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Jul 7 20:20:02 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/mm/page_owner.rst b/Documentation/mm/page_owner.rst
index 6b12f3b007ec91..383e59c42743bd 100644
--- a/Documentation/mm/page_owner.rst
+++ b/Documentation/mm/page_owner.rst
@@ -65,7 +65,14 @@ un-tracking state.
Usage
=====
-1) Build user-space helper::
+1) Build user-space helpers::
+
+To filter page_owner output:
+
+ cd tools/mm
+ make page_owner_filter
+
+To sort and analyze page_owner output:
cd tools/mm
make page_owner_sort
@@ -74,7 +81,11 @@ Usage
3) Do the job that you want to debug.
-4) Analyze information from page owner::
+4) (Optional) Filter page_owner output::
+
+ ./page_owner_filter -m handle -n 0,1,2 > filtered_page_owner.txt
+
+5) Analyze information from page owner::
cat /sys/kernel/debug/page_owner_stacks/show_stacks > stacks.txt
cat stacks.txt
@@ -263,3 +274,65 @@ STANDARD FORMAT SPECIFIERS
f free whether the page has been released or not
st stacktrace stack trace of the page allocation
ator allocator memory allocator for pages
+
+Filtering page_owner output
+============================
+
+page_owner supports filtering output at the kernel level before reading,
+which reduces the amount of data that needs to be processed in userspace.
+
+The page_owner_filter tool provides a convenient interface for this filtering
+capability. It supports two types of filters:
+
+1. **print_mode filter**: Control what information is printed for each page
+ - ``stack``: Print full stack traces (default, compatible with existing usage)
+ - ``handle``: Print only stack handle numbers (much faster, smaller output)
+ - ``stack_handle``: Print both stack traces and handle numbers
+
+ The ``handle`` mode uses numeric identifiers instead of full stack traces.
+ The mapping from handles to actual stack traces can be obtained via the
+ show_stacks_handles interface.
+
+2. **NUMA node filter**: Filter pages by NUMA node ID
+ - Supports single node: ``-n 0``
+ - Multiple nodes: ``-n 0,1,2``
+ - Ranges: ``-n 0-3``
+ - Mixed format: ``-n 0,2-3,5``
+
+Usage examples::
+
+ # Filter by print mode
+ ./page_owner_filter -m handle
+ ./page_owner_filter -m stack_handle
+
+ # Filter by NUMA node
+ ./page_owner_filter -n 0
+ ./page_owner_filter -n 0-3
+
+ # Combined filters
+ ./page_owner_filter -m stack -n 0,1,2
+ ./page_owner_filter -m handle -n 0,2-3
+
+ # Save to file
+ ./page_owner_filter -m handle -o filtered_output.txt
+
+The handle mode is particularly useful for monitoring and performance-critical
+scenarios as it dramatically reduces output size. Testing shows handle mode can
+reduce output size by ~66% (84MB vs 244MB) and improve read performance by ~4.4x
+compared to full stack output.
+
+The NUMA node filter is useful for NUMA-aware memory allocation analysis and debugging.
+
+Behind the scenes, page_owner_filter opens /sys/kernel/debug/page_owner and
+writes filter commands before reading the filtered output. The filtering uses
+per-file-descriptor state, allowing each open() to have independent filter settings.
+
+Each file descriptor maintains its own filter state, so you can have multiple
+independent filtering operations running concurrently. For example, in different
+terminals you can run different filters simultaneously::
+
+ # Terminal 1: Filter node 0
+ ./page_owner_filter -n 0 > node0_output.txt
+
+ # Terminal 2: Filter node 1 (runs concurrently)
+ ./page_owner_filter -n 1 > node1_output.txt
diff --git a/mm/page_owner.c b/mm/page_owner.c
index 8178e0be557f87..f1d4c205aff979 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -54,6 +54,24 @@ struct stack_print_ctx {
u8 flags;
};
+enum page_owner_print_mode {
+ PAGE_OWNER_PRINT_STACK,
+ PAGE_OWNER_PRINT_HANDLE,
+ PAGE_OWNER_PRINT_STACK_HANDLE,
+};
+
+static const char * const page_owner_print_mode_strings[] = {
+ [PAGE_OWNER_PRINT_STACK] = "stack",
+ [PAGE_OWNER_PRINT_HANDLE] = "handle",
+ [PAGE_OWNER_PRINT_STACK_HANDLE] = "stack_handle",
+};
+
+struct page_owner_filter_state {
+ enum page_owner_print_mode print_mode;
+ nodemask_t nid_filter;
+ bool nid_filter_enabled;
+};
+
static bool page_owner_enabled __initdata;
DEFINE_STATIC_KEY_FALSE(page_owner_inited);
@@ -547,16 +565,20 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
static ssize_t
print_page_owner(char __user *buf, size_t count, unsigned long pfn,
struct page *page, struct page_owner *page_owner,
- depot_stack_handle_t handle)
+ depot_stack_handle_t handle,
+ struct page_owner_filter_state *state)
{
int ret, pageblock_mt, page_mt;
char *kbuf;
+ enum page_owner_print_mode print_mode;
count = min_t(size_t, count, PAGE_SIZE);
kbuf = kmalloc(count, GFP_KERNEL);
if (!kbuf)
return -ENOMEM;
+ print_mode = state->print_mode;
+
ret = scnprintf(kbuf, count,
"Page allocated via order %u, mask %#x(%pGg), pid %d, tgid %d (%s), ts %llu ns\n",
page_owner->order, page_owner->gfp_mask,
@@ -575,9 +597,18 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
migratetype_names[pageblock_mt],
&page->flags);
- ret += stack_depot_snprint(handle, kbuf + ret, count - ret, 0);
- if (ret >= count)
- goto err;
+ if (print_mode != PAGE_OWNER_PRINT_HANDLE) {
+ ret += stack_depot_snprint(handle, kbuf + ret, count - ret, 0);
+ if (ret >= count)
+ goto err;
+ }
+
+ if (print_mode != PAGE_OWNER_PRINT_STACK) {
+ ret += scnprintf(kbuf + ret, count - ret, "handle: %u\n",
+ handle);
+ if (ret >= count)
+ goto err;
+ }
if (page_owner->last_migrate_reason != -1) {
ret += scnprintf(kbuf + ret, count - ret,
@@ -664,6 +695,7 @@ read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos)
struct page_ext *page_ext;
struct page_owner *page_owner;
depot_stack_handle_t handle;
+ struct page_owner_filter_state *state = file->private_data;
if (!static_branch_unlikely(&page_owner_inited))
return -EINVAL;
@@ -740,15 +772,31 @@ read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos)
if (!handle)
goto ext_put_continue;
+ if (state->nid_filter_enabled) {
+ int nid;
+ memdesc_flags_t page_flags = READ_ONCE(page->flags);
+
+ /*
+ * Bypass PF_POISONED_CHECK() in page_to_nid() to avoid
+ * VM_BUG_ON when accessing poisoned pages.
+ */
+ if (page_flags.f == PAGE_POISON_PATTERN)
+ goto ext_put_continue;
+ nid = memdesc_nid(page_flags);
+ if (!node_isset(nid, state->nid_filter))
+ goto ext_put_continue;
+ }
+
/* Record the next PFN to read in the file offset */
*ppos = pfn + 1;
page_owner_tmp = *page_owner;
page_ext_put(page_ext);
return print_page_owner(buf, count, pfn, page,
- &page_owner_tmp, handle);
+ &page_owner_tmp, handle, state);
ext_put_continue:
page_ext_put(page_ext);
+ cond_resched();
}
return 0;
@@ -847,7 +895,113 @@ static void init_early_allocated_pages(void)
init_pages_in_zone(zone);
}
+static int page_owner_open(struct inode *inode, struct file *file)
+{
+ struct page_owner_filter_state *state;
+
+ state = kzalloc_obj(*state);
+ if (!state)
+ return -ENOMEM;
+
+ state->print_mode = PAGE_OWNER_PRINT_STACK;
+ nodes_clear(state->nid_filter);
+ state->nid_filter_enabled = false;
+ file->private_data = state;
+ return 0;
+}
+
+static int page_owner_release(struct inode *inode, struct file *file)
+{
+ kfree(file->private_data);
+ return 0;
+}
+
+static ssize_t page_owner_write(struct file *file,
+ const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ char *kbuf;
+ char *orig;
+ char *token;
+ int ret;
+ struct page_owner_filter_state *state = file->private_data;
+ enum page_owner_print_mode new_print_mode;
+ nodemask_t new_nid_filter;
+ bool new_nid_filter_enabled;
+
+ /*
+ * Maximum input length for filter commands:
+ * - 32: print_mode command max length is 17 ("mode=stack_handle")
+ * with sufficient buffer
+ * - 6 * MAX_NUMNODES: worst case for nid list
+ * Worst case per node: ",NNNNN" (comma + 5-digit node number) = 6 bytes
+ */
+ if (count > 32 + 6 * MAX_NUMNODES)
+ return -EINVAL;
+
+ kbuf = memdup_user_nul(buf, count);
+ if (IS_ERR(kbuf))
+ return PTR_ERR(kbuf);
+
+ orig = kbuf;
+
+ new_print_mode = state->print_mode;
+ new_nid_filter = state->nid_filter;
+ new_nid_filter_enabled = state->nid_filter_enabled;
+
+ while ((token = strsep(&kbuf, " \t\n")) != NULL) {
+ if (*token == '\0')
+ continue;
+
+ if (!strncmp(token, "mode=", 5)) {
+ ret = sysfs_match_string(page_owner_print_mode_strings,
+ token + 5);
+ if (ret < 0)
+ goto out_free;
+ new_print_mode = ret;
+ } else if (!strncmp(token, "nid=", 4)) {
+ ret = nodelist_parse(token + 4, new_nid_filter);
+ if (ret < 0)
+ goto out_free;
+
+ if (nodes_empty(new_nid_filter)) {
+ ret = -EINVAL;
+ goto out_free;
+ }
+
+ /*
+ * We want to filter memory allocations by numa nodes, so make sure
+ * that the specified nodes have memory.
+ */
+ if (!nodes_subset(new_nid_filter, node_states[N_MEMORY])) {
+ ret = -EINVAL;
+ goto out_free;
+ }
+
+ new_nid_filter_enabled = true;
+ } else {
+ ret = -EINVAL;
+ goto out_free;
+ }
+ }
+
+ /* Commit all filter changes */
+ state->print_mode = new_print_mode;
+ state->nid_filter = new_nid_filter;
+ state->nid_filter_enabled = new_nid_filter_enabled;
+
+ ret = count;
+
+out_free:
+ kfree(orig);
+ return ret;
+}
+
static const struct file_operations page_owner_fops = {
+ .owner = THIS_MODULE,
+ .open = page_owner_open,
+ .release = page_owner_release,
+ .write = page_owner_write,
.read = read_page_owner,
.llseek = lseek_page_owner,
};
@@ -980,7 +1134,7 @@ static int __init pageowner_init(void)
return 0;
}
- debugfs_create_file("page_owner", 0400, NULL, NULL, &page_owner_fops);
+ debugfs_create_file("page_owner", 0600, NULL, NULL, &page_owner_fops);
dir = debugfs_create_dir("page_owner_stacks", NULL);
debugfs_create_file("show_stacks", 0400, dir,
(void *)(STACK_PRINT_FLAG_STACK |
diff --git a/tools/mm/Makefile b/tools/mm/Makefile
index f5725b5c23aa26..858186a6eefdbd 100644
--- a/tools/mm/Makefile
+++ b/tools/mm/Makefile
@@ -3,7 +3,7 @@
#
include ../scripts/Makefile.include
-BUILD_TARGETS=page-types slabinfo page_owner_sort thp_swap_allocator_test
+BUILD_TARGETS=page-types slabinfo page_owner_sort page_owner_filter thp_swap_allocator_test
INSTALL_TARGETS = $(BUILD_TARGETS) thpmaps
LIB_DIR = ../lib/api
@@ -23,7 +23,7 @@ $(LIBS):
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
clean:
- $(RM) page-types slabinfo page_owner_sort thp_swap_allocator_test
+ $(RM) page-types slabinfo page_owner_sort page_owner_filter thp_swap_allocator_test
make -C $(LIB_DIR) clean
sbindir ?= /usr/sbin
diff --git a/tools/mm/page_owner_filter.c b/tools/mm/page_owner_filter.c
new file mode 100644
index 00000000000000..1d1f0a38678a2f
--- /dev/null
+++ b/tools/mm/page_owner_filter.c
@@ -0,0 +1,310 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * User-space helper to filter page_owner output per-fd
+ *
+ * Example use:
+ * ./page_owner_filter -m handle
+ * ./page_owner_filter -m stack_handle
+ * ./page_owner_filter -n 0,1,2
+ *
+ * See Documentation/mm/page_owner.rst
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <getopt.h>
+#include <signal.h>
+
+#define MAX_CMD_LEN 512
+
+static void usage(const char *prog)
+{
+ fprintf(stderr, "Usage: %s [OPTIONS]\n", prog);
+ fprintf(stderr, "\nOptions:\n");
+ fprintf(stderr, " -m, --mode MODE : print_mode (stack, handle, or stack_handle)\n");
+ fprintf(stderr, " -n, --nid NID_LIST : NUMA node IDs (comma-separated or ranges)\n");
+ fprintf(stderr, " -o, --output FILE : output file (default: stdout)\n");
+ fprintf(stderr, " -h, --help : show this help message\n");
+ fprintf(stderr, "\nExamples:\n");
+ fprintf(stderr, " %s -m stack\n", prog);
+ fprintf(stderr, " %s -m handle\n", prog);
+ fprintf(stderr, " %s -m stack_handle\n", prog);
+ fprintf(stderr, " %s -m stack -o output.txt\n", prog);
+ fprintf(stderr, " %s -n 0,1,2\n", prog);
+ fprintf(stderr, " %s -m stack -n 0\n", prog);
+}
+
+static int validate_mode(const char *mode)
+{
+ if (strcmp(mode, "stack") == 0 ||
+ strcmp(mode, "handle") == 0 ||
+ strcmp(mode, "stack_handle") == 0)
+ return 0;
+
+ fprintf(stderr, "Error: Invalid mode '%s'\n", mode);
+ fprintf(stderr, "Valid modes: stack, handle, stack_handle\n");
+ return -1;
+}
+
+static int validate_nid_list(const char *nid_list)
+{
+ const char *p;
+ int i = 0;
+ int has_digit = 0;
+ int in_range = 0;
+ int prev_num = 0;
+ int curr_num = 0;
+
+ if (!nid_list || strlen(nid_list) == 0)
+ return 0;
+
+ for (p = nid_list; *p; p++) {
+ if (*p == ',') {
+ if (!has_digit) {
+ fprintf(stderr, "Error: Invalid nid_list format\n");
+ return -1;
+ }
+ if (in_range && prev_num > curr_num) {
+ fprintf(stderr,
+ "Error: Invalid range %d-%d (start must be <= end)\n",
+ prev_num, curr_num);
+ return -1;
+ }
+ i = 0;
+ has_digit = 0;
+ in_range = 0;
+ prev_num = 0;
+ curr_num = 0;
+ continue;
+ }
+
+ if (*p == '-') {
+ if (!has_digit) {
+ fprintf(stderr,
+ "Error: Invalid nid_list format ");
+ fprintf(stderr,
+ "(dash without preceding number)\n");
+ return -1;
+ }
+ if (in_range) {
+ fprintf(stderr, "Error: Multiple dashes in nid_list\n");
+ return -1;
+ }
+ prev_num = curr_num;
+ curr_num = 0;
+ i = 0;
+ has_digit = 0;
+ in_range = 1;
+ continue;
+ }
+
+ if (!isdigit((unsigned char)*p)) {
+ fprintf(stderr, "Error: Invalid character '%c' in nid_list\n", *p);
+ return -1;
+ }
+
+ if (i > 5) {
+ fprintf(stderr, "Error: NID too long (max 65536)\n");
+ return -1;
+ }
+ curr_num = curr_num * 10 + (*p - '0');
+ i++;
+ has_digit = 1;
+ }
+
+ if (!has_digit) {
+ fprintf(stderr, "Error: Invalid nid_list format\n");
+ return -1;
+ }
+
+ if (in_range && prev_num > curr_num) {
+ fprintf(stderr,
+ "Error: Invalid range %d-%d (start must be <= end)\n",
+ prev_num, curr_num);
+ return -1;
+ }
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ const char *output_file = NULL;
+ char filter_cmd[MAX_CMD_LEN];
+ FILE *output = NULL;
+ int fd = -1;
+ ssize_t ret;
+ char buf[4096];
+ int opt;
+ size_t cmd_len = 0;
+
+ signal(SIGPIPE, SIG_IGN);
+
+ static struct option long_options[] = {
+ {"mode", required_argument, 0, 'm'},
+ {"nid", required_argument, 0, 'n'},
+ {"output", required_argument, 0, 'o'},
+ {"help", no_argument, 0, 'h'},
+ {0, 0, 0, 0}
+ };
+
+ filter_cmd[0] = '\0';
+
+ if (argc > 1) {
+ for (int i = 1; i < argc; i++) {
+ if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
+ usage(argv[0]);
+ return 0;
+ }
+ }
+ }
+
+ /* Check if page_owner exists and is readable */
+ if (access("/sys/kernel/debug/page_owner", F_OK) != 0) {
+ if (errno == ENOENT)
+ fprintf(stderr, "Error: /sys/kernel/debug/page_owner does not exist\n");
+ else
+ perror("Error accessing /sys/kernel/debug/page_owner");
+ fprintf(stderr, "Make sure page_owner is enabled in kernel\n");
+ return 1;
+ }
+
+ while ((opt = getopt_long(argc, argv, "m:n:o:h", long_options, NULL)) != -1) {
+ int len;
+
+ switch (opt) {
+ case 'm': {
+ const char *mode = optarg;
+
+ if (validate_mode(mode) < 0)
+ return 1;
+ len = snprintf(filter_cmd + cmd_len, MAX_CMD_LEN - cmd_len,
+ "%smode=%s", cmd_len > 0 ? " " : "", mode);
+ if (len < 0 || cmd_len + len >= MAX_CMD_LEN) {
+ fprintf(stderr, "Error: Command too long\n");
+ return 1;
+ }
+ cmd_len += len;
+ break;
+ }
+ case 'n': {
+ const char *nid_list = optarg;
+
+ if (validate_nid_list(nid_list) < 0)
+ return 1;
+ len = snprintf(filter_cmd + cmd_len, MAX_CMD_LEN - cmd_len,
+ "%snid=%s", cmd_len > 0 ? " " : "", nid_list);
+ if (len < 0 || cmd_len + len >= MAX_CMD_LEN) {
+ fprintf(stderr, "Error: Command too long\n");
+ return 1;
+ }
+ cmd_len += len;
+ break;
+ }
+ case 'o':
+ output_file = optarg;
+ break;
+ case 'h':
+ /* Already handled above */
+ break;
+ default:
+ usage(argv[0]);
+ return 1;
+ }
+ }
+
+ /* At least one filter must be specified */
+ if (cmd_len == 0) {
+ fprintf(stderr, "Error: At least one filter (-m or -n) must be specified\n\n");
+ usage(argv[0]);
+ return 1;
+ }
+
+ /* Open page_owner for read-write - this will fail if kernel doesn't support write */
+ fd = open("/sys/kernel/debug/page_owner", O_RDWR);
+ if (fd < 0) {
+ if (errno == EACCES || errno == EPERM) {
+ fprintf(stderr, "Error: /sys/kernel/debug/page_owner ");
+ fprintf(stderr, "does not support write access\n");
+ fprintf(stderr, "This kernel does not support ");
+ fprintf(stderr, "per-fd filtering.\n");
+ fprintf(stderr, "Please ensure you have a kernel with ");
+ fprintf(stderr, "per-fd filtering support.\n");
+ } else {
+ perror("Error opening /sys/kernel/debug/page_owner");
+ }
+ return 1;
+ }
+
+ if (output_file) {
+ output = fopen(output_file, "w");
+ if (!output) {
+ perror("open output file");
+ close(fd);
+ return 1;
+ }
+ } else {
+ output = stdout;
+ }
+
+ ret = write(fd, filter_cmd, strlen(filter_cmd));
+
+ if (ret < 0) {
+ if (errno == EINVAL) {
+ fprintf(stderr, "Error: Kernel rejected the filter command.\n");
+ fprintf(stderr, "Possible causes:\n");
+ fprintf(stderr, " - Kernel does not support per-fd filtering\n");
+ fprintf(stderr, " - NUMA node has no memory\n");
+ fprintf(stderr, " - Unknown reason\n");
+ } else {
+ perror("write filter command");
+ }
+ goto out;
+ }
+
+ if ((size_t)ret != strlen(filter_cmd))
+ fprintf(stderr, "Warning: Partial write (%zd/%zu)\n", ret, strlen(filter_cmd));
+
+ /* Read and display filtered output */
+ ret = 0;
+ while ((ret = read(fd, buf, sizeof(buf))) > 0) {
+ size_t written = fwrite(buf, 1, ret, output);
+
+ if (written != (size_t)ret) {
+ if (errno == EPIPE) {
+ /* Pipe closed, treat as success */
+ ret = 0;
+ goto out;
+ }
+ perror("write output");
+ ret = -1;
+ goto out;
+ }
+ }
+
+ if (ret < 0) {
+ perror("read page_owner");
+ goto out;
+ }
+
+ if (fflush(output)) {
+ if (errno == EPIPE) {
+ /* Pipe closed, treat as success */
+ ret = 0;
+ } else {
+ perror("flush output");
+ ret = -1;
+ }
+ }
+
+out:
+ close(fd);
+ if (output != stdout)
+ fclose(output);
+ return ret < 0 ? 1 : 0;
+}
|
| 3/2 | 2026/07/07 20:20 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 8961 output: 27 thoughts: 236 Thoughts: **My Assessment of the Page Owner Filter Patch** Okay, so I'm looking at this patch that tweaks `mm/page_owner.c` and throws in a new tool, `tools/mm/page_owner_filter.c`. My initial thought is, this is a potentially useful addition. It's giving us some filtering options for `/sys/kernel/debug/page_owner`, which could be huge for isolating memory allocation issues. Being able to filter the output of that is a game changer for debugging. The patch implements filtering by parsing commands written to the file descriptor; specifically it's looking for "mode=" and "nid=". That's all handled by `page_owner_write`. A `struct page_owner_filter_state` is allocated and stored per-file open, meaning each session gets its own filter, which is smart. Then, the actual `read_page_owner` function uses this state to do the actual filtering. Now, my key questions: Is this code actually *reachable*? Yep, debugfs, so `/sys/kernel/debug/page_owner`. That's good; it's right where we'd expect it. Does it *function* as claimed? Yes, the core logic is there. The `page_owner_write` function parses our input and the new state is used to filter reads. And the next thing I'm thinking is, *this needs fuzzing*. Any time you're parsing user input, you're opening the door to potential vulnerabilities. `page_owner_write` is the obvious target. Alright, let's make sure the dependencies are in place. The core functionality, `mm/page_owner.c`, is conditional on `CONFIG_PAGE_OWNER`. Let me verify that's the case. Make sure that's enabled. |
| 4/2 | 2026/07/07 20:20 | tool | grepper |
0mArgs:{
"Expression": "PAGE_OWNER",
"PathPrefix": "mm/Makefile"
}Results: {
"Output": "mm/Makefile=119=obj-$(CONFIG_DEBUG_VM_PGTABLE) += debug_vm_pgtable.o\nmm/Makefile:120:obj-$(CONFIG_PAGE_OWNER) += page_owner.o\nmm/Makefile-121-obj-$(CONFIG_MEMORY_ISOLATION) += page_isolation.o\n"
} |
| 5/2 | 2026/07/07 20:20 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 9708 output: 31 thoughts: 51 Thoughts: Okay, here's my take on those thoughts, framed as my own mental processing: **Dependencies and Debugging Configuration** Right, so `CONFIG_PAGE_OWNER` is definitely a mandatory requirement here. Got it. And I know from experience that we also need `CONFIG_DEBUG_FS` enabled, since the functionality is exposed through the debug filesystem. Best practice is to ensure both are available, before we get any further. Now, before I dive too deep, I should probably double-check if `CONFIG_PAGE_OWNER` has any other hidden dependencies. I'll need to check the Kconfig files or related documentation to make certain. Better safe than sorry when dealing with kernel configurations! |
| 6/2 | 2026/07/07 20:20 | tool | grepper |
0mArgs:{
"Expression": "config PAGE_OWNER",
"PathPrefix": "mm/Kconfig.debug"
}Results: {
"Output": "mm/Kconfig.debug=73=config SLUB_RCU_DEBUG\n--\nmm/Kconfig.debug-104-\nmm/Kconfig.debug:105:config PAGE_OWNER\nmm/Kconfig.debug-106-\tbool \"Track page owner\"\n"
} |
| 7/2 | 2026/07/07 20:20 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 10019 output: 34 thoughts: 19 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience: **Diving into Debug Configuration** Okay, so I'm thinking, where's the best place to dig into these debug options? My instinct is to check the `mm/Kconfig.debug` file, specifically around line 105. That's usually where these granular debug features are defined, right? I'm anticipating that's where I'll find the relevant configuration options that determine how the kernel's memory management debugging is handled, especially since we're looking at things that are typically enabled during development or for very specific troubleshooting. Hopefully, the Kconfig file will offer a clear overview of the dependencies, defaults, and descriptions. This should give me a solid starting point for understanding how the debug features in this part of the kernel work. |
| 8/2 | 2026/07/07 20:20 | tool | read-file |
0mArgs:{
"File": "mm/Kconfig.debug",
"FirstLine": 100,
"LineCount": 20
}Results: {
"Contents": " 100:\t\t This is designed as a debugging feature, not a security feature.\n 101:\t\t Objects are sometimes recycled without RCU delay under memory pressure.\n 102:\t\n 103:\t\t If unsure, say N.\n 104:\t\n 105:\tconfig PAGE_OWNER\n 106:\t\tbool \"Track page owner\"\n 107:\t\tdepends on DEBUG_KERNEL \u0026\u0026 STACKTRACE_SUPPORT\n 108:\t\tselect DEBUG_FS\n 109:\t\tselect STACKTRACE\n 110:\t\tselect STACKDEPOT\n 111:\t\tselect PAGE_EXTENSION\n 112:\t\thelp\n 113:\t\t This keeps track of what call chain is the owner of a page, may\n 114:\t\t help to find bare alloc_page(s) leaks. Even if you include this\n 115:\t\t feature on your build, it is disabled in default. You should pass\n 116:\t\t \"page_owner=on\" to boot parameter in order to enable it. Eats\n 117:\t\t a fair amount of memory if enabled. See tools/mm/page_owner_sort.c\n 118:\t\t for user-space helper.\n 119:\t\n"
} |
| 9/2 | 2026/07/07 20:20 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 10571 output: 32 thoughts: 207 Thoughts: **Analyzing the Implementation of `page_owner=on` for Syzkaller** Okay, so the message says, "You should pass `page_owner=on` to a boot parameter to enable it." Right, that makes sense. Hang on, though, Syzkaller *can* pass boot parameters, but it's not a configuration option, or is it? Could I enable it through sysfs or debugfs? No, that's not how it works; it's a boot-time thing. Now I recall that maybe, just maybe, Syzkaller already has `page_owner=on` set as a default boot parameter for some of its configurations... or perhaps not. Either way, we can definitely specify `PAGE_OWNER` directly in the configuration settings, can't we? Alright, the patch adds functions: `page_owner_write`, `page_owner_open`, `page_owner_release`, and `print_page_owner`. These are the key functions I should be paying attention to. Let me note those down: `page_owner_write`, `page_owner_open`, `page_owner_release`, `print_page_owner`, and of course the `read_page_owner` function as well. I need to understand how these integrate. Okay, let's start by digging into `page_owner_write` in `mm/page_owner.c`. This is where the magic probably happens. |
| 10/2 | 2026/07/07 20:20 | tool | grepper |
0mArgs:{
"Expression": "page_owner_write",
"PathPrefix": "mm/page_owner.c"
}Results: {
"Output": "mm/page_owner.c=913=static int page_owner_release(struct inode *inode, struct file *file)\n--\nmm/page_owner.c-918-\nmm/page_owner.c:919:static ssize_t page_owner_write(struct file *file,\nmm/page_owner.c-920-\t\t\t\t const char __user *buf,\n--\nmm/page_owner.c=1000=static const struct file_operations page_owner_fops = {\n--\nmm/page_owner.c-1003-\t.release\t= page_owner_release,\nmm/page_owner.c:1004:\t.write\t\t= page_owner_write,\nmm/page_owner.c-1005-\t.read\t\t= read_page_owner,\n"
} |
| 11/2 | 2026/07/07 20:20 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 11335 output: 32 thoughts: 19 Thoughts: Here's my take on those functions: **Digging into `page_owner_open` and `page_owner_release`** Okay, so I need to refresh my understanding of how `page_owner_open` and `page_owner_release` are interacting. Given my familiarity with [User's field], I know these are likely critical functions for managing resource ownership and lifecycle, specifically at the page level. I need to quickly identify the key aspects of these functions. Are they locking mechanisms, reference counters, or something more sophisticated? What data structures are they manipulating? I'm hoping to quickly grasp the implications for concurrency, resource contention, and, of course, potential bottlenecks within the system. The details of implementation must quickly become clear in my mind. Are they using atomic operations or rely on other synchronization primitives. |
| 12/2 | 2026/07/07 20:20 | tool | grepper |
0mArgs:{
"Expression": "page_owner_open",
"PathPrefix": "mm/page_owner.c"
}Results: {
"Output": "mm/page_owner.c=890=static void init_early_allocated_pages(void)\n--\nmm/page_owner.c-897-\nmm/page_owner.c:898:static int page_owner_open(struct inode *inode, struct file *file)\nmm/page_owner.c-899-{\n--\nmm/page_owner.c=1000=static const struct file_operations page_owner_fops = {\nmm/page_owner.c-1001-\t.owner\t\t= THIS_MODULE,\nmm/page_owner.c:1002:\t.open\t\t= page_owner_open,\nmm/page_owner.c-1003-\t.release\t= page_owner_release,\n"
} |
| 13/2 | 2026/07/07 20:20 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 11758 output: 166 thoughts: 470 Thoughts: **Analyzing the Page Owner Filtering Patch**
Okay, so this patch is adding filtering to the `page_owner` functionality, specifically within `mm/page_owner.c`. My initial thoughts are that this is interesting from a fuzzing perspective, especially because it introduces user-facing changes. Let's break it down.
First, it adds a new function, `page_owner_write`, which parses user input. This is *crucial* because anything that takes input from a user, especially for kernel features, screams "potential fuzzing target". The `read_page_owner` function utilizes the filter state, and `print_page_owner` formats output according to the filters. These are all part of the same filtration chain.
The key symbols to focus on are `page_owner_write`, `page_owner_open`, `page_owner_release`, `print_page_owner`, and `read_page_owner`.
Looking at the configurations, `PAGE_OWNER` seems to be the controlling flag, which also selects `DEBUG_FS` and `PAGE_EXTENSION`. These are important to note, as the configurations can have an impact.
Now, I'm spotting a change in the debugfs file permissions: it's going from read-only to read/write! The patch changes `debugfs_create_file("page_owner", 0400, NULL, NULL, &page_owner_fops)` to `debugfs_create_file("page_owner", 0600, NULL, NULL, &page_owner_fops)`. This is significant because it explicitly enables writing to the file, which reinforces the need to fuzz the write handler.
So, the core logic here: the patch's purpose is to allow filtering of page owner information through `/sys/kernel/debug/page_owner`. The addition of `page_owner_write` for user input, plus the modifications to `read_page_owner` and `print_page_owner` to handle filtering logic, make it a definite target for fuzzing. User input parsing is the weak point.
Let me just double-check... `page_owner_write` is static, which is good. Syzkaller can fuzz those as long as they're in the focus list. And looking back, `read_page_owner` and `print_page_owner` were already there, but they were modified, meaning they're also interesting.
To summarize, all the functions are listed:
`page_owner_open`
`page_owner_release`
`page_owner_write`
`print_page_owner`
`read_page_owner`
I think I've got a solid understanding of this patch's implications for fuzzing, and I'm ready to set the results.
|
| 14/2 | 2026/07/07 20:20 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"PAGE_OWNER"
],
"FocusSymbols": [
"page_owner_write",
"page_owner_open",
"page_owner_release",
"print_page_owner",
"read_page_owner"
],
"Reasoning": "The patch introduces a new write handler (`page_owner_write`) for the `/sys/kernel/debug/page_owner` debugfs file to support filtering. It also modifies the read handler (`read_page_owner`) and output formatting (`print_page_owner`) to apply the filters. These changes involve parsing user input and managing per-fd state, which are functional changes reachable from userspace and should be fuzzed.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"PAGE_OWNER"
],
"FocusSymbols": [
"page_owner_write",
"page_owner_open",
"page_owner_release",
"print_page_owner",
"read_page_owner"
],
"Reasoning": "The patch introduces a new write handler (`page_owner_write`) for the `/sys/kernel/debug/page_owner` debugfs file to support filtering. It also modifies the read handler (`read_page_owner`) and output formatting (`print_page_owner`) to apply the filters. These changes involve parsing user input and managing per-fd state, which are functional changes reachable from userspace and should be fuzzed.",
"WorthFuzzing": true
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|