* [BUG] bpf, sockmap: spurious wakeup by tcp_msg_wait_data() causing unexpected EAGAIN in recvfrom() @ 2026-07-09 18:43 Nnamdi Onyeyiri 2026-07-09 18:55 ` sashiko-bot 0 siblings, 1 reply; 3+ messages in thread From: Nnamdi Onyeyiri @ 2026-07-09 18:43 UTC (permalink / raw) To: nnamdio, nnamdi.onyeyiri, john.fastabend, jakub, jiayuan.chen, edumazet, ncardwell, kuniyu, davem, kuba, pabeni, horms Cc: netdev, bpf Hi, We've encounted what appears to be a bug with bpf when invoking recvfrom() on an ipv4 tcp socket that has been added to a sockmap. It results in unexpected EAGAIN errors, that we've diagnosed as the result of spurious wakeups from tcp_msg_wait_data(). This has been confirmed to still be present on the mainline kernel, and I have written a reproducer at: https://github.com/Nnamdi/recvfrom_sockmap_eagain/ Attched is a patch that we found resolved this using kpatch locally. It just causes spurious wakups to go round the loop again. I'd like to get a sense of whether this is expected behaviour, or really is a bug, in which case, is this the correct fix? Thanks, Nnamdi. ----8<---- From 0e0c342363b2e435297ab1feda402cde6ad54525 Mon Sep 17 00:00:00 2001 From: Nnamdi Onyeyiri <nnamdio@gmail.com> Date: Thu, 9 Jul 2026 13:06:33 +0100 Subject: [PATCH] bpf, sockmap: handle spurious tcp_msg_wait_data() wakeup recvfrom()/recv() are documented as only returning EAGAIN for blocking sockets when they have a receive timeout configured. however, adding a blocking ipv4 tcp socket without a receive timeout to a sockmap will cause EAGAIN errors sporadically. this happens when tcp_msg_wait_data() wakes spuriously (returning 0) in which case, if no receive timeout is configured, we loop again instead of returning -EAGAIN. Signed-off-by: Nnamdi Onyeyiri <nnamdio@gmail.com> --- net/ipv4/tcp_bpf.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c index cc0bd73f3..38fd391ff 100644 --- a/net/ipv4/tcp_bpf.c +++ b/net/ipv4/tcp_bpf.c @@ -317,6 +317,8 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk, } if (data && !sk_psock_queue_empty(psock)) goto msg_bytes_ready; + if (!data && timeo == MAX_SCHEDULE_TIMEOUT) + goto msg_bytes_ready; copied = -EAGAIN; } out: @@ -390,6 +392,8 @@ static int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, sk_psock_put(sk, psock); return tcp_recvmsg(sk, msg, len, flags); } + if (!data && timeo == MAX_SCHEDULE_TIMEOUT) + goto msg_bytes_ready; copied = -EAGAIN; } ret = copied; -- 2.52.0 ^ permalink raw reply related [flat|nested] 3+ messages in thread
* Re: [BUG] bpf, sockmap: spurious wakeup by tcp_msg_wait_data() causing unexpected EAGAIN in recvfrom() 2026-07-09 18:43 [BUG] bpf, sockmap: spurious wakeup by tcp_msg_wait_data() causing unexpected EAGAIN in recvfrom() Nnamdi Onyeyiri @ 2026-07-09 18:55 ` sashiko-bot 2026-07-10 20:17 ` Nnamdi Onyeyiri 0 siblings, 1 reply; 3+ messages in thread From: sashiko-bot @ 2026-07-09 18:55 UTC (permalink / raw) To: Nnamdi Onyeyiri; +Cc: bpf Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: New issues: - [Critical] The patch introduces an unkillable infinite kernel loop (DoS) in `tcp_bpf_recvmsg` when a blocking socket without a receive timeout receives a signal. Pre-existing issues: - [High] A race condition during stream parsing causes blocking sockets to prematurely return `-EAGAIN` when data is present. - [High] The patch fails to fix spurious wakeups for sockets with finite receive timeouts (SO_RCVTIMEO), leaving them susceptible to premature -EAGAIN returns. -- --- Patch [1]: [BUG] bpf, sockmap: spurious wakeup by tcp_msg_wait_data() causing unexpected EAGAIN in recvfrom() --- commit 39fdd170f1532151400920cba0bc08398cb63cb8 Author: Nnamdi Onyeyiri <nnamdio@gmail.com> bpf, sockmap: handle spurious tcp_msg_wait_data() wakeup This patch attempts to resolve unexpected EAGAIN errors on blocking ipv4 tcp sockets added to a sockmap by looping on spurious wakeups. It adds checks in tcp_bpf_recvmsg and tcp_bpf_recvmsg_parser to jump back to msg_bytes_ready when no data is found and there is no receive timeout configured. > diff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c > index cc0bd73f36b6d..38fd391ffdb31 100644 > --- a/net/ipv4/tcp_bpf.c > +++ b/net/ipv4/tcp_bpf.c > @@ -317,6 +317,8 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk, > } > if (data && !sk_psock_queue_empty(psock)) > goto msg_bytes_ready; [Severity: High] This is a pre-existing issue, but does this logic incorrectly assume that if data is present, the parsed data is already in the psock? If data arrives in sk_receive_queue and triggers a wakeup, but the thread re-acquires lock_sock before the stream parser can execute and populate psock->ingress_msg, we bypass the retry and erroneously return -EAGAIN. > + if (!data && timeo == MAX_SCHEDULE_TIMEOUT) > + goto msg_bytes_ready; [Severity: High] This is a pre-existing issue, but does this skip the fix for sockets configured with finite timeouts (SO_RCVTIMEO)? By explicitly checking for MAX_SCHEDULE_TIMEOUT, finite timeouts will still randomly fail with -EAGAIN on spurious wakeups. Additionally, because tcp_msg_wait_data() takes timeo by value, the remaining time calculated by sk_wait_event() is discarded. If this loop were enabled for finite timeouts, it would reset the timeout on every iteration. [ ... ] > @@ -390,6 +392,8 @@ static int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, > sk_psock_put(sk, psock); > return tcp_recvmsg(sk, msg, len, flags); > } > + if (!data && timeo == MAX_SCHEDULE_TIMEOUT) > + goto msg_bytes_ready; [Severity: Critical] Could this new unconditional branch cause an infinite kernel loop if the process receives a signal? Unlike tcp_bpf_recvmsg_parser(), tcp_bpf_recvmsg() lacks a signal_pending(current) check inside its loop. If a blocking socket without a timeout receives a signal, sk_wait_event() returns immediately without sleeping. The new check then jumps back to msg_bytes_ready, causing the kernel to spin indefinitely without delivering the signal, resulting in a denial of service. > copied = -EAGAIN; > } > ret = copied; -- Sashiko AI review · https://sashiko.dev/#/patchset/ak_rR-Skd8Mvn4mH@localhost.localdomain?part=1 ^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [BUG] bpf, sockmap: spurious wakeup by tcp_msg_wait_data() causing unexpected EAGAIN in recvfrom() 2026-07-09 18:55 ` sashiko-bot @ 2026-07-10 20:17 ` Nnamdi Onyeyiri 0 siblings, 0 replies; 3+ messages in thread From: Nnamdi Onyeyiri @ 2026-07-10 20:17 UTC (permalink / raw) To: sashiko-reviews Cc: bpf, netdev, john.fastabend, jakub, jiayuan.chen, edumazet, ncardwell, kuniyu, davem, kuba, pabeni, horms Hi, The updated patch below addresses the issues raised by sashiko-bot. The closed socket and signal handling code was added to tcp_bpf_recvmsg(), and the fix was updated to work for sockets with SO_RCVTIMEO set. Please let me know if any more changes are required, or if the patch would need to be submitted some other way, I'm happy to adjust as necessary. Thanks! ----8<---- From 20a1275aa5ecb927ff049971454b62682356ef0e Mon Sep 17 00:00:00 2001 From: Nnamdi Onyeyiri <nnamdio@gmail.com> Date: Fri, 10 Jul 2026 20:47:36 +0100 Subject: [PATCH v2 1/1] bpf, sockmap: handle spurious tcp_msg_wait_data() wakeup recvfrom()/recv() are documented as only returning EAGAIN for blocking sockets when they have a receive timeout configured. however, adding a blocking ipv4 tcp socket without a receive timeout to a sockmap will cause EAGAIN errors sporadically. a socket with a receive timeout may return EAGAIN before the timeout expires. this happens when tcp_msg_wait_data() wakes spuriously (returning 0) in which case, if there is no timeout, or the timeout has not yet expired, we loop again instead of returning. Signed-off-by: Nnamdi Onyeyiri <nnamdio@gmail.com> --- net/ipv4/tcp_bpf.c | 55 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c index cc0bd73f36b6..e3109edb9621 100644 --- a/net/ipv4/tcp_bpf.c +++ b/net/ipv4/tcp_bpf.c @@ -179,7 +179,7 @@ EXPORT_SYMBOL_GPL(tcp_bpf_sendmsg_redir); #ifdef CONFIG_BPF_SYSCALL static int tcp_msg_wait_data(struct sock *sk, struct sk_psock *psock, - long timeo) + long *timeo) { DEFINE_WAIT_FUNC(wait, woken_wake_function); int ret = 0; @@ -187,12 +187,12 @@ static int tcp_msg_wait_data(struct sock *sk, struct sk_psock *psock, if (sk->sk_shutdown & RCV_SHUTDOWN) return 1; - if (!timeo) + if (!(*timeo)) return ret; add_wait_queue(sk_sleep(sk), &wait); sk_set_bit(SOCKWQ_ASYNC_WAITDATA, sk); - ret = sk_wait_event(sk, &timeo, + ret = sk_wait_event(sk, timeo, !list_empty(&psock->ingress_msg) || !skb_queue_empty_lockless(&sk->sk_receive_queue), &wait); sk_clear_bit(SOCKWQ_ASYNC_WAITDATA, sk); @@ -229,6 +229,7 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk, int copied_from_self = 0; int copied = 0; u32 seq; + long timeo; if (unlikely(flags & MSG_ERRQUEUE)) return inet_recv_error(sk, msg, len); @@ -262,6 +263,8 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk, } } + timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT); + msg_bytes_ready: copied = __sk_msg_recvmsg(sk, psock, msg, len, flags, &copied_from_self); /* The typical case for EFAULT is the socket was gracefully @@ -280,7 +283,6 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk, } seq += copied_from_self; if (!copied) { - long timeo; int data; if (sock_flag(sk, SOCK_DONE)) @@ -299,7 +301,6 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk, goto out; } - timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT); if (!timeo) { copied = -EAGAIN; goto out; @@ -310,13 +311,15 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk, goto out; } - data = tcp_msg_wait_data(sk, psock, timeo); + data = tcp_msg_wait_data(sk, psock, &timeo); if (data < 0) { copied = data; goto unlock; } if (data && !sk_psock_queue_empty(psock)) goto msg_bytes_ready; + if (!data && timeo > 0) + goto msg_bytes_ready; copied = -EAGAIN; } out: @@ -355,6 +358,7 @@ static int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, { struct sk_psock *psock; int copied, ret; + long timeo; if (unlikely(flags & MSG_ERRQUEUE)) return inet_recv_error(sk, msg, len); @@ -371,14 +375,45 @@ static int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, return tcp_recvmsg(sk, msg, len, flags); } lock_sock(sk); + + timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT); + msg_bytes_ready: copied = sk_msg_recvmsg(sk, psock, msg, len, flags); if (!copied) { - long timeo; int data; - timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT); - data = tcp_msg_wait_data(sk, psock, timeo); + if (sock_flag(sk, SOCK_DONE)) { + ret = 0; + goto unlock; + } + + if (sk->sk_err) { + ret = sock_error(sk); + goto unlock; + } + + if (sk->sk_shutdown & RCV_SHUTDOWN) { + ret = 0; + goto unlock; + } + + if (sk->sk_state == TCP_CLOSE) { + ret = -ENOTCONN; + goto unlock; + } + + if (!timeo) { + ret = -EAGAIN; + goto unlock; + } + + if (signal_pending(current)) { + ret = sock_intr_errno(timeo); + goto unlock; + } + + data = tcp_msg_wait_data(sk, psock, &timeo); if (data < 0) { ret = data; goto unlock; @@ -390,6 +425,8 @@ static int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, sk_psock_put(sk, psock); return tcp_recvmsg(sk, msg, len, flags); } + if (!data && timeo > 0) + goto msg_bytes_ready; copied = -EAGAIN; } ret = copied; -- 2.52.0 ^ permalink raw reply related [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-07-10 20:17 UTC | newest] Thread overview: 3+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-07-09 18:43 [BUG] bpf, sockmap: spurious wakeup by tcp_msg_wait_data() causing unexpected EAGAIN in recvfrom() Nnamdi Onyeyiri 2026-07-09 18:55 ` sashiko-bot 2026-07-10 20:17 ` Nnamdi Onyeyiri
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox