* [PATCH net 0/2] tcp: clamp return value of tcp_wmem_schedule() to non-negative
@ 2026-10-15 7:37 Octavian Purdila
2026-10-15 7:37 ` [PATCH net 1/2] " Octavian Purdila
` (2 more replies)
0 siblings, 3 replies; 12+ messages in thread
From: Octavian Purdila @ 2026-10-15 7:37 UTC (permalink / raw)
To: Eric Dumazet, Neal Cardwell, Kuniyuki Iwashima, David S . Miller,
Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Shuah Khan, Willem de Bruijn, Soheil Hassas Yeganeh,
Wei Wang, Shakeel Butt, netdev, linux-kselftest, linux-kernel,
Octavian Purdila
This series fixes a bug in tcp_wmem_schedule() where a negative
sk->sk_forward_alloc under TCP memory pressure causes tcp_wmem_schedule()
to return a negative copy length when sk->sk_wmem_queued already reaches
or exceeds sysctl_tcp_wmem[0].
sk->sk_forward_alloc can transiently become negative when tcp_collapse()
re-packs partially read or high payload-to-truesize SKBs in the receive
queue and charges newly allocated SKBs via sk_mem_charge() before
freeing the original SKBs. When a subsequent send hits memory pressure
in sk_wmem_schedule() with sk->sk_wmem_queued >= sysctl_tcp_wmem[0],
tcp_wmem_schedule() returns min(copy, sk->sk_forward_alloc) < 0.
Callers such as tcp_sendmsg_locked() then pass that negative copy length
to skb_copy_to_page_nocache() -> copy_from_iter_full() ->
check_copy_size(), triggering WARN_ON_ONCE(bytes > INT_MAX) and
returning -EFAULT instead of waiting for memory or returning -EAGAIN.
Patch 1 clamps the return value of tcp_wmem_schedule() to [0, copy] so
a negative sk->sk_forward_alloc is treated as 0 available bytes.
Patch 2 adds a packetdrill selftest that reproduces the negative
sk->sk_forward_alloc condition via tcp_collapse() under memory pressure
and verifies that a non-blocking write fails with EAGAIN rather than
EFAULT.
Octavian Purdila (2):
tcp: clamp return value of tcp_wmem_schedule() to non-negative
selftests: net: packetdrill: add test for negative sk_forward_alloc in
tcp_wmem_schedule()
net/ipv4/tcp.c | 2 +-
.../testing/selftests/net/packetdrill/config | 1 +
.../tcp_wmem_schedule_negative_fwd_alloc.pkt | 116 ++++++++++++++++++
3 files changed, 118 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/net/packetdrill/tcp_wmem_schedule_negative_fwd_alloc.pkt
--
2.56.0.176.g3575e65682-goog
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH net 1/2] tcp: clamp return value of tcp_wmem_schedule() to non-negative
2026-10-15 7:37 [PATCH net 0/2] tcp: clamp return value of tcp_wmem_schedule() to non-negative Octavian Purdila
@ 2026-10-15 7:37 ` Octavian Purdila
2026-10-16 7:42 ` Kuniyuki Iwashima
2026-10-15 7:37 ` [PATCH net 2/2] selftests: net: packetdrill: add test for negative sk_forward_alloc in tcp_wmem_schedule() Octavian Purdila
2026-10-15 7:43 ` [PATCH net 0/2] tcp: clamp return value of tcp_wmem_schedule() to non-negative netdev-bot+sinfo
2 siblings, 1 reply; 12+ messages in thread
From: Octavian Purdila @ 2026-10-15 7:37 UTC (permalink / raw)
To: Eric Dumazet, Neal Cardwell, Kuniyuki Iwashima, David S . Miller,
Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Shuah Khan, Willem de Bruijn, Soheil Hassas Yeganeh,
Wei Wang, Shakeel Butt, netdev, linux-kselftest, linux-kernel,
Octavian Purdila
When sk_wmem_schedule(sk, copy) fails under TCP memory pressure and
sk->sk_wmem_queued already reaches or exceeds sysctl_tcp_wmem[0],
tcp_wmem_schedule() returns min(copy, sk->sk_forward_alloc).
However, sk->sk_forward_alloc is a signed int that can transiently
become negative when tcp_collapse() re-packs partially read or high
payload-to-truesize SKBs in the receive queue and charges newly
allocated SKBs via sk_mem_charge() before freeing the original SKB.
If tcp_wmem_schedule() returns a negative value, tcp_sendmsg_locked()
passes that negative copy length to skb_copy_to_page_nocache() ->
copy_from_iter_full() -> check_copy_size(), where it is converted to an
unsigned size_t > INT_MAX, triggering a WARN_ON_ONCE(bytes > INT_MAX)
and returning -EFAULT instead of waiting for memory or returning
-EAGAIN.
Clamp the return value of tcp_wmem_schedule() to [0, copy] so callers
treat negative sk_forward_alloc as 0 available bytes.
Fixes: 849b425cd091 ("tcp: fix possible freeze in tx path under memory pressure")
Assisted-by: LLM
Signed-off-by: Octavian Purdila <tavip@google.com>
---
net/ipv4/tcp.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c
index 87ef6d5cbfeba..0e062fcd94a6c 100644
--- a/net/ipv4/tcp.c
+++ b/net/ipv4/tcp.c
@@ -1037,7 +1037,7 @@ int tcp_wmem_schedule(struct sock *sk, int copy)
left = READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_wmem[0]) - sk->sk_wmem_queued;
if (left > 0)
sk_forced_mem_schedule(sk, min(left, copy));
- return min(copy, sk->sk_forward_alloc);
+ return clamp(sk->sk_forward_alloc, 0, copy);
}
void tcp_free_fastopen_req(struct tcp_sock *tp)
--
2.56.0.176.g3575e65682-goog
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH net 2/2] selftests: net: packetdrill: add test for negative sk_forward_alloc in tcp_wmem_schedule()
2026-10-15 7:37 [PATCH net 0/2] tcp: clamp return value of tcp_wmem_schedule() to non-negative Octavian Purdila
2026-10-15 7:37 ` [PATCH net 1/2] " Octavian Purdila
@ 2026-10-15 7:37 ` Octavian Purdila
2026-10-16 7:54 ` Kuniyuki Iwashima
2026-10-19 8:13 ` netdev-bot+sashiko
2026-10-15 7:43 ` [PATCH net 0/2] tcp: clamp return value of tcp_wmem_schedule() to non-negative netdev-bot+sinfo
2 siblings, 2 replies; 12+ messages in thread
From: Octavian Purdila @ 2026-10-15 7:37 UTC (permalink / raw)
To: Eric Dumazet, Neal Cardwell, Kuniyuki Iwashima, David S . Miller,
Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Shuah Khan, Willem de Bruijn, Soheil Hassas Yeganeh,
Wei Wang, Shakeel Butt, netdev, linux-kselftest, linux-kernel,
Octavian Purdila
Add a packetdrill test that drives sk->sk_forward_alloc negative via
tcp_collapse() on the receive queue while sk->sk_wmem_queued exceeds
sysctl_tcp_wmem[0], and verifies that a subsequent non-blocking write()
under TCP memory pressure fails with EAGAIN rather than returning a
negative copy length from tcp_wmem_schedule() and failing with EFAULT.
Assisted-by: LLM
Signed-off-by: Octavian Purdila <tavip@google.com>
---
.../testing/selftests/net/packetdrill/config | 1 +
.../tcp_wmem_schedule_negative_fwd_alloc.pkt | 116 ++++++++++++++++++
2 files changed, 117 insertions(+)
create mode 100644 tools/testing/selftests/net/packetdrill/tcp_wmem_schedule_negative_fwd_alloc.pkt
diff --git a/tools/testing/selftests/net/packetdrill/config b/tools/testing/selftests/net/packetdrill/config
index b7df8bf309200..dc3cd72a8da8c 100644
--- a/tools/testing/selftests/net/packetdrill/config
+++ b/tools/testing/selftests/net/packetdrill/config
@@ -1,6 +1,7 @@
CONFIG_HZ=1000
CONFIG_HZ_1000=y
CONFIG_IPV6=y
+CONFIG_MEMCG=y
CONFIG_NET_NS=y
CONFIG_NET_SCH_FIFO=y
CONFIG_NET_SCH_FQ=y
diff --git a/tools/testing/selftests/net/packetdrill/tcp_wmem_schedule_negative_fwd_alloc.pkt b/tools/testing/selftests/net/packetdrill/tcp_wmem_schedule_negative_fwd_alloc.pkt
new file mode 100644
index 0000000000000..db232b54c554e
--- /dev/null
+++ b/tools/testing/selftests/net/packetdrill/tcp_wmem_schedule_negative_fwd_alloc.pkt
@@ -0,0 +1,116 @@
+// SPDX-License-Identifier: GPL-2.0
+// Test that tcp_wmem_schedule() clamps negative sk->sk_forward_alloc to 0
+// when sk_wmem_schedule() fails under memory pressure and
+// sk->sk_wmem_queued >= sysctl_tcp_wmem[0], returning EAGAIN rather than EFAULT.
+
+--mtu=65535
+--tolerance_usecs=100000
+
+`set -e
+./defaults.sh
+ip link set dev tun0 mtu 65535
+sysctl -q net.ipv4.tcp_rmem="4096 131072 6291456"
+sysctl -q net.ipv4.tcp_wmem="4096 16384 4194304"
+cgroot=$(mount -t cgroup2 | head -1 | awk '{print $3}')
+[ -n "$cgroot" ]
+echo +memory > "${cgroot}/cgroup.subtree_control"
+mkdir -p "${cgroot}/pkt_${PPID}"
+echo $PPID > "${cgroot}/pkt_${PPID}/cgroup.procs"
+`
+
+// Create listening socket in pkt_${PPID} memcg and establish connection
+0.000 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
++0.000 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
++0.000 bind(3, ..., ...) = 0
++0.000 listen(3, 1) = 0
+
++0.000 < S 0:0(0) win 65535 <mss 36000,nop,wscale 7>
++0.000 > S. 0:0(0) ack 1 <...>
++0.000 < . 1:1(0) ack 1 win 512
++0.000 accept(3, ..., ...) = 4
+
+// Move packetdrill back to its original cgroup so only the sockets remain charged to pkt_${PPID}
++0.000 `cgroot=$(mount -t cgroup2 | head -1 | awk '{print $3}')
+runner_pid=$(awk '/^PPid:/ {print $2}' /proc/$PPID/status)
+orig_cg=$(sed -n 's/^0:://p' /proc/$runner_pid/cgroup)
+echo $PPID > "${cgroot}${orig_cg}/cgroup.procs"`
+
++0.000 setsockopt(4, SOL_SOCKET, SO_SNDBUF, [2097152], 4) = 0
++0.000 setsockopt(4, SOL_SOCKET, SO_RCVBUF, [145536], 4) = 0
+
+// 1. Warm up rcv_ssthresh to full window (~256 KB)
++0.000 < P. 1:36001(36000) ack 1 win 512
++0.000 > . 1:1(0) ack 36001
++0.000 read(4, ..., 36000) = 36000
+
++0.000 < P. 36001:72001(36000) ack 1 win 512
++0.000 > . 1:1(0) ack 72001
++0.000 read(4, ..., 36000) = 36000
+
++0.000 < P. 72001:108001(36000) ack 1 win 512
++0.000 > . 1:1(0) ack 108001
++0.000 read(4, ..., 36000) = 36000
+
++0.000 < P. 108001:144001(36000) ack 1 win 512
++0.000 > . 1:1(0) ack 144001
++0.000 read(4, ..., 36000) = 36000
+
+// 2. Queue 500 KB on TX so sk_wmem_queued >= sysctl_tcp_wmem[0]
++0.000 write(4, ..., 500000) = 500000
+
+// 3a. Inject 6 high-density 36 KB segments (216 KB payload, 222.7 KB truesize)
+// and read 100 bytes from the head SKB so before(skb->seq, tp->copied_seq) is true.
++0.000 < P. 144001:180001(36000) ack 1 win 512
++0.000 read(4, ..., 100) = 100
+
++0.000 < P. 180001:216001(36000) ack 1 win 512
++0.000 < P. 216001:252001(36000) ack 1 win 512
++0.000 < P. 252001:288001(36000) ack 1 win 512
++0.000 < P. 288001:324001(36000) ack 1 win 512
++0.000 < P. 324001:360001(36000) ack 1 win 512
+
+// 3b. Inject 22 smaller 1000-byte segments into the remaining advertised rcv_wnd.
+// Their higher truesize pushes sk_rmem_alloc > sk_rcvbuf (291072),
+// triggering tcp_prune_queue -> tcp_collapse on the 216 KB of 36 KB SKBs
+// and driving sk->sk_forward_alloc negative (-32768).
++0.000 < P. 360001:361001(1000) ack 1 win 512
++0.000 < P. 361001:362001(1000) ack 1 win 512
++0.000 < P. 362001:363001(1000) ack 1 win 512
++0.000 < P. 363001:364001(1000) ack 1 win 512
++0.000 < P. 364001:365001(1000) ack 1 win 512
++0.000 < P. 365001:366001(1000) ack 1 win 512
++0.000 < P. 366001:367001(1000) ack 1 win 512
++0.000 < P. 367001:368001(1000) ack 1 win 512
++0.000 < P. 368001:369001(1000) ack 1 win 512
++0.000 < P. 369001:370001(1000) ack 1 win 512
++0.000 < P. 370001:371001(1000) ack 1 win 512
++0.000 < P. 371001:372001(1000) ack 1 win 512
++0.000 < P. 372001:373001(1000) ack 1 win 512
++0.000 < P. 373001:374001(1000) ack 1 win 512
++0.000 < P. 374001:375001(1000) ack 1 win 512
++0.000 < P. 375001:376001(1000) ack 1 win 512
++0.000 < P. 376001:377001(1000) ack 1 win 512
++0.000 < P. 377001:378001(1000) ack 1 win 512
++0.000 < P. 378001:379001(1000) ack 1 win 512
++0.000 < P. 379001:380001(1000) ack 1 win 512
++0.000 < P. 380001:381001(1000) ack 1 win 512
++0.000 < P. 381001:382001(1000) ack 1 win 512
+
+// 4. Cap the socket memcg at its current usage and expect -EAGAIN on non-blocking write.
++0.010 `cgroot=$(mount -t cgroup2 | head -1 | awk '{print $3}')
+echo 0 > "${cgroot}/pkt_${PPID}/memory.high"
+cat "${cgroot}/pkt_${PPID}/memory.current" > "${cgroot}/pkt_${PPID}/memory.max"`
++0.000 fcntl(4, F_SETFL, O_RDWR|O_NONBLOCK) = 0
++0.000 write(4, ..., 16384) = -1 EAGAIN (Resource temporarily unavailable)
+
++0.000 close(4) = 0
++0.000 close(3) = 0
+
+// Untimed cleanup command executed by packetdrill on both pass and early failure.
+`cgroot=$(mount -t cgroup2 | head -1 | awk '{print $3}')
+[ -n "$cgroot" ] || exit 0
+runner_pid=$(awk '/^PPid:/ {print $2}' /proc/$PPID/status)
+orig_cg=$(sed -n 's/^0:://p' /proc/$runner_pid/cgroup)
+echo $PPID > "${cgroot}${orig_cg}/cgroup.procs" 2>/dev/null || true
+echo $$ > "${cgroot}${orig_cg}/cgroup.procs" 2>/dev/null || true
+rmdir "${cgroot}/pkt_${PPID}"`
--
2.56.0.176.g3575e65682-goog
^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH net 0/2] tcp: clamp return value of tcp_wmem_schedule() to non-negative
2026-10-15 7:37 [PATCH net 0/2] tcp: clamp return value of tcp_wmem_schedule() to non-negative Octavian Purdila
2026-10-15 7:37 ` [PATCH net 1/2] " Octavian Purdila
2026-10-15 7:37 ` [PATCH net 2/2] selftests: net: packetdrill: add test for negative sk_forward_alloc in tcp_wmem_schedule() Octavian Purdila
@ 2026-10-15 7:43 ` netdev-bot+sinfo
2026-10-15 7:56 ` Octavian Purdila
2 siblings, 1 reply; 12+ messages in thread
From: netdev-bot+sinfo @ 2026-10-15 7:43 UTC (permalink / raw)
To: Octavian Purdila
Cc: Eric Dumazet, Neal Cardwell, Kuniyuki Iwashima, David S . Miller,
Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan,
Willem de Bruijn, Soheil Hassas Yeganeh, Wei Wang, Shakeel Butt,
netdev, linux-kselftest, linux-kernel
Hi!
This is an automated message. This series looks like a fix, but its
commit messages seem to be missing some information:
- How the issue was discovered, e.g. hit in production, hit during
development, syzbot report, manual code inspection, LLM or static
analysis tool scan.
Please do not repost the series just to address the above. Instead,
reply to this email with the missing information, so that reviewers
can take it into account. If the series needs another revision for
other reasons, please include the information in the commit messages
then.
The evaluation is done by an LLM so it may be wrong, if you think
that is the case please reply and explain.
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH net 0/2] tcp: clamp return value of tcp_wmem_schedule() to non-negative
2026-10-15 7:43 ` [PATCH net 0/2] tcp: clamp return value of tcp_wmem_schedule() to non-negative netdev-bot+sinfo
@ 2026-10-15 7:56 ` Octavian Purdila
0 siblings, 0 replies; 12+ messages in thread
From: Octavian Purdila @ 2026-10-15 7:56 UTC (permalink / raw)
To: netdev-bot+sinfo
Cc: Eric Dumazet, Neal Cardwell, Kuniyuki Iwashima, David S . Miller,
Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan,
Willem de Bruijn, Wei Wang, Shakeel Butt, netdev, linux-kselftest,
linux-kernel
On Thu, Oct 15, 2026 at 12:43 AM <netdev-bot+sinfo@kernel.org> wrote:
>
> Hi!
>
> This is an automated message. This series looks like a fix, but its
> commit messages seem to be missing some information:
>
> - How the issue was discovered, e.g. hit in production, hit during
> development, syzbot report, manual code inspection, LLM or static
> analysis tool scan.
>
The issue was discovered in production, here is an example warning:
------------[ cut here ]------------
WARNING: CPU: 1 PID: 939331 at include/linux/thread_info.h:249
tcp_sendmsg+0x10c1/0x1530
Modules linked in: kvm_amd hdi2_standard_ftl hdi2_megablocks hdi2
xenolith stg elephant_dev_num nv_p2p_glue vfat fat livepatch_v1(OEK)
k10temp i2c_piix4 i2c_via_ipmi i2c_mux_ltc4306 google_bmc_usb gq
cdc_acm xhci_pciJun 18 01:48:26.051200 rdht15 kernel: [66690.105140]
CPU: 1 UID: 368767421 PID: 939331 Comm: DefaultEventMan Kdump: loaded
Tainted: P S W OE K 6.12.63-smp-1400.86.0.0 #1
Tainted: [P]=PROPRIETARY_MODULE, [S]=CPU_OUT_OF_SPEC, [W]=WARN,
[O]=OOT_MODULE, [E]=UNSIGNED_MODULE, [K]=LIVEPATCH
Hardware name: Google, Inc.
Arcadia_IT_80/Arcadia_IT_80, BIOS 34.86.0-0 01/25/2026
RIP: 0010:tcp_sendmsg+0x10c1/0x1530
Code: ee 44 8b 64 24 3c 0f 84 ae f5 ff ff 48 8b 7c 24 48 e8 f3 a7 23
00 41 be f2 ff ff ff 4c 8b 6c 24 10 44 8b 24 24 e9 4a 03 00 00 <0f> 0b
31 c0 e9 7e f5 ff ff 41 80 8f 84 00 00 00 01 e9 00 f6 ff ff
RSP: 0018:ffffbaff64a6fc38 EFLAGS: 00010282
RAX: 0000000000000062 RBX: ffff9c45670d3c00 RCX: 25c2e5210d8ebf01
RDX: 0000000000600000 RSI: 00000000fff8427f RDI: ffff9c43f2fc0062
RBP: ffffffffffe4b17f R08: 0000000000000000 R09: 0000000000000002
R10: ffff9c4c187b6200 R11: 00000000000001b6 R12: 0000000000000000
R13: ffff9c4383ffc1e8 R14: 00000000ffe4b17f R15: ffff9c40fe838600
FS: 00007fe39fbaf700(0000) GS:ffff9c874ce40000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 000010986487a238 CR3: 0000000973fb8006 CR4: 0000000000f70ef0
PKRU: 55555554
Call Trace:
<TASK>
[<ffffffffa5c218a2>] __sys_sendmsg+0x292/0x490
[<ffffffffa5c6281f>] ? restore_sigcontext+0x15f/0x1c0
[<ffffffffa5c6e1d0>] ? __pfx_flush_tlb_func+0x10/0x10
[<ffffffffa5c6e965>] ? flush_tlb_local+0x285/0x2a0
[<ffffffffa5ba1b29>] ? sched_clock+0x9/0x10
[<ffffffffa5bd7900>] ? sched_clock_cpu+0x10/0x180
[<ffffffffa5be362a>] ? generic_smp_call_function_single_interrupt+0x19a/0x430
[<ffffffffa5f51848>] do_syscall_64+0x88/0x340
[<ffffffffa6000136>] entry_SYSCALL_64_after_hwframe+0x76/0x7e
RIP: 0033:0x5580e3f28a3d.
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH net 1/2] tcp: clamp return value of tcp_wmem_schedule() to non-negative
2026-10-15 7:37 ` [PATCH net 1/2] " Octavian Purdila
@ 2026-10-16 7:42 ` Kuniyuki Iwashima
2026-10-16 8:19 ` Eric Dumazet
0 siblings, 1 reply; 12+ messages in thread
From: Kuniyuki Iwashima @ 2026-10-16 7:42 UTC (permalink / raw)
To: Octavian Purdila
Cc: Eric Dumazet, Neal Cardwell, David S . Miller, Jakub Kicinski,
Paolo Abeni, Simon Horman, Shuah Khan, Willem de Bruijn,
Soheil Hassas Yeganeh, Wei Wang, Shakeel Butt, netdev,
linux-kselftest, linux-kernel
On Thu, Oct 15, 2026 at 12:37 AM Octavian Purdila <tavip@google.com> wrote:
>
> When sk_wmem_schedule(sk, copy) fails under TCP memory pressure and
> sk->sk_wmem_queued already reaches or exceeds sysctl_tcp_wmem[0],
> tcp_wmem_schedule() returns min(copy, sk->sk_forward_alloc).
>
> However, sk->sk_forward_alloc is a signed int that can transiently
> become negative when tcp_collapse() re-packs partially read or high
> payload-to-truesize SKBs in the receive queue and charges newly
> allocated SKBs via sk_mem_charge() before freeing the original SKB.
>
> If tcp_wmem_schedule() returns a negative value, tcp_sendmsg_locked()
> passes that negative copy length to skb_copy_to_page_nocache() ->
> copy_from_iter_full() -> check_copy_size(), where it is converted to an
> unsigned size_t > INT_MAX, triggering a WARN_ON_ONCE(bytes > INT_MAX)
> and returning -EFAULT instead of waiting for memory or returning
> -EAGAIN.
>
> Clamp the return value of tcp_wmem_schedule() to [0, copy] so callers
> treat negative sk_forward_alloc as 0 available bytes.
>
> Fixes: 849b425cd091 ("tcp: fix possible freeze in tx path under memory pressure")
> Assisted-by: LLM
> Signed-off-by: Octavian Purdila <tavip@google.com>
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
Basically sk->sk_forward_alloc should not be negative, and
I was able to avoid that by refactoring tcp_collapse(), but it was
a bit invasive for stable and late in rc8, so I'll post it in the next cycle.
$ git diff --stat HEAD~4
net/ipv4/tcp_input.c | 158 ...
1 file changed, 109 insertions(+), 49 deletions(-)
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH net 2/2] selftests: net: packetdrill: add test for negative sk_forward_alloc in tcp_wmem_schedule()
2026-10-15 7:37 ` [PATCH net 2/2] selftests: net: packetdrill: add test for negative sk_forward_alloc in tcp_wmem_schedule() Octavian Purdila
@ 2026-10-16 7:54 ` Kuniyuki Iwashima
2026-10-16 8:22 ` Eric Dumazet
2026-10-19 8:13 ` netdev-bot+sashiko
1 sibling, 1 reply; 12+ messages in thread
From: Kuniyuki Iwashima @ 2026-10-16 7:54 UTC (permalink / raw)
To: Octavian Purdila
Cc: Eric Dumazet, Neal Cardwell, David S . Miller, Jakub Kicinski,
Paolo Abeni, Simon Horman, Shuah Khan, Willem de Bruijn,
Soheil Hassas Yeganeh, Wei Wang, Shakeel Butt, netdev,
linux-kselftest, linux-kernel
On Thu, Oct 15, 2026 at 12:37 AM Octavian Purdila <tavip@google.com> wrote:
>
> Add a packetdrill test that drives sk->sk_forward_alloc negative via
> tcp_collapse() on the receive queue while sk->sk_wmem_queued exceeds
> sysctl_tcp_wmem[0], and verifies that a subsequent non-blocking write()
> under TCP memory pressure fails with EAGAIN rather than returning a
> negative copy length from tcp_wmem_schedule() and failing with EFAULT.
>
> Assisted-by: LLM
> Signed-off-by: Octavian Purdila <tavip@google.com>
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
Thanks !
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH net 1/2] tcp: clamp return value of tcp_wmem_schedule() to non-negative
2026-10-16 7:42 ` Kuniyuki Iwashima
@ 2026-10-16 8:19 ` Eric Dumazet
0 siblings, 0 replies; 12+ messages in thread
From: Eric Dumazet @ 2026-10-16 8:19 UTC (permalink / raw)
To: Kuniyuki Iwashima
Cc: Octavian Purdila, Neal Cardwell, David S . Miller, Jakub Kicinski,
Paolo Abeni, Simon Horman, Shuah Khan, Willem de Bruijn,
Soheil Hassas Yeganeh, Wei Wang, Shakeel Butt, netdev,
linux-kselftest, linux-kernel
Le ven. 16 oct. 2026 à 09:42, Kuniyuki Iwashima <kuniyu@google.com> a écrit :
>
> On Thu, Oct 15, 2026 at 12:37 AM Octavian Purdila <tavip@google.com> wrote:
> >
> > When sk_wmem_schedule(sk, copy) fails under TCP memory pressure and
> > sk->sk_wmem_queued already reaches or exceeds sysctl_tcp_wmem[0],
> > tcp_wmem_schedule() returns min(copy, sk->sk_forward_alloc).
> >
> > However, sk->sk_forward_alloc is a signed int that can transiently
> > become negative when tcp_collapse() re-packs partially read or high
> > payload-to-truesize SKBs in the receive queue and charges newly
> > allocated SKBs via sk_mem_charge() before freeing the original SKB.
> >
> > If tcp_wmem_schedule() returns a negative value, tcp_sendmsg_locked()
> > passes that negative copy length to skb_copy_to_page_nocache() ->
> > copy_from_iter_full() -> check_copy_size(), where it is converted to an
> > unsigned size_t > INT_MAX, triggering a WARN_ON_ONCE(bytes > INT_MAX)
> > and returning -EFAULT instead of waiting for memory or returning
> > -EAGAIN.
> >
> > Clamp the return value of tcp_wmem_schedule() to [0, copy] so callers
> > treat negative sk_forward_alloc as 0 available bytes.
> >
> > Fixes: 849b425cd091 ("tcp: fix possible freeze in tx path under memory pressure")
> > Assisted-by: LLM
> > Signed-off-by: Octavian Purdila <tavip@google.com>
>
> Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
>
Reviewed-by: Eric Dumazet <edumazet@kernel.org>
> Basically sk->sk_forward_alloc should not be negative, and
> I was able to avoid that by refactoring tcp_collapse(), but it was
> a bit invasive for stable and late in rc8, so I'll post it in the next cycle.
>
> $ git diff --stat HEAD~4
> net/ipv4/tcp_input.c | 158 ...
> 1 file changed, 109 insertions(+), 49 deletions(-)
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH net 2/2] selftests: net: packetdrill: add test for negative sk_forward_alloc in tcp_wmem_schedule()
2026-10-16 7:54 ` Kuniyuki Iwashima
@ 2026-10-16 8:22 ` Eric Dumazet
2026-10-16 8:23 ` Eric Dumazet
0 siblings, 1 reply; 12+ messages in thread
From: Eric Dumazet @ 2026-10-16 8:22 UTC (permalink / raw)
To: Kuniyuki Iwashima
Cc: Octavian Purdila, Neal Cardwell, David S . Miller, Jakub Kicinski,
Paolo Abeni, Simon Horman, Shuah Khan, Willem de Bruijn,
Soheil Hassas Yeganeh, Wei Wang, Shakeel Butt, netdev,
linux-kselftest, linux-kernel
Le ven. 16 oct. 2026 à 09:54, Kuniyuki Iwashima <kuniyu@google.com> a écrit :
>
> On Thu, Oct 15, 2026 at 12:37 AM Octavian Purdila <tavip@google.com> wrote:
> >
> > Add a packetdrill test that drives sk->sk_forward_alloc negative via
> > tcp_collapse() on the receive queue while sk->sk_wmem_queued exceeds
> > sysctl_tcp_wmem[0], and verifies that a subsequent non-blocking write()
> > under TCP memory pressure fails with EAGAIN rather than returning a
> > negative copy length from tcp_wmem_schedule() and failing with EFAULT.
> >
> > Assisted-by: LLM
> > Signed-off-by: Octavian Purdila <tavip@google.com>
>
> Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
>
> Thanks !
Are you sure this test is passing on arches with 6K pages (as some
arm64 and ppc64 platforms) ?
We might need to add a check to only run it on 4K platforms.
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH net 2/2] selftests: net: packetdrill: add test for negative sk_forward_alloc in tcp_wmem_schedule()
2026-10-16 8:22 ` Eric Dumazet
@ 2026-10-16 8:23 ` Eric Dumazet
2026-10-16 8:59 ` Octavian Purdila
0 siblings, 1 reply; 12+ messages in thread
From: Eric Dumazet @ 2026-10-16 8:23 UTC (permalink / raw)
To: Kuniyuki Iwashima
Cc: Octavian Purdila, Neal Cardwell, David S . Miller, Jakub Kicinski,
Paolo Abeni, Simon Horman, Shuah Khan, Willem de Bruijn, Wei Wang,
Shakeel Butt, netdev, linux-kselftest, linux-kernel
Le ven. 16 oct. 2026 à 10:22, Eric Dumazet <edumazet@kernel.org> a écrit :
>
> Le ven. 16 oct. 2026 à 09:54, Kuniyuki Iwashima <kuniyu@google.com> a écrit :
> >
> > On Thu, Oct 15, 2026 at 12:37 AM Octavian Purdila <tavip@google.com> wrote:
> > >
> > > Add a packetdrill test that drives sk->sk_forward_alloc negative via
> > > tcp_collapse() on the receive queue while sk->sk_wmem_queued exceeds
> > > sysctl_tcp_wmem[0], and verifies that a subsequent non-blocking write()
> > > under TCP memory pressure fails with EAGAIN rather than returning a
> > > negative copy length from tcp_wmem_schedule() and failing with EFAULT.
> > >
> > > Assisted-by: LLM
> > > Signed-off-by: Octavian Purdila <tavip@google.com>
> >
> > Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
> >
> > Thanks !
>
> Are you sure this test is passing on arches with 6K pages (as some
> arm64 and ppc64 platforms) ?
This was meant to read : 64K pages.
> We might need to add a check to only run it on 4K platforms.
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH net 2/2] selftests: net: packetdrill: add test for negative sk_forward_alloc in tcp_wmem_schedule()
2026-10-16 8:23 ` Eric Dumazet
@ 2026-10-16 8:59 ` Octavian Purdila
0 siblings, 0 replies; 12+ messages in thread
From: Octavian Purdila @ 2026-10-16 8:59 UTC (permalink / raw)
To: Eric Dumazet
Cc: Kuniyuki Iwashima, Neal Cardwell, David S . Miller,
Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan,
Willem de Bruijn, Wei Wang, Shakeel Butt, netdev, linux-kselftest,
linux-kernel
On Fri, Oct 16, 2026 at 1:23 AM Eric Dumazet <edumazet@kernel.org> wrote:
>
> Le ven. 16 oct. 2026 à 10:22, Eric Dumazet <edumazet@kernel.org> a écrit :
> >
> > Le ven. 16 oct. 2026 à 09:54, Kuniyuki Iwashima <kuniyu@google.com> a écrit :
> > >
> > > On Thu, Oct 15, 2026 at 12:37 AM Octavian Purdila <tavip@google.com> wrote:
> > > >
> > > > Add a packetdrill test that drives sk->sk_forward_alloc negative via
> > > > tcp_collapse() on the receive queue while sk->sk_wmem_queued exceeds
> > > > sysctl_tcp_wmem[0], and verifies that a subsequent non-blocking write()
> > > > under TCP memory pressure fails with EAGAIN rather than returning a
> > > > negative copy length from tcp_wmem_schedule() and failing with EFAULT.
> > > >
> > > > Assisted-by: LLM
> > > > Signed-off-by: Octavian Purdila <tavip@google.com>
> > >
> > > Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
> > >
> > > Thanks !
> >
> > Are you sure this test is passing on arches with 6K pages (as some
> > arm64 and ppc64 platforms) ?
>
> This was meant to read : 64K pages.
>
> > We might need to add a check to only run it on 4K platforms.
Ah, yes it would not pass. Thanks! I plan to add a check like the one
below in v2:
diff --git a/tools/testing/selftests/net/packetdrill/ksft_runner.sh
b/tools/testing/selftests/net/packetdrill/ksft_runner.sh
index 0a97d5ae34699..e2a418357bf1c 100755
--- a/tools/testing/selftests/net/packetdrill/ksft_runner.sh
+++ b/tools/testing/selftests/net/packetdrill/ksft_runner.sh
@@ -52,6 +52,12 @@ if [[ -n "${KSFT_MACHINE_SLOW}" ]]; then
failfunc=ktap_test_xfail
fi
+pagesize=$(grep -E '^--define=REQUIRES_PAGESIZE=' $script | cut -d
'=' -f 3 | tr -d '"')
+if [[ -n "$pagesize" && "$(getconf PAGESIZE)" != "$pagesize" ]]; then
+ ktap_skip_all "requires ${pagesize}B PAGE_SIZE"
+ exit "$KSFT_SKIP"
+fi
+
ip_versions=$(grep -E '^--ip_version=' $script | cut -d '=' -f 2)
if [[ -z $ip_versions ]]; then
ip_versions="ipv4 ipv6 ipv4-mapped-ipv6"
diff --git a/tools/testing/selftests/net/packetdrill/tcp_wmem_schedule_negative_fwd_alloc.pkt
b/tools/testing/selftests/net/packetdrill/tcp_wmem_schedule_negative_fwd_alloc.pkt
index 8e31db546f432..5e6a40e740b42 100644
--- a/tools/testing/selftests/net/packetdrill/tcp_wmem_schedule_negative_fwd_alloc.pkt
+++ b/tools/testing/selftests/net/packetdrill/tcp_wmem_schedule_negative_fwd_alloc.pkt
@@ -3,6 +3,7 @@
// when sk_wmem_schedule() fails under memory pressure and
// sk->sk_wmem_queued >= sysctl_tcp_wmem[0], returning EAGAIN rather
than EFAULT.
+--define=REQUIRES_PAGESIZE="4096"
--mtu=65535
--tolerance_usecs=100000
^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH net 2/2] selftests: net: packetdrill: add test for negative sk_forward_alloc in tcp_wmem_schedule()
2026-10-15 7:37 ` [PATCH net 2/2] selftests: net: packetdrill: add test for negative sk_forward_alloc in tcp_wmem_schedule() Octavian Purdila
2026-10-16 7:54 ` Kuniyuki Iwashima
@ 2026-10-19 8:13 ` netdev-bot+sashiko
1 sibling, 0 replies; 12+ messages in thread
From: netdev-bot+sashiko @ 2026-10-19 8:13 UTC (permalink / raw)
To: tavip
Cc: edumazet, ncardwell, kuniyu, davem, kuba, pabeni, horms, shuah,
willemb, soheil, weiwan, shakeel.butt, netdev, linux-kselftest,
linux-kernel
Thank you for your contribution! Sashiko AI review found 6 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 3 · Low: 3
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] When cgroup2 or memcg is missing, the new test reports FAIL
instead of SKIP.
- [Medium] The test relies on SO_SNDBUF=2097152 (line 38) and
SO_RCVBUF=145536 (line 39) taking effect exactly.
- [Medium] The memcg cap at step 4 is racy, and the comment at line 32
('so only the sockets remain charged to pkt_${PPID}') is wrong.
- [Low] tools/testing/selftests/net/packetdrill/config adds
`CONFIG_MEMCG=y` but not `CONFIG_CGROUPS=y`.
- [Low] The init command enables the memory controller for all children
of the cgroup2 root (`echo +memory >…
- [Low] Teardown depends on packetdrill exiting normally.
--
selftests: net: packetdrill: add test for negative sk_forward_alloc in tcp_wmem_schedule()
This adds a packetdrill test that drives sk_forward_alloc negative via
tcp_collapse() while sk_wmem_queued exceeds tcp_wmem[0]. It then caps the
socket's memcg and checks that a non-blocking write() fails with EAGAIN
rather than EFAULT. CONFIG_MEMCG=y is also added to the packetdrill config
fragment.
> diff --git a/tools/testing/selftests/net/packetdrill/config b/tools/testing/selftests/net/packetdrill/config
> index b7df8bf30920..dc3cd72a8da8 100644
> --- a/tools/testing/selftests/net/packetdrill/config
> +++ b/tools/testing/selftests/net/packetdrill/config
> @@ -1,6 +1,7 @@
> CONFIG_HZ=1000
> CONFIG_HZ_1000=y
> CONFIG_IPV6=y
> +CONFIG_MEMCG=y
[Severity: Low]
Should this fragment also add CONFIG_CGROUPS=y?
In init/Kconfig, MEMCG sits inside the CGROUPS block:
menuconfig CGROUPS
...
if CGROUPS
...
config MEMCG
bool "Memory controller"
If this fragment is merged onto a base config with CGROUPS=n,
merge_config only prints a warning and MEMCG is dropped. The new test
then has no memory controller to use.
Other fragments that need memcg, such as
tools/testing/selftests/cgroup/config, list both CONFIG_CGROUPS=y and
CONFIG_MEMCG=y.
[ ... ]
> diff --git a/tools/testing/selftests/net/packetdrill/tcp_wmem_schedule_negative_fwd_alloc.pkt b/tools/testing/selftests/net/packetdrill/tcp_wmem_schedule_negative_fwd_alloc.pkt
> new file mode 100644
> index 000000000000..db232b54c554
> --- /dev/null
> +++ b/tools/testing/selftests/net/packetdrill/tcp_wmem_schedule_negative_fwd_alloc.pkt
> @@ -0,0 +1,116 @@
[ ... ]
> +`set -e
> +./defaults.sh
> +ip link set dev tun0 mtu 65535
> +sysctl -q net.ipv4.tcp_rmem="4096 131072 6291456"
> +sysctl -q net.ipv4.tcp_wmem="4096 16384 4194304"
> +cgroot=$(mount -t cgroup2 | head -1 | awk '{print $3}')
> +[ -n "$cgroot" ]
[Severity: Medium]
What happens on systems where this check or the cgroup writes below
fail?
The init command runs under set -e. Any of these makes it exit non-zero
and abort packetdrill:
- no cgroup2 mount (hybrid v1 setups, minimal VMs), so the
[ -n "$cgroot" ] check fails
- the memory controller is bound to cgroup v1, so the +memory write
gets ENOENT
- the test runs in a container whose cgroup namespace root holds
processes, so the +memory write gets EBUSY
- cgroupfs is read-only, so the writes and the mkdir fail
ksft_runner.sh treats any non-zero packetdrill exit as a failure:
&& ktap_test_pass $ip_version || $failfunc $ip_version
Here failfunc=ktap_test_fail. A missing optional feature is therefore
reported as three failures (ipv4, ipv6, ipv4-mapped-ipv6) instead of a
skip. No other packetdrill test depends on cgroups.
Could these prerequisites be detected and reported as a skip?
> +echo +memory > "${cgroot}/cgroup.subtree_control"
[Severity: Low]
Does anything turn the memory controller off at the root again?
The cleanup command at the end of the script moves tasks back and runs
rmdir "${cgroot}/pkt_${PPID}". It never writes -memory. On hosts where
memory was not already enabled in the root cgroup.subtree_control, memcg
accounting stays on for every existing and future top-level cgroup after
the test finishes.
Everything else the test changes is either per-netns or removed
explicitly. This looks like the only setup step with no matching
teardown.
Could the previous state be saved here and restored in cleanup?
> +mkdir -p "${cgroot}/pkt_${PPID}"
> +echo $PPID > "${cgroot}/pkt_${PPID}/cgroup.procs"
> +`
[Severity: Low]
Can a cgroup left over from an earlier run get reused here?
Packetdrill runs the cleanup command itself. If packetdrill is killed by
a signal (a kselftest timeout or SIGKILL), pkt_<pid> stays behind with
the limits set in step 4:
echo 0 > "${cgroot}/pkt_${PPID}/memory.high"
cat "${cgroot}/pkt_${PPID}/memory.current" > "${cgroot}/pkt_${PPID}/memory.max"
mkdir -p succeeds on an existing directory, and nothing resets
memory.high or memory.max before packetdrill is moved in. unshare -n
does not create a PID namespace. A later run that gets the same PID
would then do its socket setup and page faults under a near-zero cap.
Would a plain mkdir avoid this? Resetting memory.high and memory.max to
max after creating the directory would also work.
[ ... ]
> ++0.000 setsockopt(4, SOL_SOCKET, SO_SNDBUF, [2097152], 4) = 0
> ++0.000 setsockopt(4, SOL_SOCKET, SO_RCVBUF, [145536], 4) = 0
[Severity: Medium]
Does the test rely on these sizes being granted exactly?
sk_setsockopt() in net/core/sock.c clamps both values without reporting
it, and still returns 0:
val = min_t(u32, val, READ_ONCE(sysctl_wmem_max));
...
__sock_set_rcvbuf(sk, min_t(u32, val, READ_ONCE(sysctl_rmem_max)));
net.core.wmem_max and net.core.rmem_max are global and read-only inside
a child netns. The test cannot raise them from its unshare -n
environment.
Suppose the host has lowered net.core.wmem_max, for example to 212992,
which gives an sk_sndbuf of 425984. The blocking
write(4, ..., 500000) in step 2 would then sit in
sk_stream_wait_memory(). Every injected segment carries ack 1, so no
send space is ever freed, and the script never reaches step 4.
Even if it did reach step 4, __sk_mem_raise_allocated() force charges
in this case:
if (sk->sk_wmem_queued + size >= sk->sk_sndbuf) {
/* Force charge with __GFP_NOFAIL */
The final write would then succeed instead of returning EAGAIN. A
lowered rmem_max would likewise change the 291072 byte sk_rcvbuf that
the collapse step depends on.
Packetdrill runs privileged. Would SO_SNDBUFFORCE and SO_RCVBUFFORCE, or
a getsockopt() check, make this deterministic?
[ ... ]
> +// 4. Cap the socket memcg at its current usage and expect -EAGAIN on non-blocking write.
> ++0.010 `cgroot=$(mount -t cgroup2 | head -1 | awk '{print $3}')
> +echo 0 > "${cgroot}/pkt_${PPID}/memory.high"
> +cat "${cgroot}/pkt_${PPID}/memory.current" > "${cgroot}/pkt_${PPID}/memory.max"`
> ++0.000 fcntl(4, F_SETFL, O_RDWR|O_NONBLOCK) = 0
> ++0.000 write(4, ..., 16384) = -1 EAGAIN (Resource temporarily unavailable)
[Severity: Medium]
Is this cap reliable enough for the EAGAIN check?
An earlier comment in the script says:
// Move packetdrill back to its original cgroup so only the sockets remain charged to pkt_${PPID}
Cgroup v2 does not move existing charges when a task migrates. Whatever
packetdrill allocated while in pkt_${PPID} stays charged there. The same
goes for the sh, awk and sed helpers it forked. Any of those charges can
be released at a later point.
In mm/memcontrol.c, memory_high_write() calls drain_all_stock() once.
drain_all_stock() can skip the drain entirely, and for remote CPUs it
only queues work:
if (!mutex_trylock(&percpu_charge_mutex))
return;
...
else
schedule_drain_work(cpu, &memcg_st->work);
memory_max_write() returns straight away, because max equals the
current usage:
if (nr_pages <= max)
break;
try_charge_memcg() uses the per-CPU stock before checking any limit:
if (consume_stock(memcg, nr_pages))
return 0;
Stock left for pkt_${PPID} on packetdrill's CPU could satisfy the
16384 byte charge. So could a remote drain or an uncharge that completes
after memory.current is read. Either way the charge in
tcp_wmem_schedule()->sk_wmem_schedule()->__sk_mem_raise_allocated()
would succeed and write() would return data. The test would then fail
intermittently with no kernel bug involved.
On systems with swap, can memory.high=0 also swap out packetdrill's own
anon pages that are charged to pkt_${PPID}? Swapping them back in would
charge the capped memcg, which has no task that can be killed.
Packetdrill could then stall in repeated page fault OOM retries.
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261015073736.2944215-1-tavip%40google.com
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-10-19 8:13 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-15 7:37 [PATCH net 0/2] tcp: clamp return value of tcp_wmem_schedule() to non-negative Octavian Purdila
2026-10-15 7:37 ` [PATCH net 1/2] " Octavian Purdila
2026-10-16 7:42 ` Kuniyuki Iwashima
2026-10-16 8:19 ` Eric Dumazet
2026-10-15 7:37 ` [PATCH net 2/2] selftests: net: packetdrill: add test for negative sk_forward_alloc in tcp_wmem_schedule() Octavian Purdila
2026-10-16 7:54 ` Kuniyuki Iwashima
2026-10-16 8:22 ` Eric Dumazet
2026-10-16 8:23 ` Eric Dumazet
2026-10-16 8:59 ` Octavian Purdila
2026-10-19 8:13 ` netdev-bot+sashiko
2026-10-15 7:43 ` [PATCH net 0/2] tcp: clamp return value of tcp_wmem_schedule() to non-negative netdev-bot+sinfo
2026-10-15 7:56 ` Octavian Purdila
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox