AI Trajectory


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

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [
    "anon_pipe_prefill_and_lock",
    "anon_pipe_trim_pool_and_unlock",
    "anon_pipe_get_page",
    "anon_pipe_put_page",
    "anon_pipe_prealloc_push",
    "anon_pipe_prealloc_pop"
  ],
  "Reasoning": "The patch modifies the page preallocation logic for pipes, introducing a new preallocation pool and changing how pages are allocated and freed during pipe reads and writes. This is a core functional change that should be fuzzed to ensure no memory leaks, use-after-frees, or race conditions occur.",
  "WorthFuzzing": true
}

1/1 2026/07/10 10:03 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 7a80c498218747af054fb0a189a1ac5d7974f5dd\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Fri Jul 10 10:03:07 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/fs/pipe.c b/fs/pipe.c\nindex 429b0714ec5755..b14d5bb2b45a45 100644\n--- a/fs/pipe.c\n+++ b/fs/pipe.c\n@@ -111,75 +111,95 @@ void pipe_double_lock(struct pipe_inode_info *pipe1,\n \tpipe_lock(pipe2);\n }\n \n-#define PIPE_PREALLOC_MAX 8\n+static struct page *anon_pipe_prealloc_pop(struct anon_pipe_prealloc *prealloc)\n+{\n+\tif (!prealloc-\u003ecount)\n+\t\treturn NULL;\n \n-struct anon_pipe_prealloc {\n-\tstruct page *pages[PIPE_PREALLOC_MAX];\n-\tunsigned int count;\n-};\n+\tprealloc-\u003ecount--;\n+\n+\treturn prealloc-\u003epages[prealloc-\u003ecount];\n+}\n+\n+/* Push a page to the prealloc pool. Returns true if added, false if full. */\n+static bool anon_pipe_prealloc_push(struct anon_pipe_prealloc *prealloc,\n+\t\t\t\t    struct page *page)\n+{\n+\tif (prealloc-\u003ecount \u003e= PIPE_PREALLOC_MAX)\n+\t\treturn false;\n+\tprealloc-\u003epages[prealloc-\u003ecount++] = page;\n+\treturn true;\n+}\n \n /*\n- * Pre-allocate pages outside pipe-\u003emutex for multi-page writes.\n- * alloc_page() with GFP_HIGHUSER can sleep in reclaim and runs memcg\n- * charging; doing it under the mutex stalls a concurrent reader.\n- *\n- * Loop alloc_page() instead of alloc_pages_bulk_*(): the bulk path refuses\n- * __GFP_ACCOUNT under memcg (see commit 8dcb3060d81d \"memcg: page_alloc:\n- * skip bulk allocator for __GFP_ACCOUNT\") and silently degrades to a single\n- * page. A per-page loop keeps memcg accounting and the task NUMA mempolicy\n- * honoured for every page; the per-call overhead is small compared to the\n- * pipe-\u003emutex hold-time being shrunk. Any shortfall is covered by the\n- * in-lock alloc_page() fallback in anon_pipe_get_page().\n+ * Top up the pipe's own pool, then take pipe-\u003emutex and return with it held.\n+ * The shortfall is allocated outside the lock; the push and the caller's write\n+ * then run under a single lock acquisition, avoiding a separate prefill\n+ * lock/unlock cycle. anon_pipe_get_page() drains the pool instead of allocating\n+ * under the lock.\n  */\n-static void anon_pipe_get_page_prealloc(struct anon_pipe_prealloc *prealloc,\n-\t\t\t\t\tsize_t total_len)\n+static void anon_pipe_prefill_and_lock(struct pipe_inode_info *pipe, size_t total_len)\n {\n-\tunsigned int want, i;\n-\tstruct page *page;\n-\n-\tprealloc-\u003ecount = 0;\n-\tif (total_len \u003c= PAGE_SIZE)\n-\t\treturn;\n+\tstruct page *pages[PIPE_PREALLOC_MAX];\n+\tunsigned int want, have, need, n = 0;\n \n \twant = min_t(unsigned int, DIV_ROUND_UP(total_len, PAGE_SIZE),\n \t\t     PIPE_PREALLOC_MAX);\n+\t/* Unlocked read; the pool is refilled under the lock below. */\n+\thave = min_t(unsigned int, READ_ONCE(pipe-\u003eprealloc.count), want);\n+\tneed = want - have;\n+\n+\tif (!need) {\n+\t\tmutex_lock(\u0026pipe-\u003emutex);\n+\t\treturn;\n+\t}\n+\n+\twhile (n \u003c need) {\n+\t\tstruct page *page = alloc_page(GFP_HIGHUSER | __GFP_ACCOUNT);\n \n-\tfor (i = 0; i \u003c want; i++) {\n-\t\tpage = alloc_page(GFP_HIGHUSER | __GFP_ACCOUNT);\n \t\tif (!page)\n \t\t\tbreak;\n-\t\tprealloc-\u003epages[prealloc-\u003ecount++] = page;\n+\t\tpages[n++] = page;\n \t}\n+\n+\tmutex_lock(\u0026pipe-\u003emutex);\n+\twhile (n \u0026\u0026 anon_pipe_prealloc_push(\u0026pipe-\u003eprealloc, pages[n - 1]))\n+\t\tn--;\n+\n+\t/*\n+\t * Just flush any extra page that got affected by the TOCTOU\n+\t * effect\n+\t */\n+\twhile (n)\n+\t\tput_page(pages[--n]);\n }\n \n-static struct page *anon_pipe_prealloc_pop(struct anon_pipe_prealloc *prealloc)\n+/*\n+ * Called with pipe-\u003emutex held. Trim the pool down to PIPE_PREALLOC_KEEP under\n+ * the lock, drop it, then free the excess outside the critical section.\n+ */\n+static void anon_pipe_trim_pool_and_unlock(struct pipe_inode_info *pipe)\n {\n-\tif (!prealloc-\u003ecount)\n-\t\treturn NULL;\n+\tstruct page *excess[PIPE_PREALLOC_MAX];\n+\tunsigned int nexcess = 0;\n \n-\tprealloc-\u003ecount--;\n+\twhile (pipe-\u003eprealloc.count \u003e PIPE_PREALLOC_KEEP)\n+\t\texcess[nexcess++] = anon_pipe_prealloc_pop(\u0026pipe-\u003eprealloc);\n+\tmutex_unlock(\u0026pipe-\u003emutex);\n \n-\treturn prealloc-\u003epages[prealloc-\u003ecount];\n+\twhile (nexcess)\n+\t\tput_page(excess[--nexcess]);\n }\n \n-static struct page *anon_pipe_get_page(struct pipe_inode_info *pipe,\n-\t\t\t\t       struct anon_pipe_prealloc *prealloc)\n+static struct page *anon_pipe_get_page(struct pipe_inode_info *pipe)\n {\n \tstruct page *page;\n \n-\t/* Drain prealloc first to keep tmp_page[] hot for later small writes. */\n-\tpage = anon_pipe_prealloc_pop(prealloc);\n+\t/* Drain the prealloc pool before allocating. Called with mutex held. */\n+\tpage = anon_pipe_prealloc_pop(\u0026pipe-\u003eprealloc);\n \tif (page)\n \t\treturn page;\n \n-\tfor (int i = 0; i \u003c ARRAY_SIZE(pipe-\u003etmp_page); i++) {\n-\t\tif (pipe-\u003etmp_page[i]) {\n-\t\t\tpage = pipe-\u003etmp_page[i];\n-\t\t\tpipe-\u003etmp_page[i] = NULL;\n-\t\t\treturn page;\n-\t\t}\n-\t}\n-\n \t/* FWIW: This is called with pipe-\u003emutex held */\n \treturn alloc_page(GFP_HIGHUSER | __GFP_ACCOUNT);\n }\n@@ -187,48 +207,11 @@ static struct page *anon_pipe_get_page(struct pipe_inode_info *pipe,\n static void anon_pipe_put_page(struct pipe_inode_info *pipe,\n \t\t\t       struct page *page)\n {\n-\tif (page_count(page) == 1) {\n-\t\tfor (int i = 0; i \u003c ARRAY_SIZE(pipe-\u003etmp_page); i++) {\n-\t\t\tif (!pipe-\u003etmp_page[i]) {\n-\t\t\t\tpipe-\u003etmp_page[i] = page;\n-\t\t\t\treturn;\n-\t\t\t}\n-\t\t}\n-\t}\n-\n-\tput_page(page);\n-}\n-\n-/*\n- * Stash leftover prealloc pages in tmp_page[] so the next write to this\n- * pipe gets a hot page without entering the allocator.\n- */\n-static void anon_pipe_refill_tmp_pages(struct pipe_inode_info *pipe,\n-\t\t\t\t       struct anon_pipe_prealloc *prealloc)\n-{\n-\tint i, idx;\n-\n-\tif (!prealloc-\u003ecount)\n+\tif (page_count(page) == 1 \u0026\u0026\n+\t    anon_pipe_prealloc_push(\u0026pipe-\u003eprealloc, page))\n \t\treturn;\n \n-\tfor (i = 0; i \u003c ARRAY_SIZE(pipe-\u003etmp_page); i++) {\n-\t\tif (pipe-\u003etmp_page[i])\n-\t\t\tcontinue;\n-\t\tif (!prealloc-\u003ecount)\n-\t\t\treturn;\n-\t\tidx = --prealloc-\u003ecount;\n-\t\tpipe-\u003etmp_page[i] = prealloc-\u003epages[idx];\n-\t\tprealloc-\u003epages[idx] = NULL;\n-\t}\n-}\n-\n-/* Runs after mutex_unlock() to keep put_page() out of the critical section. */\n-static void anon_pipe_free_pages(struct anon_pipe_prealloc *prealloc)\n-{\n-\twhile (prealloc-\u003ecount) {\n-\t\tprealloc-\u003ecount--;\n-\t\tput_page(prealloc-\u003epages[prealloc-\u003ecount]);\n-\t}\n+\tput_page(page);\n }\n \n static void anon_pipe_buf_release(struct pipe_inode_info *pipe,\n@@ -485,7 +468,8 @@ anon_pipe_read(struct kiocb *iocb, struct iov_iter *to)\n \t}\n \tif (pipe_is_empty(pipe))\n \t\twake_next_reader = false;\n-\tmutex_unlock(\u0026pipe-\u003emutex);\n+\t/* Consumed buffers may have refilled the pool; trim it and unlock. */\n+\tanon_pipe_trim_pool_and_unlock(pipe);\n \n \tif (wake_writer)\n \t\twake_up_interruptible_sync_poll(\u0026pipe-\u003ewr_wait, EPOLLOUT | EPOLLWRNORM);\n@@ -524,7 +508,6 @@ anon_pipe_write(struct kiocb *iocb, struct iov_iter *from)\n {\n \tstruct file *filp = iocb-\u003eki_filp;\n \tstruct pipe_inode_info *pipe = filp-\u003eprivate_data;\n-\tstruct anon_pipe_prealloc prealloc;\n \tunsigned int head;\n \tssize_t ret = 0;\n \tsize_t total_len = iov_iter_count(from);\n@@ -548,9 +531,7 @@ anon_pipe_write(struct kiocb *iocb, struct iov_iter *from)\n \tif (unlikely(total_len == 0))\n \t\treturn 0;\n \n-\tanon_pipe_get_page_prealloc(\u0026prealloc, total_len);\n-\n-\tmutex_lock(\u0026pipe-\u003emutex);\n+\tanon_pipe_prefill_and_lock(pipe, total_len);\n \n \tif (!pipe-\u003ereaders) {\n \t\tif ((iocb-\u003eki_flags \u0026 IOCB_NOSIGNAL) == 0)\n@@ -607,7 +588,7 @@ anon_pipe_write(struct kiocb *iocb, struct iov_iter *from)\n \t\t\tstruct page *page;\n \t\t\tint copied;\n \n-\t\t\tpage = anon_pipe_get_page(pipe, \u0026prealloc);\n+\t\t\tpage = anon_pipe_get_page(pipe);\n \t\t\tif (unlikely(!page)) {\n \t\t\t\tif (!ret)\n \t\t\t\t\tret = -ENOMEM;\n@@ -671,11 +652,9 @@ anon_pipe_write(struct kiocb *iocb, struct iov_iter *from)\n \t\twake_next_writer = true;\n \t}\n out:\n-\tanon_pipe_refill_tmp_pages(pipe, \u0026prealloc);\n \tif (pipe_is_full(pipe))\n \t\twake_next_writer = false;\n-\tmutex_unlock(\u0026pipe-\u003emutex);\n-\tanon_pipe_free_pages(\u0026prealloc);\n+\tanon_pipe_trim_pool_and_unlock(pipe);\n \n \t/*\n \t * If we do do a wakeup event, we do a 'sync' wakeup, because we\n@@ -956,10 +935,8 @@ void free_pipe_info(struct pipe_inode_info *pipe)\n \tif (pipe-\u003ewatch_queue)\n \t\tput_watch_queue(pipe-\u003ewatch_queue);\n #endif\n-\tfor (i = 0; i \u003c ARRAY_SIZE(pipe-\u003etmp_page); i++) {\n-\t\tif (pipe-\u003etmp_page[i])\n-\t\t\t__free_page(pipe-\u003etmp_page[i]);\n-\t}\n+\tfor (i = 0; i \u003c pipe-\u003eprealloc.count; i++)\n+\t\t__free_page(pipe-\u003eprealloc.pages[i]);\n \tkfree(pipe-\u003ebufs);\n \tkfree(pipe);\n }\ndiff --git a/include/linux/pipe_fs_i.h b/include/linux/pipe_fs_i.h\nindex 7f6a92ac970478..4e2e4f44fbe5f5 100644\n--- a/include/linux/pipe_fs_i.h\n+++ b/include/linux/pipe_fs_i.h\n@@ -14,6 +14,9 @@\n #define PIPE_BUF_FLAG_LOSS\t0x40\t/* Message loss happened after this buffer */\n #endif\n \n+#define PIPE_PREALLOC_MAX\t8\t/* max pages in prealloc pool */\n+#define PIPE_PREALLOC_KEEP\t2\t/* keep at least this many after trim */\n+\n /**\n  *\tstruct pipe_buffer - a linux kernel pipe buffer\n  *\t@page: the page containing the data for the pipe buffer\n@@ -58,6 +61,20 @@ union pipe_index {\n \t};\n };\n \n+/**\n+ *\tstruct anon_pipe_prealloc - per-pipe page preallocation pool\n+ *\t@pages: array of cached pages (pool)\n+ *\t@count: number of pages currently in the pool\n+ *\n+ * Each pipe keeps a small bounded pool of preallocated pages to reduce\n+ * allocation overhead during writes. The pool is bounded at PIPE_PREALLOC_MAX\n+ * and trimmed down to PIPE_PREALLOC_KEEP after a write completes.\n+ */\n+struct anon_pipe_prealloc {\n+\tstruct page *pages[PIPE_PREALLOC_MAX];\n+\tunsigned int count;\n+};\n+\n /**\n  *\tstruct pipe_inode_info - a linux kernel pipe\n  *\t@mutex: mutex protecting the whole thing\n@@ -68,7 +85,7 @@ union pipe_index {\n  *\t@max_usage: The maximum number of slots that may be used in the ring\n  *\t@ring_size: total number of buffers (should be a power of 2)\n  *\t@nr_accounted: The amount this pipe accounts for in user-\u003epipe_bufs\n- *\t@tmp_page: cached released page\n+ *\t@prealloc: per-pipe page preallocation pool\n  *\t@readers: number of current readers of this pipe\n  *\t@writers: number of current writers of this pipe\n  *\t@files: number of struct file referring this pipe (protected by -\u003ei_lock)\n@@ -99,7 +116,7 @@ struct pipe_inode_info {\n #ifdef CONFIG_WATCH_QUEUE\n \tbool note_loss;\n #endif\n-\tstruct page *tmp_page[2];\n+\tstruct anon_pipe_prealloc prealloc;\n \tstruct fasync_struct *fasync_readers;\n \tstruct fasync_struct *fasync_writers;\n \tstruct pipe_buffer *bufs;\n"
}

2/1 2026/07/10 10:03 agent patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [
    "anon_pipe_prefill_and_lock",
    "anon_pipe_trim_pool_and_unlock",
    "anon_pipe_get_page",
    "anon_pipe_put_page",
    "anon_pipe_prealloc_push",
    "anon_pipe_prealloc_pop"
  ],
  "Reasoning": "The patch modifies the page preallocation logic for pipes, introducing a new preallocation pool and changing how pages are allocated and freed during pipe reads and writes. This is a core functional change that should be fuzzed to ensure no memory leaks, use-after-frees, or race conditions occur.",
  "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 7a80c498218747af054fb0a189a1ac5d7974f5dd
Author: syz-cluster <triage@syzkaller.com>
Date:   Fri Jul 10 10:03:07 2026 +0000

    syz-cluster: applied patch under review

diff --git a/fs/pipe.c b/fs/pipe.c
index 429b0714ec5755..b14d5bb2b45a45 100644
--- a/fs/pipe.c
+++ b/fs/pipe.c
@@ -111,75 +111,95 @@ void pipe_double_lock(struct pipe_inode_info *pipe1,
 	pipe_lock(pipe2);
 }
 
-#define PIPE_PREALLOC_MAX 8
+static struct page *anon_pipe_prealloc_pop(struct anon_pipe_prealloc *prealloc)
+{
+	if (!prealloc->count)
+		return NULL;
 
-struct anon_pipe_prealloc {
-	struct page *pages[PIPE_PREALLOC_MAX];
-	unsigned int count;
-};
+	prealloc->count--;
+
+	return prealloc->pages[prealloc->count];
+}
+
+/* Push a page to the prealloc pool. Returns true if added, false if full. */
+static bool anon_pipe_prealloc_push(struct anon_pipe_prealloc *prealloc,
+				    struct page *page)
+{
+	if (prealloc->count >= PIPE_PREALLOC_MAX)
+		return false;
+	prealloc->pages[prealloc->count++] = page;
+	return true;
+}
 
 /*
- * Pre-allocate pages outside pipe->mutex for multi-page writes.
- * alloc_page() with GFP_HIGHUSER can sleep in reclaim and runs memcg
- * charging; doing it under the mutex stalls a concurrent reader.
- *
- * Loop alloc_page() instead of alloc_pages_bulk_*(): the bulk path refuses
- * __GFP_ACCOUNT under memcg (see commit 8dcb3060d81d "memcg: page_alloc:
- * skip bulk allocator for __GFP_ACCOUNT") and silently degrades to a single
- * page. A per-page loop keeps memcg accounting and the task NUMA mempolicy
- * honoured for every page; the per-call overhead is small compared to the
- * pipe->mutex hold-time being shrunk. Any shortfall is covered by the
- * in-lock alloc_page() fallback in anon_pipe_get_page().
+ * Top up the pipe's own pool, then take pipe->mutex and return with it held.
+ * The shortfall is allocated outside the lock; the push and the caller's write
+ * then run under a single lock acquisition, avoiding a separate prefill
+ * lock/unlock cycle. anon_pipe_get_page() drains the pool instead of allocating
+ * under the lock.
  */
-static void anon_pipe_get_page_prealloc(struct anon_pipe_prealloc *prealloc,
-					size_t total_len)
+static void anon_pipe_prefill_and_lock(struct pipe_inode_info *pipe, size_t total_len)
 {
-	unsigned int want, i;
-	struct page *page;
-
-	prealloc->count = 0;
-	if (total_len <= PAGE_SIZE)
-		return;
+	struct page *pages[PIPE_PREALLOC_MAX];
+	unsigned int want, have, need, n = 0;
 
 	want = min_t(unsigned int, DIV_ROUND_UP(total_len, PAGE_SIZE),
 		     PIPE_PREALLOC_MAX);
+	/* Unlocked read; the pool is refilled under the lock below. */
+	have = min_t(unsigned int, READ_ONCE(pipe->prealloc.count), want);
+	need = want - have;
+
+	if (!need) {
+		mutex_lock(&pipe->mutex);
+		return;
+	}
+
+	while (n < need) {
+		struct page *page = alloc_page(GFP_HIGHUSER | __GFP_ACCOUNT);
 
-	for (i = 0; i < want; i++) {
-		page = alloc_page(GFP_HIGHUSER | __GFP_ACCOUNT);
 		if (!page)
 			break;
-		prealloc->pages[prealloc->count++] = page;
+		pages[n++] = page;
 	}
+
+	mutex_lock(&pipe->mutex);
+	while (n && anon_pipe_prealloc_push(&pipe->prealloc, pages[n - 1]))
+		n--;
+
+	/*
+	 * Just flush any extra page that got affected by the TOCTOU
+	 * effect
+	 */
+	while (n)
+		put_page(pages[--n]);
 }
 
-static struct page *anon_pipe_prealloc_pop(struct anon_pipe_prealloc *prealloc)
+/*
+ * Called with pipe->mutex held. Trim the pool down to PIPE_PREALLOC_KEEP under
+ * the lock, drop it, then free the excess outside the critical section.
+ */
+static void anon_pipe_trim_pool_and_unlock(struct pipe_inode_info *pipe)
 {
-	if (!prealloc->count)
-		return NULL;
+	struct page *excess[PIPE_PREALLOC_MAX];
+	unsigned int nexcess = 0;
 
-	prealloc->count--;
+	while (pipe->prealloc.count > PIPE_PREALLOC_KEEP)
+		excess[nexcess++] = anon_pipe_prealloc_pop(&pipe->prealloc);
+	mutex_unlock(&pipe->mutex);
 
-	return prealloc->pages[prealloc->count];
+	while (nexcess)
+		put_page(excess[--nexcess]);
 }
 
-static struct page *anon_pipe_get_page(struct pipe_inode_info *pipe,
-				       struct anon_pipe_prealloc *prealloc)
+static struct page *anon_pipe_get_page(struct pipe_inode_info *pipe)
 {
 	struct page *page;
 
-	/* Drain prealloc first to keep tmp_page[] hot for later small writes. */
-	page = anon_pipe_prealloc_pop(prealloc);
+	/* Drain the prealloc pool before allocating. Called with mutex held. */
+	page = anon_pipe_prealloc_pop(&pipe->prealloc);
 	if (page)
 		return page;
 
-	for (int i = 0; i < ARRAY_SIZE(pipe->tmp_page); i++) {
-		if (pipe->tmp_page[i]) {
-			page = pipe->tmp_page[i];
-			pipe->tmp_page[i] = NULL;
-			return page;
-		}
-	}
-
 	/* FWIW: This is called with pipe->mutex held */
 	return alloc_page(GFP_HIGHUSER | __GFP_ACCOUNT);
 }
@@ -187,48 +207,11 @@ static struct page *anon_pipe_get_page(struct pipe_inode_info *pipe,
 static void anon_pipe_put_page(struct pipe_inode_info *pipe,
 			       struct page *page)
 {
-	if (page_count(page) == 1) {
-		for (int i = 0; i < ARRAY_SIZE(pipe->tmp_page); i++) {
-			if (!pipe->tmp_page[i]) {
-				pipe->tmp_page[i] = page;
-				return;
-			}
-		}
-	}
-
-	put_page(page);
-}
-
-/*
- * Stash leftover prealloc pages in tmp_page[] so the next write to this
- * pipe gets a hot page without entering the allocator.
- */
-static void anon_pipe_refill_tmp_pages(struct pipe_inode_info *pipe,
-				       struct anon_pipe_prealloc *prealloc)
-{
-	int i, idx;
-
-	if (!prealloc->count)
+	if (page_count(page) == 1 &&
+	    anon_pipe_prealloc_push(&pipe->prealloc, page))
 		return;
 
-	for (i = 0; i < ARRAY_SIZE(pipe->tmp_page); i++) {
-		if (pipe->tmp_page[i])
-			continue;
-		if (!prealloc->count)
-			return;
-		idx = --prealloc->count;
-		pipe->tmp_page[i] = prealloc->pages[idx];
-		prealloc->pages[idx] = NULL;
-	}
-}
-
-/* Runs after mutex_unlock() to keep put_page() out of the critical section. */
-static void anon_pipe_free_pages(struct anon_pipe_prealloc *prealloc)
-{
-	while (prealloc->count) {
-		prealloc->count--;
-		put_page(prealloc->pages[prealloc->count]);
-	}
+	put_page(page);
 }
 
 static void anon_pipe_buf_release(struct pipe_inode_info *pipe,
@@ -485,7 +468,8 @@ anon_pipe_read(struct kiocb *iocb, struct iov_iter *to)
 	}
 	if (pipe_is_empty(pipe))
 		wake_next_reader = false;
-	mutex_unlock(&pipe->mutex);
+	/* Consumed buffers may have refilled the pool; trim it and unlock. */
+	anon_pipe_trim_pool_and_unlock(pipe);
 
 	if (wake_writer)
 		wake_up_interruptible_sync_poll(&pipe->wr_wait, EPOLLOUT | EPOLLWRNORM);
@@ -524,7 +508,6 @@ anon_pipe_write(struct kiocb *iocb, struct iov_iter *from)
 {
 	struct file *filp = iocb->ki_filp;
 	struct pipe_inode_info *pipe = filp->private_data;
-	struct anon_pipe_prealloc prealloc;
 	unsigned int head;
 	ssize_t ret = 0;
 	size_t total_len = iov_iter_count(from);
@@ -548,9 +531,7 @@ anon_pipe_write(struct kiocb *iocb, struct iov_iter *from)
 	if (unlikely(total_len == 0))
 		return 0;
 
-	anon_pipe_get_page_prealloc(&prealloc, total_len);
-
-	mutex_lock(&pipe->mutex);
+	anon_pipe_prefill_and_lock(pipe, total_len);
 
 	if (!pipe->readers) {
 		if ((iocb->ki_flags & IOCB_NOSIGNAL) == 0)
@@ -607,7 +588,7 @@ anon_pipe_write(struct kiocb *iocb, struct iov_iter *from)
 			struct page *page;
 			int copied;
 
-			page = anon_pipe_get_page(pipe, &prealloc);
+			page = anon_pipe_get_page(pipe);
 			if (unlikely(!page)) {
 				if (!ret)
 					ret = -ENOMEM;
@@ -671,11 +652,9 @@ anon_pipe_write(struct kiocb *iocb, struct iov_iter *from)
 		wake_next_writer = true;
 	}
 out:
-	anon_pipe_refill_tmp_pages(pipe, &prealloc);
 	if (pipe_is_full(pipe))
 		wake_next_writer = false;
-	mutex_unlock(&pipe->mutex);
-	anon_pipe_free_pages(&prealloc);
+	anon_pipe_trim_pool_and_unlock(pipe);
 
 	/*
 	 * If we do do a wakeup event, we do a 'sync' wakeup, because we
@@ -956,10 +935,8 @@ void free_pipe_info(struct pipe_inode_info *pipe)
 	if (pipe->watch_queue)
 		put_watch_queue(pipe->watch_queue);
 #endif
-	for (i = 0; i < ARRAY_SIZE(pipe->tmp_page); i++) {
-		if (pipe->tmp_page[i])
-			__free_page(pipe->tmp_page[i]);
-	}
+	for (i = 0; i < pipe->prealloc.count; i++)
+		__free_page(pipe->prealloc.pages[i]);
 	kfree(pipe->bufs);
 	kfree(pipe);
 }
diff --git a/include/linux/pipe_fs_i.h b/include/linux/pipe_fs_i.h
index 7f6a92ac970478..4e2e4f44fbe5f5 100644
--- a/include/linux/pipe_fs_i.h
+++ b/include/linux/pipe_fs_i.h
@@ -14,6 +14,9 @@
 #define PIPE_BUF_FLAG_LOSS	0x40	/* Message loss happened after this buffer */
 #endif
 
+#define PIPE_PREALLOC_MAX	8	/* max pages in prealloc pool */
+#define PIPE_PREALLOC_KEEP	2	/* keep at least this many after trim */
+
 /**
  *	struct pipe_buffer - a linux kernel pipe buffer
  *	@page: the page containing the data for the pipe buffer
@@ -58,6 +61,20 @@ union pipe_index {
 	};
 };
 
+/**
+ *	struct anon_pipe_prealloc - per-pipe page preallocation pool
+ *	@pages: array of cached pages (pool)
+ *	@count: number of pages currently in the pool
+ *
+ * Each pipe keeps a small bounded pool of preallocated pages to reduce
+ * allocation overhead during writes. The pool is bounded at PIPE_PREALLOC_MAX
+ * and trimmed down to PIPE_PREALLOC_KEEP after a write completes.
+ */
+struct anon_pipe_prealloc {
+	struct page *pages[PIPE_PREALLOC_MAX];
+	unsigned int count;
+};
+
 /**
  *	struct pipe_inode_info - a linux kernel pipe
  *	@mutex: mutex protecting the whole thing
@@ -68,7 +85,7 @@ union pipe_index {
  *	@max_usage: The maximum number of slots that may be used in the ring
  *	@ring_size: total number of buffers (should be a power of 2)
  *	@nr_accounted: The amount this pipe accounts for in user->pipe_bufs
- *	@tmp_page: cached released page
+ *	@prealloc: per-pipe page preallocation pool
  *	@readers: number of current readers of this pipe
  *	@writers: number of current writers of this pipe
  *	@files: number of struct file referring this pipe (protected by ->i_lock)
@@ -99,7 +116,7 @@ struct pipe_inode_info {
 #ifdef CONFIG_WATCH_QUEUE
 	bool note_loss;
 #endif
-	struct page *tmp_page[2];
+	struct anon_pipe_prealloc prealloc;
 	struct fasync_struct *fasync_readers;
 	struct fasync_struct *fasync_writers;
 	struct pipe_buffer *bufs;

3/2 2026/07/10 10:03 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 5394
							output: 24
							thoughts: 473
						

Thoughts:
**Analyzing the Pipe Page Preallocation Patch**

Okay, so I've got this patch – looks like it's touching `fs/pipe.c` and `include/linux/pipe_fs_i.h`. Right away, I see it's about changing how pipe page preallocation works. This means we're dealing with a fundamental aspect of how pipes function in the kernel, and that's a red flag – a good red flag for fuzzing, that is.

The patch introduces a whole new set of functions: `anon_pipe_prefill_and_lock`, `anon_pipe_trim_pool_and_unlock`, `anon_pipe_prealloc_push`, and `anon_pipe_prealloc_pop`, and also modifies existing ones like `anon_pipe_read`, `anon_pipe_write`, and `free_pipe_info`. It's also changing the `struct pipe_inode_info` to use this new `struct anon_pipe_prealloc prealloc` instead of that old `struct page *tmp_page[2]`.

My immediate thought is, this is a functional change. It's got potential for interesting behavior, so fuzzing is a *must*.

I'll quickly check for any specific kernel configs. Let's see... `fs/pipe.c` is always compiled. Perfect, no dependencies there.

Now, let's identify the key functions or "focus symbols" for fuzzing.  I've got a list of modified functions, as below:

- `anon_pipe_prealloc_pop`
- `anon_pipe_prealloc_push`
- `anon_pipe_prefill_and_lock`
- `anon_pipe_trim_pool_and_unlock`
- `anon_pipe_get_page`
- `anon_pipe_put_page`
- `anon_pipe_buf_release`
- `anon_pipe_read`
- `anon_pipe_write`
- `free_pipe_info`

`anon_pipe_read` and `anon_pipe_write` are in the hot path, so those are high priority, obviously.  `anon_pipe_prefill_and_lock` and `anon_pipe_trim_pool_and_unlock` look like good candidates as well – likely to be involved in the core preallocation logic.  `anon_pipe_prealloc_pop` and `anon_pipe_prealloc_push` are static, but might be inlined. I should keep them on the list regardless.  Hold on... are `anon_pipe_read` and `anon_pipe_write` actually static functions in `fs/pipe.c`? They might be exported or assigned to `file_operations`. I need to verify that; I'll check `fs/pipe.c` just to be sure.



4/2 2026/07/10 10:03 tool grepper
0m Args:
{
  "Expression": "anon_pipe_prefill_and_lock"
}

Results:
{
  "Output": "fs/pipe.c=125=static bool anon_pipe_prealloc_push(struct anon_pipe_prealloc *prealloc,\n--\nfs/pipe.c-140- */\nfs/pipe.c:141:static void anon_pipe_prefill_and_lock(struct pipe_inode_info *pipe, size_t total_len)\nfs/pipe.c-142-{\n--\nfs/pipe.c=507=anon_pipe_write(struct kiocb *iocb, struct iov_iter *from)\n--\nfs/pipe.c-533-\nfs/pipe.c:534:\tanon_pipe_prefill_and_lock(pipe, total_len);\nfs/pipe.c-535-\n"
}

5/2 2026/07/10 10:03 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 6710
							output: 25
							thoughts: 17
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience, and written as if they were my own:

**Analyzing `anon_pipe_trim_pool_and_unlock`**

Alright, time to dive into `anon_pipe_trim_pool_and_unlock`. I need to refresh my memory on the intricacies of this function. Let's see... it's a critical component, handling the pool trimming and unlocking operations within the anonymous pipe mechanism. My understanding is it directly deals with resource management. I want to check how efficiently it handles the removal of unused segments, making sure it doesn’t introduce undue fragmentation or block processes unnecessarily during unlocking. The locking protocol in this part is particularly prone to contention, so I will scrutinize that area for any potential bottlenecks or race conditions. Any recent changes, patches, or performance regressions that have touched this function? I'll review any relevant test cases, benchmarks, and perhaps some instrumentation data, if it exists, to identify if any optimization opportunities or potential issues. Given the critical nature of pipes in overall system performance, I want to confirm that this is operating optimally.



6/2 2026/07/10 10:03 tool grepper
0m Args:
{
  "Expression": "anon_pipe_trim_pool_and_unlock"
}

Results:
{
  "Output": "fs/pipe.c=141=static void anon_pipe_prefill_and_lock(struct pipe_inode_info *pipe, size_t total_len)\n--\nfs/pipe.c-180- */\nfs/pipe.c:181:static void anon_pipe_trim_pool_and_unlock(struct pipe_inode_info *pipe)\nfs/pipe.c-182-{\n--\nfs/pipe.c=344=anon_pipe_read(struct kiocb *iocb, struct iov_iter *to)\n--\nfs/pipe.c-471-\t/* Consumed buffers may have refilled the pool; trim it and unlock. */\nfs/pipe.c:472:\tanon_pipe_trim_pool_and_unlock(pipe);\nfs/pipe.c-473-\n--\nfs/pipe.c=507=anon_pipe_write(struct kiocb *iocb, struct iov_iter *from)\n--\nfs/pipe.c-656-\t\twake_next_writer = false;\nfs/pipe.c:657:\tanon_pipe_trim_pool_and_unlock(pipe);\nfs/pipe.c-658-\n"
}

7/2 2026/07/10 10:03 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 7296
							output: 159
							thoughts: 369
						

Thoughts:
**My Fuzzing Strategy: Pipe Page Preallocation**

Okay, let's get down to brass tacks. I'm focusing on fuzzing this pipe page preallocation patch. I know the main entry points for I/O are `anon_pipe_read` and `anon_pipe_write` – probably hot paths, but they're likely going to get tested extensively anyway. This patch's specific functions are my primary targets, and I need to hone in on them.

I've got the list:

*   `anon_pipe_prefill_and_lock`
*   `anon_pipe_trim_pool_and_unlock`
*   `anon_pipe_get_page`
*   `anon_pipe_put_page`
*   `anon_pipe_prealloc_push`
*   `anon_pipe_prealloc_pop`

Those are the key symbols. I'm not seeing any config dependencies here – pipes are always enabled, which simplifies things. The core of this change involves a new preallocation pool and modified page allocation/deallocation in the pipe code. This smells like a prime candidate for memory corruption – leaks, UAFs, races. Gotta hammer those!

Just double checking, there is `free_pipe_info` also modified, but it's pretty generic.

Alright, I'm confident in this direction. I'll call set-results now and move forward.



8/2 2026/07/10 10:03 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [
    "anon_pipe_prefill_and_lock",
    "anon_pipe_trim_pool_and_unlock",
    "anon_pipe_get_page",
    "anon_pipe_put_page",
    "anon_pipe_prealloc_push",
    "anon_pipe_prealloc_pop"
  ],
  "Reasoning": "The patch modifies the page preallocation logic for pipes, introducing a new preallocation pool and changing how pages are allocated and freed during pipe reads and writes. This is a core functional change that should be fuzzed to ensure no memory leaks, use-after-frees, or race conditions occur.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [
    "anon_pipe_prefill_and_lock",
    "anon_pipe_trim_pool_and_unlock",
    "anon_pipe_get_page",
    "anon_pipe_put_page",
    "anon_pipe_prealloc_push",
    "anon_pipe_prealloc_pop"
  ],
  "Reasoning": "The patch modifies the page preallocation logic for pipes, introducing a new preallocation pool and changing how pages are allocated and freed during pipe reads and writes. This is a core functional change that should be fuzzed to ensure no memory leaks, use-after-frees, or race conditions occur.",
  "WorthFuzzing": true
}

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