AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/11/02 13:31 flow patch-triage
7m Args:
null

Results:
{
  "EnableConfigs": [
    "BPF_SYSCALL",
    "BPF_STREAM_PARSER",
    "NET_SOCK_MSG",
    "CGROUP_BPF",
    "INET"
  ],
  "FocusSymbols": [
    "tcp_bpf_sendmsg",
    "tcp_bpf_send_verdict",
    "tcp_bpf_push",
    "tcp_bpf_recvmsg_parser",
    "tcp_bpf_clone",
    "tcp_bpf_redir_allowed",
    "sk_psock_msg_verdict",
    "sk_psock_skb_state",
    "sk_psock_backlog",
    "__sk_msg_recvmsg",
    "sock_map_link",
    "bpf_msg_push_data",
    "bpf_msg_pop_data",
    "sk_stream_wait_memory"
  ],
  "KMSANReasoning": "The patch series addresses several edge cases and state synchronization issues in BPF sockmap, psock handling, and TCP BPF redirects:\n1. Prevents attaching verdict/parser programs to TCP listening sockets and restores default socket callbacks on cloned sockets.\n2. Guards against redirecting to dead sockets or sockets lacking `sk_socket` / wait queues, avoiding NULL pointer dereferences or crashes when waiting on connect/locks.\n3. Unmarks the scatterlist end bit when reusing/allocating slots in `bpf_msg_push_data` and `bpf_msg_pop_data`.\n4. Updates `msg_rx-\u003esg.start` on fault in `__sk_msg_recvmsg`, resets `sk_msg` curr/copybreak on corking, resets `msg-\u003esk_redir` prior to verdict runs, and improves cork synchronization across socket unlock/relock.\n\nNone of the changes introduce or modify kernel-to-user memory transfers of uninitialized structures (info leaks), nor do they create branches on uninitialized memory or access uninitialized buffer bytes. The potential failure modes (NULL pointer dereference, use-after-free, locking/concurrency races, state machine misuse) are well covered by standard KASAN and LOCKDEP. Consequently, a dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false,
  "Reasoning": "The patch series modifies core networking and BPF socket map (sockmap / TCP BPF / skmsg) logic. It addresses race conditions, state transitions, redirect validations, memory wait handling for dead sockets, and TCP listener handling within sockmap. These paths are fully reachable via standard network socket and BPF syscalls in virtualized QEMU environments.",
  "WorthFuzzing": true
}

1/1 2026/11/02 13:31 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 4360c9911c069182e1d97b5058f08941e7121d41\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Mon Nov 2 13:31:53 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/Documentation/bpf/map_sockmap.rst b/Documentation/bpf/map_sockmap.rst\nindex 2d630686a00baa..eb466b86df1707 100644\n--- a/Documentation/bpf/map_sockmap.rst\n+++ b/Documentation/bpf/map_sockmap.rst\n@@ -33,7 +33,9 @@ of ``__SK_DROP``, ``__SK_PASS``, or ``__SK_REDIRECT``.\n \n When a socket is inserted into one of these maps, its socket callbacks are\n replaced and a ``struct sk_psock`` is attached to it. Additionally, this\n-``sk_psock`` inherits the programs that are attached to the map.\n+``sk_psock`` inherits the programs that are attached to the map. A TCP listener\n+is an exception. It gets a ``struct sk_psock``, but it keeps its callbacks and\n+inherits no programs.\n \n A sock object may be in multiple maps, but can only inherit a single\n parse or verdict program. If adding a sock object to a map would result\ndiff --git a/include/linux/skmsg.h b/include/linux/skmsg.h\nindex d5e35f24738d5c..f0e27b4b929f10 100644\n--- a/include/linux/skmsg.h\n+++ b/include/linux/skmsg.h\n@@ -196,6 +196,25 @@ static inline void sk_msg_init(struct sk_msg *msg)\n \tsg_init_marker(msg-\u003esg.data, NR_MSG_FRAG_IDS);\n }\n \n+/*\n+ * sk_msg_memcopy_from_iter() writes to byte sg.copybreak of element sg.curr,\n+ * counted from the offset of that element. Point both past the last byte of\n+ * the message, after something other than a copy has changed its elements.\n+ */\n+static inline void sk_msg_reset_curr(struct sk_msg *msg)\n+{\n+\tif (!msg-\u003esg.size) {\n+\t\tmsg-\u003esg.curr = msg-\u003esg.start;\n+\t\tmsg-\u003esg.copybreak = 0;\n+\t} else {\n+\t\tu32 i = msg-\u003esg.end;\n+\n+\t\tsk_msg_iter_var_prev(i);\n+\t\tmsg-\u003esg.curr = i;\n+\t\tmsg-\u003esg.copybreak = msg-\u003esg.data[i].length;\n+\t}\n+}\n+\n static inline void sk_msg_xfer(struct sk_msg *dst, struct sk_msg *src,\n \t\t\t       int which, u32 size)\n {\ndiff --git a/include/net/tcp.h b/include/net/tcp.h\nindex 3560adddf82154..23a22d70718eae 100644\n--- a/include/net/tcp.h\n+++ b/include/net/tcp.h\n@@ -1845,6 +1845,12 @@ static inline void tcp_adjust_rcv_ssthresh(struct sock *sk)\n void tcp_cleanup_rbuf(struct sock *sk, int copied);\n void __tcp_cleanup_rbuf(struct sock *sk, int copied);\n \n+/* Is the socket in a state in which TCP may send an ACK? */\n+static inline bool tcp_can_send_ack(const struct sock *sk)\n+{\n+\treturn !((1 \u003c\u003c inet_sk_state_load(sk)) \u0026\n+\t       (TCPF_SYN_SENT | TCPF_SYN_RECV | TCPF_TIME_WAIT | TCPF_CLOSE | TCPF_LISTEN));\n+}\n \n /* We provision sk_rcvbuf around 200% of sk_rcvlowat.\n  * If 87.5 % (7/8) of the space has been consumed, we want to override\ndiff --git a/net/core/filter.c b/net/core/filter.c\nindex 9245009d9f1ebc..d68b5215040506 100644\n--- a/net/core/filter.c\n+++ b/net/core/filter.c\n@@ -2677,20 +2677,6 @@ BPF_CALL_2(bpf_msg_cork_bytes, struct sk_msg *, msg, u32, bytes)\n \treturn 0;\n }\n \n-static void sk_msg_reset_curr(struct sk_msg *msg)\n-{\n-\tif (!msg-\u003esg.size) {\n-\t\tmsg-\u003esg.curr = msg-\u003esg.start;\n-\t\tmsg-\u003esg.copybreak = 0;\n-\t} else {\n-\t\tu32 i = msg-\u003esg.end;\n-\n-\t\tsk_msg_iter_var_prev(i);\n-\t\tmsg-\u003esg.curr = i;\n-\t\tmsg-\u003esg.copybreak = msg-\u003esg.data[i].length;\n-\t}\n-}\n-\n static bool sk_msg_elem_is_copy(const struct sk_msg *msg, u32 i)\n {\n \treturn test_bit(i, msg-\u003esg.copy);\n@@ -3009,6 +2995,8 @@ BPF_CALL_4(bpf_msg_push_data, struct sk_msg *, msg, u32, start,\n \tmsg-\u003esg.size += len;\n \tsk_msg_clear_elem_copy(msg, new);\n \tsg_set_page(\u0026msg-\u003esg.data[new], page, len + copy, 0);\n+\t/* A free slot can have the end mark */\n+\tsg_unmark_end(\u0026msg-\u003esg.data[new]);\n \tif (rsge.length) {\n \t\tget_page(sg_page(\u0026rsge));\n \t\tsk_msg_iter_var_next(new);\n@@ -3142,6 +3130,7 @@ BPF_CALL_4(bpf_msg_pop_data, struct sk_msg *, msg, u32, start,\n \t\t\t\tsg_set_page(nsge,\n \t\t\t\t\t    sg_page(sge),\n \t\t\t\t\t    b, sge-\u003eoffset + pop + a);\n+\t\t\t\tsg_unmark_end(nsge);\n \t\t\t\tsk_msg_set_elem_copy(msg, i, sge_copy);\n \t\t\t} else {\n \t\t\t\tstruct page *page, *orig;\ndiff --git a/net/core/skmsg.c b/net/core/skmsg.c\nindex df385a5a961e83..8a3b8536c0c218 100644\n--- a/net/core/skmsg.c\n+++ b/net/core/skmsg.c\n@@ -443,6 +443,9 @@ int __sk_msg_recvmsg(struct sock *sk, struct sk_psock *psock, struct msghdr *msg\n \t\t\tif (copy)\n \t\t\t\tcopy = copy_page_to_iter(page, sge-\u003eoffset, copy, iter);\n \t\t\tif (!copy) {\n+\t\t\t\t/* The elements in front of i are used up */\n+\t\t\t\tif (!peek)\n+\t\t\t\t\tmsg_rx-\u003esg.start = i;\n \t\t\t\tcopied = copied ? copied : -EFAULT;\n \t\t\t\tgoto out;\n \t\t\t}\n@@ -661,7 +664,8 @@ static void sk_psock_skb_state(struct sk_psock *psock,\n \t\t\t       int len, int off)\n {\n \tspin_lock_bh(\u0026psock-\u003eingress_lock);\n-\tif (sk_psock_test_state(psock, SK_PSOCK_TX_ENABLED)) {\n+\t/* No new state once the psock is stopped, but never keep an old one */\n+\tif (!len || sk_psock_test_state(psock, SK_PSOCK_TX_ENABLED)) {\n \t\tstate-\u003elen = len;\n \t\tstate-\u003eoff = off;\n \t}\n@@ -932,6 +936,13 @@ int sk_psock_msg_verdict(struct sock *sk, struct sk_psock *psock,\n \n \tsk_msg_compute_data_pointers(msg);\n \tmsg-\u003esk = sk;\n+\t/*\n+\t * The prog may have run on this sk_msg before: it is a cork, or the\n+\t * rest of a message of which only a part was sent. The target of that\n+\t * run is not the target of this run, and the sk_msg holds no reference\n+\t * to it.\n+\t */\n+\tmsg-\u003esk_redir = NULL;\n \tret = bpf_prog_run_pin_on_cpu(prog, msg);\n \tmsg-\u003esk = NULL;\n \tret = sk_psock_map_verd(ret, msg-\u003esk_redir);\ndiff --git a/net/core/sock_map.c b/net/core/sock_map.c\nindex efe49551caba9e..282c6a4b2b05fe 100644\n--- a/net/core/sock_map.c\n+++ b/net/core/sock_map.c\n@@ -228,6 +228,14 @@ static int sock_map_link(struct bpf_map *map, struct sock *sk)\n \tstruct sk_psock *psock;\n \tint ret;\n \n+\t/*\n+\t * A listener receives no data. With the callbacks of a verdict program\n+\t * or a parser it would no longer wake accept(), and its children would\n+\t * copy them.\n+\t */\n+\tif (sk_is_tcp(sk) \u0026\u0026 sk-\u003esk_state == TCP_LISTEN)\n+\t\tgoto no_progs;\n+\n \tstream_verdict = READ_ONCE(progs-\u003estream_verdict);\n \tif (stream_verdict) {\n \t\tstream_verdict = bpf_prog_inc_not_zero(stream_verdict);\n@@ -262,6 +270,7 @@ static int sock_map_link(struct bpf_map *map, struct sock *sk)\n \t\t}\n \t}\n \n+no_progs:\n \tpsock = sock_map_psock_get_checked(sk);\n \tif (IS_ERR(psock)) {\n \t\tret = PTR_ERR(psock);\ndiff --git a/net/core/stream.c b/net/core/stream.c\nindex cb255e2a3a5dd7..334124d683a7a0 100644\n--- a/net/core/stream.c\n+++ b/net/core/stream.c\n@@ -150,7 +150,11 @@ int sk_stream_wait_memory(struct sock *sk, long *timeo_p)\n \t\t\t\t    (sk_stream_memory_free(sk) \u0026\u0026 !vm_wait),\n \t\t\t\t    \u0026wait);\n \t\tsk-\u003esk_write_pending--;\n-\t\tif (ret \u003c 0)\n+\t\t/*\n+\t\t * A sender which sockmap has redirected to sk does not keep it\n+\t\t * open. A dead socket has no sk_wq.\n+\t\t */\n+\t\tif (ret \u003c 0 || sock_flag(sk, SOCK_DEAD))\n \t\t\tgoto do_error;\n \n \t\tif (vm_wait) {\ndiff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c\nindex 8cf63a9e7fed15..788ce08eef4d7a 100644\n--- a/net/ipv4/tcp_bpf.c\n+++ b/net/ipv4/tcp_bpf.c\n@@ -88,6 +88,24 @@ static int bpf_tcp_ingress(struct sock *sk, struct sk_psock *psock,\n \treturn ret;\n }\n \n+/*\n+ * The target of a redirect belongs to somebody else. A child that nobody has\n+ * accepted yet, and an orphan, have no struct socket and no wait queue.\n+ * tcp_sendmsg_locked() needs both. See sendmsg_locked() in skbuff.c.\n+ * The sender holds no reference to the file of the target, which can be closed\n+ * whenever tcp_sendmsg_locked() sleeps. sk_stream_wait_connect() and\n+ * inet_wait_for_connect() go back to the wait queue when they wake up, so the\n+ * sender must not wait for a connect.\n+ */\n+static bool tcp_bpf_redir_allowed(const struct sock *sk)\n+{\n+\tif (!sk-\u003esk_socket)\n+\t\treturn false;\n+\tif ((1 \u003c\u003c sk-\u003esk_state) \u0026 (TCPF_SYN_SENT | TCPF_SYN_RECV))\n+\t\treturn tcp_passive_fastopen(sk);\n+\treturn true;\n+}\n+\n static int tcp_bpf_push(struct sock *sk, struct sk_msg *msg, u32 apply_bytes,\n \t\t\tint flags, bool uncharge)\n {\n@@ -109,6 +127,10 @@ static int tcp_bpf_push(struct sock *sk, struct sk_msg *msg, u32 apply_bytes,\n \t\tpage = sg_page(sge);\n \n retry:\n+\t\t/* Only a redirect sends data which is not charged to sk */\n+\t\tif (!uncharge \u0026\u0026 unlikely(!tcp_bpf_redir_allowed(sk)))\n+\t\t\treturn sock_flag(sk, SOCK_DEAD) ? -EPIPE : -EINVAL;\n+\n \t\tmsghdr.msg_flags = flags | MSG_SPLICE_PAGES;\n \t\thas_tx_ulp = tls_sw_has_ctx_tx(sk);\n \t\tif (has_tx_ulp)\n@@ -321,7 +343,8 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk,\n \tif (!peek)\n \t\tWRITE_ONCE(tcp-\u003ecopied_seq, seq);\n \ttcp_rcv_space_adjust(sk);\n-\tif (copied \u003e 0)\n+\t/* Data on the psock outlives the connection that it came in on */\n+\tif (copied \u003e 0 \u0026\u0026 tcp_can_send_ack(sk))\n \t\t__tcp_cleanup_rbuf(sk, copied);\n \n unlock:\n@@ -446,6 +469,11 @@ static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,\n \t\t\t\treturn -ENOMEM;\n \t\t\t}\n \t\t}\n+\t\t/*\n+\t\t * A push or a redirect may have taken bytes off the front of\n+\t\t * element sg.curr. The next sendmsg() appends to the cork.\n+\t\t */\n+\t\tsk_msg_reset_curr(msg);\n \t\tmemcpy(psock-\u003ecork, msg, sizeof(*msg));\n \t\treturn 0;\n \t}\n@@ -457,8 +485,22 @@ static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,\n \n \tswitch (psock-\u003eeval) {\n \tcase __SK_PASS:\n+\t\t/* The push may sleep without the socket lock, and it may fail */\n+\t\tif (!psock-\u003eapply_bytes)\n+\t\t\tpsock-\u003eeval = __SK_NONE;\n+\t\torigsize = msg-\u003esg.size;\n \t\tret = tcp_bpf_push(sk, msg, tosend, flags, true);\n \t\tif (unlikely(ret)) {\n+\t\t\t/* A part of it may have gone out */\n+\t\t\tsk_msg_apply_bytes(psock, origsize - msg-\u003esg.size);\n+\t\t\t/* Another sender can use up the rest during the push */\n+\t\t\tif (!psock-\u003eapply_bytes) {\n+\t\t\t\tpsock-\u003eeval = __SK_NONE;\n+\t\t\t\tif (psock-\u003esk_redir) {\n+\t\t\t\t\tsock_put(psock-\u003esk_redir);\n+\t\t\t\t\tpsock-\u003esk_redir = NULL;\n+\t\t\t\t}\n+\t\t\t}\n \t\t\t*copied -= sk_msg_free(sk, msg);\n \t\t\tbreak;\n \t\t}\n@@ -475,7 +517,11 @@ static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,\n \t\t\tpsock-\u003eeval = __SK_NONE;\n \t\t\tpsock-\u003esk_redir = NULL;\n \t\t}\n-\t\tif (psock-\u003ecork) {\n+\t\t/*\n+\t\t * Another thread can make a cork while this one sends msg\n+\t\t * without the socket lock. Leave that cork alone.\n+\t\t */\n+\t\tif (psock-\u003ecork == msg) {\n \t\t\tcork = true;\n \t\t\tpsock-\u003ecork = NULL;\n \t\t}\n@@ -498,6 +544,14 @@ static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,\n \t\t\tif (!cork)\n \t\t\t\t*copied -= free;\n \t\t}\n+\t\t/*\n+\t\t * The verdict was for tosend bytes. The rest goes back, unless\n+\t\t * another thread made a new cork while the lock was released.\n+\t\t */\n+\t\tif (cork \u0026\u0026 msg-\u003esg.size \u0026\u0026 !psock-\u003ecork) {\n+\t\t\tpsock-\u003ecork = msg;\n+\t\t\tcork = false;\n+\t\t}\n \t\tif (cork) {\n \t\t\tsk_msg_free(sk, msg);\n \t\t\tkfree(msg);\n@@ -507,9 +561,12 @@ static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,\n \t\tbreak;\n \tcase __SK_DROP:\n \tdefault:\n-\t\tsk_msg_free(sk, msg);\n+\t\t/* All of it is thrown away, not only tosend bytes */\n+\t\t*copied -= sk_msg_free(sk, msg) + delta;\n \t\tsk_msg_apply_bytes(psock, tosend);\n-\t\t*copied -= (tosend + delta);\n+\t\t/* The verdict holds for apply_bytes, not for ever */\n+\t\tif (!psock-\u003eapply_bytes)\n+\t\t\tpsock-\u003eeval = __SK_NONE;\n \t\treturn -EACCES;\n \t}\n \n@@ -538,7 +595,7 @@ static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)\n \tint flags;\n \n \t/* Don't let internal flags through */\n-\tflags = (msg-\u003emsg_flags \u0026 ~MSG_SENDPAGE_DECRYPTED);\n+\tflags = (msg-\u003emsg_flags \u0026 ~(MSG_SENDPAGE_DECRYPTED | MSG_FASTOPEN));\n \tflags |= MSG_NO_SHARED_FRAGS;\n \n \tpsock = sk_psock_get(sk);\n@@ -574,8 +631,15 @@ static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)\n \t\t\tcopy = msg_tx-\u003esg.size - osize;\n \t\t}\n \n-\t\tret = sk_msg_memcopy_from_iter(sk, \u0026msg-\u003emsg_iter, msg_tx,\n-\t\t\t\t\t       copy);\n+\t\t/*\n+\t\t * copy is 0 if the ring of the cork was full already and its\n+\t\t * last element could not be extended. Flush the cork below,\n+\t\t * and let the next pass take the data.\n+\t\t */\n+\t\tret = 0;\n+\t\tif (copy)\n+\t\t\tret = sk_msg_memcopy_from_iter(sk, \u0026msg-\u003emsg_iter,\n+\t\t\t\t\t\t       msg_tx, copy);\n \t\tif (ret \u003c 0) {\n \t\t\tsk_msg_trim(sk, msg_tx, osize);\n \t\t\terr = ret;\n@@ -768,7 +832,18 @@ void tcp_bpf_clone(const struct sock *sk, struct sock *newsk)\n {\n \tstruct proto *prot = newsk-\u003esk_prot;\n \n-\tif (is_insidevar(prot, tcp_bpf_prots))\n-\t\tnewsk-\u003esk_prot = sk-\u003esk_prot_creator;\n+\tif (!is_insidevar(prot, tcp_bpf_prots))\n+\t\treturn;\n+\n+\tnewsk-\u003esk_prot = sk-\u003esk_prot_creator;\n+\t/*\n+\t * sock_map_link() gives a listener no programs, but a socket which got\n+\t * them in another state and listens now has the psock's sk_data_ready\n+\t * and sk_write_space, and sock_copy() has copied them.\n+\t * sk_psock_init() refuses a socket that has sk_user_data or a ULP, so\n+\t * what the psock replaced were the defaults of a TCP socket.\n+\t */\n+\tnewsk-\u003esk_data_ready = sock_def_readable;\n+\tnewsk-\u003esk_write_space = sk_stream_write_space;\n }\n #endif /* CONFIG_BPF_SYSCALL */\ndiff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c\nindex 7a22e9388e0e9a..d62cfbf2d490c2 100644\n--- a/net/mptcp/protocol.c\n+++ b/net/mptcp/protocol.c\n@@ -619,12 +619,6 @@ static void mptcp_set_timeout(struct sock *sk)\n \t__mptcp_set_timeout(sk, tout);\n }\n \n-static inline bool tcp_can_send_ack(const struct sock *ssk)\n-{\n-\treturn !((1 \u003c\u003c inet_sk_state_load(ssk)) \u0026\n-\t       (TCPF_SYN_SENT | TCPF_SYN_RECV | TCPF_TIME_WAIT | TCPF_CLOSE | TCPF_LISTEN));\n-}\n-\n void __mptcp_subflow_send_ack(struct sock *ssk)\n {\n \tif (tcp_can_send_ack(ssk))\n"
}

2/1 2026/11/02 13:31 agent patch-evaluator
4m Model:
core

Args:
null

Results:
{
  "EnableConfigs": [
    "BPF_SYSCALL",
    "BPF_STREAM_PARSER",
    "NET_SOCK_MSG",
    "CGROUP_BPF",
    "INET"
  ],
  "FocusSymbols": [
    "tcp_bpf_sendmsg",
    "tcp_bpf_send_verdict",
    "tcp_bpf_push",
    "tcp_bpf_recvmsg_parser",
    "tcp_bpf_clone",
    "tcp_bpf_redir_allowed",
    "sk_psock_msg_verdict",
    "sk_psock_skb_state",
    "sk_psock_backlog",
    "__sk_msg_recvmsg",
    "sock_map_link",
    "bpf_msg_push_data",
    "bpf_msg_pop_data",
    "sk_stream_wait_memory"
  ],
  "Reasoning": "The patch series modifies core networking and BPF socket map (sockmap / TCP BPF / skmsg) logic. It addresses race conditions, state transitions, redirect validations, memory wait handling for dead sockets, and TCP listener handling within sockmap. These paths are fully reachable via standard network socket and BPF syscalls in virtualized QEMU environments.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.

================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.

- REACHABILITY IS THE PRIMARY GATE:
  Fuzzing can only discover bugs in code that can actually execute in standard virtualized
  environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
  If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
  regardless of whether it adds assertions or complex logic.

- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
  Patch authors routinely label changes as "cleanups", "refactorings", or state
  "No functional change intended". Do NOT take these claims at face value.
  Code refactorings that rearrange logic, introduce helper functions, or alter state management
  in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
  If reachable executable code is modified or refactored, it MUST be fuzzed.

- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
  When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
  BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
  Even if the author believes the invariant always holds, fuzzing is essential to verify whether
  an unusual sequence of operations can violate it.

================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:

- Non-kernel and non-executable changes:
  * Modifications to Documentation/, comments, or spelling fixes.
  * User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
    that do not affect the compiled kernel image (vmlinux) or kernel modules.
  * Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
    that do not alter control flow or data structures.
  * Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
  * Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
    ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
  * Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
    physical PCI hot-unplug or manual sysfs driver unbinding.
  * Code paths exclusive to architectures other than the target architecture.

================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
  * Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
    BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
  * Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
  * Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
  * Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).

================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:

- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
  Do NOT list generic, ubiquitous functions called by almost every program in the corpus
  (including, but not limited to: general memory allocators and deallocators, page fault
  and trap handlers, or core synchronization primitives; this is not an exhaustive list).
  Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
  which severely dilutes fuzzing effort away from the actual changes.

- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
  List functions that specifically implement the logic being added or altered, or direct API entrypoints
  for the subsystem feature under review.

- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
  Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
  distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
  If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
  functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).

================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any '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:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit 4360c9911c069182e1d97b5058f08941e7121d41
Author: syz-cluster <triage@syzkaller.com>
Date:   Mon Nov 2 13:31:53 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/bpf/map_sockmap.rst b/Documentation/bpf/map_sockmap.rst
index 2d630686a00baa..eb466b86df1707 100644
--- a/Documentation/bpf/map_sockmap.rst
+++ b/Documentation/bpf/map_sockmap.rst
@@ -33,7 +33,9 @@ of ``__SK_DROP``, ``__SK_PASS``, or ``__SK_REDIRECT``.
 
 When a socket is inserted into one of these maps, its socket callbacks are
 replaced and a ``struct sk_psock`` is attached to it. Additionally, this
-``sk_psock`` inherits the programs that are attached to the map.
+``sk_psock`` inherits the programs that are attached to the map. A TCP listener
+is an exception. It gets a ``struct sk_psock``, but it keeps its callbacks and
+inherits no programs.
 
 A sock object may be in multiple maps, but can only inherit a single
 parse or verdict program. If adding a sock object to a map would result
diff --git a/include/linux/skmsg.h b/include/linux/skmsg.h
index d5e35f24738d5c..f0e27b4b929f10 100644
--- a/include/linux/skmsg.h
+++ b/include/linux/skmsg.h
@@ -196,6 +196,25 @@ static inline void sk_msg_init(struct sk_msg *msg)
 	sg_init_marker(msg->sg.data, NR_MSG_FRAG_IDS);
 }
 
+/*
+ * sk_msg_memcopy_from_iter() writes to byte sg.copybreak of element sg.curr,
+ * counted from the offset of that element. Point both past the last byte of
+ * the message, after something other than a copy has changed its elements.
+ */
+static inline void sk_msg_reset_curr(struct sk_msg *msg)
+{
+	if (!msg->sg.size) {
+		msg->sg.curr = msg->sg.start;
+		msg->sg.copybreak = 0;
+	} else {
+		u32 i = msg->sg.end;
+
+		sk_msg_iter_var_prev(i);
+		msg->sg.curr = i;
+		msg->sg.copybreak = msg->sg.data[i].length;
+	}
+}
+
 static inline void sk_msg_xfer(struct sk_msg *dst, struct sk_msg *src,
 			       int which, u32 size)
 {
diff --git a/include/net/tcp.h b/include/net/tcp.h
index 3560adddf82154..23a22d70718eae 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -1845,6 +1845,12 @@ static inline void tcp_adjust_rcv_ssthresh(struct sock *sk)
 void tcp_cleanup_rbuf(struct sock *sk, int copied);
 void __tcp_cleanup_rbuf(struct sock *sk, int copied);
 
+/* Is the socket in a state in which TCP may send an ACK? */
+static inline bool tcp_can_send_ack(const struct sock *sk)
+{
+	return !((1 << inet_sk_state_load(sk)) &
+	       (TCPF_SYN_SENT | TCPF_SYN_RECV | TCPF_TIME_WAIT | TCPF_CLOSE | TCPF_LISTEN));
+}
 
 /* We provision sk_rcvbuf around 200% of sk_rcvlowat.
  * If 87.5 % (7/8) of the space has been consumed, we want to override
diff --git a/net/core/filter.c b/net/core/filter.c
index 9245009d9f1ebc..d68b5215040506 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -2677,20 +2677,6 @@ BPF_CALL_2(bpf_msg_cork_bytes, struct sk_msg *, msg, u32, bytes)
 	return 0;
 }
 
-static void sk_msg_reset_curr(struct sk_msg *msg)
-{
-	if (!msg->sg.size) {
-		msg->sg.curr = msg->sg.start;
-		msg->sg.copybreak = 0;
-	} else {
-		u32 i = msg->sg.end;
-
-		sk_msg_iter_var_prev(i);
-		msg->sg.curr = i;
-		msg->sg.copybreak = msg->sg.data[i].length;
-	}
-}
-
 static bool sk_msg_elem_is_copy(const struct sk_msg *msg, u32 i)
 {
 	return test_bit(i, msg->sg.copy);
@@ -3009,6 +2995,8 @@ BPF_CALL_4(bpf_msg_push_data, struct sk_msg *, msg, u32, start,
 	msg->sg.size += len;
 	sk_msg_clear_elem_copy(msg, new);
 	sg_set_page(&msg->sg.data[new], page, len + copy, 0);
+	/* A free slot can have the end mark */
+	sg_unmark_end(&msg->sg.data[new]);
 	if (rsge.length) {
 		get_page(sg_page(&rsge));
 		sk_msg_iter_var_next(new);
@@ -3142,6 +3130,7 @@ BPF_CALL_4(bpf_msg_pop_data, struct sk_msg *, msg, u32, start,
 				sg_set_page(nsge,
 					    sg_page(sge),
 					    b, sge->offset + pop + a);
+				sg_unmark_end(nsge);
 				sk_msg_set_elem_copy(msg, i, sge_copy);
 			} else {
 				struct page *page, *orig;
diff --git a/net/core/skmsg.c b/net/core/skmsg.c
index df385a5a961e83..8a3b8536c0c218 100644
--- a/net/core/skmsg.c
+++ b/net/core/skmsg.c
@@ -443,6 +443,9 @@ int __sk_msg_recvmsg(struct sock *sk, struct sk_psock *psock, struct msghdr *msg
 			if (copy)
 				copy = copy_page_to_iter(page, sge->offset, copy, iter);
 			if (!copy) {
+				/* The elements in front of i are used up */
+				if (!peek)
+					msg_rx->sg.start = i;
 				copied = copied ? copied : -EFAULT;
 				goto out;
 			}
@@ -661,7 +664,8 @@ static void sk_psock_skb_state(struct sk_psock *psock,
 			       int len, int off)
 {
 	spin_lock_bh(&psock->ingress_lock);
-	if (sk_psock_test_state(psock, SK_PSOCK_TX_ENABLED)) {
+	/* No new state once the psock is stopped, but never keep an old one */
+	if (!len || sk_psock_test_state(psock, SK_PSOCK_TX_ENABLED)) {
 		state->len = len;
 		state->off = off;
 	}
@@ -932,6 +936,13 @@ int sk_psock_msg_verdict(struct sock *sk, struct sk_psock *psock,
 
 	sk_msg_compute_data_pointers(msg);
 	msg->sk = sk;
+	/*
+	 * The prog may have run on this sk_msg before: it is a cork, or the
+	 * rest of a message of which only a part was sent. The target of that
+	 * run is not the target of this run, and the sk_msg holds no reference
+	 * to it.
+	 */
+	msg->sk_redir = NULL;
 	ret = bpf_prog_run_pin_on_cpu(prog, msg);
 	msg->sk = NULL;
 	ret = sk_psock_map_verd(ret, msg->sk_redir);
diff --git a/net/core/sock_map.c b/net/core/sock_map.c
index efe49551caba9e..282c6a4b2b05fe 100644
--- a/net/core/sock_map.c
+++ b/net/core/sock_map.c
@@ -228,6 +228,14 @@ static int sock_map_link(struct bpf_map *map, struct sock *sk)
 	struct sk_psock *psock;
 	int ret;
 
+	/*
+	 * A listener receives no data. With the callbacks of a verdict program
+	 * or a parser it would no longer wake accept(), and its children would
+	 * copy them.
+	 */
+	if (sk_is_tcp(sk) && sk->sk_state == TCP_LISTEN)
+		goto no_progs;
+
 	stream_verdict = READ_ONCE(progs->stream_verdict);
 	if (stream_verdict) {
 		stream_verdict = bpf_prog_inc_not_zero(stream_verdict);
@@ -262,6 +270,7 @@ static int sock_map_link(struct bpf_map *map, struct sock *sk)
 		}
 	}
 
+no_progs:
 	psock = sock_map_psock_get_checked(sk);
 	if (IS_ERR(psock)) {
 		ret = PTR_ERR(psock);
diff --git a/net/core/stream.c b/net/core/stream.c
index cb255e2a3a5dd7..334124d683a7a0 100644
--- a/net/core/stream.c
+++ b/net/core/stream.c
@@ -150,7 +150,11 @@ int sk_stream_wait_memory(struct sock *sk, long *timeo_p)
 				    (sk_stream_memory_free(sk) && !vm_wait),
 				    &wait);
 		sk->sk_write_pending--;
-		if (ret < 0)
+		/*
+		 * A sender which sockmap has redirected to sk does not keep it
+		 * open. A dead socket has no sk_wq.
+		 */
+		if (ret < 0 || sock_flag(sk, SOCK_DEAD))
 			goto do_error;
 
 		if (vm_wait) {
diff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c
index 8cf63a9e7fed15..788ce08eef4d7a 100644
--- a/net/ipv4/tcp_bpf.c
+++ b/net/ipv4/tcp_bpf.c
@@ -88,6 +88,24 @@ static int bpf_tcp_ingress(struct sock *sk, struct sk_psock *psock,
 	return ret;
 }
 
+/*
+ * The target of a redirect belongs to somebody else. A child that nobody has
+ * accepted yet, and an orphan, have no struct socket and no wait queue.
+ * tcp_sendmsg_locked() needs both. See sendmsg_locked() in skbuff.c.
+ * The sender holds no reference to the file of the target, which can be closed
+ * whenever tcp_sendmsg_locked() sleeps. sk_stream_wait_connect() and
+ * inet_wait_for_connect() go back to the wait queue when they wake up, so the
+ * sender must not wait for a connect.
+ */
+static bool tcp_bpf_redir_allowed(const struct sock *sk)
+{
+	if (!sk->sk_socket)
+		return false;
+	if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV))
+		return tcp_passive_fastopen(sk);
+	return true;
+}
+
 static int tcp_bpf_push(struct sock *sk, struct sk_msg *msg, u32 apply_bytes,
 			int flags, bool uncharge)
 {
@@ -109,6 +127,10 @@ static int tcp_bpf_push(struct sock *sk, struct sk_msg *msg, u32 apply_bytes,
 		page = sg_page(sge);
 
 retry:
+		/* Only a redirect sends data which is not charged to sk */
+		if (!uncharge && unlikely(!tcp_bpf_redir_allowed(sk)))
+			return sock_flag(sk, SOCK_DEAD) ? -EPIPE : -EINVAL;
+
 		msghdr.msg_flags = flags | MSG_SPLICE_PAGES;
 		has_tx_ulp = tls_sw_has_ctx_tx(sk);
 		if (has_tx_ulp)
@@ -321,7 +343,8 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk,
 	if (!peek)
 		WRITE_ONCE(tcp->copied_seq, seq);
 	tcp_rcv_space_adjust(sk);
-	if (copied > 0)
+	/* Data on the psock outlives the connection that it came in on */
+	if (copied > 0 && tcp_can_send_ack(sk))
 		__tcp_cleanup_rbuf(sk, copied);
 
 unlock:
@@ -446,6 +469,11 @@ static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,
 				return -ENOMEM;
 			}
 		}
+		/*
+		 * A push or a redirect may have taken bytes off the front of
+		 * element sg.curr. The next sendmsg() appends to the cork.
+		 */
+		sk_msg_reset_curr(msg);
 		memcpy(psock->cork, msg, sizeof(*msg));
 		return 0;
 	}
@@ -457,8 +485,22 @@ static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,
 
 	switch (psock->eval) {
 	case __SK_PASS:
+		/* The push may sleep without the socket lock, and it may fail */
+		if (!psock->apply_bytes)
+			psock->eval = __SK_NONE;
+		origsize = msg->sg.size;
 		ret = tcp_bpf_push(sk, msg, tosend, flags, true);
 		if (unlikely(ret)) {
+			/* A part of it may have gone out */
+			sk_msg_apply_bytes(psock, origsize - msg->sg.size);
+			/* Another sender can use up the rest during the push */
+			if (!psock->apply_bytes) {
+				psock->eval = __SK_NONE;
+				if (psock->sk_redir) {
+					sock_put(psock->sk_redir);
+					psock->sk_redir = NULL;
+				}
+			}
 			*copied -= sk_msg_free(sk, msg);
 			break;
 		}
@@ -475,7 +517,11 @@ static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,
 			psock->eval = __SK_NONE;
 			psock->sk_redir = NULL;
 		}
-		if (psock->cork) {
+		/*
+		 * Another thread can make a cork while this one sends msg
+		 * without the socket lock. Leave that cork alone.
+		 */
+		if (psock->cork == msg) {
 			cork = true;
 			psock->cork = NULL;
 		}
@@ -498,6 +544,14 @@ static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,
 			if (!cork)
 				*copied -= free;
 		}
+		/*
+		 * The verdict was for tosend bytes. The rest goes back, unless
+		 * another thread made a new cork while the lock was released.
+		 */
+		if (cork && msg->sg.size && !psock->cork) {
+			psock->cork = msg;
+			cork = false;
+		}
 		if (cork) {
 			sk_msg_free(sk, msg);
 			kfree(msg);
@@ -507,9 +561,12 @@ static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,
 		break;
 	case __SK_DROP:
 	default:
-		sk_msg_free(sk, msg);
+		/* All of it is thrown away, not only tosend bytes */
+		*copied -= sk_msg_free(sk, msg) + delta;
 		sk_msg_apply_bytes(psock, tosend);
-		*copied -= (tosend + delta);
+		/* The verdict holds for apply_bytes, not for ever */
+		if (!psock->apply_bytes)
+			psock->eval = __SK_NONE;
 		return -EACCES;
 	}
 
@@ -538,7 +595,7 @@ static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 	int flags;
 
 	/* Don't let internal flags through */
-	flags = (msg->msg_flags & ~MSG_SENDPAGE_DECRYPTED);
+	flags = (msg->msg_flags & ~(MSG_SENDPAGE_DECRYPTED | MSG_FASTOPEN));
 	flags |= MSG_NO_SHARED_FRAGS;
 
 	psock = sk_psock_get(sk);
@@ -574,8 +631,15 @@ static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 			copy = msg_tx->sg.size - osize;
 		}
 
-		ret = sk_msg_memcopy_from_iter(sk, &msg->msg_iter, msg_tx,
-					       copy);
+		/*
+		 * copy is 0 if the ring of the cork was full already and its
+		 * last element could not be extended. Flush the cork below,
+		 * and let the next pass take the data.
+		 */
+		ret = 0;
+		if (copy)
+			ret = sk_msg_memcopy_from_iter(sk, &msg->msg_iter,
+						       msg_tx, copy);
 		if (ret < 0) {
 			sk_msg_trim(sk, msg_tx, osize);
 			err = ret;
@@ -768,7 +832,18 @@ void tcp_bpf_clone(const struct sock *sk, struct sock *newsk)
 {
 	struct proto *prot = newsk->sk_prot;
 
-	if (is_insidevar(prot, tcp_bpf_prots))
-		newsk->sk_prot = sk->sk_prot_creator;
+	if (!is_insidevar(prot, tcp_bpf_prots))
+		return;
+
+	newsk->sk_prot = sk->sk_prot_creator;
+	/*
+	 * sock_map_link() gives a listener no programs, but a socket which got
+	 * them in another state and listens now has the psock's sk_data_ready
+	 * and sk_write_space, and sock_copy() has copied them.
+	 * sk_psock_init() refuses a socket that has sk_user_data or a ULP, so
+	 * what the psock replaced were the defaults of a TCP socket.
+	 */
+	newsk->sk_data_ready = sock_def_readable;
+	newsk->sk_write_space = sk_stream_write_space;
 }
 #endif /* CONFIG_BPF_SYSCALL */
diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
index 7a22e9388e0e9a..d62cfbf2d490c2 100644
--- a/net/mptcp/protocol.c
+++ b/net/mptcp/protocol.c
@@ -619,12 +619,6 @@ static void mptcp_set_timeout(struct sock *sk)
 	__mptcp_set_timeout(sk, tout);
 }
 
-static inline bool tcp_can_send_ack(const struct sock *ssk)
-{
-	return !((1 << inet_sk_state_load(ssk)) &
-	       (TCPF_SYN_SENT | TCPF_SYN_RECV | TCPF_TIME_WAIT | TCPF_CLOSE | TCPF_LISTEN));
-}
-
 void __mptcp_subflow_send_ack(struct sock *ssk)
 {
 	if (tcp_can_send_ack(ssk))

3/2 2026/11/02 13:31 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 8273
							output: 37
							thoughts: 1577
						

Thoughts:
**My Analysis of the Linux Kernel Patch**

Okay, so I've got a patch series to evaluate against HEAD. My job is to determine if it's worth fuzzing with syzkaller, identify the key functions to focus on, and figure out any necessary configurations. Let's dig in.

The commit message itself is pretty generic: "syz-cluster: applied patch under review." Not much to go on, but that's fine. I'll get the real story from the code.

Alright, the files touched are:

1.  `Documentation/bpf/map_sockmap.rst`: Docs change. Not interesting for fuzzing directly, but good to know what's being changed.
2.  `include/linux/skmsg.h`: A static inline, `sk_msg_reset_curr` is defined here, which means it moved. Could be important, could not be.
3.  `include/net/tcp.h`: Another static inline, `tcp_can_send_ack` moves here from mptcp. Again, potentially important if related to data paths.
4.  `net/core/filter.c`: This is where things get interesting. `bpf_msg_push_data` and `bpf_msg_pop_data` have new additions (`sg_unmark_end`) and the removal of the previously mentioned static inline. This is a core BPF networking area, so definitely something to watch.
5.  `net/core/skmsg.c`: Several changes here: `__sk_msg_recvmsg` gets an update to the error path, and a couple of state checks are changed in `sk_psock_skb_state`. Also, `msg->sk_redir = NULL;` is added in `sk_psock_msg_verdict`. All of this relates to message handling and BPF, which is a good sign.
6.  `net/core/sock_map.c`: Adds a TCP listen state check in `sock_map_link`. This is specific and important as it adds BPF interaction with a TCP listen socket.
7.  `net/core/stream.c`: A check for `SOCK_DEAD` is added in `sk_stream_wait_memory`. Another state check that will be good for fuzzing.
8.  `net/ipv4/tcp_bpf.c`: This is the big one. Several functions are modified, related to BPF integration with TCP:

    *   `tcp_bpf_redir_allowed` is added (and static, we need to check if inlined), a good starting point.
    *   `tcp_bpf_push` now has a check for this, which means that it matters.
    *   `tcp_bpf_recvmsg_parser` includes a check related to ack sending, which is also a good starting point.
    *   `tcp_bpf_send_verdict` has various fixes (probably bug fixes, but we'll take them.)
    *   `tcp_bpf_sendmsg` gets changes to flags and a conditional `copy` block.
    *   `tcp_bpf_clone` resets `sk_data_ready` and `sk_write_space`. This looks really interesting.

9.  `net/mptcp/protocol.c`: `tcp_can_send_ack` is removed.

From my perspective, this patch series touches core networking, BPF, sockmap, TCP, stream and skmsg functionality. And most importantly, all of these are reachable via the standard syscalls available in a normal QEMU/KVM environment that syzkaller already uses. Syzkaller actively tests these areas and the changes modify core logic, error handling, socket states, cork handling and more. **Definitely WorthFuzzing is `true`.**

Now, for `FocusSymbols`. Let's create the shortlist based on those changes:

*   `bpf_msg_push_data`
*   `bpf_msg_pop_data`
*   `__sk_msg_recvmsg`
*   `sk_psock_skb_state`
*   `sk_psock_msg_verdict`
*   `sock_map_link`
*   `sk_stream_wait_memory`
*   `tcp_bpf_redir_allowed` (This is static, and we'll have to use `grep` to figure out if it is inlined or not and what it's used in.)
*   `tcp_bpf_push`
*   `tcp_bpf_recvmsg_parser`
*   `tcp_bpf_


4/2 2026/11/02 13:32 tool grepper
0m Args:
{
  "Expression": "tcp_bpf_",
  "PathPrefixes": [
    "net/ipv4/tcp_bpf.c"
  ]
}

Results:
{
  "Output": "net/ipv4/tcp_bpf.c=33=static int bpf_tcp_ingress(struct sock *sk, struct sk_psock *psock,\n--\nnet/ipv4/tcp_bpf.c-99- */\nnet/ipv4/tcp_bpf.c:100:static bool tcp_bpf_redir_allowed(const struct sock *sk)\nnet/ipv4/tcp_bpf.c-101-{\n--\nnet/ipv4/tcp_bpf.c-108-\nnet/ipv4/tcp_bpf.c:109:static int tcp_bpf_push(struct sock *sk, struct sk_msg *msg, u32 apply_bytes,\nnet/ipv4/tcp_bpf.c-110-\t\t\tint flags, bool uncharge)\n--\nnet/ipv4/tcp_bpf.c-130-\t\t/* Only a redirect sends data which is not charged to sk */\nnet/ipv4/tcp_bpf.c:131:\t\tif (!uncharge \u0026\u0026 unlikely(!tcp_bpf_redir_allowed(sk)))\nnet/ipv4/tcp_bpf.c-132-\t\t\treturn sock_flag(sk, SOCK_DEAD) ? -EPIPE : -EINVAL;\n--\nnet/ipv4/tcp_bpf.c-173-\nnet/ipv4/tcp_bpf.c:174:static int tcp_bpf_push_locked(struct sock *sk, struct sk_msg *msg,\nnet/ipv4/tcp_bpf.c-175-\t\t\t       u32 apply_bytes, int flags, bool uncharge)\n--\nnet/ipv4/tcp_bpf.c-179-\tlock_sock(sk);\nnet/ipv4/tcp_bpf.c:180:\tret = tcp_bpf_push(sk, msg, apply_bytes, flags, uncharge);\nnet/ipv4/tcp_bpf.c-181-\trelease_sock(sk);\n--\nnet/ipv4/tcp_bpf.c-184-\nnet/ipv4/tcp_bpf.c:185:int tcp_bpf_sendmsg_redir(struct sock *sk, bool ingress,\nnet/ipv4/tcp_bpf.c-186-\t\t\t  struct sk_msg *msg, u32 bytes, int flags)\n--\nnet/ipv4/tcp_bpf.c-194-\tret = ingress ? bpf_tcp_ingress(sk, psock, msg, bytes) :\nnet/ipv4/tcp_bpf.c:195:\t\t\ttcp_bpf_push_locked(sk, msg, bytes, flags, false);\nnet/ipv4/tcp_bpf.c-196-\tsk_psock_put(sk, psock);\n--\nnet/ipv4/tcp_bpf.c-198-}\nnet/ipv4/tcp_bpf.c:199:EXPORT_SYMBOL_GPL(tcp_bpf_sendmsg_redir);\nnet/ipv4/tcp_bpf.c-200-\n--\nnet/ipv4/tcp_bpf.c=224=static bool is_next_msg_fin(struct sk_psock *psock)\n--\nnet/ipv4/tcp_bpf.c-241-\nnet/ipv4/tcp_bpf.c:242:static int tcp_bpf_recvmsg_parser(struct sock *sk,\nnet/ipv4/tcp_bpf.c-243-\t\t\t\t  struct msghdr *msg,\n--\nnet/ipv4/tcp_bpf.c-355-\nnet/ipv4/tcp_bpf.c:356:static int tcp_bpf_ioctl(struct sock *sk, int cmd, int *karg)\nnet/ipv4/tcp_bpf.c-357-{\n--\nnet/ipv4/tcp_bpf.c-388-\nnet/ipv4/tcp_bpf.c:389:static int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,\nnet/ipv4/tcp_bpf.c-390-\t\t\t   int flags)\n--\nnet/ipv4/tcp_bpf.c-438-\nnet/ipv4/tcp_bpf.c:439:static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,\nnet/ipv4/tcp_bpf.c-440-\t\t\t\tstruct sk_msg *msg, int *copied, int flags)\n--\nnet/ipv4/tcp_bpf.c-491-\t\torigsize = msg-\u003esg.size;\nnet/ipv4/tcp_bpf.c:492:\t\tret = tcp_bpf_push(sk, msg, tosend, flags, true);\nnet/ipv4/tcp_bpf.c-493-\t\tif (unlikely(ret)) {\n--\nnet/ipv4/tcp_bpf.c-530-\t\torigsize = msg-\u003esg.size;\nnet/ipv4/tcp_bpf.c:531:\t\tret = tcp_bpf_sendmsg_redir(sk_redir, redir_ingress,\nnet/ipv4/tcp_bpf.c-532-\t\t\t\t\t    msg, tosend, flags);\n--\nnet/ipv4/tcp_bpf.c-588-\nnet/ipv4/tcp_bpf.c:589:static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)\nnet/ipv4/tcp_bpf.c-590-{\n--\nnet/ipv4/tcp_bpf.c-661-\nnet/ipv4/tcp_bpf.c:662:\t\terr = tcp_bpf_send_verdict(sk, psock, msg_tx, \u0026copied, flags);\nnet/ipv4/tcp_bpf.c-663-\t\tif (unlikely(err \u003c 0))\n--\nnet/ipv4/tcp_bpf.c=699=static DEFINE_SPINLOCK(tcpv6_prot_lock);\nnet/ipv4/tcp_bpf.c:700:static struct proto tcp_bpf_prots[TCP_BPF_NUM_PROTS][TCP_BPF_NUM_CFGS];\nnet/ipv4/tcp_bpf.c-701-\nnet/ipv4/tcp_bpf.c:702:static void tcp_bpf_rebuild_protos(struct proto prot[TCP_BPF_NUM_CFGS],\nnet/ipv4/tcp_bpf.c-703-\t\t\t\t   struct proto *base)\n--\nnet/ipv4/tcp_bpf.c-707-\tprot[TCP_BPF_BASE].close\t\t= sock_map_close;\nnet/ipv4/tcp_bpf.c:708:\tprot[TCP_BPF_BASE].recvmsg\t\t= tcp_bpf_recvmsg;\nnet/ipv4/tcp_bpf.c-709-\tprot[TCP_BPF_BASE].sock_is_readable\t= sk_msg_is_readable;\nnet/ipv4/tcp_bpf.c:710:\tprot[TCP_BPF_BASE].ioctl\t\t= tcp_bpf_ioctl;\nnet/ipv4/tcp_bpf.c-711-\nnet/ipv4/tcp_bpf.c-712-\tprot[TCP_BPF_TX]\t\t\t= prot[TCP_BPF_BASE];\nnet/ipv4/tcp_bpf.c:713:\tprot[TCP_BPF_TX].sendmsg\t\t= tcp_bpf_sendmsg;\nnet/ipv4/tcp_bpf.c-714-\nnet/ipv4/tcp_bpf.c-715-\tprot[TCP_BPF_RX]\t\t\t= prot[TCP_BPF_BASE];\nnet/ipv4/tcp_bpf.c:716:\tprot[TCP_BPF_RX].recvmsg\t\t= tcp_bpf_recvmsg_parser;\nnet/ipv4/tcp_bpf.c-717-\nnet/ipv4/tcp_bpf.c-718-\tprot[TCP_BPF_TXRX]\t\t\t= prot[TCP_BPF_TX];\nnet/ipv4/tcp_bpf.c:719:\tprot[TCP_BPF_TXRX].recvmsg\t\t= tcp_bpf_recvmsg_parser;\nnet/ipv4/tcp_bpf.c-720-}\nnet/ipv4/tcp_bpf.c-721-\nnet/ipv4/tcp_bpf.c:722:static void tcp_bpf_check_v6_needs_rebuild(struct proto *ops)\nnet/ipv4/tcp_bpf.c-723-{\n--\nnet/ipv4/tcp_bpf.c-726-\t\tif (likely(ops != tcpv6_prot_saved)) {\nnet/ipv4/tcp_bpf.c:727:\t\t\ttcp_bpf_rebuild_protos(tcp_bpf_prots[TCP_BPF_IPV6], ops);\nnet/ipv4/tcp_bpf.c-728-\t\t\tsmp_store_release(\u0026tcpv6_prot_saved, ops);\n--\nnet/ipv4/tcp_bpf.c-733-\nnet/ipv4/tcp_bpf.c:734:static int __init tcp_bpf_v4_build_proto(void)\nnet/ipv4/tcp_bpf.c-735-{\nnet/ipv4/tcp_bpf.c:736:\ttcp_bpf_rebuild_protos(tcp_bpf_prots[TCP_BPF_IPV4], \u0026tcp_prot);\nnet/ipv4/tcp_bpf.c-737-\treturn 0;\nnet/ipv4/tcp_bpf.c-738-}\nnet/ipv4/tcp_bpf.c:739:late_initcall(tcp_bpf_v4_build_proto);\nnet/ipv4/tcp_bpf.c-740-\nnet/ipv4/tcp_bpf.c:741:static int tcp_bpf_assert_proto_ops(struct proto *ops)\nnet/ipv4/tcp_bpf.c-742-{\n--\nnet/ipv4/tcp_bpf.c-751-#if IS_ENABLED(CONFIG_BPF_STREAM_PARSER)\nnet/ipv4/tcp_bpf.c:752:int tcp_bpf_strp_read_sock(struct strparser *strp, read_descriptor_t *desc,\nnet/ipv4/tcp_bpf.c-753-\t\t\t   sk_read_actor_t recv_actor)\n--\nnet/ipv4/tcp_bpf.c-775-\t * For SK_REDIRECT, we need to ack the frame immediately but for\nnet/ipv4/tcp_bpf.c:776:\t * SK_PASS, we want to delay the ack until tcp_bpf_recvmsg_parser().\nnet/ipv4/tcp_bpf.c-777-\t */\n--\nnet/ipv4/tcp_bpf.c-786-\nnet/ipv4/tcp_bpf.c:787:int tcp_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool restore)\nnet/ipv4/tcp_bpf.c-788-{\n--\nnet/ipv4/tcp_bpf.c-813-\tif (sk-\u003esk_family == AF_INET6) {\nnet/ipv4/tcp_bpf.c:814:\t\tif (tcp_bpf_assert_proto_ops(psock-\u003esk_proto))\nnet/ipv4/tcp_bpf.c-815-\t\t\treturn -EINVAL;\nnet/ipv4/tcp_bpf.c-816-\nnet/ipv4/tcp_bpf.c:817:\t\ttcp_bpf_check_v6_needs_rebuild(psock-\u003esk_proto);\nnet/ipv4/tcp_bpf.c-818-\t}\n--\nnet/ipv4/tcp_bpf.c-820-\t/* Pairs with lockless read in sk_clone_lock() */\nnet/ipv4/tcp_bpf.c:821:\tsock_replace_proto(sk, \u0026tcp_bpf_prots[family][config]);\nnet/ipv4/tcp_bpf.c-822-\treturn 0;\nnet/ipv4/tcp_bpf.c-823-}\nnet/ipv4/tcp_bpf.c:824:EXPORT_SYMBOL_GPL(tcp_bpf_update_proto);\nnet/ipv4/tcp_bpf.c-825-\n--\nnet/ipv4/tcp_bpf.c-830- */\nnet/ipv4/tcp_bpf.c:831:void tcp_bpf_clone(const struct sock *sk, struct sock *newsk)\nnet/ipv4/tcp_bpf.c-832-{\n--\nnet/ipv4/tcp_bpf.c-834-\nnet/ipv4/tcp_bpf.c:835:\tif (!is_insidevar(prot, tcp_bpf_prots))\nnet/ipv4/tcp_bpf.c-836-\t\treturn;\n"
}

5/2 2026/11/02 13:32 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 14249
							output: 21
							thoughts: 80
						

Thoughts:
Here's my thinking process:

**Diving into the Code Changes**

Okay, let's get down to brass tacks and thoroughly examine these modifications. I need to understand the implications of each change, file by file.

First up, the documentation. The change in `Documentation/bpf/map_sockmap.rst` is, well, documentation. No need to dive into code yet.

Next, `include/linux/skmsg.h`. Ah, an inline helper function, `sk_msg_reset_curr`, has been introduced. This is interesting. The addition of a new helper function like this usually means we're trying to achieve something specific with message handling. This screams performance or resource management optimization. But I need to know *where* it's used. Just knowing it exists isn't enough. I'll need to figure out the use case to understand the purpose.

My next step is clear. I'll fire up a `grep` for `sk_msg_reset_curr`. That should immediately pinpoint all the places this new helper function is being invoked. Then I can analyze the calling context to discern the functional impact of this change.



6/2 2026/11/02 13:32 tool grepper
0m Args:
{
  "Expression": "sk_msg_reset_curr"
}

Results:
{
  "Output": "include/linux/skmsg.h=192=static inline void sk_msg_init(struct sk_msg *msg)\n--\ninclude/linux/skmsg.h-203- */\ninclude/linux/skmsg.h:204:static inline void sk_msg_reset_curr(struct sk_msg *msg)\ninclude/linux/skmsg.h-205-{\n--\nnet/core/filter.c=2719=BPF_CALL_4(bpf_msg_pull_data, struct sk_msg *, msg, u32, start,\n--\nnet/core/filter.c-2839-out:\nnet/core/filter.c:2840:\tsk_msg_reset_curr(msg);\nnet/core/filter.c-2841-\tmsg-\u003edata = sg_virt(\u0026msg-\u003esg.data[first_sge]) + start - offset;\n--\nnet/core/filter.c=2856=BPF_CALL_4(bpf_msg_push_data, struct sk_msg *, msg, u32, start,\n--\nnet/core/filter.c-3007-\nnet/core/filter.c:3008:\tsk_msg_reset_curr(msg);\nnet/core/filter.c-3009-\tsk_msg_compute_data_pointers(msg);\n--\nnet/core/filter.c=3063=BPF_CALL_4(bpf_msg_pop_data, struct sk_msg *, msg, u32, start,\n--\nnet/core/filter.c-3192-\tmsg-\u003esg.size -= (len - pop);\nnet/core/filter.c:3193:\tsk_msg_reset_curr(msg);\nnet/core/filter.c-3194-\tsk_msg_compute_data_pointers(msg);\n--\nnet/ipv4/tcp_bpf.c=439=static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,\n--\nnet/ipv4/tcp_bpf.c-475-\t\t */\nnet/ipv4/tcp_bpf.c:476:\t\tsk_msg_reset_curr(msg);\nnet/ipv4/tcp_bpf.c-477-\t\tmemcpy(psock-\u003ecork, msg, sizeof(*msg));\n"
}

7/2 2026/11/02 13:32 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 15194
							output: 21
							thoughts: 82
						

Thoughts:
Okay, here's my take on those thoughts, framed as an expert's internal monologue:

**Investigating the Data Flow and Acknowledgement Logic**

Right, so I'm seeing that `sk_msg_reset_curr` is a core function, clearly involved in managing the message buffers, since it's called by the data manipulation routines: `bpf_msg_pull_data`, `bpf_msg_push_data`, and `bpf_msg_pop_data`. It's also getting invoked by `tcp_bpf_send_verdict`, which is interesting because that's related to BPF-based verdicts in the TCP stack. That suggests this `sk_msg_reset_curr` function has a critical role in how the BPF programs interact with the TCP stack at a low level. It's almost certainly handling the bookkeeping when moving data in or out for BPF inspection and modification, and when verdicts change.

Now, let's switch gears slightly. My focus needs to be on the acknowledgements now. The next thing I'm going to look at is the `tcp_can_send_ack` function. I want to see *how* it's used. Finding where it's called will give me a better understanding of the conditions under which the TCP stack decides to send an ACK. What's driving the ACK behavior? I need to understand its role in this whole flow. Time to fire up the grep and figure out the call sites.



8/2 2026/11/02 13:32 tool grepper
0m Args:
{
  "Expression": "tcp_can_send_ack"
}

Results:
{
  "Output": "include/net/tcp.h=1846=void __tcp_cleanup_rbuf(struct sock *sk, int copied);\n--\ninclude/net/tcp.h-1848-/* Is the socket in a state in which TCP may send an ACK? */\ninclude/net/tcp.h:1849:static inline bool tcp_can_send_ack(const struct sock *sk)\ninclude/net/tcp.h-1850-{\n--\nnet/ipv4/tcp_bpf.c=242=static int tcp_bpf_recvmsg_parser(struct sock *sk,\n--\nnet/ipv4/tcp_bpf.c-346-\t/* Data on the psock outlives the connection that it came in on */\nnet/ipv4/tcp_bpf.c:347:\tif (copied \u003e 0 \u0026\u0026 tcp_can_send_ack(sk))\nnet/ipv4/tcp_bpf.c-348-\t\t__tcp_cleanup_rbuf(sk, copied);\n--\nnet/mptcp/protocol.c=622=void __mptcp_subflow_send_ack(struct sock *ssk)\nnet/mptcp/protocol.c-623-{\nnet/mptcp/protocol.c:624:\tif (tcp_can_send_ack(ssk))\nnet/mptcp/protocol.c-625-\t\ttcp_send_ack(ssk);\n--\nnet/mptcp/protocol.c=645=static void mptcp_subflow_cleanup_rbuf(struct sock *ssk, int copied)\n--\nnet/mptcp/protocol.c-649-\tslow = lock_sock_fast(ssk);\nnet/mptcp/protocol.c:650:\tif (tcp_can_send_ack(ssk))\nnet/mptcp/protocol.c-651-\t\ttcp_cleanup_rbuf(ssk, copied);\n"
}

9/2 2026/11/02 13:32 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 16091
							output: 36
							thoughts: 77
						

Thoughts:
Here's my summary, as I understand it:

**Analyzing the Call Sites of `tcp_can_send_ack`**

Okay, so I need to track down the usage of `tcp_can_send_ack`. I see it's called from within `net/ipv4/tcp_bpf.c`, specifically in `tcp_bpf_recvmsg_parser`, and also over in `net/mptcp/protocol.c`. That gives me a good starting point, right? Both are related to the handling of incoming data and probably are checking under which circumstances an acknowledgment can be sent. Now, I want to see *how* these functions are interacting with the stack, and how this relates to any recent changes.

I'll check out the diff in `net/core/skmsg.c` around `__sk_msg_recvmsg`. This area is crucial; it's where the socket message reception happens. Understanding any changes around that function, and how it interacts with the calls to `tcp_can_send_ack` in these two locations will provide the wider picture. I need to see if there's been any refactoring, performance improvements, or bug fixes that have modified this critical section of the stack. I expect this would shed some light on the contexts in which these calls are relevant.



10/2 2026/11/02 13:32 tool read-file
0m Args:
{
  "File": "net/core/skmsg.c",
  "FirstLine": 435,
  "LineCount": 25
}

Results:
{
  "Contents": " 435:\t\t\t\tstruct page *page;\n 436:\t\t\t\tint copy;\n 437:\t\n 438:\t\t\t\tsge = sk_msg_elem(msg_rx, i);\n 439:\t\t\t\tcopy = sge-\u003elength;\n 440:\t\t\t\tpage = sg_page(sge);\n 441:\t\t\t\tif (copied + copy \u003e len)\n 442:\t\t\t\t\tcopy = len - copied;\n 443:\t\t\t\tif (copy)\n 444:\t\t\t\t\tcopy = copy_page_to_iter(page, sge-\u003eoffset, copy, iter);\n 445:\t\t\t\tif (!copy) {\n 446:\t\t\t\t\t/* The elements in front of i are used up */\n 447:\t\t\t\t\tif (!peek)\n 448:\t\t\t\t\t\tmsg_rx-\u003esg.start = i;\n 449:\t\t\t\t\tcopied = copied ? copied : -EFAULT;\n 450:\t\t\t\t\tgoto out;\n 451:\t\t\t\t}\n 452:\t\n 453:\t\t\t\tcopied += copy;\n 454:\t\t\t\tif (from_self \u0026\u0026 copied_from_self)\n 455:\t\t\t\t\t*copied_from_self += copy;\n 456:\t\n 457:\t\t\t\tif (likely(!peek)) {\n 458:\t\t\t\t\tsge-\u003eoffset += copy;\n 459:\t\t\t\t\tsge-\u003elength -= copy;\n"
}

11/2 2026/11/02 13:32 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 16841
							output: 36
							thoughts: 27
						

Thoughts:
Here's my summary, as though I'm thinking through this process:

**Diving into `sk_psock_skb_state` in `net/core/skmsg.c`**

Okay, so we're looking at `sk_psock_skb_state`. My assumption is that this function, nestled within `net/core/skmsg.c`, plays a critical role in managing the state of SKBs (Socket Buffers) related to packet sockets, particularly in the context of passing messages between kernel components. Given its location in the network core, I expect it's intimately involved with flow control, resource management, and potentially even security aspects pertaining to packets handled by packet sockets. It is a key function to explore. I'll need to dig in to the code and see exactly what's going on.



12/2 2026/11/02 13:32 tool read-file
0m Args:
{
  "File": "net/core/skmsg.c",
  "FirstLine": 655,
  "LineCount": 25
}

Results:
{
  "Contents": " 655:\t\t\t\treturn -EAGAIN;\n 656:\t\t\treturn skb_send_sock(psock-\u003esk, skb, off, len);\n 657:\t\t}\n 658:\t\n 659:\t\treturn sk_psock_skb_ingress(psock, skb, off, len);\n 660:\t}\n 661:\t\n 662:\tstatic void sk_psock_skb_state(struct sk_psock *psock,\n 663:\t\t\t\t       struct sk_psock_work_state *state,\n 664:\t\t\t\t       int len, int off)\n 665:\t{\n 666:\t\tspin_lock_bh(\u0026psock-\u003eingress_lock);\n 667:\t\t/* No new state once the psock is stopped, but never keep an old one */\n 668:\t\tif (!len || sk_psock_test_state(psock, SK_PSOCK_TX_ENABLED)) {\n 669:\t\t\tstate-\u003elen = len;\n 670:\t\t\tstate-\u003eoff = off;\n 671:\t\t}\n 672:\t\tspin_unlock_bh(\u0026psock-\u003eingress_lock);\n 673:\t}\n 674:\t\n 675:\tstatic void sk_psock_backlog(struct work_struct *work)\n 676:\t{\n 677:\t\tstruct delayed_work *dwork = to_delayed_work(work);\n 678:\t\tstruct sk_psock *psock = container_of(dwork, struct sk_psock, work);\n 679:\t\tstruct sk_psock_work_state *state = \u0026psock-\u003ework_state;\n"
}

13/2 2026/11/02 13:32 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 17523
							output: 38
							thoughts: 46
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Analyzing the `sk_psock_skb_state` Static Variable**

Alright, so the first thing that jumps out at me is that `sk_psock_skb_state` is declared as `static`. That means it's got internal linkage, which is immediately relevant. We know that it's called within `sk_psock_backlog`, that's the established context. My immediate next step is always to verify the reach of a `static` variable. So, I need to understand *where* `sk_psock_skb_state` is actually being invoked. Knowing the call context, now I need to dive into where that variable is used, outside of `sk_psock_backlog`. It's a static, so it's probably *not* going to be a simple cross-module call, and the potential impact of changes there is important. Now let's go hunt down those call sites.



14/2 2026/11/02 13:32 tool grepper
0m Args:
{
  "Expression": "sk_psock_skb_state",
  "PathPrefixes": [
    "net/core/skmsg.c"
  ]
}

Results:
{
  "Output": "net/core/skmsg.c=650=static int sk_psock_handle_skb(struct sk_psock *psock, struct sk_buff *skb,\n--\nnet/core/skmsg.c-661-\nnet/core/skmsg.c:662:static void sk_psock_skb_state(struct sk_psock *psock,\nnet/core/skmsg.c-663-\t\t\t       struct sk_psock_work_state *state,\n--\nnet/core/skmsg.c=675=static void sk_psock_backlog(struct work_struct *work)\n--\nnet/core/skmsg.c-724-\t\t\t\tif (ret == -EAGAIN) {\nnet/core/skmsg.c:725:\t\t\t\t\tsk_psock_skb_state(psock, state, len, off);\nnet/core/skmsg.c-726-\t\t\t\t\t/* Restore redir info we cleared before */\n--\nnet/core/skmsg.c-744-\t\t/* The entire skb sent, clear state */\nnet/core/skmsg.c:745:\t\tsk_psock_skb_state(psock, state, 0, 0);\nnet/core/skmsg.c-746-\t\tskb = skb_dequeue(\u0026psock-\u003eingress_skb);\n"
}

15/2 2026/11/02 13:32 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 18172
							output: 23
							thoughts: 57
						

Thoughts:
Okay, let's dive into this code.

**Investigating Network Socket Processing**

Alright, my first thought is to take a look at `sk_psock_backlog`. It's a workqueue function, right? That's what the code indicates, and that's a good starting point for understanding how these things are handled. Now, I'm also curious about `sk_psock_msg_verdict`. Where does that fit into the picture? Let's use `grep` to quickly trace its usage and see where it's being called. That should give me a better understanding of its role in the process. I want to see the whole lifecycle.



16/2 2026/11/02 13:32 tool grepper
0m Args:
{
  "Expression": "sk_psock_msg_verdict"
}

Results:
{
  "Output": "include/linux/skmsg.h=478=void sk_psock_stop_verdict(struct sock *sk, struct sk_psock *psock);\ninclude/linux/skmsg.h-479-\ninclude/linux/skmsg.h:480:int sk_psock_msg_verdict(struct sock *sk, struct sk_psock *psock,\ninclude/linux/skmsg.h-481-\t\t\t struct sk_msg *msg);\n--\nnet/core/skmsg.c=911=static int sk_psock_map_verd(int verdict, bool redir)\n--\nnet/core/skmsg.c-923-\nnet/core/skmsg.c:924:int sk_psock_msg_verdict(struct sock *sk, struct sk_psock *psock,\nnet/core/skmsg.c-925-\t\t\t struct sk_msg *msg)\n--\nnet/core/skmsg.c-966-}\nnet/core/skmsg.c:967:EXPORT_SYMBOL_GPL(sk_psock_msg_verdict);\nnet/core/skmsg.c-968-\n--\nnet/ipv4/tcp_bpf.c=439=static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,\n--\nnet/ipv4/tcp_bpf.c-455-\t\tdelta = msg-\u003esg.size;\nnet/ipv4/tcp_bpf.c:456:\t\tpsock-\u003eeval = sk_psock_msg_verdict(sk, psock, msg);\nnet/ipv4/tcp_bpf.c-457-\t\tdelta -= msg-\u003esg.size;\n"
}

17/2 2026/11/02 13:32 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 18832
							output: 37
							thoughts: 80
						

Thoughts:
Here's my summary, as I see it:

**Investigating the `sk_psock_msg_verdict` Call Chain**

Okay, so `sk_psock_msg_verdict` is exported and used within the kernel. My initial thought is to trace where it's being called, and the code shows it originates from `tcp_bpf_send_verdict` (and potentially `udp bpf`). That's a good starting point. Now, I want to see how these pieces fit together. Given that the function name suggests a socket-related action, the next logical step is to dig into the `sock_map` functionality to get a wider picture. Specifically, I'm thinking about the linking aspect, so let's look at `sock_map_link`. I'm going to jump into `net/core/sock_map.c` around line 228 – that area usually contains the socket map details. I expect to find some key relationships in this area, but will delve deeper to know for sure. This should give me a good understanding of how the verdict is used in the overall flow, specifically how sockets are mapped.



18/2 2026/11/02 13:32 tool read-file
0m Args:
{
  "File": "net/core/sock_map.c",
  "FirstLine": 220,
  "LineCount": 40
}

Results:
{
  "Contents": " 220:\t\n 221:\tstatic int sock_map_link(struct bpf_map *map, struct sock *sk)\n 222:\t{\n 223:\t\tstruct sk_psock_progs *progs = sock_map_progs(map);\n 224:\t\tstruct bpf_prog *stream_verdict = NULL;\n 225:\t\tstruct bpf_prog *stream_parser = NULL;\n 226:\t\tstruct bpf_prog *skb_verdict = NULL;\n 227:\t\tstruct bpf_prog *msg_parser = NULL;\n 228:\t\tstruct sk_psock *psock;\n 229:\t\tint ret;\n 230:\t\n 231:\t\t/*\n 232:\t\t * A listener receives no data. With the callbacks of a verdict program\n 233:\t\t * or a parser it would no longer wake accept(), and its children would\n 234:\t\t * copy them.\n 235:\t\t */\n 236:\t\tif (sk_is_tcp(sk) \u0026\u0026 sk-\u003esk_state == TCP_LISTEN)\n 237:\t\t\tgoto no_progs;\n 238:\t\n 239:\t\tstream_verdict = READ_ONCE(progs-\u003estream_verdict);\n 240:\t\tif (stream_verdict) {\n 241:\t\t\tstream_verdict = bpf_prog_inc_not_zero(stream_verdict);\n 242:\t\t\tif (IS_ERR(stream_verdict))\n 243:\t\t\t\treturn PTR_ERR(stream_verdict);\n 244:\t\t}\n 245:\t\n 246:\t\tstream_parser = READ_ONCE(progs-\u003estream_parser);\n 247:\t\tif (stream_parser) {\n 248:\t\t\tstream_parser = bpf_prog_inc_not_zero(stream_parser);\n 249:\t\t\tif (IS_ERR(stream_parser)) {\n 250:\t\t\t\tret = PTR_ERR(stream_parser);\n 251:\t\t\t\tgoto out_put_stream_verdict;\n 252:\t\t\t}\n 253:\t\t}\n 254:\t\n 255:\t\tmsg_parser = READ_ONCE(progs-\u003emsg_parser);\n 256:\t\tif (msg_parser) {\n 257:\t\t\tmsg_parser = bpf_prog_inc_not_zero(msg_parser);\n 258:\t\t\tif (IS_ERR(msg_parser)) {\n 259:\t\t\t\tret = PTR_ERR(msg_parser);\n"
}

19/2 2026/11/02 13:32 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 19865
							output: 35
							thoughts: 16
						

Thoughts:
Here's your summary:

**Digging into the `sock_map_link` Call Chain**

Okay, so I need to understand the context around `sock_map_link`. Let's start by figuring out where this function is actually called from. I need to trace its use within the codebase. No point just staring at the function definition; I need to see how it's being *used* in order to grasp its purpose and what it's trying to accomplish. Time to fire up the search tools and identify all the call sites. This should give me a solid starting point for understanding its role in the bigger picture, and how it interacts with other networking components.



20/2 2026/11/02 13:33 tool grepper
0m Args:
{
  "Expression": "sock_map_link",
  "PathPrefixes": [
    "net/core/sock_map.c"
  ]
}

Results:
{
  "Output": "net/core/sock_map.c=201=static struct sk_psock *sock_map_psock_get_checked(struct sock *sk)\n--\nnet/core/sock_map.c-220-\nnet/core/sock_map.c:221:static int sock_map_link(struct bpf_map *map, struct sock *sk)\nnet/core/sock_map.c-222-{\n--\nnet/core/sock_map.c=484=static int sock_map_update_common(struct bpf_map *map, u32 idx,\n--\nnet/core/sock_map.c-502-\nnet/core/sock_map.c:503:\tret = sock_map_link(map, sk);\nnet/core/sock_map.c-504-\tif (ret \u003c 0)\n--\nnet/core/sock_map.c=1007=static int sock_hash_update_common(struct bpf_map *map, void *key,\n--\nnet/core/sock_map.c-1025-\nnet/core/sock_map.c:1026:\tret = sock_map_link(map, sk);\nnet/core/sock_map.c-1027-\tif (ret \u003c 0)\n--\nnet/core/sock_map.c=1751=struct sockmap_link {\n--\nnet/core/sock_map.c-1755-\nnet/core/sock_map.c:1756:static void sock_map_link_release(struct bpf_link *link)\nnet/core/sock_map.c-1757-{\n--\nnet/core/sock_map.c-1772-\nnet/core/sock_map.c:1773:static int sock_map_link_detach(struct bpf_link *link)\nnet/core/sock_map.c-1774-{\nnet/core/sock_map.c:1775:\tsock_map_link_release(link);\nnet/core/sock_map.c-1776-\treturn 0;\n--\nnet/core/sock_map.c-1778-\nnet/core/sock_map.c:1779:static void sock_map_link_dealloc(struct bpf_link *link)\nnet/core/sock_map.c-1780-{\n--\nnet/core/sock_map.c-1787- */\nnet/core/sock_map.c:1788:static int sock_map_link_update_prog(struct bpf_link *link,\nnet/core/sock_map.c-1789-\t\t\t\t     struct bpf_prog *prog,\n--\nnet/core/sock_map.c-1847-\nnet/core/sock_map.c:1848:static u32 sock_map_link_get_map_id(const struct sockmap_link *sockmap_link)\nnet/core/sock_map.c-1849-{\n--\nnet/core/sock_map.c-1858-\nnet/core/sock_map.c:1859:static int sock_map_link_fill_info(const struct bpf_link *link,\nnet/core/sock_map.c-1860-\t\t\t\t   struct bpf_link_info *info)\n--\nnet/core/sock_map.c-1862-\tconst struct sockmap_link *sockmap_link = container_of(link, struct sockmap_link, link);\nnet/core/sock_map.c:1863:\tu32 map_id = sock_map_link_get_map_id(sockmap_link);\nnet/core/sock_map.c-1864-\n--\nnet/core/sock_map.c-1869-\nnet/core/sock_map.c:1870:static void sock_map_link_show_fdinfo(const struct bpf_link *link,\nnet/core/sock_map.c-1871-\t\t\t\t      struct seq_file *seq)\n--\nnet/core/sock_map.c-1873-\tconst struct sockmap_link *sockmap_link = container_of(link, struct sockmap_link, link);\nnet/core/sock_map.c:1874:\tu32 map_id = sock_map_link_get_map_id(sockmap_link);\nnet/core/sock_map.c-1875-\n--\nnet/core/sock_map.c-1879-\nnet/core/sock_map.c:1880:static const struct bpf_link_ops sock_map_link_ops = {\nnet/core/sock_map.c:1881:\t.release = sock_map_link_release,\nnet/core/sock_map.c:1882:\t.dealloc = sock_map_link_dealloc,\nnet/core/sock_map.c:1883:\t.detach = sock_map_link_detach,\nnet/core/sock_map.c:1884:\t.update_prog = sock_map_link_update_prog,\nnet/core/sock_map.c:1885:\t.fill_link_info = sock_map_link_fill_info,\nnet/core/sock_map.c:1886:\t.show_fdinfo = sock_map_link_show_fdinfo,\nnet/core/sock_map.c-1887-};\nnet/core/sock_map.c-1888-\nnet/core/sock_map.c:1889:int sock_map_link_create(const union bpf_attr *attr, struct bpf_prog *prog)\nnet/core/sock_map.c-1890-{\n--\nnet/core/sock_map.c-1914-\tattach_type = attr-\u003elink_create.attach_type;\nnet/core/sock_map.c:1915:\tbpf_link_init(\u0026sockmap_link-\u003elink, BPF_LINK_TYPE_SOCKMAP, \u0026sock_map_link_ops, prog,\nnet/core/sock_map.c-1916-\t\t      attach_type);\n"
}

21/2 2026/11/02 13:33 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 21635
							output: 35
							thoughts: 76
						

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

**Navigating Socket Memory Management**

Okay, so I'm tracking down the usage of `sock_map_link`. I recall that this function is called within the `sock_map_update_common` and `sock_hash_update_common` functions, so I understand the general context. I need to understand its role in memory management. Given this, I'm going to shift my focus to `net/core/stream.c`. My hunch is this is the right area to examine the interplay between these functions and how sockets handle memory allocation and potentially, how they wait for memory availability. Specifically, I'm going to zero in on lines near line 150 of that file, as a starting point. `sk_stream_wait_memory` sounds like a promising function to investigate in context with `sock_map_link` because it suggests the system is waiting for memory and a linked data structure of sockets would need to change based on available memory.



22/2 2026/11/02 13:33 tool read-file
0m Args:
{
  "File": "net/core/stream.c",
  "FirstLine": 135,
  "LineCount": 35
}

Results:
{
  "Contents": " 135:\t\n 136:\t\t\tif (READ_ONCE(sk-\u003esk_err) || (sk-\u003esk_shutdown \u0026 SEND_SHUTDOWN))\n 137:\t\t\t\tgoto do_error;\n 138:\t\t\tif (!*timeo_p)\n 139:\t\t\t\tgoto do_eagain;\n 140:\t\t\tif (signal_pending(current))\n 141:\t\t\t\tgoto do_interrupted;\n 142:\t\t\tsk_clear_bit(SOCKWQ_ASYNC_NOSPACE, sk);\n 143:\t\t\tif (sk_stream_memory_free(sk) \u0026\u0026 !vm_wait)\n 144:\t\t\t\tbreak;\n 145:\t\n 146:\t\t\tsk_set_nospace(sk);\n 147:\t\t\tsk-\u003esk_write_pending++;\n 148:\t\t\tret = sk_wait_event(sk, \u0026current_timeo, READ_ONCE(sk-\u003esk_err) ||\n 149:\t\t\t\t\t    (READ_ONCE(sk-\u003esk_shutdown) \u0026 SEND_SHUTDOWN) ||\n 150:\t\t\t\t\t    (sk_stream_memory_free(sk) \u0026\u0026 !vm_wait),\n 151:\t\t\t\t\t    \u0026wait);\n 152:\t\t\tsk-\u003esk_write_pending--;\n 153:\t\t\t/*\n 154:\t\t\t * A sender which sockmap has redirected to sk does not keep it\n 155:\t\t\t * open. A dead socket has no sk_wq.\n 156:\t\t\t */\n 157:\t\t\tif (ret \u003c 0 || sock_flag(sk, SOCK_DEAD))\n 158:\t\t\t\tgoto do_error;\n 159:\t\n 160:\t\t\tif (vm_wait) {\n 161:\t\t\t\tvm_wait -= current_timeo;\n 162:\t\t\t\tcurrent_timeo = *timeo_p;\n 163:\t\t\t\tif (current_timeo != MAX_SCHEDULE_TIMEOUT \u0026\u0026\n 164:\t\t\t\t    (current_timeo -= vm_wait) \u003c 0)\n 165:\t\t\t\t\tcurrent_timeo = 0;\n 166:\t\t\t\tvm_wait = 0;\n 167:\t\t\t}\n 168:\t\t\t*timeo_p = current_timeo;\n 169:\t\t}\n"
}

23/2 2026/11/02 13:33 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 22546
							output: 21
							thoughts: 63
						

Thoughts:
Okay, here's my take on those thoughts, framed as my own mental processing:

**Analyzing `sk_stream_wait_memory` and Socket Redirection**

Right, let's dissect that comment nestled within `sk_stream_wait_memory`. It states that if a sender has been sockmap-redirected to a specific `sk` (socket structure), it *won't* keep the socket open. And, crucially, a dead socket won't have an `sk_wq` (socket wait queue). That's interesting, and potentially points to a resource management issue or a race condition in certain redirect scenarios.

The implications are clear: sockmap's redirection mechanism might be more fragile than initially assumed. It's hinting at a state transition where the connection's lifecycle is altered post-redirection. So, I need to understand this call better, to see *how* this socket redirection actually plays out.

So, where is `sk_stream_wait_memory` even *called*? Time for a grep. Let's trace those calls to see where this potential problem surfaces.



24/2 2026/11/02 13:33 tool grepper
0m Args:
{
  "Expression": "sk_stream_wait_memory"
}

Results:
{
  "Output": "include/net/sock.h=1244=int sk_stream_wait_connect(struct sock *sk, long *timeo_p);\ninclude/net/sock.h:1245:int sk_stream_wait_memory(struct sock *sk, long *timeo_p);\ninclude/net/sock.h-1246-void sk_stream_wait_close(struct sock *sk, long timeo_p);\n--\nnet/core/stream.c=100=void sk_stream_wait_close(struct sock *sk, long timeout)\n--\nnet/core/stream.c-116-/**\nnet/core/stream.c:117: * sk_stream_wait_memory - Wait for more memory for a socket\nnet/core/stream.c-118- * @sk: socket to wait for memory\n--\nnet/core/stream.c-120- */\nnet/core/stream.c:121:int sk_stream_wait_memory(struct sock *sk, long *timeo_p)\nnet/core/stream.c-122-{\n--\nnet/core/stream.c-190-}\nnet/core/stream.c:191:EXPORT_SYMBOL(sk_stream_wait_memory);\nnet/core/stream.c-192-\n--\nnet/ipv4/tcp.c=1119=int tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size)\n--\nnet/ipv4/tcp.c-1414-\nnet/ipv4/tcp.c:1415:\t\terr = sk_stream_wait_memory(sk, \u0026timeo);\nnet/ipv4/tcp.c-1416-\t\tif (err != 0)\n--\nnet/ipv4/tcp_bpf.c=589=static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)\n--\nnet/ipv4/tcp_bpf.c-668-wait_for_memory:\nnet/ipv4/tcp_bpf.c:669:\t\terr = sk_stream_wait_memory(sk, \u0026timeo);\nnet/ipv4/tcp_bpf.c-670-\t\tif (err) {\n--\nnet/ipv4/tcp_output.c=3028=static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle,\n--\nnet/ipv4/tcp_output.c-3118-\t\t/* Argh, we hit an empty skb(), presumably a thread\nnet/ipv4/tcp_output.c:3119:\t\t * is sleeping in sendmsg()/sk_stream_wait_memory().\nnet/ipv4/tcp_output.c-3120-\t\t * We do not want to send a pure-ack packet and have\n--\nnet/kcm/kcmsock.c=752=static int kcm_sendmsg(struct socket *sock, struct msghdr *msg, size_t len)\n--\nnet/kcm/kcmsock.c-782-\t\tsk_set_nospace(sk);\nnet/kcm/kcmsock.c:783:\t\terr = sk_stream_wait_memory(sk, \u0026timeo);\nnet/kcm/kcmsock.c-784-\t\tif (err)\n--\nnet/kcm/kcmsock.c-792-\t\t\tkcm_push(kcm);\nnet/kcm/kcmsock.c:793:\t\t\terr = sk_stream_wait_memory(sk, \u0026timeo);\nnet/kcm/kcmsock.c-794-\t\t\tif (err)\n--\nnet/kcm/kcmsock.c-893-\t\tkcm_push(kcm);\nnet/kcm/kcmsock.c:894:\t\terr = sk_stream_wait_memory(sk, \u0026timeo);\nnet/kcm/kcmsock.c-895-\t\tif (err)\n--\nnet/mptcp/protocol.c=1993=static int mptcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)\n--\nnet/mptcp/protocol.c-2104-\t\t__mptcp_push_pending(sk, msg-\u003emsg_flags);\nnet/mptcp/protocol.c:2105:\t\tret = sk_stream_wait_memory(sk, \u0026timeo);\nnet/mptcp/protocol.c-2106-\t\tif (ret)\n--\nnet/sctp/socket.c=1598=static int sctp_error(struct sock *sk, int flags, int err)\n--\nnet/sctp/socket.c-1627- */\nnet/sctp/socket.c:1628:/* BUG:  We do not implement the equivalent of sk_stream_wait_memory(). */\nnet/sctp/socket.c-1629-\n--\nnet/smc/smc_tx.c=77=static int smc_tx_wait(struct smc_sock *smc, int flags)\n--\nnet/smc/smc_tx.c-84-\nnet/smc/smc_tx.c:85:\t/* similar to sk_stream_wait_memory */\nnet/smc/smc_tx.c-86-\ttimeo = sock_sndtimeo(sk, flags \u0026 MSG_DONTWAIT);\n--\nnet/tls/tls_device.c=421=static int tls_push_data(struct sock *sk,\n--\nnet/tls/tls_device.c-469-\t\tif (unlikely(rc)) {\nnet/tls/tls_device.c:470:\t\t\trc = sk_stream_wait_memory(sk, \u0026timeo);\nnet/tls/tls_device.c-471-\t\t\tif (!rc)\n--\nnet/tls/tls_sw.c=766=static int tls_sw_sendmsg_locked(struct sock *sk, struct msghdr *msg,\n--\nnet/tls/tls_sw.c-970-wait_for_memory:\nnet/tls/tls_sw.c:971:\t\tret = sk_stream_wait_memory(sk, \u0026timeo);\nnet/tls/tls_sw.c-972-\t\tif (ret) {\n--\nnet/xfrm/espintcp.c=311=static int espintcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)\n--\nnet/xfrm/espintcp.c-350-\nnet/xfrm/espintcp.c:351:\t\terr = sk_stream_wait_memory(sk, \u0026timeo);\nnet/xfrm/espintcp.c-352-\t\tif (err)\n"
}

25/2 2026/11/02 13:33 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 24570
							output: 36
							thoughts: 656
						

Thoughts:
**Analyzing Potential Fuzzing Targets for Network BPF Changes**

Okay, so I'm looking at how to best focus fuzzing efforts related to these network BPF changes. My initial thought was `sk_stream_wait_memory`, since it's involved in TCP, TCP-BPF, TLS, and MPTCP when there's a memory wait condition. However, it's not universally called across *all* system calls that use memory, though it is quite general for stream sockets and waiting on memory.

Then, wait a second, let me check the rules. Rule 4 specifically says to avoid listing ubiquitous lifecycle hot-paths like memory allocators, core synchronization primitives, and page fault handlers. The concern is it will dilute the fuzzing effort. The goal is to specifically target functions implementing the added or altered logic. Let's list those.

Alright, so let's start with `net/core/filter.c`: I see `bpf_msg_push_data` and `bpf_msg_pop_data` are good candidates. Moving on to `net/core/skmsg.c`, I've got `__sk_msg_recvmsg`, `sk_psock_skb_state` (but that's static, so it might be inlined or a separate symbol so I'll check its callers and see if `sk_psock_backlog` is a good choice), and `sk_psock_msg_verdict`. Then from `net/core/sock_map.c`, I have `sock_map_link`.

Now looking at `net/ipv4/tcp_bpf.c`: `tcp_bpf_push`, `tcp_bpf_recvmsg_parser`, `tcp_bpf_send_verdict`, `tcp_bpf_sendmsg`, `tcp_bpf_clone`, and `tcp_bpf_redir_allowed` (that's static too so I'll need to double check how it's used). I need to verify that those functions I listed in `net/ipv4/tcp_bpf.c` are actually in the vmlinux symbols.

Wait, I should check how `tcp_bpf.o` is compiled to understand if the static functions are inlined or separate symbols. Let's see if I can `grep` for `bpf_msg_push_data` to see where it's defined and how it's exposed, and how the BPF helper is exposed. I'm seeing that the `BPF_CALL_4` macro expands to `___bpf_msg_push_data` and `bpf_msg_push_data`.

Hang on, in vmlinux, BPF helpers usually have the symbol `bpf_msg_push_data`! Okay, let's look at `net/core/filter.c` around line 2995 to confirm. That feels like a better path.



26/2 2026/11/02 13:33 tool read-file
0m Args:
{
  "File": "net/core/filter.c",
  "FirstLine": 2985,
  "LineCount": 35
}

Results:
{
  "Contents": "2985:\t\t\t\tnnsge_copy = sk_msg_elem_is_copy(msg, i);\n2986:\t\t\t} else {\n2987:\t\t\t\tnsge = sk_msg_elem_cpy(msg, i);\n2988:\t\t\t\tnsge_copy = sk_msg_elem_is_copy(msg, i);\n2989:\t\t\t}\n2990:\t\t}\n2991:\t\n2992:\tplace_new:\n2993:\t\t/* Place newly allocated data buffer */\n2994:\t\tsk_mem_charge(msg-\u003esk, len);\n2995:\t\tmsg-\u003esg.size += len;\n2996:\t\tsk_msg_clear_elem_copy(msg, new);\n2997:\t\tsg_set_page(\u0026msg-\u003esg.data[new], page, len + copy, 0);\n2998:\t\t/* A free slot can have the end mark */\n2999:\t\tsg_unmark_end(\u0026msg-\u003esg.data[new]);\n3000:\t\tif (rsge.length) {\n3001:\t\t\tget_page(sg_page(\u0026rsge));\n3002:\t\t\tsk_msg_iter_var_next(new);\n3003:\t\t\tmsg-\u003esg.data[new] = rsge;\n3004:\t\t\tsk_msg_set_elem_copy(msg, new, rsge_copy);\n3005:\t\t}\n3006:\t\tsk_msg_clear_elem_copy(msg, msg-\u003esg.end);\n3007:\t\n3008:\t\tsk_msg_reset_curr(msg);\n3009:\t\tsk_msg_compute_data_pointers(msg);\n3010:\t\treturn 0;\n3011:\t}\n3012:\t\n3013:\tstatic const struct bpf_func_proto bpf_msg_push_data_proto = {\n3014:\t\t.func\t\t= bpf_msg_push_data,\n3015:\t\t.gpl_only\t= false,\n3016:\t\t.ret_type\t= RET_INTEGER,\n3017:\t\t.arg1_type\t= ARG_PTR_TO_CTX,\n3018:\t\t.arg2_type\t= ARG_ANYTHING,\n3019:\t\t.arg3_type\t= ARG_ANYTHING,\n"
}

27/2 2026/11/02 13:33 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 26510
							output: 20
							thoughts: 53
						

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

**Diving into the BPF Data Flow**

Okay, so I'm trying to figure out the data manipulation within this BPF setup. The key here seems to be `bpf_msg_push_data` and `bpf_msg_pop_data`. That's where the data is actually being handled. Makes sense, they're the obvious candidates for pushing and pulling the data around.

Now, where does the real magic happen? I need to understand the connection. My brain immediately goes to `tcp_bpf_clone`. I'm curious what context is using that function. Time to check the callers of `tcp_bpf_clone` and see how this all fits together. I need to trace this control flow.



28/2 2026/11/02 13:33 tool grepper
0m Args:
{
  "Expression": "tcp_bpf_clone"
}

Results:
{
  "Output": "include/net/tcp.h=2891=int tcp_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool restore);\ninclude/net/tcp.h:2892:void tcp_bpf_clone(const struct sock *sk, struct sock *newsk);\ninclude/net/tcp.h-2893-#ifdef CONFIG_BPF_STREAM_PARSER\n--\ninclude/net/tcp.h=2908=int tcp_bpf_sendmsg_redir(struct sock *sk, bool ingress,\n--\ninclude/net/tcp.h-2912-#if !defined(CONFIG_BPF_SYSCALL) || !defined(CONFIG_NET_SOCK_MSG)\ninclude/net/tcp.h:2913:static inline void tcp_bpf_clone(const struct sock *sk, struct sock *newsk)\ninclude/net/tcp.h-2914-{\n--\nnet/ipv4/tcp_bpf.c=824=EXPORT_SYMBOL_GPL(tcp_bpf_update_proto);\n--\nnet/ipv4/tcp_bpf.c-830- */\nnet/ipv4/tcp_bpf.c:831:void tcp_bpf_clone(const struct sock *sk, struct sock *newsk)\nnet/ipv4/tcp_bpf.c-832-{\n--\nnet/ipv4/tcp_minisocks.c=546=struct sock *tcp_create_openreq_child(const struct sock *sk,\n--\nnet/ipv4/tcp_minisocks.c-663-\tnewtp-\u003ebpf_chg_cc_inprogress = 0;\nnet/ipv4/tcp_minisocks.c:664:\ttcp_bpf_clone(sk, newsk);\nnet/ipv4/tcp_minisocks.c-665-\n"
}

29/2 2026/11/02 13:33 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 27221
							output: 20
							thoughts: 251
						

Thoughts:
**My Deep Dive into TCP BPF Integration**

Okay, so I'm tracing this `tcp_bpf_clone` function. It's triggered by `tcp_create_openreq_child`, which, if I recall correctly, means a new child TCP socket is being spun up during the SYN-ACK or accept process. Good. Now, I need to understand how this interacts with the broader BPF framework within the TCP stack.

My next immediate investigation will focus on `tcp_bpf_sendmsg`, `tcp_bpf_send_verdict`, and `tcp_bpf_recvmsg_parser`. I've got to see where those fit in. Crucially, I have to figure out the role of `tcp_bpf_prots`. Ah, right, those are installed into `sk->sk_prot` by `tcp_bpf_update_proto` when a socket is added to a sockmap. Got it, so that's the connection point.

So, when the socket starts sending data, `tcp_bpf_sendmsg` gets called, and *that* then invokes `tcp_bpf_send_verdict`, which, as expected, determines if it's a push or a redirect. On the receive side, for incoming messages, `tcp_bpf_recvmsg_parser` comes into play. I need to keep those send/receive paths clear in my mind.

Now, a related function, `__sk_msg_recvmsg`, is called by `sk_msg_recvmsg` within `net/core/skmsg.c`. The next thing I want to do is see what calls *those* functions, so I can see how these BPF hooks are actually integrated in the overall data path of socket communication.



30/2 2026/11/02 13:33 tool grepper
0m Args:
{
  "Expression": "sk_msg_recvmsg"
}

Results:
{
  "Output": "include/linux/skmsg.h=143=int sk_msg_memcopy_from_iter(struct sock *sk, struct iov_iter *from,\ninclude/linux/skmsg.h-144-\t\t\t     struct sk_msg *msg, u32 bytes);\ninclude/linux/skmsg.h:145:int sk_msg_recvmsg(struct sock *sk, struct sk_psock *psock, struct msghdr *msg,\ninclude/linux/skmsg.h-146-\t\t   int len, int flags);\ninclude/linux/skmsg.h:147:int __sk_msg_recvmsg(struct sock *sk, struct sk_psock *psock, struct msghdr *msg,\ninclude/linux/skmsg.h-148-\t\t     int len, int flags, int *copied_from_self);\n--\nnet/core/skmsg.c=411=EXPORT_SYMBOL_GPL(sk_msg_memcopy_from_iter);\nnet/core/skmsg.c-412-\nnet/core/skmsg.c:413:int __sk_msg_recvmsg(struct sock *sk, struct sk_psock *psock, struct msghdr *msg,\nnet/core/skmsg.c-414-\t\t     int len, int flags, int *copied_from_self)\n--\nnet/core/skmsg.c-503-/* Receive sk_msg from psock-\u003eingress_msg to @msg. */\nnet/core/skmsg.c:504:int sk_msg_recvmsg(struct sock *sk, struct sk_psock *psock, struct msghdr *msg,\nnet/core/skmsg.c-505-\t\t   int len, int flags)\nnet/core/skmsg.c-506-{\nnet/core/skmsg.c:507:\treturn __sk_msg_recvmsg(sk, psock, msg, len, flags, NULL);\nnet/core/skmsg.c-508-}\nnet/core/skmsg.c:509:EXPORT_SYMBOL_GPL(sk_msg_recvmsg);\nnet/core/skmsg.c-510-\n--\nnet/ipv4/tcp_bpf.c=242=static int tcp_bpf_recvmsg_parser(struct sock *sk,\n--\nnet/ipv4/tcp_bpf.c-286-msg_bytes_ready:\nnet/ipv4/tcp_bpf.c:287:\tcopied = __sk_msg_recvmsg(sk, psock, msg, len, flags, \u0026copied_from_self);\nnet/ipv4/tcp_bpf.c-288-\t/* The typical case for EFAULT is the socket was gracefully\n--\nnet/ipv4/tcp_bpf.c=389=static int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,\n--\nnet/ipv4/tcp_bpf.c-410-msg_bytes_ready:\nnet/ipv4/tcp_bpf.c:411:\tcopied = sk_msg_recvmsg(sk, psock, msg, len, flags);\nnet/ipv4/tcp_bpf.c-412-\tif (!copied) {\n--\nnet/ipv4/udp_bpf.c=63=static int udp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,\n--\nnet/ipv4/udp_bpf.c-85-msg_bytes_ready:\nnet/ipv4/udp_bpf.c:86:\tcopied = sk_msg_recvmsg(sk, psock, msg, len, flags);\nnet/ipv4/udp_bpf.c-87-\tif (!copied) {\n--\nnet/unix/unix_bpf.c=51=static int unix_bpf_recvmsg(struct sock *sk, struct msghdr *msg,\n--\nnet/unix/unix_bpf.c-76-msg_bytes_ready:\nnet/unix/unix_bpf.c:77:\tcopied = sk_msg_recvmsg(sk, psock, msg, len, flags);\nnet/unix/unix_bpf.c-78-\tif (!copied) {\n--\nnet/vmw_vsock/vsock_bpf.c=76=static int vsock_bpf_recvmsg(struct sock *sk, struct msghdr *msg,\n--\nnet/vmw_vsock/vsock_bpf.c-100-\nnet/vmw_vsock/vsock_bpf.c:101:\tcopied = sk_msg_recvmsg(sk, psock, msg, len, flags);\nnet/vmw_vsock/vsock_bpf.c-102-\twhile (copied == 0) {\n--\nnet/vmw_vsock/vsock_bpf.c-115-\nnet/vmw_vsock/vsock_bpf.c:116:\t\tcopied = sk_msg_recvmsg(sk, psock, msg, len, flags);\nnet/vmw_vsock/vsock_bpf.c-117-\t}\n"
}

31/2 2026/11/02 13:33 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 29160
							output: 36
							thoughts: 83
						

Thoughts:
Here's my summary, as I see it:

**Verifying Symbol Export**

Okay, so I see that `__sk_msg_recvmsg` is *indeed* exported, and I can confirm that because it's declared with `EXPORT_SYMBOL_GPL`. This suggests it's intended for broader usage, perhaps by GPL-licensed kernel modules. But, always, I need to be *sure*. I'm going to quickly double-check the source code. Specifically, I'll go right to lines 490 through 515 in `net/core/skmsg.c` to look for the `EXPORT_SYMBOL_GPL` declaration. Just a quick glance will do it, but I always believe in confirming the obvious!



32/2 2026/11/02 13:33 tool read-file
0m Args:
{
  "File": "net/core/skmsg.c",
  "FirstLine": 490,
  "LineCount": 25
}

Results:
{
  "Contents": " 490:\t\t\t}\n 491:\t\n 492:\t\t\tmsg_rx-\u003esg.start = i;\n 493:\t\t\tif (!sge-\u003elength \u0026\u0026 (i == msg_rx-\u003esg.end || sg_is_last(sge))) {\n 494:\t\t\t\tmsg_rx = sk_psock_dequeue_msg(psock);\n 495:\t\t\t\tkfree_sk_msg(msg_rx);\n 496:\t\t\t}\n 497:\t\t\tmsg_rx = sk_psock_peek_msg(psock);\n 498:\t\t}\n 499:\tout:\n 500:\t\treturn copied;\n 501:\t}\n 502:\t\n 503:\t/* Receive sk_msg from psock-\u003eingress_msg to @msg. */\n 504:\tint sk_msg_recvmsg(struct sock *sk, struct sk_psock *psock, struct msghdr *msg,\n 505:\t\t\t   int len, int flags)\n 506:\t{\n 507:\t\treturn __sk_msg_recvmsg(sk, psock, msg, len, flags, NULL);\n 508:\t}\n 509:\tEXPORT_SYMBOL_GPL(sk_msg_recvmsg);\n 510:\t\n 511:\tbool sk_msg_is_readable(struct sock *sk)\n 512:\t{\n 513:\t\tstruct sk_psock *psock;\n 514:\t\tbool empty = true;\n"
}

33/2 2026/11/02 13:33 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 29839
							output: 40
							thoughts: 123
						

Thoughts:
Okay, let's break this down.

**Understanding Symbol Visibility and Compilation Dependencies**

So, `__sk_msg_recvmsg` is clearly defined with external linkage within `net/core/skmsg.c`. That means it's a globally accessible symbol within the kernel, making it a distinct entity in `vmlinux`. And of course, the fact that `sk_msg_recvmsg` is *also* exported just reinforces that. Now, the key question is: What Kconfig options are required to get the kernel to actually compile and *reach* that code?  I need to figure out what triggers the inclusion of `net/core/skmsg.c`, `net/core/sock_map.c`, and `net/ipv4/tcp_bpf.c` in the build process, specifically to understand the dependencies around this symbol. I need to track down the Kconfig settings.



34/2 2026/11/02 13:34 tool grepper
0m Args:
{
  "Expression": "skmsg\\.o|sock_map\\.o|tcp_bpf\\.o",
  "PathPrefixes": [
    "net/"
  ]
}

Results:
{
  "Output": "net/core/Makefile=43=obj-$(CONFIG_FAILOVER) += failover.o\nnet/core/Makefile:44:obj-$(CONFIG_NET_SOCK_MSG) += skmsg.o\nnet/core/Makefile:45:obj-$(CONFIG_BPF_SYSCALL) += sock_map.o\nnet/core/Makefile-46-obj-$(CONFIG_BPF_SYSCALL) += bpf_sk_storage.o\n--\nnet/ipv4/Makefile=63=obj-$(CONFIG_TCP_CONG_ILLINOIS) += tcp_illinois.o\nnet/ipv4/Makefile:64:obj-$(CONFIG_NET_SOCK_MSG) += tcp_bpf.o\nnet/ipv4/Makefile-65-obj-$(CONFIG_BPF_SYSCALL) += udp_bpf.o\n"
}

35/2 2026/11/02 13:34 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 30407
							output: 20
							thoughts: 103
						

Thoughts:
**Investigating the Configuration Landscape for Socket Messaging**

Okay, so I'm diving into this `net/core/Makefile` and `net/ipv4/Makefile`, and I see these intriguing lines: `obj-$(CONFIG_NET_SOCK_MSG) += skmsg.o`, `obj-$(CONFIG_BPF_SYSCALL) += sock_map.o`, and `obj-$(CONFIG_NET_SOCK_MSG) += tcp_bpf.o`. Right away, the repeated mention of `CONFIG_NET_SOCK_MSG` and its association with both `skmsg.o` and `tcp_bpf.o` catches my attention. It's clearly a key piece of the puzzle here.

My instinct is to understand the context of `CONFIG_NET_SOCK_MSG`. Where is it defined? What does its configuration actually *do*? I need to track down its definition in the Kconfig files. That's the logical next step to properly deciphering the build dependencies and what these modules contribute to the overall networking stack, especially since it affects both core and ipv4 components. The presence of both `skmsg.o` and `tcp_bpf.o` suggests a broader scope than just a single, isolated function. Understanding the Kconfig's role is crucial here.



36/2 2026/11/02 13:34 tool grepper
0m Args:
{
  "Expression": "config NET_SOCK_MSG"
}

Results:
{
  "Output": "net/Kconfig=474=config NET_SELFTESTS\n--\nnet/Kconfig-477-\nnet/Kconfig:478:config NET_SOCK_MSG\nnet/Kconfig-479-\tbool\n"
}

37/2 2026/11/02 13:34 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 30876
							output: 20
							thoughts: 15
						

Thoughts:
Okay, here's my interpretation of that thought process, tailored for an expert audience, and rendered in the first person:

**Investigating the `CONFIG_NET_SOCK_MSG` Symbol**

Alright, let's dive into this. The initial thought, "Let's see what selects `CONFIG_NET_SOCK_MSG`," immediately tells me we're likely in a kernel configuration context, probably related to some network socket messaging functionality. This isn't a casual exploration; we're trying to understand the dependencies and implications of enabling or disabling a specific feature – `CONFIG_NET_SOCK_MSG`. My immediate goal is to pinpoint the configuration options, code paths, or broader functionalities that *require* this symbol to be set. Knowing which other features depend on this allows for informed decisions regarding module compilation, build time impacts, and potential functional regressions. Are we talking about core networking components, a specific protocol, perhaps an optional feature flag? The scope is unclear, but the focus is clearly on dependency resolution. My next action will be to use the standard tools (likely `grep` or `kconfig-tui`) to trace the "selects" relationship, to understand how this is used and why it's chosen. From there, I'll be able to decide what the implications are in practice.



38/2 2026/11/02 13:34 tool grepper
0m Args:
{
  "Expression": "select NET_SOCK_MSG"
}

Results:
{
  "Output": "kernel/bpf/Kconfig=31=config BPF_SYSCALL\n--\nkernel/bpf/Kconfig-37-\tselect BINARY_PRINTF\nkernel/bpf/Kconfig:38:\tselect NET_SOCK_MSG if NET\nkernel/bpf/Kconfig-39-\tselect NET_XGRESS if NET\n--\nnet/Kconfig=360=config BPF_STREAM_PARSER\n--\nnet/Kconfig-365-\tselect STREAM_PARSER\nnet/Kconfig:366:\tselect NET_SOCK_MSG\nnet/Kconfig-367-\thelp\n--\nnet/ipv4/Kconfig=382=config INET_ESPINTCP\n--\nnet/ipv4/Kconfig-385-\tselect STREAM_PARSER\nnet/ipv4/Kconfig:386:\tselect NET_SOCK_MSG\nnet/ipv4/Kconfig-387-\tselect XFRM_ESPINTCP\n--\nnet/ipv6/Kconfig=90=config INET6_ESPINTCP\n--\nnet/ipv6/Kconfig-93-\tselect STREAM_PARSER\nnet/ipv6/Kconfig:94:\tselect NET_SOCK_MSG\nnet/ipv6/Kconfig-95-\tselect XFRM_ESPINTCP\n--\nnet/tls/Kconfig=5=config TLS\n--\nnet/tls/Kconfig-10-\tselect CRYPTO_GCM\nnet/tls/Kconfig:11:\tselect NET_SOCK_MSG\nnet/tls/Kconfig-12-\tdefault n\n"
}

39/2 2026/11/02 13:34 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 31578
							output: 31
							thoughts: 76
						

Thoughts:
Okay, here's my interpretation of these thoughts, formatted as you requested:

**Navigating the Kernel's BPF and Networking Configuration**

Alright, let's see... so, I'm noting something interesting with the BPF configuration. It looks like when `BPF_SYSCALL` is enabled, specifically when the networking component, `NET`, is included, it automatically pulls in `NET_SOCK_MSG`. This makes sense, as a BPF syscall related to networking would naturally need the message structure. I'm already anticipating the interaction of BPF and socket messaging.

Now, I'm also seeing that `BPF_STREAM_PARSER` in `net/Kconfig` also selects `NET_SOCK_MSG`, which has me making some quick mental associations. I understand that `STREAM_PARSER` is also selected. I need to understand that better. It's likely involved in dissecting or manipulating streams of data, probably at the socket level. To fully grasp this connection, I need to understand the relationship between stream parsing and socket messaging more clearly, so let's dig into the details of `BPF_STREAM_PARSER` in `net/Kconfig` to see the specifics.



40/2 2026/11/02 13:34 tool grepper
0m Args:
{
  "Expression": "config BPF_STREAM_PARSER",
  "PathPrefixes": [
    "net/"
  ]
}

Results:
{
  "Output": "net/Kconfig=353=config BQL\n--\nnet/Kconfig-359-\nnet/Kconfig:360:config BPF_STREAM_PARSER\nnet/Kconfig-361-\tbool \"enable BPF STREAM_PARSER\"\n"
}

41/2 2026/11/02 13:34 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 32019
							output: 32
							thoughts: 23
						

Thoughts:
Okay, let's dive into this. Here's how I'd approach that kernel configuration file investigation:

**Investigating Network Configuration Dependencies**

Alright, so I need to figure out what dependencies are at play for a specific network configuration option. Let's start by zeroing in on `net/Kconfig`, and specifically, line 360, roughly, given my knowledge of the typical structure of these things. I'll need to see precisely what that configuration option depends on - what other settings need to be enabled before it becomes available. This will likely provide a clue about the underlying functionality, and the scope of its impact. I'm expecting to find `depends on` statements, maybe `select` statements, which, depending on the context, could tell the whole story. Let's see what we find!



42/2 2026/11/02 13:34 tool read-file
0m Args:
{
  "File": "net/Kconfig",
  "FirstLine": 355,
  "LineCount": 20
}

Results:
{
  "Contents": " 355:\t\tprompt \"Enable Byte Queue Limits\"\n 356:\t\tdepends on SYSFS\n 357:\t\tselect DQL\n 358:\t\tdefault y\n 359:\t\n 360:\tconfig BPF_STREAM_PARSER\n 361:\t\tbool \"enable BPF STREAM_PARSER\"\n 362:\t\tdepends on INET\n 363:\t\tdepends on BPF_SYSCALL\n 364:\t\tdepends on CGROUP_BPF\n 365:\t\tselect STREAM_PARSER\n 366:\t\tselect NET_SOCK_MSG\n 367:\t\thelp\n 368:\t\t  Enabling this allows a TCP stream parser to be used with\n 369:\t\t  BPF_MAP_TYPE_SOCKMAP.\n 370:\t\n 371:\tconfig NET_FLOW_LIMIT\n 372:\t\tbool \"Net flow limit\"\n 373:\t\tdepends on RPS\n 374:\t\tdefault y\n"
}

43/2 2026/11/02 13:34 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 32507
							output: 30
							thoughts: 156
						

Thoughts:
Okay, let's see. My mind is immediately drawn to this `BPF_STREAM_PARSER` business. Interesting, it seems enabling it opens the door to using a TCP stream parser in conjunction with `BPF_MAP_TYPE_SOCKMAP`. That's a significant capability, I recognize the implications immediately.

I should verify the implementation details. I'm seeing this being used in `net/ipv4/tcp_bpf.c`, specifically under a `#if IS_ENABLED(CONFIG_BPF_STREAM_PARSER)` block. I can see there's a function named `tcp_bpf_strp_read_sock(...)` that's likely integral to this functionality.

Now let's cross-reference and look in `net/core/sock_map.c`, because that's where `SOCKMAP` is defined. I'll need to grep through the `net/` directory, looking for any instances of either `CONFIG_BPF_STREAM_PARSER` or `BPF_STREAM_PARSER`. This should provide a complete picture of how this stream parser integrates within the networking subsystem.



44/2 2026/11/02 13:34 tool grepper
0m Args:
{
  "Expression": "BPF_STREAM_PARSER",
  "PathPrefixes": [
    "net/"
  ]
}

Results:
{
  "Output": "net/Kconfig=353=config BQL\n--\nnet/Kconfig-359-\nnet/Kconfig:360:config BPF_STREAM_PARSER\nnet/Kconfig-361-\tbool \"enable BPF STREAM_PARSER\"\n--\nnet/core/skmsg.c=548=static int sk_psock_skb_ingress_enqueue(struct sk_buff *skb,\n--\nnet/core/skmsg.c-576-\nnet/core/skmsg.c:577:#if IS_ENABLED(CONFIG_BPF_STREAM_PARSER)\nnet/core/skmsg.c-578-\tpsock-\u003eingress_bytes += len;\n--\nnet/core/skmsg.c=1072=static void sk_psock_write_space(struct sock *sk)\n--\nnet/core/skmsg.c-1088-\nnet/core/skmsg.c:1089:#if IS_ENABLED(CONFIG_BPF_STREAM_PARSER)\nnet/core/skmsg.c-1090-static void sk_psock_strp_read(struct strparser *strp, struct sk_buff *skb)\n--\nnet/core/skmsg.c=1208=static void sk_psock_done_strp(struct sk_psock *psock)\n--\nnet/core/skmsg.c-1210-}\nnet/core/skmsg.c:1211:#endif /* CONFIG_BPF_STREAM_PARSER */\nnet/core/skmsg.c-1212-\n--\nnet/core/sock_map.c=1491=static int sock_map_prog_link_lookup(struct bpf_map *map, struct bpf_prog ***pprog,\n--\nnet/core/sock_map.c-1505-\t\tbreak;\nnet/core/sock_map.c:1506:#if IS_ENABLED(CONFIG_BPF_STREAM_PARSER)\nnet/core/sock_map.c-1507-\tcase BPF_SK_SKB_STREAM_PARSER:\n--\nnet/ipv4/tcp_bpf.c=741=static int tcp_bpf_assert_proto_ops(struct proto *ops)\n--\nnet/ipv4/tcp_bpf.c-750-\nnet/ipv4/tcp_bpf.c:751:#if IS_ENABLED(CONFIG_BPF_STREAM_PARSER)\nnet/ipv4/tcp_bpf.c-752-int tcp_bpf_strp_read_sock(struct strparser *strp, read_descriptor_t *desc,\n--\nnet/ipv4/tcp_bpf.c-784-}\nnet/ipv4/tcp_bpf.c:785:#endif /* CONFIG_BPF_STREAM_PARSER */\nnet/ipv4/tcp_bpf.c-786-\n"
}

45/2 2026/11/02 13:34 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 33665
							output: 21
							thoughts: 134
						

Thoughts:
**Analyzing Kernel Configuration Dependencies**

Okay, let's break this down. I see we're dealing with a few key kernel configuration options here: `BPF_SYSCALL`, `BPF_STREAM_PARSER`, `NET_SOCK_MSG`, and `INET`. Nothing too surprising, given the work I'm involved in, but the interdependencies are what's key.

Right off the bat, `BPF_SYSCALL` indicates a direct dependency on the base `BPF` configuration option, which is standard. Then things get a bit more complex with `BPF_STREAM_PARSER` because it depends on `INET`, as expected for anything handling network streams, but also includes `BPF_SYSCALL` and `CGROUP_BPF`. This makes sense, given the potential for filtering network streams through BPF. I have to make sure that the network interface is configured.

Finally, `NET_SOCK_MSG` is conditionally selected by `BPF_SYSCALL` when `NET` is also selected. That tells me that, under the network umbrella, the BPF syscall aspect of the kernel enables socket message passing.

Given the dependency on `CGROUP_BPF` I'm going to quickly check `init/Kconfig` or `kernel/bpf/Kconfig`. It's possible there are more options I need to know about.



46/2 2026/11/02 13:34 tool grepper
0m Args:
{
  "Expression": "config CGROUP_BPF"
}

Results:
{
  "Output": "init/Kconfig=1149=config CGROUP_PERF\n--\ninit/Kconfig-1159-\ninit/Kconfig:1160:config CGROUP_BPF\ninit/Kconfig-1161-\tbool \"Support for eBPF programs attached to cgroups\"\n"
}

47/2 2026/11/02 13:34 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 34203
							output: 37
							thoughts: 125
						

Thoughts:
Okay, let's break this down.

**Investigating BPF Configuration Options in TCP BPF Implementation**

Alright, I'm digging into BPF configurations here. Specifically, I'm looking at `BPF_SYSCALL`, `BPF_STREAM_PARSER`, and `NET_SOCK_MSG`. These are clearly relevant to the BPF landscape within the kernel. My initial thought is to check for related configurations, see where they're used.

The focus is on `net/ipv4/tcp_bpf.c` because it's in the network stack, so naturally that's where to look. I immediately spot the `#if IS_ENABLED(CONFIG_BPF_STREAM_PARSER)` which confirms this configuration is in use. Also, there's a `#endif /* CONFIG_BPF_SYSCALL */` on line 833.

Before I go further, I need to understand the relationship between `CONFIG_BPF_SYSCALL` and related configurations, so I will take a look at the surrounding code to determine how things are protected. I'll read lines 1-50 of `net/ipv4/tcp_bpf.c` and how it's protected to get the lay of the land. It's important to understand the bigger picture before diving too deep into a specific area.



48/2 2026/11/02 13:34 tool read-file
0m Args:
{
  "File": "net/ipv4/tcp_bpf.c",
  "FirstLine": 1,
  "LineCount": 40
}

Results:
{
  "Contents": "   1:\t// SPDX-License-Identifier: GPL-2.0\n   2:\t/* Copyright (c) 2017 - 2018 Covalent IO, Inc. http://covalent.io */\n   3:\t\n   4:\t#include \u003clinux/skmsg.h\u003e\n   5:\t#include \u003clinux/filter.h\u003e\n   6:\t#include \u003clinux/bpf.h\u003e\n   7:\t#include \u003clinux/init.h\u003e\n   8:\t#include \u003clinux/wait.h\u003e\n   9:\t#include \u003clinux/util_macros.h\u003e\n  10:\t\n  11:\t#include \u003cnet/inet_common.h\u003e\n  12:\t#include \u003cnet/tls.h\u003e\n  13:\t#include \u003casm/ioctls.h\u003e\n  14:\t\n  15:\tvoid tcp_eat_skb(struct sock *sk, struct sk_buff *skb)\n  16:\t{\n  17:\t\tstruct tcp_sock *tcp;\n  18:\t\tint copied;\n  19:\t\n  20:\t\tif (!skb || !skb-\u003elen || !sk_is_tcp(sk))\n  21:\t\t\treturn;\n  22:\t\n  23:\t\tif (skb_bpf_strparser(skb))\n  24:\t\t\treturn;\n  25:\t\n  26:\t\ttcp = tcp_sk(sk);\n  27:\t\tcopied = tcp-\u003ecopied_seq + skb-\u003elen;\n  28:\t\tWRITE_ONCE(tcp-\u003ecopied_seq, copied);\n  29:\t\ttcp_rcv_space_adjust(sk);\n  30:\t\t__tcp_cleanup_rbuf(sk, skb-\u003elen);\n  31:\t}\n  32:\t\n  33:\tstatic int bpf_tcp_ingress(struct sock *sk, struct sk_psock *psock,\n  34:\t\t\t\t   struct sk_msg *msg, u32 apply_bytes)\n  35:\t{\n  36:\t\tbool apply = apply_bytes;\n  37:\t\tstruct scatterlist *sge;\n  38:\t\tu32 size, copied = 0;\n  39:\t\tstruct sk_msg *tmp;\n  40:\t\tint i, ret = 0;\n"
}

49/2 2026/11/02 13:34 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 35236
							output: 39
							thoughts: 20
						

Thoughts:
Here's my take on those thoughts, tailored for an expert audience:

**Diving into the TCP BPF Deep End**

Okay, time to get my hands dirty. I'm thinking, let's drill down into the nitty-gritty of how BPF interacts with TCP, specifically within the IPv4 context. My gut tells me there might be something interesting to dissect in the `net/ipv4/tcp_bpf.c` file. Given my understanding of kernel internals, I'm expecting to find the core logic that hooks BPF programs into the TCP data path for IPv4 traffic. The goal is to see how the kernel *actually* implements the BPF hookpoints for TCP events, how data is passed, and how these programs ultimately influence packet processing, particularly at the bottom of the file where the fundamental logic for packet processing resides. Let's see what gems we can find there.



50/2 2026/11/02 13:34 tool read-file
0m Args:
{
  "File": "net/ipv4/tcp_bpf.c",
  "FirstLine": 820,
  "LineCount": 40
}

Results:
{
  "Contents": " 820:\t\t/* Pairs with lockless read in sk_clone_lock() */\n 821:\t\tsock_replace_proto(sk, \u0026tcp_bpf_prots[family][config]);\n 822:\t\treturn 0;\n 823:\t}\n 824:\tEXPORT_SYMBOL_GPL(tcp_bpf_update_proto);\n 825:\t\n 826:\t/* If a child got cloned from a listening socket that had tcp_bpf\n 827:\t * protocol callbacks installed, we need to restore the callbacks to\n 828:\t * the default ones because the child does not inherit the psock state\n 829:\t * that tcp_bpf callbacks expect.\n 830:\t */\n 831:\tvoid tcp_bpf_clone(const struct sock *sk, struct sock *newsk)\n 832:\t{\n 833:\t\tstruct proto *prot = newsk-\u003esk_prot;\n 834:\t\n 835:\t\tif (!is_insidevar(prot, tcp_bpf_prots))\n 836:\t\t\treturn;\n 837:\t\n 838:\t\tnewsk-\u003esk_prot = sk-\u003esk_prot_creator;\n 839:\t\t/*\n 840:\t\t * sock_map_link() gives a listener no programs, but a socket which got\n 841:\t\t * them in another state and listens now has the psock's sk_data_ready\n 842:\t\t * and sk_write_space, and sock_copy() has copied them.\n 843:\t\t * sk_psock_init() refuses a socket that has sk_user_data or a ULP, so\n 844:\t\t * what the psock replaced were the defaults of a TCP socket.\n 845:\t\t */\n 846:\t\tnewsk-\u003esk_data_ready = sock_def_readable;\n 847:\t\tnewsk-\u003esk_write_space = sk_stream_write_space;\n 848:\t}\n 849:\t#endif /* CONFIG_BPF_SYSCALL */\n"
}

51/2 2026/11/02 13:34 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 36023
							output: 40
							thoughts: 21
						

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

**Locating the BPF Syscall Configuration**

Alright, my immediate concern is pinpointing where the `#ifdef CONFIG_BPF_SYSCALL` preprocessor directive is actually defined. This is fundamental to understanding the conditional compilation behavior surrounding the BPF syscall implementation. I need to trace back to where this macro is set to evaluate its implications for the code path I'm interested in. The rest of the investigation depends on knowing under what conditions BPF syscall functionality is enabled.



52/2 2026/11/02 13:35 tool grepper
0m Args:
{
  "Expression": "CONFIG_BPF_SYSCALL",
  "PathPrefixes": [
    "net/ipv4/tcp_bpf.c"
  ]
}

Results:
{
  "Output": "net/ipv4/tcp_bpf.c=199=EXPORT_SYMBOL_GPL(tcp_bpf_sendmsg_redir);\nnet/ipv4/tcp_bpf.c-200-\nnet/ipv4/tcp_bpf.c:201:#ifdef CONFIG_BPF_SYSCALL\nnet/ipv4/tcp_bpf.c-202-static int tcp_msg_wait_data(struct sock *sk, struct sk_psock *psock,\n--\nnet/ipv4/tcp_bpf.c=831=void tcp_bpf_clone(const struct sock *sk, struct sock *newsk)\n--\nnet/ipv4/tcp_bpf.c-848-}\nnet/ipv4/tcp_bpf.c:849:#endif /* CONFIG_BPF_SYSCALL */\n"
}

53/2 2026/11/02 13:35 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 36415
							output: 37
							thoughts: 545
						

Thoughts:
**Analyzing BPF-Related Code Modifications**

Okay, so I'm digging into these code changes and immediately, I see that lines mentioning `#ifdef CONFIG_BPF_SYSCALL` are crucial. That means functions like `tcp_bpf_recvmsg_parser`, `tcp_bpf_send_verdict`, `tcp_bpf_sendmsg`, and `tcp_bpf_clone` are all conditionally compiled based on the BPF system call configuration. This is my starting point.

Now, I need to understand the impact of these changes. I'm focusing on directly modified symbols or those that are non-ubiquitous direct callers of modified static inlines, the specific areas that are relevant to this BPF-centric scope.

Here's what I've identified so far:

*   In `include/linux/skmsg.h`, the static inline function `sk_msg_reset_curr` has been touched. It's called by `bpf_msg_pull_data`, `bpf_msg_push_data`, `bpf_msg_pop_data`, and, notably, `tcp_bpf_send_verdict`. That `tcp_bpf_send_verdict` linkage is key, obviously.

*   In `include/net/tcp.h`, `tcp_can_send_ack` (also static inline) has been changed, and the key callers are `tcp_bpf_recvmsg_parser`, `__mptcp_subflow_send_ack` and `mptcp_subflow_cleanup_rbuf`. That connection between `tcp_bpf_recvmsg_parser` and `tcp_can_send_ack` is going to need further investigation.

*   In `net/core/filter.c`, `bpf_msg_push_data` and `bpf_msg_pop_data` have been modified, with an added `sg_unmark_end` call.

*   Moving to `net/core/skmsg.c`, `__sk_msg_recvmsg` is modified with `msg_rx->sg.start = i`, `sk_psock_skb_state` is now static and modified, and `sk_psock_msg_verdict` has been updated with `msg->sk_redir = NULL`. This is a significant chunk of change.

*   Finally, in `net/core/sock_map.c`, I'm looking at `sock_map_link`, which is static. It's called by `sock_map_update_common` and `sock_hash_update_common`. I need to figure out which of these two is more relevant here, or if both are. This is a bit of a sidetrack for now, but good to note. Let me check if `sock_map_link` is static. Yes, `sock_map_link` is indeed static.

I'll now need to see what is calling `sock_map_update_common`.



54/2 2026/11/02 13:35 tool grepper
0m Args:
{
  "Expression": "sock_map_update_common",
  "PathPrefixes": [
    "net/core/sock_map.c"
  ]
}

Results:
{
  "Output": "net/core/sock_map.c=469=static int sock_map_get_next_key(struct bpf_map *map, void *key, void *next)\n--\nnet/core/sock_map.c-483-\nnet/core/sock_map.c:484:static int sock_map_update_common(struct bpf_map *map, u32 idx,\nnet/core/sock_map.c-485-\t\t\t\t  struct sock *sk, u64 flags)\n--\nnet/core/sock_map.c=572=int sock_map_update_elem_sys(struct bpf_map *map, void *key, void *value,\n--\nnet/core/sock_map.c-603-\telse if (map-\u003emap_type == BPF_MAP_TYPE_SOCKMAP)\nnet/core/sock_map.c:604:\t\tret = sock_map_update_common(map, *(u32 *)key, sk, flags);\nnet/core/sock_map.c-605-\telse\n--\nnet/core/sock_map.c=613=static long sock_map_update_elem(struct bpf_map *map, void *key,\n--\nnet/core/sock_map.c-629-\telse if (map-\u003emap_type == BPF_MAP_TYPE_SOCKMAP)\nnet/core/sock_map.c:630:\t\tret = sock_map_update_common(map, *(u32 *)key, sk, flags);\nnet/core/sock_map.c-631-\telse\n--\nnet/core/sock_map.c=638=BPF_CALL_4(bpf_sock_map_update, struct bpf_sock_ops_kern *, sops,\n--\nnet/core/sock_map.c-644-\t\t   sock_map_op_okay(sops)))\nnet/core/sock_map.c:645:\t\treturn sock_map_update_common(map, *(u32 *)key, sops-\u003esk,\nnet/core/sock_map.c-646-\t\t\t\t\t      flags);\n"
}

55/2 2026/11/02 13:35 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 38190
							output: 46
							thoughts: 2739
						

Thoughts:
**Analysis of Code Changes for Focused Fuzzing**

Right, so I'm looking at this patch and figuring out the best way to leverage syzkaller to find bugs, particularly related to the sockmap changes. The goal is to avoid diluting the fuzzing effort by including functions that are too ubiquitous and would generate a ton of irrelevant test cases.

First off, `sock_map_update_elem_sys` is clearly an entrypoint for updating the sockmap from userspace, so that's a key function to target. Then, I see `sk_stream_wait_memory` in `net/core/stream.c` has been modified with a `SOCK_DEAD` check. Now, `sk_stream_wait_memory` isn't a *completely* generic function, it's specific to stream sockets when their send buffers fill up. But, that *does* happen fairly often. However, including it could focus tests on potential blocking issues, as that's where the deadlock/hang/crash bug was identified. Maybe it would be beneficial to add.

Now, let's move on to `net/ipv4/tcp_bpf.c`. There are a bunch of functions modified here, mainly static functions: `tcp_bpf_redir_allowed`, `tcp_bpf_push`, `tcp_bpf_recvmsg_parser`, `tcp_bpf_send_verdict`, `tcp_bpf_sendmsg`, and `tcp_bpf_clone`. I'm going to determine if the compiler might inline those or not, although kcov should still catch non-inlined static functions. These functions look to be of a reasonable size to be distinct symbols, so they should be added. `tcp_bpf_update_proto` is the protocol setup function.

Then, there's `sk_psock_skb_state`, a static function in `net/core/skmsg.c`, along with `sk_psock_backlog`. `sk_psock_skb_state` is probably inlined, but that shouldn't matter as syzkaller should catch `sk_psock_backlog`, which is a work handler.  `sk_psock_msg_verdict` is global, so that should be included, as well as `__sk_msg_recvmsg`, and `sk_msg_recvmsg`. I see `sock_map_link` is static. I note BPF helpers `bpf_msg_push_data` and `bpf_msg_pop_data` are in `net/core/filter.c`.

Now, in `net/mptcp/protocol.c` the patch just removed a local definition of `tcp_can_send_ack` because it moved to `<net/tcp.h>`, so the MPTCP behavior itself hasn't changed.

Now, let's make a final list of functions: `bpf_msg_push_data`, `bpf_msg_pop_data`, `__sk_msg_recvmsg`, `sk_psock_skb_state`, `sk_psock_backlog`, `sk_psock_msg_verdict`, `sock_map_link`, `sk_stream_wait_memory`, `tcp_bpf_redir_allowed`, `tcp_bpf_push`, `tcp_bpf_recvmsg_parser`, `tcp_bpf_send_verdict`, `tcp_bpf_sendmsg`, and `tcp_bpf_clone`.

Looking at that list, I'm fairly certain only `sk_stream_wait_memory` is somewhat generic, but even then, it's specific to stream sockets under memory pressure. All the others are quite directly related to the sockmap and BPF changes. But just to be extra sure, let's include all of them.

Finally, I need to make sure the right kernel config options are enabled for this fuzzing to be effective. `BPF_SYSCALL`, `NET_SOCK_MSG`, and `BPF_STREAM_PARSER` are definitely involved. `BPF_STREAM_PARSER` depends on `INET`, `CGROUP_BPF`, and `NET_SOCK_MSG`, all of which seem standard, so we should be good there. I'll double-check `net/core/skmsg.c` and `net/ipv4/tcp_bpf.c` for any other conditional compilation, but I should be set.



56/2 2026/11/02 13:35 tool grepper
0m Args:
{
  "Expression": "^#if",
  "PathPrefixes": [
    "net/ipv4/tcp_bpf.c",
    "net/core/skmsg.c"
  ]
}

Results:
{
  "Output": "net/core/skmsg.c=548=static int sk_psock_skb_ingress_enqueue(struct sk_buff *skb,\n--\nnet/core/skmsg.c-576-\nnet/core/skmsg.c:577:#if IS_ENABLED(CONFIG_BPF_STREAM_PARSER)\nnet/core/skmsg.c-578-\tpsock-\u003eingress_bytes += len;\n--\nnet/core/skmsg.c=1072=static void sk_psock_write_space(struct sock *sk)\n--\nnet/core/skmsg.c-1088-\nnet/core/skmsg.c:1089:#if IS_ENABLED(CONFIG_BPF_STREAM_PARSER)\nnet/core/skmsg.c-1090-static void sk_psock_strp_read(struct strparser *strp, struct sk_buff *skb)\n--\nnet/ipv4/tcp_bpf.c=199=EXPORT_SYMBOL_GPL(tcp_bpf_sendmsg_redir);\nnet/ipv4/tcp_bpf.c-200-\nnet/ipv4/tcp_bpf.c:201:#ifdef CONFIG_BPF_SYSCALL\nnet/ipv4/tcp_bpf.c-202-static int tcp_msg_wait_data(struct sock *sk, struct sk_psock *psock,\n--\nnet/ipv4/tcp_bpf.c=741=static int tcp_bpf_assert_proto_ops(struct proto *ops)\n--\nnet/ipv4/tcp_bpf.c-750-\nnet/ipv4/tcp_bpf.c:751:#if IS_ENABLED(CONFIG_BPF_STREAM_PARSER)\nnet/ipv4/tcp_bpf.c-752-int tcp_bpf_strp_read_sock(struct strparser *strp, read_descriptor_t *desc,\n"
}

57/2 2026/11/02 13:35 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 42438
							output: 288
							thoughts: 317
						

Thoughts:
**My Assessment of the `set-results` Configuration**

Okay, let's take a look at this. We're dealing with `BPF_SYSCALL`, `BPF_STREAM_PARSER`, `NET_SOCK_MSG`, `CGROUP_BPF`, and `INET` – so the kernel networking space, specifically leveraging BPF in relation to socket messaging. I need to make sure the `set-results` parameters are dialed in correctly.

Right, the parameters. We have `EnableConfigs` which is a string array, `FocusSymbols` also a string array, a `Reasoning` string, and a boolean `WorthFuzzing`. Let's pay particular attention to `FocusSymbols`. A focused approach is crucial for efficiently targeting the relevant code.

Now the `FocusSymbols` list is: `tcp_bpf_sendmsg`, `tcp_bpf_send_verdict`, `tcp_bpf_push`, `tcp_bpf_recvmsg_parser`, `tcp_bpf_clone`, `tcp_bpf_redir_allowed`, `sk_psock_msg_verdict`, `sk_psock_skb_state`, `sk_psock_backlog`, `__sk_msg_recvmsg`, `sock_map_link`, `bpf_msg_push_data`, `bpf_msg_pop_data`, and `sk_stream_wait_memory`. Excellent! This list is *precisely* what I'd expect. These are all highly targeted. They're not generic or ubiquitous kernel symbols. They're specifically related to the modified code paths, dealing with BPF-enabled TCP interactions, socket maps, stream parsing, verdict handling, psock state management, and even dead socket management during memory wait. This selection demonstrates a clear understanding of the target area.

Finally, the `Reasoning`. That should be clear and concise. If the `Reasoning` provides a concise explanation of the verdict and the components affected (sockmap, BPF stream parser, TCP redirect, psock state handling, and dead socket handling during stream memory wait), we have a winner. This gives a direct line to the areas affected by the patch.

Assuming the reasoning checks out and the `WorthFuzzing` boolean is set appropriately, I'm confident in invoking `set-results`. The configuration seems well thought out, and focused on the targeted code, making it an appropriate setup for fuzzing and/or analysis.



58/2 2026/11/02 13:36 tool set-results
0m Args:
{
  "EnableConfigs": [
    "BPF_SYSCALL",
    "BPF_STREAM_PARSER",
    "NET_SOCK_MSG",
    "CGROUP_BPF",
    "INET"
  ],
  "FocusSymbols": [
    "tcp_bpf_sendmsg",
    "tcp_bpf_send_verdict",
    "tcp_bpf_push",
    "tcp_bpf_recvmsg_parser",
    "tcp_bpf_clone",
    "tcp_bpf_redir_allowed",
    "sk_psock_msg_verdict",
    "sk_psock_skb_state",
    "sk_psock_backlog",
    "__sk_msg_recvmsg",
    "sock_map_link",
    "bpf_msg_push_data",
    "bpf_msg_pop_data",
    "sk_stream_wait_memory"
  ],
  "Reasoning": "The patch series modifies core networking and BPF socket map (sockmap / TCP BPF / skmsg) logic. It addresses race conditions, state transitions, redirect validations, memory wait handling for dead sockets, and TCP listener handling within sockmap. These paths are fully reachable via standard network socket and BPF syscalls in virtualized QEMU environments.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "BPF_SYSCALL",
    "BPF_STREAM_PARSER",
    "NET_SOCK_MSG",
    "CGROUP_BPF",
    "INET"
  ],
  "FocusSymbols": [
    "tcp_bpf_sendmsg",
    "tcp_bpf_send_verdict",
    "tcp_bpf_push",
    "tcp_bpf_recvmsg_parser",
    "tcp_bpf_clone",
    "tcp_bpf_redir_allowed",
    "sk_psock_msg_verdict",
    "sk_psock_skb_state",
    "sk_psock_backlog",
    "__sk_msg_recvmsg",
    "sock_map_link",
    "bpf_msg_push_data",
    "bpf_msg_pop_data",
    "sk_stream_wait_memory"
  ],
  "Reasoning": "The patch series modifies core networking and BPF socket map (sockmap / TCP BPF / skmsg) logic. It addresses race conditions, state transitions, redirect validations, memory wait handling for dead sockets, and TCP listener handling within sockmap. These paths are fully reachable via standard network socket and BPF syscalls in virtualized QEMU environments.",
  "WorthFuzzing": true
}

59/1 2026/11/02 13:36 action If
3m Args:
{
  "WorthFuzzing": true
}

Results:
null

60/2 2026/11/02 13:36 agent kmsan-evaluator
3m Model:
core

Args:
null

Results:
{
  "KMSANReasoning": "The patch series addresses several edge cases and state synchronization issues in BPF sockmap, psock handling, and TCP BPF redirects:\n1. Prevents attaching verdict/parser programs to TCP listening sockets and restores default socket callbacks on cloned sockets.\n2. Guards against redirecting to dead sockets or sockets lacking `sk_socket` / wait queues, avoiding NULL pointer dereferences or crashes when waiting on connect/locks.\n3. Unmarks the scatterlist end bit when reusing/allocating slots in `bpf_msg_push_data` and `bpf_msg_pop_data`.\n4. Updates `msg_rx-\u003esg.start` on fault in `__sk_msg_recvmsg`, resets `sk_msg` curr/copybreak on corking, resets `msg-\u003esk_redir` prior to verdict runs, and improves cork synchronization across socket unlock/relock.\n\nNone of the changes introduce or modify kernel-to-user memory transfers of uninitialized structures (info leaks), nor do they create branches on uninitialized memory or access uninitialized buffer bytes. The potential failure modes (NULL pointer dereference, use-after-free, locking/concurrency races, state machine misuse) are well covered by standard KASAN and LOCKDEP. Consequently, a dedicated KMSAN fuzzing session is not warranted.",
  "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:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit 4360c9911c069182e1d97b5058f08941e7121d41
Author: syz-cluster <triage@syzkaller.com>
Date:   Mon Nov 2 13:31:53 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/bpf/map_sockmap.rst b/Documentation/bpf/map_sockmap.rst
index 2d630686a00baa..eb466b86df1707 100644
--- a/Documentation/bpf/map_sockmap.rst
+++ b/Documentation/bpf/map_sockmap.rst
@@ -33,7 +33,9 @@ of ``__SK_DROP``, ``__SK_PASS``, or ``__SK_REDIRECT``.
 
 When a socket is inserted into one of these maps, its socket callbacks are
 replaced and a ``struct sk_psock`` is attached to it. Additionally, this
-``sk_psock`` inherits the programs that are attached to the map.
+``sk_psock`` inherits the programs that are attached to the map. A TCP listener
+is an exception. It gets a ``struct sk_psock``, but it keeps its callbacks and
+inherits no programs.
 
 A sock object may be in multiple maps, but can only inherit a single
 parse or verdict program. If adding a sock object to a map would result
diff --git a/include/linux/skmsg.h b/include/linux/skmsg.h
index d5e35f24738d5c..f0e27b4b929f10 100644
--- a/include/linux/skmsg.h
+++ b/include/linux/skmsg.h
@@ -196,6 +196,25 @@ static inline void sk_msg_init(struct sk_msg *msg)
 	sg_init_marker(msg->sg.data, NR_MSG_FRAG_IDS);
 }
 
+/*
+ * sk_msg_memcopy_from_iter() writes to byte sg.copybreak of element sg.curr,
+ * counted from the offset of that element. Point both past the last byte of
+ * the message, after something other than a copy has changed its elements.
+ */
+static inline void sk_msg_reset_curr(struct sk_msg *msg)
+{
+	if (!msg->sg.size) {
+		msg->sg.curr = msg->sg.start;
+		msg->sg.copybreak = 0;
+	} else {
+		u32 i = msg->sg.end;
+
+		sk_msg_iter_var_prev(i);
+		msg->sg.curr = i;
+		msg->sg.copybreak = msg->sg.data[i].length;
+	}
+}
+
 static inline void sk_msg_xfer(struct sk_msg *dst, struct sk_msg *src,
 			       int which, u32 size)
 {
diff --git a/include/net/tcp.h b/include/net/tcp.h
index 3560adddf82154..23a22d70718eae 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -1845,6 +1845,12 @@ static inline void tcp_adjust_rcv_ssthresh(struct sock *sk)
 void tcp_cleanup_rbuf(struct sock *sk, int copied);
 void __tcp_cleanup_rbuf(struct sock *sk, int copied);
 
+/* Is the socket in a state in which TCP may send an ACK? */
+static inline bool tcp_can_send_ack(const struct sock *sk)
+{
+	return !((1 << inet_sk_state_load(sk)) &
+	       (TCPF_SYN_SENT | TCPF_SYN_RECV | TCPF_TIME_WAIT | TCPF_CLOSE | TCPF_LISTEN));
+}
 
 /* We provision sk_rcvbuf around 200% of sk_rcvlowat.
  * If 87.5 % (7/8) of the space has been consumed, we want to override
diff --git a/net/core/filter.c b/net/core/filter.c
index 9245009d9f1ebc..d68b5215040506 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -2677,20 +2677,6 @@ BPF_CALL_2(bpf_msg_cork_bytes, struct sk_msg *, msg, u32, bytes)
 	return 0;
 }
 
-static void sk_msg_reset_curr(struct sk_msg *msg)
-{
-	if (!msg->sg.size) {
-		msg->sg.curr = msg->sg.start;
-		msg->sg.copybreak = 0;
-	} else {
-		u32 i = msg->sg.end;
-
-		sk_msg_iter_var_prev(i);
-		msg->sg.curr = i;
-		msg->sg.copybreak = msg->sg.data[i].length;
-	}
-}
-
 static bool sk_msg_elem_is_copy(const struct sk_msg *msg, u32 i)
 {
 	return test_bit(i, msg->sg.copy);
@@ -3009,6 +2995,8 @@ BPF_CALL_4(bpf_msg_push_data, struct sk_msg *, msg, u32, start,
 	msg->sg.size += len;
 	sk_msg_clear_elem_copy(msg, new);
 	sg_set_page(&msg->sg.data[new], page, len + copy, 0);
+	/* A free slot can have the end mark */
+	sg_unmark_end(&msg->sg.data[new]);
 	if (rsge.length) {
 		get_page(sg_page(&rsge));
 		sk_msg_iter_var_next(new);
@@ -3142,6 +3130,7 @@ BPF_CALL_4(bpf_msg_pop_data, struct sk_msg *, msg, u32, start,
 				sg_set_page(nsge,
 					    sg_page(sge),
 					    b, sge->offset + pop + a);
+				sg_unmark_end(nsge);
 				sk_msg_set_elem_copy(msg, i, sge_copy);
 			} else {
 				struct page *page, *orig;
diff --git a/net/core/skmsg.c b/net/core/skmsg.c
index df385a5a961e83..8a3b8536c0c218 100644
--- a/net/core/skmsg.c
+++ b/net/core/skmsg.c
@@ -443,6 +443,9 @@ int __sk_msg_recvmsg(struct sock *sk, struct sk_psock *psock, struct msghdr *msg
 			if (copy)
 				copy = copy_page_to_iter(page, sge->offset, copy, iter);
 			if (!copy) {
+				/* The elements in front of i are used up */
+				if (!peek)
+					msg_rx->sg.start = i;
 				copied = copied ? copied : -EFAULT;
 				goto out;
 			}
@@ -661,7 +664,8 @@ static void sk_psock_skb_state(struct sk_psock *psock,
 			       int len, int off)
 {
 	spin_lock_bh(&psock->ingress_lock);
-	if (sk_psock_test_state(psock, SK_PSOCK_TX_ENABLED)) {
+	/* No new state once the psock is stopped, but never keep an old one */
+	if (!len || sk_psock_test_state(psock, SK_PSOCK_TX_ENABLED)) {
 		state->len = len;
 		state->off = off;
 	}
@@ -932,6 +936,13 @@ int sk_psock_msg_verdict(struct sock *sk, struct sk_psock *psock,
 
 	sk_msg_compute_data_pointers(msg);
 	msg->sk = sk;
+	/*
+	 * The prog may have run on this sk_msg before: it is a cork, or the
+	 * rest of a message of which only a part was sent. The target of that
+	 * run is not the target of this run, and the sk_msg holds no reference
+	 * to it.
+	 */
+	msg->sk_redir = NULL;
 	ret = bpf_prog_run_pin_on_cpu(prog, msg);
 	msg->sk = NULL;
 	ret = sk_psock_map_verd(ret, msg->sk_redir);
diff --git a/net/core/sock_map.c b/net/core/sock_map.c
index efe49551caba9e..282c6a4b2b05fe 100644
--- a/net/core/sock_map.c
+++ b/net/core/sock_map.c
@@ -228,6 +228,14 @@ static int sock_map_link(struct bpf_map *map, struct sock *sk)
 	struct sk_psock *psock;
 	int ret;
 
+	/*
+	 * A listener receives no data. With the callbacks of a verdict program
+	 * or a parser it would no longer wake accept(), and its children would
+	 * copy them.
+	 */
+	if (sk_is_tcp(sk) && sk->sk_state == TCP_LISTEN)
+		goto no_progs;
+
 	stream_verdict = READ_ONCE(progs->stream_verdict);
 	if (stream_verdict) {
 		stream_verdict = bpf_prog_inc_not_zero(stream_verdict);
@@ -262,6 +270,7 @@ static int sock_map_link(struct bpf_map *map, struct sock *sk)
 		}
 	}
 
+no_progs:
 	psock = sock_map_psock_get_checked(sk);
 	if (IS_ERR(psock)) {
 		ret = PTR_ERR(psock);
diff --git a/net/core/stream.c b/net/core/stream.c
index cb255e2a3a5dd7..334124d683a7a0 100644
--- a/net/core/stream.c
+++ b/net/core/stream.c
@@ -150,7 +150,11 @@ int sk_stream_wait_memory(struct sock *sk, long *timeo_p)
 				    (sk_stream_memory_free(sk) && !vm_wait),
 				    &wait);
 		sk->sk_write_pending--;
-		if (ret < 0)
+		/*
+		 * A sender which sockmap has redirected to sk does not keep it
+		 * open. A dead socket has no sk_wq.
+		 */
+		if (ret < 0 || sock_flag(sk, SOCK_DEAD))
 			goto do_error;
 
 		if (vm_wait) {
diff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c
index 8cf63a9e7fed15..788ce08eef4d7a 100644
--- a/net/ipv4/tcp_bpf.c
+++ b/net/ipv4/tcp_bpf.c
@@ -88,6 +88,24 @@ static int bpf_tcp_ingress(struct sock *sk, struct sk_psock *psock,
 	return ret;
 }
 
+/*
+ * The target of a redirect belongs to somebody else. A child that nobody has
+ * accepted yet, and an orphan, have no struct socket and no wait queue.
+ * tcp_sendmsg_locked() needs both. See sendmsg_locked() in skbuff.c.
+ * The sender holds no reference to the file of the target, which can be closed
+ * whenever tcp_sendmsg_locked() sleeps. sk_stream_wait_connect() and
+ * inet_wait_for_connect() go back to the wait queue when they wake up, so the
+ * sender must not wait for a connect.
+ */
+static bool tcp_bpf_redir_allowed(const struct sock *sk)
+{
+	if (!sk->sk_socket)
+		return false;
+	if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV))
+		return tcp_passive_fastopen(sk);
+	return true;
+}
+
 static int tcp_bpf_push(struct sock *sk, struct sk_msg *msg, u32 apply_bytes,
 			int flags, bool uncharge)
 {
@@ -109,6 +127,10 @@ static int tcp_bpf_push(struct sock *sk, struct sk_msg *msg, u32 apply_bytes,
 		page = sg_page(sge);
 
 retry:
+		/* Only a redirect sends data which is not charged to sk */
+		if (!uncharge && unlikely(!tcp_bpf_redir_allowed(sk)))
+			return sock_flag(sk, SOCK_DEAD) ? -EPIPE : -EINVAL;
+
 		msghdr.msg_flags = flags | MSG_SPLICE_PAGES;
 		has_tx_ulp = tls_sw_has_ctx_tx(sk);
 		if (has_tx_ulp)
@@ -321,7 +343,8 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk,
 	if (!peek)
 		WRITE_ONCE(tcp->copied_seq, seq);
 	tcp_rcv_space_adjust(sk);
-	if (copied > 0)
+	/* Data on the psock outlives the connection that it came in on */
+	if (copied > 0 && tcp_can_send_ack(sk))
 		__tcp_cleanup_rbuf(sk, copied);
 
 unlock:
@@ -446,6 +469,11 @@ static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,
 				return -ENOMEM;
 			}
 		}
+		/*
+		 * A push or a redirect may have taken bytes off the front of
+		 * element sg.curr. The next sendmsg() appends to the cork.
+		 */
+		sk_msg_reset_curr(msg);
 		memcpy(psock->cork, msg, sizeof(*msg));
 		return 0;
 	}
@@ -457,8 +485,22 @@ static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,
 
 	switch (psock->eval) {
 	case __SK_PASS:
+		/* The push may sleep without the socket lock, and it may fail */
+		if (!psock->apply_bytes)
+			psock->eval = __SK_NONE;
+		origsize = msg->sg.size;
 		ret = tcp_bpf_push(sk, msg, tosend, flags, true);
 		if (unlikely(ret)) {
+			/* A part of it may have gone out */
+			sk_msg_apply_bytes(psock, origsize - msg->sg.size);
+			/* Another sender can use up the rest during the push */
+			if (!psock->apply_bytes) {
+				psock->eval = __SK_NONE;
+				if (psock->sk_redir) {
+					sock_put(psock->sk_redir);
+					psock->sk_redir = NULL;
+				}
+			}
 			*copied -= sk_msg_free(sk, msg);
 			break;
 		}
@@ -475,7 +517,11 @@ static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,
 			psock->eval = __SK_NONE;
 			psock->sk_redir = NULL;
 		}
-		if (psock->cork) {
+		/*
+		 * Another thread can make a cork while this one sends msg
+		 * without the socket lock. Leave that cork alone.
+		 */
+		if (psock->cork == msg) {
 			cork = true;
 			psock->cork = NULL;
 		}
@@ -498,6 +544,14 @@ static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,
 			if (!cork)
 				*copied -= free;
 		}
+		/*
+		 * The verdict was for tosend bytes. The rest goes back, unless
+		 * another thread made a new cork while the lock was released.
+		 */
+		if (cork && msg->sg.size && !psock->cork) {
+			psock->cork = msg;
+			cork = false;
+		}
 		if (cork) {
 			sk_msg_free(sk, msg);
 			kfree(msg);
@@ -507,9 +561,12 @@ static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,
 		break;
 	case __SK_DROP:
 	default:
-		sk_msg_free(sk, msg);
+		/* All of it is thrown away, not only tosend bytes */
+		*copied -= sk_msg_free(sk, msg) + delta;
 		sk_msg_apply_bytes(psock, tosend);
-		*copied -= (tosend + delta);
+		/* The verdict holds for apply_bytes, not for ever */
+		if (!psock->apply_bytes)
+			psock->eval = __SK_NONE;
 		return -EACCES;
 	}
 
@@ -538,7 +595,7 @@ static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 	int flags;
 
 	/* Don't let internal flags through */
-	flags = (msg->msg_flags & ~MSG_SENDPAGE_DECRYPTED);
+	flags = (msg->msg_flags & ~(MSG_SENDPAGE_DECRYPTED | MSG_FASTOPEN));
 	flags |= MSG_NO_SHARED_FRAGS;
 
 	psock = sk_psock_get(sk);
@@ -574,8 +631,15 @@ static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 			copy = msg_tx->sg.size - osize;
 		}
 
-		ret = sk_msg_memcopy_from_iter(sk, &msg->msg_iter, msg_tx,
-					       copy);
+		/*
+		 * copy is 0 if the ring of the cork was full already and its
+		 * last element could not be extended. Flush the cork below,
+		 * and let the next pass take the data.
+		 */
+		ret = 0;
+		if (copy)
+			ret = sk_msg_memcopy_from_iter(sk, &msg->msg_iter,
+						       msg_tx, copy);
 		if (ret < 0) {
 			sk_msg_trim(sk, msg_tx, osize);
 			err = ret;
@@ -768,7 +832,18 @@ void tcp_bpf_clone(const struct sock *sk, struct sock *newsk)
 {
 	struct proto *prot = newsk->sk_prot;
 
-	if (is_insidevar(prot, tcp_bpf_prots))
-		newsk->sk_prot = sk->sk_prot_creator;
+	if (!is_insidevar(prot, tcp_bpf_prots))
+		return;
+
+	newsk->sk_prot = sk->sk_prot_creator;
+	/*
+	 * sock_map_link() gives a listener no programs, but a socket which got
+	 * them in another state and listens now has the psock's sk_data_ready
+	 * and sk_write_space, and sock_copy() has copied them.
+	 * sk_psock_init() refuses a socket that has sk_user_data or a ULP, so
+	 * what the psock replaced were the defaults of a TCP socket.
+	 */
+	newsk->sk_data_ready = sock_def_readable;
+	newsk->sk_write_space = sk_stream_write_space;
 }
 #endif /* CONFIG_BPF_SYSCALL */
diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
index 7a22e9388e0e9a..d62cfbf2d490c2 100644
--- a/net/mptcp/protocol.c
+++ b/net/mptcp/protocol.c
@@ -619,12 +619,6 @@ static void mptcp_set_timeout(struct sock *sk)
 	__mptcp_set_timeout(sk, tout);
 }
 
-static inline bool tcp_can_send_ack(const struct sock *ssk)
-{
-	return !((1 << inet_sk_state_load(ssk)) &
-	       (TCPF_SYN_SENT | TCPF_SYN_RECV | TCPF_TIME_WAIT | TCPF_CLOSE | TCPF_LISTEN));
-}
-
 void __mptcp_subflow_send_ack(struct sock *ssk)
 {
 	if (tcp_can_send_ack(ssk))

61/3 2026/11/02 13:36 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 7485
							output: 36
							thoughts: 2744
						

Thoughts:
**KMSAN Assessment: Patch Series Analysis**

Alright, let's get down to brass tacks. I'm looking at this patch series and the primary question is: does this warrant a dedicated KMSAN fuzzing session, or is standard KASAN sufficient? We need to make a clear distinction. Standard KASAN is already enabled, catching common bugs like out-of-bounds access, use-after-free, and locking issues. KMSAN, however, is the big gun for uninitialized memory reads and kernel-to-user info leaks. If KASAN, LOCKDEP, or any other standard bug detector can flag an issue, KMSAN is overkill – it's an expensive tool to wield. The only time we invoke KMSAN is when there's a risk of *uninitialized* memory usage that *only* KMSAN would reveal. That means the patch has to introduce, or modify something which could cause a read of uninitialized memory.

The rules are clear: We look for changes that introduce or modify kernel structures sent to user space where fields might be uninitialized, conditional logic depending on potentially uninitialized variables, allocation of structures where uninitialized fields are read, or modifications to bounds checks that allow access to uninitialized bytes of existing buffers.

Conversely, if the changes primarily risk out-of-bounds access, overflows, null pointer dereferences, locking deadlocks, or use-after-free issues, KASAN is the tool. If all stack/heap structures touched are fully zeroed/initialized, or if the patch doesn't introduce any risk of uninitialized memory, we don't need KMSAN.

Let's dissect this patch series one change at a time. I'm focusing on the potential for uninitialized memory usage.

1.  `Documentation/bpf/map_sockmap.rst`: Documentation only, no KMSAN concerns.
2.  `include/linux/skmsg.h`: Moving `sk_msg_reset_curr` is just a relocation, no data manipulation or initialization is changed, no KMSAN concern.
3.  `include/net/tcp.h` and `net/mptcp/protocol.c`: Moving `tcp_can_send_ack` as a static inline helper. Doesn't introduce uninitialized memory issues.
4.  `net/core/filter.c`: Adding `sg_unmark_end` to clear the SG end bit on the scatterlist. This is a potential area to look at in detail. But is there a way that this can lead to an uninitialized memory read? It's clearing a bit, not initializing data. No KMSAN risk here.
5.  `net/core/skmsg.c`: Several changes here. In `__sk_msg_recvmsg`, there's a modification related to updating `msg_rx->sg.start` on failure to copy. This is related to error handling in a memory copy; it updates the start pointer and error handling. No KMSAN concern here.
    The changes in `sk_psock_skb_state`, `sk_psock_msg_verdict` and handling of `msg->sk_redir = NULL;` before running BPF program do not introduce new memory. No KMSAN concerns.
6.  `net/core/sock_map.c`: Adds a check for TCP LISTEN sockets. No uninitialized memory concerns.
7.  `net/core/stream.c`: Handles dead sockets in `sk_stream_wait_memory`. Doesn't introduce any new potential for uninitialized memory reads.
8.  `net/ipv4/tcp_bpf.c`: Added `tcp_bpf_redir_allowed` which looks safe. Many small modifications here like handling of flags etc. It deals with flags and states, but it doesn't appear to introduce a direct pathway for uninitialized memory to be read or copied to user space. It could be possible to miss a KMSAN check if something is initialized based on some flags which could be uninitialized.

The changes in `net/ipv4/tcp_bpf.c` looks to be a good place for a thorough review, but the patch does not appear to expose uninitialized memory, which could justify a NeedsKMSAN flag.

**Verdict:** Based on this initial review, I don't see anything that screams "KMSAN needed!" While the changes are substantial, they don't seem to directly introduce or expose uninitialized memory risks that would be caught *only* by KMSAN. I'd lean towards setting `NeedsKMSAN=false` for this patch series. However, a deeper inspection of the logic in `net/ipv4/tcp_bpf.c` and a fuzzing test using the normal fuzzing tools may be warranted.



62/3 2026/11/02 13:36 tool read-file
0m Args:
{
  "File": "net/core/skmsg.c",
  "FirstLine": 425,
  "LineCount": 60
}

Results:
{
  "Contents": " 425:\t\n 426:\t\twhile (copied != len) {\n 427:\t\t\tstruct scatterlist *sge;\n 428:\t\n 429:\t\t\tif (unlikely(!msg_rx))\n 430:\t\t\t\tbreak;\n 431:\t\n 432:\t\t\tfrom_self = msg_rx-\u003esk == sk;\n 433:\t\t\ti = msg_rx-\u003esg.start;\n 434:\t\t\tdo {\n 435:\t\t\t\tstruct page *page;\n 436:\t\t\t\tint copy;\n 437:\t\n 438:\t\t\t\tsge = sk_msg_elem(msg_rx, i);\n 439:\t\t\t\tcopy = sge-\u003elength;\n 440:\t\t\t\tpage = sg_page(sge);\n 441:\t\t\t\tif (copied + copy \u003e len)\n 442:\t\t\t\t\tcopy = len - copied;\n 443:\t\t\t\tif (copy)\n 444:\t\t\t\t\tcopy = copy_page_to_iter(page, sge-\u003eoffset, copy, iter);\n 445:\t\t\t\tif (!copy) {\n 446:\t\t\t\t\t/* The elements in front of i are used up */\n 447:\t\t\t\t\tif (!peek)\n 448:\t\t\t\t\t\tmsg_rx-\u003esg.start = i;\n 449:\t\t\t\t\tcopied = copied ? copied : -EFAULT;\n 450:\t\t\t\t\tgoto out;\n 451:\t\t\t\t}\n 452:\t\n 453:\t\t\t\tcopied += copy;\n 454:\t\t\t\tif (from_self \u0026\u0026 copied_from_self)\n 455:\t\t\t\t\t*copied_from_self += copy;\n 456:\t\n 457:\t\t\t\tif (likely(!peek)) {\n 458:\t\t\t\t\tsge-\u003eoffset += copy;\n 459:\t\t\t\t\tsge-\u003elength -= copy;\n 460:\t\t\t\t\tif (!msg_rx-\u003eskb) {\n 461:\t\t\t\t\t\tsk_mem_uncharge(sk, copy);\n 462:\t\t\t\t\t\tatomic_sub(copy, \u0026sk-\u003esk_rmem_alloc);\n 463:\t\t\t\t\t}\n 464:\t\t\t\t\tmsg_rx-\u003esg.size -= copy;\n 465:\t\t\t\t\tsk_psock_msg_len_add(psock, -copy);\n 466:\t\n 467:\t\t\t\t\tif (!sge-\u003elength) {\n 468:\t\t\t\t\t\tsk_msg_iter_var_next(i);\n 469:\t\t\t\t\t\tif (!msg_rx-\u003eskb)\n 470:\t\t\t\t\t\t\tput_page(page);\n 471:\t\t\t\t\t}\n 472:\t\t\t\t} else {\n 473:\t\t\t\t\t/* Lets not optimize peek case if copy_page_to_iter\n 474:\t\t\t\t\t * didn't copy the entire length lets just break.\n 475:\t\t\t\t\t */\n 476:\t\t\t\t\tif (copy != sge-\u003elength)\n 477:\t\t\t\t\t\tgoto out;\n 478:\t\t\t\t\tsk_msg_iter_var_next(i);\n 479:\t\t\t\t}\n 480:\t\n 481:\t\t\t\tif (copied == len)\n 482:\t\t\t\t\tbreak;\n 483:\t\t\t} while ((i != msg_rx-\u003esg.end) \u0026\u0026 !sg_is_last(sge));\n 484:\t\n"
}

63/3 2026/11/02 13:36 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 12123
							output: 36
							thoughts: 192
						

Thoughts:
**Analyzing the `__sk_msg_recvmsg` Function**

Okay, let's break down this `__sk_msg_recvmsg` function's logic, specifically concerning how it handles scatter-gather lists during receive operations. I'm focusing on how pages are copied to the `iter` and what happens when a fault (`!copy`) occurs.

The code seems to be tracking the start of the scatter-gather list using `msg_rx->sg.start`. The critical observation is that if a fault happens during a copy, it looks like this `msg_rx->sg.start` is updated to the current index `i`. This makes sense; if we failed to copy *from* the beginning of an element, then the new start should reflect that point. The elements *before* `i` have, presumably, already been consumed successfully.

However, I'm getting a little tripped up on the previous logic, the loop structure, and whether `msg_rx->sg.start` was updated *correctly* in previous versions of this code. My concern is that there's a loop (`do { ... } while`) which might advance the index `i` through the scatter-gather elements. Within the loop, the code modifies the scatter-gather element (`sge->offset += copy; sge->length -= copy;`). Critically, if an element had zero `length` after consuming data, the `sk_msg_iter_var_next(i)` would advance `i`. The key question is this: Was `msg_rx->sg.start` always updated *within* the loop, or did it only happen *after* the loop completed?

If `msg_rx->sg.start` was *only* updated at the end of the loop, this could lead to issues where we advanced `i` through the list, consuming some elements but not updating the start pointer. A zero-length segment, which would *advance* `i` without updating `msg_rx->sg.start`, is where I see the potential for errors.

I'm now going to go back and investigate precisely where `msg_rx->sg.start` was being updated previously. I need to understand this update logic to confirm the fix addresses the subtle potential bug I'm seeing.



64/3 2026/11/02 13:36 tool read-file
0m Args:
{
  "File": "net/core/skmsg.c",
  "FirstLine": 485,
  "LineCount": 40
}

Results:
{
  "Contents": " 485:\t\t\tif (unlikely(peek)) {\n 486:\t\t\t\tmsg_rx = sk_psock_next_msg(psock, msg_rx);\n 487:\t\t\t\tif (!msg_rx)\n 488:\t\t\t\t\tbreak;\n 489:\t\t\t\tcontinue;\n 490:\t\t\t}\n 491:\t\n 492:\t\t\tmsg_rx-\u003esg.start = i;\n 493:\t\t\tif (!sge-\u003elength \u0026\u0026 (i == msg_rx-\u003esg.end || sg_is_last(sge))) {\n 494:\t\t\t\tmsg_rx = sk_psock_dequeue_msg(psock);\n 495:\t\t\t\tkfree_sk_msg(msg_rx);\n 496:\t\t\t}\n 497:\t\t\tmsg_rx = sk_psock_peek_msg(psock);\n 498:\t\t}\n 499:\tout:\n 500:\t\treturn copied;\n 501:\t}\n 502:\t\n 503:\t/* Receive sk_msg from psock-\u003eingress_msg to @msg. */\n 504:\tint sk_msg_recvmsg(struct sock *sk, struct sk_psock *psock, struct msghdr *msg,\n 505:\t\t\t   int len, int flags)\n 506:\t{\n 507:\t\treturn __sk_msg_recvmsg(sk, psock, msg, len, flags, NULL);\n 508:\t}\n 509:\tEXPORT_SYMBOL_GPL(sk_msg_recvmsg);\n 510:\t\n 511:\tbool sk_msg_is_readable(struct sock *sk)\n 512:\t{\n 513:\t\tstruct sk_psock *psock;\n 514:\t\tbool empty = true;\n 515:\t\n 516:\t\trcu_read_lock();\n 517:\t\tpsock = sk_psock(sk);\n 518:\t\tif (likely(psock))\n 519:\t\t\tempty = list_empty(\u0026psock-\u003eingress_msg);\n 520:\t\trcu_read_unlock();\n 521:\t\treturn !empty;\n 522:\t}\n 523:\tEXPORT_SYMBOL_GPL(sk_msg_is_readable);\n 524:\t\n"
}

65/3 2026/11/02 13:36 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 13440
							output: 36
							thoughts: 203
						

Thoughts:
**Analyzing a Critical Bug Fix**

Okay, so I'm looking at this patch and immediately, line 492 jumps out at me: `msg_rx->sg.start = i;` I see that if `!copy` happened, the code would `goto out;` and that line would be bypassed.  That's a glaring issue.  The previous implementation, if elements were freed or consumed due to a fault, the `start` index wasn't updated.  This, as the code comment describes, creates the very real possibility of a double-read, using freed memory, or miscalculating the offset in a subsequent read.  This fix, by ensuring `msg_rx->sg.start = i;` is always updated even on a fault exit, prevents re-reading consumed elements – a clear bug fix. It's good to see this addressed.

Now, let's move on. I see further changes in `net/core/filter.c` specifically concerning `sg_unmark_end(&msg->sg.data[new]);`  within `bpf_msg_push_data` and `bpf_msg_pop_data`. The inclusion of `sg_unmark_end` is interesting. I need to understand why it was added, so my next step is to examine what that function actually *does* and, importantly, what problem it's solving.
I need to examine its purpose within the context of pushing and popping data within the BPF message structure.



66/3 2026/11/02 13:36 tool read-file
0m Args:
{
  "File": "net/core/filter.c",
  "FirstLine": 2990,
  "LineCount": 40
}

Results:
{
  "Contents": "2990:\t\t}\n2991:\t\n2992:\tplace_new:\n2993:\t\t/* Place newly allocated data buffer */\n2994:\t\tsk_mem_charge(msg-\u003esk, len);\n2995:\t\tmsg-\u003esg.size += len;\n2996:\t\tsk_msg_clear_elem_copy(msg, new);\n2997:\t\tsg_set_page(\u0026msg-\u003esg.data[new], page, len + copy, 0);\n2998:\t\t/* A free slot can have the end mark */\n2999:\t\tsg_unmark_end(\u0026msg-\u003esg.data[new]);\n3000:\t\tif (rsge.length) {\n3001:\t\t\tget_page(sg_page(\u0026rsge));\n3002:\t\t\tsk_msg_iter_var_next(new);\n3003:\t\t\tmsg-\u003esg.data[new] = rsge;\n3004:\t\t\tsk_msg_set_elem_copy(msg, new, rsge_copy);\n3005:\t\t}\n3006:\t\tsk_msg_clear_elem_copy(msg, msg-\u003esg.end);\n3007:\t\n3008:\t\tsk_msg_reset_curr(msg);\n3009:\t\tsk_msg_compute_data_pointers(msg);\n3010:\t\treturn 0;\n3011:\t}\n3012:\t\n3013:\tstatic const struct bpf_func_proto bpf_msg_push_data_proto = {\n3014:\t\t.func\t\t= bpf_msg_push_data,\n3015:\t\t.gpl_only\t= false,\n3016:\t\t.ret_type\t= RET_INTEGER,\n3017:\t\t.arg1_type\t= ARG_PTR_TO_CTX,\n3018:\t\t.arg2_type\t= ARG_ANYTHING,\n3019:\t\t.arg3_type\t= ARG_ANYTHING,\n3020:\t\t.arg4_type\t= ARG_ANYTHING,\n3021:\t};\n3022:\t\n3023:\tstatic void sk_msg_shift_left(struct sk_msg *msg, int i)\n3024:\t{\n3025:\t\tstruct scatterlist *sge = sk_msg_elem(msg, i);\n3026:\t\tint prev;\n3027:\t\n3028:\t\tput_page(sg_page(sge));\n3029:\t\tdo {\n"
}

67/3 2026/11/02 13:36 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 14654
							output: 36
							thoughts: 264
						

Thoughts:
**Debugging a Scatterlist Bug**

Okay, hold on a second! What exactly was the purpose of `sg_unmark_end`? It's nagging at me. Let me retrace my steps. Right, in the scatterlist structure, `SG_END` is represented by a bit, specifically bit 1 in `page_link`. And now I remember: `sg_set_page` is the function that sets the page, and the way it works is by setting `page_link` to the page's address, and then it *preserves* the lower two bits of `page_link`. Brilliant! That means if a previous operation, like `sg_mark_end`, set the end bit in `msg->sg.data[new]`'s `page_link`, `sg_set_page` would *keep* that bit set!

So, the iterator on the scatterlist would erroneously stop prematurely, thinking it's reached the end of the data chain when it hasn't. That makes perfect sense. `sg_unmark_end` is there to clear that erroneous end marker. It removes the bit. Okay, so that fixes the issue. But wait... does it introduce a *different* issue? Does it expose uninitialized memory? The thought is making me uneasy. Let's see... no, the `page` itself *should* be allocated and populated with data, or it's recently been initialized, right? But... let me double-check.

Okay, let's zoom in on `bpf_msg_push_data`. I have to be sure it's allocating the page properly. Is it potentially re-using an uninitialized data buffer? I need to check the code. I'm going to look at lines 2900 to 2995 in `net/core/filter.c` right now, just to be absolutely certain.



68/3 2026/11/02 13:37 tool read-file
0m Args:
{
  "File": "net/core/filter.c",
  "FirstLine": 2940,
  "LineCount": 55
}

Results:
{
  "Contents": "2940:\t\t\tpsge-\u003elength = start - offset;\n2941:\t\t\trsge.length -= psge-\u003elength;\n2942:\t\t\trsge.offset += start - offset;\n2943:\t\n2944:\t\t\tsk_msg_iter_var_next(i);\n2945:\t\t\tsg_unmark_end(psge);\n2946:\t\t\tsg_unmark_end(\u0026rsge);\n2947:\t\t}\n2948:\t\n2949:\t\t/* Slot(s) to place newly allocated data */\n2950:\t\tsk_msg_iter_next(msg, end);\n2951:\t\tnew = i;\n2952:\t\tsk_msg_iter_var_next(i);\n2953:\t\n2954:\t\tif (i == msg-\u003esg.end) {\n2955:\t\t\tif (!rsge.length)\n2956:\t\t\t\tgoto place_new;\n2957:\t\t\tsk_msg_iter_next(msg, end);\n2958:\t\t\tgoto place_new;\n2959:\t\t}\n2960:\t\n2961:\t\t/* Shift one or two slots as needed */\n2962:\t\tsge = sk_msg_elem_cpy(msg, new);\n2963:\t\tsg_unmark_end(\u0026sge);\n2964:\t\tsge_copy = sk_msg_elem_is_copy(msg, new);\n2965:\t\n2966:\t\tnsge = sk_msg_elem_cpy(msg, i);\n2967:\t\tnsge_copy = sk_msg_elem_is_copy(msg, i);\n2968:\t\tif (rsge.length) {\n2969:\t\t\tsk_msg_iter_var_next(i);\n2970:\t\t\tnnsge = sk_msg_elem_cpy(msg, i);\n2971:\t\t\tnnsge_copy = sk_msg_elem_is_copy(msg, i);\n2972:\t\t\tsk_msg_iter_next(msg, end);\n2973:\t\t}\n2974:\t\n2975:\t\twhile (i != msg-\u003esg.end) {\n2976:\t\t\tmsg-\u003esg.data[i] = sge;\n2977:\t\t\tsk_msg_set_elem_copy(msg, i, sge_copy);\n2978:\t\t\tsge = nsge;\n2979:\t\t\tsge_copy = nsge_copy;\n2980:\t\t\tsk_msg_iter_var_next(i);\n2981:\t\t\tif (rsge.length) {\n2982:\t\t\t\tnsge = nnsge;\n2983:\t\t\t\tnsge_copy = nnsge_copy;\n2984:\t\t\t\tnnsge = sk_msg_elem_cpy(msg, i);\n2985:\t\t\t\tnnsge_copy = sk_msg_elem_is_copy(msg, i);\n2986:\t\t\t} else {\n2987:\t\t\t\tnsge = sk_msg_elem_cpy(msg, i);\n2988:\t\t\t\tnsge_copy = sk_msg_elem_is_copy(msg, i);\n2989:\t\t\t}\n2990:\t\t}\n2991:\t\n2992:\tplace_new:\n2993:\t\t/* Place newly allocated data buffer */\n2994:\t\tsk_mem_charge(msg-\u003esk, len);\n"
}

69/3 2026/11/02 13:37 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 16248
							output: 36
							thoughts: 24
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Diving into `bpf_msg_pop_data` at 3130**

Alright, given what we're looking at, I think we should specifically pinpoint our attention on the `bpf_msg_pop_data` function around line 3130. Based on previous analysis of the codebase, that area's highly relevant to the problem we are investigating. My suspicion is that some interaction with the data being 'popped' from the message queue is causing this issue, I need to isolate the area to determine if there is an unintended impact on shared resources. It is likely either an incorrect offset, or a read/write operation from the wrong address, but I need to dive into the code and see for myself.



70/3 2026/11/02 13:37 tool read-file
0m Args:
{
  "File": "net/core/filter.c",
  "FirstLine": 3110,
  "LineCount": 40
}

Results:
{
  "Contents": "3110:\t\t * Then if B is non-zero AND there is no space allocate space and\n3111:\t\t * compact A, B regions into page. If there is space shift ring to\n3112:\t\t * the right free'ing the next element in ring to place B, leaving\n3113:\t\t * A untouched except to reduce length.\n3114:\t\t */\n3115:\t\tif (start != offset) {\n3116:\t\t\tstruct scatterlist *nsge, *sge = sk_msg_elem(msg, i);\n3117:\t\t\tbool sge_copy = sk_msg_elem_is_copy(msg, i);\n3118:\t\t\tint a = start - offset;\n3119:\t\t\tint b = sge-\u003elength - pop - a;\n3120:\t\t\tu32 sge_idx = i;\n3121:\t\n3122:\t\t\tsk_msg_iter_var_next(i);\n3123:\t\n3124:\t\t\tif (b \u003e 0) {\n3125:\t\t\t\tif (space) {\n3126:\t\t\t\t\tsge-\u003elength = a;\n3127:\t\t\t\t\tsk_msg_shift_right(msg, i);\n3128:\t\t\t\t\tnsge = sk_msg_elem(msg, i);\n3129:\t\t\t\t\tget_page(sg_page(sge));\n3130:\t\t\t\t\tsg_set_page(nsge,\n3131:\t\t\t\t\t\t    sg_page(sge),\n3132:\t\t\t\t\t\t    b, sge-\u003eoffset + pop + a);\n3133:\t\t\t\t\tsg_unmark_end(nsge);\n3134:\t\t\t\t\tsk_msg_set_elem_copy(msg, i, sge_copy);\n3135:\t\t\t\t} else {\n3136:\t\t\t\t\tstruct page *page, *orig;\n3137:\t\t\t\t\tu8 *to, *from;\n3138:\t\n3139:\t\t\t\t\tpage = alloc_pages(__GFP_NOWARN |\n3140:\t\t\t\t\t\t\t   __GFP_COMP   | GFP_ATOMIC,\n3141:\t\t\t\t\t\t\t   get_order(a + b));\n3142:\t\t\t\t\tif (unlikely(!page))\n3143:\t\t\t\t\t\treturn -ENOMEM;\n3144:\t\n3145:\t\t\t\t\torig = sg_page(sge);\n3146:\t\t\t\t\tfrom = sg_virt(sge);\n3147:\t\t\t\t\tto = page_address(page);\n3148:\t\t\t\t\tmemcpy(to, from, a);\n3149:\t\t\t\t\tmemcpy(to + a, from + a + pop, b);\n"
}

71/3 2026/11/02 13:37 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 17147
							output: 36
							thoughts: 46
						

Thoughts:
Here's my analysis of the provided snippet, tailored for an expert like yourself:

**Diving into `bpf_msg_push_data` Memory Allocation**

Okay, so we're looking at the allocation of that `page` within the `bpf_msg_push_data` function. That's a critical point for understanding potential performance bottlenecks, memory leaks, or even security vulnerabilities. My immediate instinct is to pinpoint *precisely* where this `page` is being allocated and how it's being handled afterward. Seems like we need to examine that portion of `net/core/filter.c`, particularly lines 2850 through 2940. Let's start there. I'm going to want to understand the alloc flags, any potential error handling related to that allocation, and deallocation logic later on. Any chance it gets mapped and how? From there, let's trace its lifespan...



72/3 2026/11/02 13:37 tool read-file
0m Args:
{
  "File": "net/core/filter.c",
  "FirstLine": 2870,
  "LineCount": 50
}

Results:
{
  "Contents": "2870:\t\t\treturn 0;\n2871:\t\n2872:\t\t/* First find the starting scatterlist element */\n2873:\t\ti = msg-\u003esg.start;\n2874:\t\tdo {\n2875:\t\t\toffset += l;\n2876:\t\t\tl = sk_msg_elem(msg, i)-\u003elength;\n2877:\t\n2878:\t\t\tif (start \u003c offset + l)\n2879:\t\t\t\tbreak;\n2880:\t\t\tsk_msg_iter_var_next(i);\n2881:\t\t} while (i != msg-\u003esg.end);\n2882:\t\n2883:\t\tif (start \u003e offset + l)\n2884:\t\t\treturn -EINVAL;\n2885:\t\n2886:\t\tspace = MAX_MSG_FRAGS - sk_msg_elem_used(msg);\n2887:\t\n2888:\t\t/* If no space available will fallback to copy, we need at\n2889:\t\t * least one scatterlist elem available to push data into\n2890:\t\t * when start aligns to the beginning of an element or two\n2891:\t\t * when it falls inside an element. We handle the start equals\n2892:\t\t * offset case because its the common case for inserting a\n2893:\t\t * header.\n2894:\t\t */\n2895:\t\tif (!space || (space == 1 \u0026\u0026 start != offset))\n2896:\t\t\tcopy = msg-\u003esg.data[i].length;\n2897:\t\n2898:\t\tif (unlikely(copy + len \u003c copy))\n2899:\t\t\treturn -EINVAL;\n2900:\t\n2901:\t\tpage = alloc_pages(__GFP_NOWARN | GFP_ATOMIC | __GFP_COMP,\n2902:\t\t\t\t   get_order(copy + len));\n2903:\t\tif (unlikely(!page))\n2904:\t\t\treturn -ENOMEM;\n2905:\t\n2906:\t\tif (copy) {\n2907:\t\t\tint front, back;\n2908:\t\n2909:\t\t\traw = page_address(page);\n2910:\t\n2911:\t\t\tif (i == msg-\u003esg.end)\n2912:\t\t\t\tsk_msg_iter_var_prev(i);\n2913:\t\t\tpsge = sk_msg_elem(msg, i);\n2914:\t\t\tfront = start - offset;\n2915:\t\t\tback = psge-\u003elength - front;\n2916:\t\t\tfrom = sg_virt(psge);\n2917:\t\n2918:\t\t\tif (front)\n2919:\t\t\t\tmemcpy(raw, from, front);\n"
}

73/3 2026/11/02 13:37 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 18134
							output: 36
							thoughts: 261
						

Thoughts:
**Investigating Uninitialized Memory in BPF Message Handling**

Okay, hold on a second! I'm looking at `bpf_msg_push_data` and something's bugging me. Specifically, it's that `alloc_pages(...)` call. I noticed it *doesn't* use `__GFP_ZERO`! That's a red flag. Now, when `bpf_msg_push_data` pushes `len` bytes into this newly allocated `page`, the question is: are those bytes being zeroed out *by* `bpf_msg_push_data` itself, or is the BPF program responsible for initializing them?

Let me see... right, `bpf_msg_push_data` extends the message by `len` bytes, and then the BPF program writes data into it using `msg->data` and `msg->data_end`. Okay, that suggests the BPF program *should* be initializing the data. But, I need to be sure.

Wait a moment, was `alloc_pages` modified as part of this patch I'm reviewing? No, it's been there for a while. So what *did* change? Ah, right, that `sg_unmark_end(&msg->sg.data[new]);` call. I see that was added. Why? Why would we need to `sg_unmark_end`? Does a free slot have the end mark?

Okay, time to dig deeper. Let's check the commit message or the history surrounding this change to understand the context. I'm going to search the Git history, maybe look for similar commits in `net/core/filter.c`, or maybe do a `grep` through the commits to try and figure out what's going on. I need to understand why this unmarking is necessary.



74/3 2026/11/02 13:37 tool grepper
0m Args:
{
  "Expression": "A free slot can have the end mark",
  "PathPrefixes": [
    "net/core/filter.c"
  ]
}

Results:
{
  "Output": "net/core/filter.c=2856=BPF_CALL_4(bpf_msg_push_data, struct sk_msg *, msg, u32, start,\n--\nnet/core/filter.c-2997-\tsg_set_page(\u0026msg-\u003esg.data[new], page, len + copy, 0);\nnet/core/filter.c:2998:\t/* A free slot can have the end mark */\nnet/core/filter.c-2999-\tsg_unmark_end(\u0026msg-\u003esg.data[new]);\n"
}

75/3 2026/11/02 13:37 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 18976
							output: 23
							thoughts: 48
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Investigating Recent Network Code Changes**

Right, so I need to quickly get a handle on the recent modifications to this network stack component. My initial instinct is to jump straight into the `git log` to get a chronological view. I need to understand the 'why' behind these changes. Context is key, and the log is the fastest way to get it, especially if the commit messages are well-written (and if not, well, I’ll have some questions!). I'm particularly interested in anything touching `tcp_bpf_redir_allowed` or `sk_msg_reset_curr`. Those seem like likely indicators of the area where the problem lies. I'll probably run a `grep` through the log, to see what those keywords bring up. Let’s get that history and see what’s been happening around those functions. I want to see which commits touch these functions, to provide context.



76/3 2026/11/02 13:37 tool grepper
0m Args:
{
  "Expression": "The target of a redirect belongs to somebody else"
}

Results:
{
  "Output": "net/ipv4/tcp_bpf.c=33=static int bpf_tcp_ingress(struct sock *sk, struct sk_psock *psock,\n--\nnet/ipv4/tcp_bpf.c-91-/*\nnet/ipv4/tcp_bpf.c:92: * The target of a redirect belongs to somebody else. A child that nobody has\nnet/ipv4/tcp_bpf.c-93- * accepted yet, and an orphan, have no struct socket and no wait queue.\n"
}

77/3 2026/11/02 13:37 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 19392
							output: 39
							thoughts: 429
						

Thoughts:
**Analyzing this Commit: A Detailed Breakdown**

Okay, first things first, I need to figure out the exact commit this originates from. Let's see if I can leverage `git log` with a `grep` to pinpoint it. Ah, but the `grepper` tool seems limited to kernel sources only. Hmm, can I search commit logs somehow? I should note that grepper is running `git grep` on the kernel sources.

Now, let's dive into the patch itself. I want a granular understanding of each change, piece by piece.

1.  **`Documentation/bpf/map_sockmap.rst`**: This documents that the TCP listener is a special case in sockmap: it gets a `struct sk_psock`, preserves its callbacks, and doesn't inherit any programs. Makes sense.

2.  **`sk_msg_reset_curr`**: This is interesting. This function calculates `curr` and `copybreak` within the `sk_msg` structure. Originally, it was a static inline function within `net/core/filter.c`, but it has been moved to `include/linux/skmsg.h`. The code itself is fairly straightforward:

    static inline void sk_msg_reset_curr(struct sk_msg *msg)
    {
    	if (!msg->sg.size) {
    		msg->sg.curr = msg->sg.start;
    		msg->sg.copybreak = 0;
    	} else {
    		u32 i = msg->sg.end;

    		sk_msg_iter_var_prev(i);
    		msg->sg.curr = i;
    		msg->sg.copybreak = msg->sg.data[i].length;
    	}

    It appears this is crucial for managing the scatter-gather list within the `sk_msg`. Also, the function is used in `tcp_bpf_send_verdict()`:

    	/*
    	 * A push or a redirect may have taken bytes off the front of
    	 * element sg.curr. The next sendmsg() appends to the cork.
    	 */
    	sk_msg_reset_curr(msg);
    	memcpy(psock->cork, msg, sizeof(*msg));
    	return 0;

    I should investigate `tcp_bpf_send_verdict` further, specifically around line 446 in `net/ipv4/tcp_bpf.c`. I'm seeing that, after a push or redirect, `sk_msg_reset_curr` is called and a copy of the `sk_msg` is placed into the `psock->cork`.


78/3 2026/11/02 13:37 tool read-file
0m Args:
{
  "File": "net/ipv4/tcp_bpf.c",
  "FirstLine": 430,
  "LineCount": 90
}

Results:
{
  "Contents": " 430:\t\t}\n 431:\t\tret = copied;\n 432:\t\n 433:\tunlock:\n 434:\t\trelease_sock(sk);\n 435:\t\tsk_psock_put(sk, psock);\n 436:\t\treturn ret;\n 437:\t}\n 438:\t\n 439:\tstatic int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,\n 440:\t\t\t\t\tstruct sk_msg *msg, int *copied, int flags)\n 441:\t{\n 442:\t\tbool cork = false, enospc = sk_msg_full(msg), redir_ingress;\n 443:\t\tstruct sock *sk_redir;\n 444:\t\tu32 tosend, origsize, sent, delta = 0;\n 445:\t\tu32 eval;\n 446:\t\tint ret;\n 447:\t\n 448:\tmore_data:\n 449:\t\tif (psock-\u003eeval == __SK_NONE) {\n 450:\t\t\t/* Track delta in msg size to add/subtract it on SK_DROP from\n 451:\t\t\t * returned to user copied size. This ensures user doesn't\n 452:\t\t\t * get a positive return code with msg_cut_data and SK_DROP\n 453:\t\t\t * verdict.\n 454:\t\t\t */\n 455:\t\t\tdelta = msg-\u003esg.size;\n 456:\t\t\tpsock-\u003eeval = sk_psock_msg_verdict(sk, psock, msg);\n 457:\t\t\tdelta -= msg-\u003esg.size;\n 458:\t\t}\n 459:\t\n 460:\t\tif (msg-\u003ecork_bytes \u0026\u0026\n 461:\t\t    msg-\u003ecork_bytes \u003e msg-\u003esg.size \u0026\u0026 !enospc) {\n 462:\t\t\tpsock-\u003ecork_bytes = msg-\u003ecork_bytes - msg-\u003esg.size;\n 463:\t\t\tif (!psock-\u003ecork) {\n 464:\t\t\t\tpsock-\u003ecork = kzalloc_obj(*psock-\u003ecork,\n 465:\t\t\t\t\t\t\t  GFP_ATOMIC | __GFP_NOWARN);\n 466:\t\t\t\tif (!psock-\u003ecork) {\n 467:\t\t\t\t\tsk_msg_free(sk, msg);\n 468:\t\t\t\t\t*copied = 0;\n 469:\t\t\t\t\treturn -ENOMEM;\n 470:\t\t\t\t}\n 471:\t\t\t}\n 472:\t\t\t/*\n 473:\t\t\t * A push or a redirect may have taken bytes off the front of\n 474:\t\t\t * element sg.curr. The next sendmsg() appends to the cork.\n 475:\t\t\t */\n 476:\t\t\tsk_msg_reset_curr(msg);\n 477:\t\t\tmemcpy(psock-\u003ecork, msg, sizeof(*msg));\n 478:\t\t\treturn 0;\n 479:\t\t}\n 480:\t\n 481:\t\ttosend = msg-\u003esg.size;\n 482:\t\tif (psock-\u003eapply_bytes \u0026\u0026 psock-\u003eapply_bytes \u003c tosend)\n 483:\t\t\ttosend = psock-\u003eapply_bytes;\n 484:\t\teval = __SK_NONE;\n 485:\t\n 486:\t\tswitch (psock-\u003eeval) {\n 487:\t\tcase __SK_PASS:\n 488:\t\t\t/* The push may sleep without the socket lock, and it may fail */\n 489:\t\t\tif (!psock-\u003eapply_bytes)\n 490:\t\t\t\tpsock-\u003eeval = __SK_NONE;\n 491:\t\t\torigsize = msg-\u003esg.size;\n 492:\t\t\tret = tcp_bpf_push(sk, msg, tosend, flags, true);\n 493:\t\t\tif (unlikely(ret)) {\n 494:\t\t\t\t/* A part of it may have gone out */\n 495:\t\t\t\tsk_msg_apply_bytes(psock, origsize - msg-\u003esg.size);\n 496:\t\t\t\t/* Another sender can use up the rest during the push */\n 497:\t\t\t\tif (!psock-\u003eapply_bytes) {\n 498:\t\t\t\t\tpsock-\u003eeval = __SK_NONE;\n 499:\t\t\t\t\tif (psock-\u003esk_redir) {\n 500:\t\t\t\t\t\tsock_put(psock-\u003esk_redir);\n 501:\t\t\t\t\t\tpsock-\u003esk_redir = NULL;\n 502:\t\t\t\t\t}\n 503:\t\t\t\t}\n 504:\t\t\t\t*copied -= sk_msg_free(sk, msg);\n 505:\t\t\t\tbreak;\n 506:\t\t\t}\n 507:\t\t\tsk_msg_apply_bytes(psock, tosend);\n 508:\t\t\tbreak;\n 509:\t\tcase __SK_REDIRECT:\n 510:\t\t\tredir_ingress = psock-\u003eredir_ingress;\n 511:\t\t\tsk_redir = psock-\u003esk_redir;\n 512:\t\t\tsock_hold(sk_redir);\n 513:\t\t\tsk_msg_apply_bytes(psock, tosend);\n 514:\t\t\tif (!psock-\u003eapply_bytes) {\n 515:\t\t\t\t/* Clean up before releasing the sock lock. */\n 516:\t\t\t\teval = psock-\u003eeval;\n 517:\t\t\t\tpsock-\u003eeval = __SK_NONE;\n 518:\t\t\t\tpsock-\u003esk_redir = NULL;\n 519:\t\t\t}\n"
}

79/3 2026/11/02 13:37 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 21859
							output: 39
							thoughts: 29
						

Thoughts:
Okay, let's dive into this `tcp_bpf_send_verdict` function, specifically lines 520 to 570. I'm going to parse through this code block and articulate its behavior for me, as if I'm the one who wrote it, with the experience to easily grasp this.



80/3 2026/11/02 13:37 tool read-file
0m Args:
{
  "File": "net/ipv4/tcp_bpf.c",
  "FirstLine": 520,
  "LineCount": 55
}

Results:
{
  "Contents": " 520:\t\t\t/*\n 521:\t\t\t * Another thread can make a cork while this one sends msg\n 522:\t\t\t * without the socket lock. Leave that cork alone.\n 523:\t\t\t */\n 524:\t\t\tif (psock-\u003ecork == msg) {\n 525:\t\t\t\tcork = true;\n 526:\t\t\t\tpsock-\u003ecork = NULL;\n 527:\t\t\t}\n 528:\t\t\trelease_sock(sk);\n 529:\t\n 530:\t\t\torigsize = msg-\u003esg.size;\n 531:\t\t\tret = tcp_bpf_sendmsg_redir(sk_redir, redir_ingress,\n 532:\t\t\t\t\t\t    msg, tosend, flags);\n 533:\t\t\tsent = origsize - msg-\u003esg.size;\n 534:\t\n 535:\t\t\tif (eval == __SK_REDIRECT)\n 536:\t\t\t\tsock_put(sk_redir);\n 537:\t\t\tsock_put(sk_redir);\n 538:\t\n 539:\t\t\tlock_sock(sk);\n 540:\t\t\tsk_mem_uncharge(sk, sent);\n 541:\t\t\tif (unlikely(ret \u003c 0)) {\n 542:\t\t\t\tint free = sk_msg_free(sk, msg);\n 543:\t\n 544:\t\t\t\tif (!cork)\n 545:\t\t\t\t\t*copied -= free;\n 546:\t\t\t}\n 547:\t\t\t/*\n 548:\t\t\t * The verdict was for tosend bytes. The rest goes back, unless\n 549:\t\t\t * another thread made a new cork while the lock was released.\n 550:\t\t\t */\n 551:\t\t\tif (cork \u0026\u0026 msg-\u003esg.size \u0026\u0026 !psock-\u003ecork) {\n 552:\t\t\t\tpsock-\u003ecork = msg;\n 553:\t\t\t\tcork = false;\n 554:\t\t\t}\n 555:\t\t\tif (cork) {\n 556:\t\t\t\tsk_msg_free(sk, msg);\n 557:\t\t\t\tkfree(msg);\n 558:\t\t\t\tmsg = NULL;\n 559:\t\t\t\tret = 0;\n 560:\t\t\t}\n 561:\t\t\tbreak;\n 562:\t\tcase __SK_DROP:\n 563:\t\tdefault:\n 564:\t\t\t/* All of it is thrown away, not only tosend bytes */\n 565:\t\t\t*copied -= sk_msg_free(sk, msg) + delta;\n 566:\t\t\tsk_msg_apply_bytes(psock, tosend);\n 567:\t\t\t/* The verdict holds for apply_bytes, not for ever */\n 568:\t\t\tif (!psock-\u003eapply_bytes)\n 569:\t\t\t\tpsock-\u003eeval = __SK_NONE;\n 570:\t\t\treturn -EACCES;\n 571:\t\t}\n 572:\t\n 573:\t\tif (likely(!ret)) {\n 574:\t\t\tif (!psock-\u003eapply_bytes) {\n"
}

81/3 2026/11/02 13:37 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 22780
							output: 39
							thoughts: 27
						

Thoughts:
Okay, here's my interpretation of what's going on, thinking through those lines in `net/ipv4/tcp_bpf.c`:

**Deep Dive into TCP BPF Filtering**

Alright, let's get into lines 570-620 of `net/ipv4/tcp_bpf.c`. Time to brush up on this BPF implementation, specifically focusing on how it interfaces with the TCP layer. This section likely handles a crucial part of the kernel's ability to filter TCP packets based on Berkeley Packet Filter (BPF) rules. I suspect we're looking at the core logic for either: a) the application of BPF programs to incoming or outgoing TCP packets, or b) the mechanisms for attaching and detaching those programs.

Given the range, it's probably *not* the very high-level BPF program loading, but rather the internal glue. I'd wager we're dissecting either the part where the kernel *actually* executes the BPF instructions on a packet's data, checking if it matches the defined filter and taking action accordingly (e.g., dropping, accepting, or redirecting), or, it involves details of how the TCP stack passes the data to the BPF engine. The interactions between these two are often the bottleneck here. 

We might be examining the structures involved with the `skb` (socket buffer) or `sock` structures. Is there any use of helper functions from a BPF context? Are there hooks, perhaps at the `tcp_rcv_established` or `tcp_sendmsg` levels? It's even more crucial to understand how TCP flags, like SYN, ACK, FIN, and RST, are interpreted within the BPF context. How does the BPF program get access to these TCP header fields? Does the code handle partial packet processing, or does it require the full TCP header to make a decision? The devil is in the details, so I expect a lot of pointer arithmetic and bitwise operations to extract the necessary information for the BPF program.

I'm also anticipating that error handling will be present. What happens if the BPF program causes a segmentation fault? Does the kernel handle this gracefully? Are there mechanisms to prevent malicious BPF programs from crashing the system? We need to look at potential race conditions here too. Since it's within the TCP stack, concurrency and locking are a factor. What locks are in place to ensure that BPF programs are executed safely, particularly when multiple processes are communicating on the same sockets?

So, this area will be very important for performance and security. We are right in the thick of a high-performance, real-time context.



82/3 2026/11/02 13:38 tool read-file
0m Args:
{
  "File": "net/ipv4/tcp_bpf.c",
  "FirstLine": 570,
  "LineCount": 55
}

Results:
{
  "Contents": " 570:\t\t\treturn -EACCES;\n 571:\t\t}\n 572:\t\n 573:\t\tif (likely(!ret)) {\n 574:\t\t\tif (!psock-\u003eapply_bytes) {\n 575:\t\t\t\tpsock-\u003eeval =  __SK_NONE;\n 576:\t\t\t\tif (psock-\u003esk_redir) {\n 577:\t\t\t\t\tsock_put(psock-\u003esk_redir);\n 578:\t\t\t\t\tpsock-\u003esk_redir = NULL;\n 579:\t\t\t\t}\n 580:\t\t\t}\n 581:\t\t\tif (msg \u0026\u0026\n 582:\t\t\t    msg-\u003esg.data[msg-\u003esg.start].page_link \u0026\u0026\n 583:\t\t\t    msg-\u003esg.data[msg-\u003esg.start].length)\n 584:\t\t\t\tgoto more_data;\n 585:\t\t}\n 586:\t\treturn ret;\n 587:\t}\n 588:\t\n 589:\tstatic int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)\n 590:\t{\n 591:\t\tstruct sk_msg tmp, *msg_tx = NULL;\n 592:\t\tint copied = 0, err = 0, ret = 0;\n 593:\t\tstruct sk_psock *psock;\n 594:\t\tlong timeo;\n 595:\t\tint flags;\n 596:\t\n 597:\t\t/* Don't let internal flags through */\n 598:\t\tflags = (msg-\u003emsg_flags \u0026 ~(MSG_SENDPAGE_DECRYPTED | MSG_FASTOPEN));\n 599:\t\tflags |= MSG_NO_SHARED_FRAGS;\n 600:\t\n 601:\t\tpsock = sk_psock_get(sk);\n 602:\t\tif (unlikely(!psock))\n 603:\t\t\treturn tcp_sendmsg(sk, msg, size);\n 604:\t\n 605:\t\tlock_sock(sk);\n 606:\t\ttimeo = sock_sndtimeo(sk, msg-\u003emsg_flags \u0026 MSG_DONTWAIT);\n 607:\t\twhile (msg_data_left(msg)) {\n 608:\t\t\tbool enospc = false;\n 609:\t\t\tu32 copy, osize;\n 610:\t\n 611:\t\t\terr = -READ_ONCE(sk-\u003esk_err);\n 612:\t\t\tif (err)\n 613:\t\t\t\tgoto out_err;\n 614:\t\n 615:\t\t\tcopy = msg_data_left(msg);\n 616:\t\t\tif (!sk_stream_memory_free(sk))\n 617:\t\t\t\tgoto wait_for_sndbuf;\n 618:\t\t\tif (psock-\u003ecork) {\n 619:\t\t\t\tmsg_tx = psock-\u003ecork;\n 620:\t\t\t} else {\n 621:\t\t\t\tmsg_tx = \u0026tmp;\n 622:\t\t\t\tsk_msg_init(msg_tx);\n 623:\t\t\t}\n 624:\t\n"
}

83/3 2026/11/02 13:38 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 24217
							output: 39
							thoughts: 23
						

Thoughts:
Okay, let's dive into lines 625-660 of `tcp_bpf_sendmsg`.

**Analyzing the `tcp_bpf_sendmsg` Block**

Alright, so I'm focusing in on this particular section of `tcp_bpf_sendmsg`. What am I seeing? Immediately, I'm drawn to the core logic; these lines are clearly handling... the BPF insertion, right? I'm checking, first, whether a BPF program is even attached. That's the crucial initial check; no program, no need to proceed, obviously. If it *is* attached, I’m now dealing with some kind of data preparation or context setup for the BPF program itself. I can almost *feel* the process of building the arguments or data structures that the BPF program is going to utilize. The intention is undoubtedly to make data available to a BPF program for inspection or modification.

Then there's the call to the BPF program itself. The mechanics of the call are really the crux of the matter. The call likely passes that prepared data, and then it is up to the BPF program to inspect or modify the data, and provide some feedback. I have to interpret this section; are we changing some of the packets' parameters? Or is it a security control?

Finally, there is an action, the return of the program. I should now focus on what happens after the program runs. Am I discarding the packet? Modifying it, then sending it? I'll analyze the return, and consider the implications of whatever the BPF program did during execution. Now I will analyze the lines that follow; how are the return codes or the program's output, integrated with the control flow of the larger send path? That will inform me of what the BPF program's role really is, here.



84/3 2026/11/02 13:38 tool read-file
0m Args:
{
  "File": "net/ipv4/tcp_bpf.c",
  "FirstLine": 625,
  "LineCount": 50
}

Results:
{
  "Contents": " 625:\t\t\tosize = msg_tx-\u003esg.size;\n 626:\t\t\terr = sk_msg_alloc(sk, msg_tx, msg_tx-\u003esg.size + copy, msg_tx-\u003esg.end - 1);\n 627:\t\t\tif (err) {\n 628:\t\t\t\tif (err != -ENOSPC)\n 629:\t\t\t\t\tgoto wait_for_memory;\n 630:\t\t\t\tenospc = true;\n 631:\t\t\t\tcopy = msg_tx-\u003esg.size - osize;\n 632:\t\t\t}\n 633:\t\n 634:\t\t\t/*\n 635:\t\t\t * copy is 0 if the ring of the cork was full already and its\n 636:\t\t\t * last element could not be extended. Flush the cork below,\n 637:\t\t\t * and let the next pass take the data.\n 638:\t\t\t */\n 639:\t\t\tret = 0;\n 640:\t\t\tif (copy)\n 641:\t\t\t\tret = sk_msg_memcopy_from_iter(sk, \u0026msg-\u003emsg_iter,\n 642:\t\t\t\t\t\t\t       msg_tx, copy);\n 643:\t\t\tif (ret \u003c 0) {\n 644:\t\t\t\tsk_msg_trim(sk, msg_tx, osize);\n 645:\t\t\t\terr = ret;\n 646:\t\t\t\tgoto out_err;\n 647:\t\t\t}\n 648:\t\n 649:\t\t\tcopied += ret;\n 650:\t\t\tif (psock-\u003ecork_bytes) {\n 651:\t\t\t\tif (size \u003e psock-\u003ecork_bytes)\n 652:\t\t\t\t\tpsock-\u003ecork_bytes = 0;\n 653:\t\t\t\telse\n 654:\t\t\t\t\tpsock-\u003ecork_bytes -= size;\n 655:\t\t\t\tif (psock-\u003ecork_bytes \u0026\u0026 !enospc)\n 656:\t\t\t\t\tgoto out_err;\n 657:\t\t\t\t/* All cork bytes are accounted, rerun the prog. */\n 658:\t\t\t\tpsock-\u003eeval = __SK_NONE;\n 659:\t\t\t\tpsock-\u003ecork_bytes = 0;\n 660:\t\t\t}\n 661:\t\n 662:\t\t\terr = tcp_bpf_send_verdict(sk, psock, msg_tx, \u0026copied, flags);\n 663:\t\t\tif (unlikely(err \u003c 0))\n 664:\t\t\t\tgoto out_err;\n 665:\t\t\tcontinue;\n 666:\twait_for_sndbuf:\n 667:\t\t\tsk_set_nospace(sk);\n 668:\twait_for_memory:\n 669:\t\t\terr = sk_stream_wait_memory(sk, \u0026timeo);\n 670:\t\t\tif (err) {\n 671:\t\t\t\tif (msg_tx == \u0026tmp)\n 672:\t\t\t\t\tsk_msg_free(sk, msg_tx);\n 673:\t\t\t\tgoto out_err;\n 674:\t\t\t}\n"
}

85/3 2026/11/02 13:38 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 25448
							output: 38
							thoughts: 2728
						

Thoughts:
**KMSAN Analysis of Recent Patch**

Okay, let's break down this patch from a KMSAN perspective. My initial thought process is to meticulously check each change against the four KMSAN criteria.

Firstly, no kernel structures are directly copied to user space in these changes. The `copy_page_to_iter` function within `__sk_msg_recvmsg` is the only potential area, as it handles copying TCP stream payload to user space. It avoids re-copying already-processed elements with `msg_rx->sg.start = i;` and relies on data that originates from `tcp_bpf_sendmsg` and network packets, so this is safe. There are no other structure copies.

Next, I'm carefully examining all conditional logic and branching for potential uses of uninitialized variables. I'm going through each introduced or modified variable to verify its initialization. So far, all variables checked are either initialized struct fields, function parameters, or properly initialized locals. Nothing jumps out as immediately problematic. Specific variables include those within `sk_msg_reset_curr`, `tcp_can_send_ack`, `filter.c`, `skmsg.c`, `sock_map.c`, `stream.c`, and `tcp_bpf.c`.

Thirdly, this patch doesn't introduce any new allocations of complex data structures where uninitialized fields could be a concern. In fact, `kzalloc` is used correctly. The clearing of `msg->sk_redir` to `NULL` explicitly prevents stale pointer reuse.

Finally, I'm thoroughly scrutinizing bounds checks, lengths, and logic to see if any code paths could allow access to uninitialized bytes of existing buffers. I'm particularly interested in `sk_msg_reset_curr`, `bpf_msg_push_data`, `bpf_msg_pop_data`, and `tcp_bpf_sendmsg`. In particular, I looked at how `curr` and `copybreak` are used in the context of `sk_msg_reset_curr` and how that is used in `tcp_bpf_send_verdict`. Based on my analysis and the structure of these functions, I'm confident there is no immediate issue in terms of uninitialized bytes.



86/3 2026/11/02 13:38 tool grepper
0m Args:
{
  "Expression": "copybreak",
  "PathPrefixes": [
    "net/core/",
    "include/linux/skmsg.h"
  ]
}

Results:
{
  "Output": "include/linux/skmsg.h=27=struct sk_msg_sg {\n--\ninclude/linux/skmsg.h-31-\tu32\t\t\t\tsize;\ninclude/linux/skmsg.h:32:\tu32\t\t\t\tcopybreak;\ninclude/linux/skmsg.h-33-\tDECLARE_BITMAP(copy, MAX_MSG_FRAGS + 2);\n--\ninclude/linux/skmsg.h=192=static inline void sk_msg_init(struct sk_msg *msg)\n--\ninclude/linux/skmsg.h-199-/*\ninclude/linux/skmsg.h:200: * sk_msg_memcopy_from_iter() writes to byte sg.copybreak of element sg.curr,\ninclude/linux/skmsg.h-201- * counted from the offset of that element. Point both past the last byte of\n--\ninclude/linux/skmsg.h=204=static inline void sk_msg_reset_curr(struct sk_msg *msg)\n--\ninclude/linux/skmsg.h-207-\t\tmsg-\u003esg.curr = msg-\u003esg.start;\ninclude/linux/skmsg.h:208:\t\tmsg-\u003esg.copybreak = 0;\ninclude/linux/skmsg.h-209-\t} else {\n--\ninclude/linux/skmsg.h-213-\t\tmsg-\u003esg.curr = i;\ninclude/linux/skmsg.h:214:\t\tmsg-\u003esg.copybreak = msg-\u003esg.data[i].length;\ninclude/linux/skmsg.h-215-\t}\n--\nnet/core/skmsg.c=264=void sk_msg_trim(struct sock *sk, struct sk_msg *msg, int len)\n--\nnet/core/skmsg.c-286-\tsk_mem_uncharge(sk, trim);\nnet/core/skmsg.c:287:\t/* Adjust copybreak if it falls into the trimmed part of last buf */\nnet/core/skmsg.c:288:\tif (msg-\u003esg.curr == i \u0026\u0026 msg-\u003esg.copybreak \u003e msg-\u003esg.data[i].length)\nnet/core/skmsg.c:289:\t\tmsg-\u003esg.copybreak = msg-\u003esg.data[i].length;\nnet/core/skmsg.c-290-out:\n--\nnet/core/skmsg.c-294-\t/* If we trim data a full sg elem before curr pointer update\nnet/core/skmsg.c:295:\t * copybreak and current so that any future copy operations\nnet/core/skmsg.c-296-\t * start at new copy location.\n--\nnet/core/skmsg.c-301-\t\tmsg-\u003esg.curr = msg-\u003esg.start;\nnet/core/skmsg.c:302:\t\tmsg-\u003esg.copybreak = 0;\nnet/core/skmsg.c-303-\t} else if (sk_msg_iter_dist(msg-\u003esg.start, msg-\u003esg.curr) \u003e=\n--\nnet/core/skmsg.c-306-\t\tmsg-\u003esg.curr = i;\nnet/core/skmsg.c:307:\t\tmsg-\u003esg.copybreak = msg-\u003esg.data[i].length;\nnet/core/skmsg.c-308-\t}\n--\nnet/core/skmsg.c=312=int sk_msg_zerocopy_from_iter(struct sock *sk, struct iov_iter *from,\n--\nnet/core/skmsg.c-352-\t\t/* When zerocopy is mixed with sk_msg_*copy* operations we\nnet/core/skmsg.c:353:\t\t * may have a copybreak set in this case clear and prefer\nnet/core/skmsg.c-354-\t\t * zerocopy remainder when possible.\nnet/core/skmsg.c-355-\t\t */\nnet/core/skmsg.c:356:\t\tmsg-\u003esg.copybreak = 0;\nnet/core/skmsg.c-357-\t\tmsg-\u003esg.curr = msg-\u003esg.end;\n--\nnet/core/skmsg.c=369=int sk_msg_memcopy_from_iter(struct sock *sk, struct iov_iter *from,\n--\nnet/core/skmsg.c-379-\t\t/* This is possible if a trim operation shrunk the buffer */\nnet/core/skmsg.c:380:\t\tif (msg-\u003esg.copybreak \u003e= sge-\u003elength) {\nnet/core/skmsg.c:381:\t\t\tmsg-\u003esg.copybreak = 0;\nnet/core/skmsg.c-382-\t\t\tsk_msg_iter_var_next(i);\n--\nnet/core/skmsg.c-387-\nnet/core/skmsg.c:388:\t\tbuf_size = sge-\u003elength - msg-\u003esg.copybreak;\nnet/core/skmsg.c-389-\t\tcopy = (buf_size \u003e bytes) ? bytes : buf_size;\nnet/core/skmsg.c:390:\t\tto = sg_virt(sge) + msg-\u003esg.copybreak;\nnet/core/skmsg.c:391:\t\tmsg-\u003esg.copybreak += copy;\nnet/core/skmsg.c-392-\t\tif (sk-\u003esk_route_caps \u0026 NETIF_F_NOCACHE_COPY)\n--\nnet/core/skmsg.c-403-\t\t\tbreak;\nnet/core/skmsg.c:404:\t\tmsg-\u003esg.copybreak = 0;\nnet/core/skmsg.c-405-\t\tsk_msg_iter_var_next(i);\n"
}

87/3 2026/11/02 13:38 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 30115
							output: 303
							thoughts: 1235
						

Thoughts:
**KMSAN Assessment: No Need for Dedicated Fuzzing**

Okay, let's break this down. My initial assessment is that these changes, while complex, don't warrant KMSAN fuzzing. I need to justify this, of course, and I've got to do it methodically.

First, I see the core logic of `sk_msg_memcopy_from_iter()` being correctly used to write into the correct memory location, respecting potential byte offsets from push or redirect operations due to `sk_msg_reset_curr(msg)`. That's good, it's about the correct addressing.

Now, let's drill into the bug fixes and enhancements this patch series is targeting. We're looking at TCP listener sockmap handling, corking issues, and various message state management concerns:

1.  **TCP Listener Sockmap:** The changes prevent attaching stream verdict/parser programs to TCP listen sockets, correctly manage callback inheritance in cloned sockets (`tcp_bpf_clone`), restrict redirects to listener sockets or orphan children. There's also some work in `sk_stream_wait_memory` to handle dead sockets and the usual cleanup of the receive buffer in `tcp_bpf_recvmsg_parser`. These are state machine and concurrency issues, and importantly, they seem to address cases where sockets in an invalid state are being used.
2.  **Cork and Message State:** The changes include correctly handling corking with `sk_msg_reset_curr` in `tcp_bpf_send_verdict`, which means there are some locking concerns here. Also, there are the usual `MSG_FASTOPEN` flag stripping and the `copy` is 0 check to avoid calling `sk_msg_memcopy_from_iter(sk, &msg->msg_iter, msg_tx, 0);` in `tcp_bpf_sendmsg`. Then we are also dealing with concurrency / unlocking and also correctly frees/updates cork and eval state in `tcp_bpf_send_verdict`. There are some scatterlist tweaks in `filter.c` and a few other subtle state updates related to receiving messages.

Now, I'm thinking about KMSAN specifically. My radar's up for uninitialized memory risks. Let's go through the checklist:

*   **NULL dereference, deadlocks, use-after-free, state machine races:** The fixes primarily address these, specifically with the TCP sockmap, including redirecting to dead/orphan sockets with `sk->sk_socket == NULL` and race conditions with corking/locking and the other issues described. These are all *well* handled by KASAN, LOCKDEP, and the existing kernel facilities.
*   **Uninitialized memory:** I don't see any changes that introduce uninitialized memory leaks to user space, or where we're reading uninitialized fields. All stack/heap structures touched or introduced by the patch are fully zeroed or initialized. This is crucial.

Therefore, since the code changes do not appear to introduce or expose uninitialized memory risks that are detectable *only* by KMSAN, `NeedsKMSAN` should be false.

Let's summarize to make it really clear:

*   The patch fixes edge cases and race conditions in BPF sockmap/psock. This includes TCP listener handling, socket callback cloning, redirects, managing `sk_socket` pointer lifecycle, and ensuring that scatterlist end markers are handled correctly. It also deals with concurrency in cork state.
*   Critically, the patch does **not** introduce new user-space-facing structures, ioctls, or potential information leaks.
*   No uninitialized variables or struct fields are read or branched on.
*   All data structures touched are either already initialized or represent TCP stream state.
*   The bugs this is fixing are fully addressable with KASAN, LOCKDEP, and existing kernel debugging facilities. No need for KMSAN.



88/3 2026/11/02 13:39 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch series addresses several edge cases and state synchronization issues in BPF sockmap, psock handling, and TCP BPF redirects:\n1. Prevents attaching verdict/parser programs to TCP listening sockets and restores default socket callbacks on cloned sockets.\n2. Guards against redirecting to dead sockets or sockets lacking `sk_socket` / wait queues, avoiding NULL pointer dereferences or crashes when waiting on connect/locks.\n3. Unmarks the scatterlist end bit when reusing/allocating slots in `bpf_msg_push_data` and `bpf_msg_pop_data`.\n4. Updates `msg_rx-\u003esg.start` on fault in `__sk_msg_recvmsg`, resets `sk_msg` curr/copybreak on corking, resets `msg-\u003esk_redir` prior to verdict runs, and improves cork synchronization across socket unlock/relock.\n\nNone of the changes introduce or modify kernel-to-user memory transfers of uninitialized structures (info leaks), nor do they create branches on uninitialized memory or access uninitialized buffer bytes. The potential failure modes (NULL pointer dereference, use-after-free, locking/concurrency races, state machine misuse) are well covered by standard KASAN and LOCKDEP. Consequently, a dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch series addresses several edge cases and state synchronization issues in BPF sockmap, psock handling, and TCP BPF redirects:\n1. Prevents attaching verdict/parser programs to TCP listening sockets and restores default socket callbacks on cloned sockets.\n2. Guards against redirecting to dead sockets or sockets lacking `sk_socket` / wait queues, avoiding NULL pointer dereferences or crashes when waiting on connect/locks.\n3. Unmarks the scatterlist end bit when reusing/allocating slots in `bpf_msg_push_data` and `bpf_msg_pop_data`.\n4. Updates `msg_rx-\u003esg.start` on fault in `__sk_msg_recvmsg`, resets `sk_msg` curr/copybreak on corking, resets `msg-\u003esk_redir` prior to verdict runs, and improves cork synchronization across socket unlock/relock.\n\nNone of the changes introduce or modify kernel-to-user memory transfers of uninitialized structures (info leaks), nor do they create branches on uninitialized memory or access uninitialized buffer bytes. The potential failure modes (NULL pointer dereference, use-after-free, locking/concurrency races, state machine misuse) are well covered by standard KASAN and LOCKDEP. Consequently, a dedicated KMSAN fuzzing session is not warranted.",
  "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)