* [PATCH net 0/2] bpf, sockmap: fix copied_seq after partial TCP read
@ 2026-07-02 14:09 Dong Chenchen
2026-07-02 14:09 ` [PATCH net 1/2] bpf, sockmap: account only unread data in tcp_eat_skb Dong Chenchen
2026-07-02 14:09 ` [PATCH net 2/2] selftests/bpf: cover sockmap drop after partial TCP read Dong Chenchen
0 siblings, 2 replies; 5+ messages in thread
From: Dong Chenchen @ 2026-07-02 14:09 UTC (permalink / raw)
To: daniel, edumazet, ncardwell, kuniyu, john.fastabend, jakub,
jiayuan.chen
Cc: davem, kuba, pabeni, horms, zhangchangzhong, netdev, bpf,
Dong Chenchen
tcp_eat_skb() assumes that an skb dequeued by the sockmap verdict path
has not previously been consumed. However, a socket can be inserted
into a sockmap after userspace has partially read the skb at the head of
its receive queue.
When new data invokes the verdict path, tcp_eat_skb() advances
copied_seq by the full skb length. This counts the already-read prefix
twice, moves copied_seq beyond rcv_nxt, and makes later native TCP reads
fail. TCP_ZEROCOPY_RECEIVE then triggers the tcp_recvmsg_locked()
sequence warning reported by syzbot.
TCP recvmsg seq # bug 2: copied AA28C633, seq AA28C601, rcvnxt AA28C602
WARNING: net/ipv4/tcp.c:2745 at tcp_recvmsg_locked
RIP: 0010:tcp_recvmsg_locked (net/ipv4/tcp.c:2745)
Call Trace:
<TASK>
tcp_zerocopy_receive (net/ipv4/tcp.c:1995 net/ipv4/tcp.c:2227)
do_tcp_getsockopt (net/ipv4/tcp.c:4771)
tcp_getsockopt (net/ipv4/tcp.c:4869)
do_sock_getsockopt (net/socket.c:2487)
__sys_getsockopt (net/socket.c:2518)
__x64_sys_getsockopt (net/socket.c:2525 net/socket.c:2522)
do_syscall_64 (arch/x86/entry/syscall_64.c:63)
entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
Patch 1 advances copied_seq to the skb end sequence and accounts only
the unread sequence-space delta during receive-buffer cleanup.
Patch 2 adds a deterministic regression test which reproduces the
tcp_recvmsg_locked() warning on the unpatched kernel.
Dong Chenchen (2):
bpf, sockmap: account only unread data in tcp_eat_skb
selftests/bpf: cover sockmap drop after partial TCP read
net/ipv4/tcp_bpf.c | 9 ++-
.../selftests/bpf/prog_tests/sockmap_basic.c | 73 +++++++++++++++++++
2 files changed, 78 insertions(+), 4 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH net 1/2] bpf, sockmap: account only unread data in tcp_eat_skb
2026-07-02 14:09 [PATCH net 0/2] bpf, sockmap: fix copied_seq after partial TCP read Dong Chenchen
@ 2026-07-02 14:09 ` Dong Chenchen
2026-07-03 14:01 ` sashiko-bot
2026-07-02 14:09 ` [PATCH net 2/2] selftests/bpf: cover sockmap drop after partial TCP read Dong Chenchen
1 sibling, 1 reply; 5+ messages in thread
From: Dong Chenchen @ 2026-07-02 14:09 UTC (permalink / raw)
To: daniel, edumazet, ncardwell, kuniyu, john.fastabend, jakub,
jiayuan.chen
Cc: davem, kuba, pabeni, horms, zhangchangzhong, netdev, bpf,
Dong Chenchen, syzbot+06dbd397158ec0ea4983
tcp_eat_skb() advances copied_seq by the full skb length when a sockmap
verdict drops or redirects an skb. This assumes none of the skb has
previously been consumed.
That assumption does not hold when userspace partially reads an skb
before adding the socket to a sockmap. A later packet invokes the
verdict path, which dequeues the partially consumed skb. Adding its full
length counts the consumed prefix twice and can move copied_seq beyond
rcv_nxt, causing subsequent native TCP reads to fail.
The following sequence reproduces the corruption:
1. TCP receives a 200-byte segment; skb sits on sk_receive_queue.
2. Userspace reads 50 bytes //copied_seq = 50, rcv_nxt = 200
3. Socket is inserted into a sockmap with an SK_DROP verdict.
4. A 1-byte segment arrives and tcp_try_coalesce() merges it with the
existing skb. //skb->len = 201, copied_seq = 50, rcv_nxt = 201
5. The verdict path calls tcp_eat_skb(), which does:
copied_seq += skb->len; // copied_seq = 251, rcv_nxt = 201
This counts the 50 already-read bytes again.
6. After removing the socket from the map, native receive triggers the
sequence warning:
TCP recvmsg seq # bug 2: copied AA28C633, seq AA28C601,
rcvnxt AA28C602, fl 40
WARNING: net/ipv4/tcp.c:2745 at tcp_recvmsg_locked+0x45e/0x9f0
Fix tcp_eat_skb() to advance copied_seq to the skb TCP end sequence and
pass only the distance from the old copied_seq to end_seq to
__tcp_cleanup_rbuf().
Fixes: e5c6de5fa025 ("bpf, sockmap: Incorrectly handling copied_seq")
Reported-by: syzbot+06dbd397158ec0ea4983@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=06dbd397158ec0ea4983
Signed-off-by: Dong Chenchen <dongchenchen2@huawei.com>
---
net/ipv4/tcp_bpf.c | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c
index cc0bd73f36b6..d640f8e06529 100644
--- a/net/ipv4/tcp_bpf.c
+++ b/net/ipv4/tcp_bpf.c
@@ -15,7 +15,7 @@
void tcp_eat_skb(struct sock *sk, struct sk_buff *skb)
{
struct tcp_sock *tcp;
- int copied;
+ u32 end_seq, delta;
if (!skb || !skb->len || !sk_is_tcp(sk))
return;
@@ -24,10 +24,11 @@ void tcp_eat_skb(struct sock *sk, struct sk_buff *skb)
return;
tcp = tcp_sk(sk);
- copied = tcp->copied_seq + skb->len;
- WRITE_ONCE(tcp->copied_seq, copied);
+ end_seq = TCP_SKB_CB(skb)->end_seq;
+ delta = end_seq - tcp->copied_seq;
+ WRITE_ONCE(tcp->copied_seq, end_seq);
tcp_rcv_space_adjust(sk);
- __tcp_cleanup_rbuf(sk, skb->len);
+ __tcp_cleanup_rbuf(sk, delta);
}
static int bpf_tcp_ingress(struct sock *sk, struct sk_psock *psock,
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH net 2/2] selftests/bpf: cover sockmap drop after partial TCP read
2026-07-02 14:09 [PATCH net 0/2] bpf, sockmap: fix copied_seq after partial TCP read Dong Chenchen
2026-07-02 14:09 ` [PATCH net 1/2] bpf, sockmap: account only unread data in tcp_eat_skb Dong Chenchen
@ 2026-07-02 14:09 ` Dong Chenchen
2026-07-03 14:01 ` sashiko-bot
1 sibling, 1 reply; 5+ messages in thread
From: Dong Chenchen @ 2026-07-02 14:09 UTC (permalink / raw)
To: daniel, edumazet, ncardwell, kuniyu, john.fastabend, jakub,
jiayuan.chen
Cc: davem, kuba, pabeni, horms, zhangchangzhong, netdev, bpf,
Dong Chenchen
Add a regression test for a TCP socket that is partially read before it
is inserted into a sockmap with an SK_DROP verdict.
The test leaves part of the original skb on the receive queue, adds the
socket to the map, and sends another byte to drive the verdict path.
After removing the socket from the map, it invokes TCP_ZEROCOPY_RECEIVE
on newly arrived native data.
Without the tcp_eat_skb() fix, copied_seq includes the already consumed
prefix, TCP_ZEROCOPY_RECEIVE triggers the sequence warning, and no data
is copied. With the fix, the new byte is copied without a warning.
Signed-off-by: Dong Chenchen <dongchenchen2@huawei.com>
---
.../selftests/bpf/prog_tests/sockmap_basic.c | 73 +++++++++++++++++++
1 file changed, 73 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
index cb3229711f93..106dd03cde84 100644
--- a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
+++ b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
@@ -1275,6 +1275,77 @@ static void test_sockmap_copied_seq(bool strp)
test_sockmap_pass_prog__destroy(skel);
}
+static void test_sockmap_drop_after_partial_read(void)
+{
+ int map, err, sent, recvd, zero = 0, on = 1;
+ struct test_sockmap_drop_prog *skel;
+ int c0 = -1, p0 = -1, c1 = -1, p1 = -1;
+ struct tcp_zerocopy_receive zc = {};
+ socklen_t zc_len = sizeof(zc);
+ char buf[200] = {}, rcv[50], addr[100];
+ struct bpf_program *prog;
+
+ skel = test_sockmap_drop_prog__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "open_and_load"))
+ return;
+
+ if (create_socket_pairs(AF_INET, SOCK_STREAM, &c0, &c1, &p0, &p1))
+ goto end;
+
+ sent = xsend(c0, buf, sizeof(buf), 0);
+ if (!ASSERT_EQ(sent, sizeof(buf), "xsend(native)"))
+ goto end;
+
+ recvd = recv_timeout(p0, rcv, sizeof(rcv), MSG_DONTWAIT, 1);
+ if (!ASSERT_EQ(recvd, sizeof(rcv), "recv_timeout(partial)"))
+ goto end;
+
+ prog = skel->progs.prog_skb_verdict;
+ map = bpf_map__fd(skel->maps.sock_map_rx);
+ err = bpf_prog_attach(bpf_program__fd(prog), map,
+ BPF_SK_SKB_STREAM_VERDICT, 0);
+ if (!ASSERT_OK(err, "bpf_prog_attach"))
+ goto end;
+
+ err = bpf_map_update_elem(map, &zero, &p0, BPF_NOEXIST);
+ if (!ASSERT_OK(err, "bpf_map_update_elem"))
+ goto end;
+
+ sent = xsend(c0, buf, 1, 0);
+ if (!ASSERT_EQ(sent, 1, "xsend(drop)"))
+ goto end;
+
+ err = bpf_map_delete_elem(map, &zero);
+ if (!ASSERT_OK(err, "bpf_map_delete_elem"))
+ goto end;
+
+ sent = xsend(c0, buf, 1, 0);
+ if (!ASSERT_EQ(sent, 1, "xsend(native again)"))
+ goto end;
+
+ err = setsockopt(p0, SOL_SOCKET, SO_ZEROCOPY, &on, sizeof(on));
+ if (!ASSERT_OK(err, "setsockopt(SO_ZEROCOPY)"))
+ goto end;
+
+ zc.copybuf_address = (__u64)(unsigned long)addr;
+ zc.copybuf_len = sizeof(addr);
+ err = getsockopt(p0, IPPROTO_TCP, TCP_ZEROCOPY_RECEIVE, &zc, &zc_len);
+ if (!ASSERT_OK(err, "getsockopt(TCP_ZEROCOPY_RECEIVE)"))
+ goto end;
+
+ ASSERT_EQ(zc.copybuf_len, 1, "TCP_ZEROCOPY_RECEIVE copied");
+end:
+ if (c0 >= 0)
+ close(c0);
+ if (p0 >= 0)
+ close(p0);
+ if (c1 >= 0)
+ close(c1);
+ if (p1 >= 0)
+ close(p1);
+ test_sockmap_drop_prog__destroy(skel);
+}
+
/* Wait until FIONREAD returns the expected value or timeout */
static int wait_for_fionread(int fd, int expected, unsigned int timeout_ms)
{
@@ -1447,6 +1518,8 @@ void test_sockmap_basic(void)
test_sockmap_copied_seq(false);
if (test__start_subtest("sockmap recover with strp"))
test_sockmap_copied_seq(true);
+ if (test__start_subtest("sockmap drop after partial read"))
+ test_sockmap_drop_after_partial_read();
if (test__start_subtest("sockmap tcp multi channels"))
test_sockmap_multi_channels(SOCK_STREAM);
if (test__start_subtest("sockmap udp multi channels"))
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH net 1/2] bpf, sockmap: account only unread data in tcp_eat_skb
2026-07-02 14:09 ` [PATCH net 1/2] bpf, sockmap: account only unread data in tcp_eat_skb Dong Chenchen
@ 2026-07-03 14:01 ` sashiko-bot
0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-07-03 14:01 UTC (permalink / raw)
To: Dong Chenchen; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [High] tcp_eat_skb() forcefully advancing copied_seq to end_seq corrupts sequence tracking when an SK_DROP/SK_REDIRECT follows an SK_PASS.
Pre-existing issues:
- [High] This problem wasn't introduced by this patch, but the fix is incomplete: it addresses copied_seq corruption for SK_DROP and SK_REDIRECT, but leaves the exact same vulnerability unpatched for SK_PASS, leading to duplicate data delivery and sequence corruption.
--
commit 53da102476a74ffdf7989fd6066b68acdf141844
Author: Dong Chenchen <dongchenchen2@huawei.com>
bpf, sockmap: account only unread data in tcp_eat_skb
tcp_eat_skb() advances copied_seq by the full skb length when a sockmap
verdict drops or redirects an skb, assuming none of the skb has been consumed.
This assumption does not hold when userspace partially reads an skb before
adding the socket to a sockmap. This patch fixes tcp_eat_skb() to advance
copied_seq to the skb TCP end sequence.
Closes: https://syzkaller.appspot.com/bug?extid=06dbd397158ec0ea4983
> diff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c
> index cc0bd73f36b6d..d640f8e065297 100644
> --- a/net/ipv4/tcp_bpf.c
> +++ b/net/ipv4/tcp_bpf.c
[ ... ]
> @@ -24,10 +24,11 @@ void tcp_eat_skb(struct sock *sk, struct sk_buff *skb)
> return;
>
> tcp = tcp_sk(sk);
> - copied = tcp->copied_seq + skb->len;
> - WRITE_ONCE(tcp->copied_seq, copied);
> + end_seq = TCP_SKB_CB(skb)->end_seq;
> + delta = end_seq - tcp->copied_seq;
> + WRITE_ONCE(tcp->copied_seq, end_seq);
[Severity: High]
Does forcefully advancing copied_seq to end_seq corrupt sequence tracking when
an SK_DROP or SK_REDIRECT follows an SK_PASS?
If a packet is evaluated as SK_PASS and enqueued to ingress_msg, and a
subsequent packet is evaluated as SK_DROP or SK_REDIRECT, this unconditional
overwrite sets copied_seq to the dropped skb's end_seq. This jumps over the
unread SK_PASS sequence space.
When userspace later reads the SK_PASS data, it will increment copied_seq
beyond rcv_nxt, leading to sequence warnings and breaking native TCP reads.
[Severity: High]
This is a pre-existing issue, but does this fix leave the exact same
vulnerability unpatched for SK_PASS?
While this patch addresses copied_seq corruption for SK_DROP and SK_REDIRECT,
the vulnerability seems to remain for SK_PASS, leading to duplicate data
delivery and sequence corruption.
If userspace performs a partial native read on a socket (advancing
copied_seq), and the socket is then added to a sockmap:
A packet arrival triggers tcp_read_skb(), which dequeues the partially-read
skb without trimming the already-read prefix or accounting for the difference
between copied_seq and the skb's start sequence:
net/ipv4/tcp.c:tcp_read_skb() {
...
while ((skb = skb_peek(&sk->sk_receive_queue)) != NULL) {
__skb_unlink(skb, &sk->sk_receive_queue);
...
}
If the BPF program returns SK_PASS, the full skb->len and off=0 are
unconditionally used to enqueue the packet, blindly pushing already-read data
into the ingress queue:
net/core/skmsg.c:sk_psock_verdict_apply() {
...
if (skb_queue_empty(&psock->ingress_skb)) {
len = skb->len;
off = 0;
...
}
When userspace reads from the sockmap, it will receive the already-read prefix
again, and copied_seq will be incremented by the full length, overshooting
rcv_nxt.
> tcp_rcv_space_adjust(sk);
> - __tcp_cleanup_rbuf(sk, skb->len);
> + __tcp_cleanup_rbuf(sk, delta);
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260702140959.1806754-1-dongchenchen2@huawei.com?part=1
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net 2/2] selftests/bpf: cover sockmap drop after partial TCP read
2026-07-02 14:09 ` [PATCH net 2/2] selftests/bpf: cover sockmap drop after partial TCP read Dong Chenchen
@ 2026-07-03 14:01 ` sashiko-bot
0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-07-03 14:01 UTC (permalink / raw)
To: Dong Chenchen; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The test `test_sockmap_drop_after_partial_read` relies on asynchronous packet processing without any synchronization, making it inherently flaky.
--
commit 375b8cdbb19df93db8af9c80a28cbeb5b3bdb312
Author: Dong Chenchen <dongchenchen2@huawei.com>
selftests/bpf: cover sockmap drop after partial TCP read
This commit adds a regression test for a TCP socket that is partially read
before it is inserted into a sockmap with an SK_DROP verdict. The test
sends an additional byte to drive the verdict path, removes the socket,
and checks sequence numbers via TCP_ZEROCOPY_RECEIVE.
> diff --git a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
> index cb3229711f93a..106dd03cde84e 100644
> --- a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
> +++ b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
[ ... ]
> +static void test_sockmap_drop_after_partial_read(void)
> +{
[ ... ]
> + err = bpf_map_update_elem(map, &zero, &p0, BPF_NOEXIST);
> + if (!ASSERT_OK(err, "bpf_map_update_elem"))
> + goto end;
> +
> + sent = xsend(c0, buf, 1, 0);
> + if (!ASSERT_EQ(sent, 1, "xsend(drop)"))
> + goto end;
> +
> + err = bpf_map_delete_elem(map, &zero);
[Severity: Medium]
Will this map deletion race with the asynchronous packet processing?
Because TCP delivery and BPF program execution occur asynchronously in softirq
context, the map deletion might remove the socket from the map before the
queued byte is processed and dropped by the BPF program.
If that happens, the byte could bypass the SK_DROP verdict and be delivered
normally. This would add an unexpected byte to the receive queue, causing the
subsequent TCP_ZEROCOPY_RECEIVE step to copy 2 bytes instead of 1, which fails
the final ASSERT_EQ(zc.copybuf_len, 1, ...) assertion and leads to test
flakiness.
Is there a need for some form of synchronization here before deleting the map
element to ensure the packet is actually processed and dropped?
> + if (!ASSERT_OK(err, "bpf_map_delete_elem"))
> + goto end;
> +
> + sent = xsend(c0, buf, 1, 0);
> + if (!ASSERT_EQ(sent, 1, "xsend(native again)"))
> + goto end;
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260702140959.1806754-1-dongchenchen2@huawei.com?part=2
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-07-03 14:01 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-02 14:09 [PATCH net 0/2] bpf, sockmap: fix copied_seq after partial TCP read Dong Chenchen
2026-07-02 14:09 ` [PATCH net 1/2] bpf, sockmap: account only unread data in tcp_eat_skb Dong Chenchen
2026-07-03 14:01 ` sashiko-bot
2026-07-02 14:09 ` [PATCH net 2/2] selftests/bpf: cover sockmap drop after partial TCP read Dong Chenchen
2026-07-03 14:01 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox