* [PATCH net] tcp: purge Fast Open RST queue when listen() fails
@ 2026-11-14 11:12 Eric Dumazet
2026-11-14 16:14 ` Hyunwoo Kim
2026-11-15 21:17 ` netdev-bot+sashiko
0 siblings, 2 replies; 4+ messages in thread
From: Eric Dumazet @ 2026-11-14 11:12 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni, Kuniyuki Iwashima,
Neal Cardwell
Cc: Simon Horman, netdev, Eric Dumazet, Sashiko AI review,
Hyunwoo Kim
inet_csk_listen_start() moves the socket to TCP_LISTEN before calling
->get_port() and ->hash(). If one of them fails, the socket goes back
to TCP_CLOSE without going through inet_csk_listen_stop().
If the socket was a Fast Open listener in a previous life, and was
stopped with shutdown(SHUT_RD) or connect(AF_UNSPEC) while an accepted
Fast Open child was still in TCP_SYN_RECV state, the request_sock of
this child still points to the listener.
A valid RST received for this child while the listener is transiently
in TCP_LISTEN state makes reqsk_fastopen_remove() queue the
request_sock in rskq_rst_head. When listen() fails, for instance with
-EADDRINUSE because another SO_REUSEADDR socket started listening on
the same port, nothing reclaims it:
- __tcp_close(), tcp_disconnect() and tcp_abort() only call
inet_csk_listen_stop() for sockets in TCP_LISTEN state.
- A later successful listen() overwrites rskq_rst_head in
reqsk_queue_alloc().
The request_sock is leaked, together with the reference it holds
on the listener.
Move the rskq_rst_head purge to a helper, and call it from the
inet_csk_listen_start() error path as well, after the socket state
has been set back to TCP_CLOSE: reqsk_fastopen_remove() checks
sk_state under fastopenq.lock, so it either queued the request before
the purge acquires the lock, or does not queue it at all.
Reported-by: Sashiko AI review <netdev-bot+sashiko@kernel.org>
Closes: https://lore.kernel.org/netdev/179450310167.2199510.18107423940014989443@kernel.org/
Fixes: 0536fcc039a8 ("tcp: prepare fastopen code for upcoming listener changes")
Signed-off-by: Eric Dumazet <edumazet@kernel.org>
---
Cc: Hyunwoo Kim <imv4bel@gmail.com>
---
net/ipv4/inet_connection_sock.c | 35 ++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c
index ca3057192e6d..c33b0095468a 100644
--- a/net/ipv4/inet_connection_sock.c
+++ b/net/ipv4/inet_connection_sock.c
@@ -1331,6 +1331,22 @@ static int inet_ulp_can_listen(const struct sock *sk)
return 0;
}
+/* Free all the reqs queued in rskq_rst_head. */
+static void reqsk_fastopen_purge_rst(struct request_sock_queue *queue)
+{
+ struct request_sock *next, *req;
+
+ spin_lock_bh(&queue->fastopenq.lock);
+ req = queue->fastopenq.rskq_rst_head;
+ queue->fastopenq.rskq_rst_head = NULL;
+ spin_unlock_bh(&queue->fastopenq.lock);
+ while (req != NULL) {
+ next = req->dl_next;
+ reqsk_put(req);
+ req = next;
+ }
+}
+
static void reqsk_queue_alloc(struct request_sock_queue *queue)
{
queue->fastopenq.rskq_rst_head = NULL;
@@ -1373,6 +1389,12 @@ int inet_csk_listen_start(struct sock *sk)
}
inet_sk_set_state(sk, TCP_CLOSE);
+
+ /* A Fast Open child accepted before a previous shutdown() of this
+ * listener might have queued its request in rskq_rst_head while
+ * we were transiently in TCP_LISTEN state.
+ */
+ reqsk_fastopen_purge_rst(&icsk->icsk_accept_queue);
return err;
}
@@ -1477,7 +1499,7 @@ void inet_csk_listen_stop(struct sock *sk)
{
struct inet_connection_sock *icsk = inet_csk(sk);
struct request_sock_queue *queue = &icsk->icsk_accept_queue;
- struct request_sock *next, *req;
+ struct request_sock *req;
/* Following specs, it would be better either to send FIN
* (and enter FIN-WAIT-1, it is normal close)
@@ -1533,16 +1555,7 @@ void inet_csk_listen_stop(struct sock *sk)
if (!has_current_bpf_ctx())
cond_resched();
}
- /* Free all the reqs queued in rskq_rst_head. */
- spin_lock_bh(&queue->fastopenq.lock);
- req = queue->fastopenq.rskq_rst_head;
- queue->fastopenq.rskq_rst_head = NULL;
- spin_unlock_bh(&queue->fastopenq.lock);
- while (req != NULL) {
- next = req->dl_next;
- reqsk_put(req);
- req = next;
- }
+ reqsk_fastopen_purge_rst(queue);
WARN_ON_ONCE(sk->sk_ack_backlog);
}
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH net] tcp: purge Fast Open RST queue when listen() fails
2026-11-14 11:12 [PATCH net] tcp: purge Fast Open RST queue when listen() fails Eric Dumazet
@ 2026-11-14 16:14 ` Hyunwoo Kim
2026-11-14 17:06 ` Kuniyuki Iwashima
2026-11-15 21:17 ` netdev-bot+sashiko
1 sibling, 1 reply; 4+ messages in thread
From: Hyunwoo Kim @ 2026-11-14 16:14 UTC (permalink / raw)
To: Eric Dumazet
Cc: David S . Miller, Jakub Kicinski, Paolo Abeni, Kuniyuki Iwashima,
Neal Cardwell, Simon Horman, netdev, Sashiko AI review, imv4bel
On Sat, Nov 14, 2026 at 12:12:47PM +0100, Eric Dumazet wrote:
> inet_csk_listen_start() moves the socket to TCP_LISTEN before calling
> ->get_port() and ->hash(). If one of them fails, the socket goes back
> to TCP_CLOSE without going through inet_csk_listen_stop().
>
> If the socket was a Fast Open listener in a previous life, and was
> stopped with shutdown(SHUT_RD) or connect(AF_UNSPEC) while an accepted
> Fast Open child was still in TCP_SYN_RECV state, the request_sock of
> this child still points to the listener.
>
> A valid RST received for this child while the listener is transiently
> in TCP_LISTEN state makes reqsk_fastopen_remove() queue the
> request_sock in rskq_rst_head. When listen() fails, for instance with
> -EADDRINUSE because another SO_REUSEADDR socket started listening on
> the same port, nothing reclaims it:
>
> - __tcp_close(), tcp_disconnect() and tcp_abort() only call
> inet_csk_listen_stop() for sockets in TCP_LISTEN state.
>
> - A later successful listen() overwrites rskq_rst_head in
> reqsk_queue_alloc().
>
> The request_sock is leaked, together with the reference it holds
> on the listener.
>
> Move the rskq_rst_head purge to a helper, and call it from the
> inet_csk_listen_start() error path as well, after the socket state
> has been set back to TCP_CLOSE: reqsk_fastopen_remove() checks
> sk_state under fastopenq.lock, so it either queued the request before
> the purge acquires the lock, or does not queue it at all.
>
> Reported-by: Sashiko AI review <netdev-bot+sashiko@kernel.org>
> Closes: https://lore.kernel.org/netdev/179450310167.2199510.18107423940014989443@kernel.org/
> Fixes: 0536fcc039a8 ("tcp: prepare fastopen code for upcoming listener changes")
> Signed-off-by: Eric Dumazet <edumazet@kernel.org>
Reviewed-by: Hyunwoo Kim <imv4bel@gmail.com>
LGTM.
> ---
> Cc: Hyunwoo Kim <imv4bel@gmail.com>
> ---
> net/ipv4/inet_connection_sock.c | 35 ++++++++++++++++++++++-----------
> 1 file changed, 24 insertions(+), 11 deletions(-)
>
> diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c
> index ca3057192e6d..c33b0095468a 100644
> --- a/net/ipv4/inet_connection_sock.c
> +++ b/net/ipv4/inet_connection_sock.c
> @@ -1331,6 +1331,22 @@ static int inet_ulp_can_listen(const struct sock *sk)
> return 0;
> }
>
> +/* Free all the reqs queued in rskq_rst_head. */
> +static void reqsk_fastopen_purge_rst(struct request_sock_queue *queue)
> +{
> + struct request_sock *next, *req;
> +
> + spin_lock_bh(&queue->fastopenq.lock);
> + req = queue->fastopenq.rskq_rst_head;
> + queue->fastopenq.rskq_rst_head = NULL;
> + spin_unlock_bh(&queue->fastopenq.lock);
> + while (req != NULL) {
> + next = req->dl_next;
> + reqsk_put(req);
> + req = next;
> + }
> +}
> +
> static void reqsk_queue_alloc(struct request_sock_queue *queue)
> {
> queue->fastopenq.rskq_rst_head = NULL;
> @@ -1373,6 +1389,12 @@ int inet_csk_listen_start(struct sock *sk)
> }
>
> inet_sk_set_state(sk, TCP_CLOSE);
> +
> + /* A Fast Open child accepted before a previous shutdown() of this
> + * listener might have queued its request in rskq_rst_head while
> + * we were transiently in TCP_LISTEN state.
> + */
> + reqsk_fastopen_purge_rst(&icsk->icsk_accept_queue);
> return err;
> }
>
> @@ -1477,7 +1499,7 @@ void inet_csk_listen_stop(struct sock *sk)
> {
> struct inet_connection_sock *icsk = inet_csk(sk);
> struct request_sock_queue *queue = &icsk->icsk_accept_queue;
> - struct request_sock *next, *req;
> + struct request_sock *req;
>
> /* Following specs, it would be better either to send FIN
> * (and enter FIN-WAIT-1, it is normal close)
> @@ -1533,16 +1555,7 @@ void inet_csk_listen_stop(struct sock *sk)
> if (!has_current_bpf_ctx())
> cond_resched();
> }
> - /* Free all the reqs queued in rskq_rst_head. */
> - spin_lock_bh(&queue->fastopenq.lock);
> - req = queue->fastopenq.rskq_rst_head;
> - queue->fastopenq.rskq_rst_head = NULL;
> - spin_unlock_bh(&queue->fastopenq.lock);
> - while (req != NULL) {
> - next = req->dl_next;
> - reqsk_put(req);
> - req = next;
> - }
> + reqsk_fastopen_purge_rst(queue);
> WARN_ON_ONCE(sk->sk_ack_backlog);
> }
>
> --
> 2.53.0
>
Best regards,
Hyunwoo Kim
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH net] tcp: purge Fast Open RST queue when listen() fails
2026-11-14 16:14 ` Hyunwoo Kim
@ 2026-11-14 17:06 ` Kuniyuki Iwashima
0 siblings, 0 replies; 4+ messages in thread
From: Kuniyuki Iwashima @ 2026-11-14 17:06 UTC (permalink / raw)
To: Hyunwoo Kim
Cc: Eric Dumazet, David S . Miller, Jakub Kicinski, Paolo Abeni,
Neal Cardwell, Simon Horman, netdev, Sashiko AI review
On Sat, Nov 14, 2026 at 8:14 AM Hyunwoo Kim <imv4bel@gmail.com> wrote:
>
> On Sat, Nov 14, 2026 at 12:12:47PM +0100, Eric Dumazet wrote:
> > inet_csk_listen_start() moves the socket to TCP_LISTEN before calling
> > ->get_port() and ->hash(). If one of them fails, the socket goes back
> > to TCP_CLOSE without going through inet_csk_listen_stop().
> >
> > If the socket was a Fast Open listener in a previous life, and was
> > stopped with shutdown(SHUT_RD) or connect(AF_UNSPEC) while an accepted
> > Fast Open child was still in TCP_SYN_RECV state, the request_sock of
> > this child still points to the listener.
> >
> > A valid RST received for this child while the listener is transiently
> > in TCP_LISTEN state makes reqsk_fastopen_remove() queue the
> > request_sock in rskq_rst_head. When listen() fails, for instance with
> > -EADDRINUSE because another SO_REUSEADDR socket started listening on
> > the same port, nothing reclaims it:
> >
> > - __tcp_close(), tcp_disconnect() and tcp_abort() only call
> > inet_csk_listen_stop() for sockets in TCP_LISTEN state.
> >
> > - A later successful listen() overwrites rskq_rst_head in
> > reqsk_queue_alloc().
> >
> > The request_sock is leaked, together with the reference it holds
> > on the listener.
> >
> > Move the rskq_rst_head purge to a helper, and call it from the
> > inet_csk_listen_start() error path as well, after the socket state
> > has been set back to TCP_CLOSE: reqsk_fastopen_remove() checks
> > sk_state under fastopenq.lock, so it either queued the request before
> > the purge acquires the lock, or does not queue it at all.
> >
> > Reported-by: Sashiko AI review <netdev-bot+sashiko@kernel.org>
> > Closes: https://lore.kernel.org/netdev/179450310167.2199510.18107423940014989443@kernel.org/
> > Fixes: 0536fcc039a8 ("tcp: prepare fastopen code for upcoming listener changes")
> > Signed-off-by: Eric Dumazet <edumazet@kernel.org>
>
> Reviewed-by: Hyunwoo Kim <imv4bel@gmail.com>
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH net] tcp: purge Fast Open RST queue when listen() fails
2026-11-14 11:12 [PATCH net] tcp: purge Fast Open RST queue when listen() fails Eric Dumazet
2026-11-14 16:14 ` Hyunwoo Kim
@ 2026-11-15 21:17 ` netdev-bot+sashiko
1 sibling, 0 replies; 4+ messages in thread
From: netdev-bot+sashiko @ 2026-11-15 21:17 UTC (permalink / raw)
To: edumazet
Cc: davem, kuba, pabeni, kuniyu, ncardwell, horms, netdev,
netdev-bot+sashiko, imv4bel
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 0 · Low: 0
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).
- [High] The fix is incomplete.
--
tcp: purge Fast Open RST queue when listen() fails
This patch moves the rskq_rst_head purge out of inet_csk_listen_stop()
into a new helper, reqsk_fastopen_purge_rst(). It also calls that helper
from the inet_csk_listen_start() error path, after the socket has been
set back to TCP_CLOSE. The goal is to reclaim Fast Open request_socks
that were queued while the socket was briefly in TCP_LISTEN.
Closes: https://lore.kernel.org/netdev/179450310167.2199510.18107423940014989443@kernel.org/
> diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c
> index ca3057192e6da..c33b0095468af 100644
> --- a/net/ipv4/inet_connection_sock.c
> +++ b/net/ipv4/inet_connection_sock.c
[ ... ]
> @@ -1373,6 +1389,12 @@ int inet_csk_listen_start(struct sock *sk)
> }
>
> inet_sk_set_state(sk, TCP_CLOSE);
> +
> + /* A Fast Open child accepted before a previous shutdown() of this
> + * listener might have queued its request in rskq_rst_head while
> + * we were transiently in TCP_LISTEN state.
> + */
> + reqsk_fastopen_purge_rst(&icsk->icsk_accept_queue);
> return err;
> }
[Severity: High]
Does this error path also need to drain rskq_accept_head?
The commit message says the socket goes back to TCP_CLOSE "without going
through inet_csk_listen_stop()". This path only cleans up rskq_rst_head,
but the accept queue can be filled during the same TCP_LISTEN window.
After shutdown(SHUT_RD) or connect(AF_UNSPEC), inet_csk_listen_stop()
drains the accept queue. TCP_NEW_SYN_RECV request_socks still stay in
ehash with rsk_listener pointing at this socket, until
reqsk_timer_handler() drops them.
Suppose listen() is called again, and the peer's final ACK for one of
those requests arrives while sk_state is TCP_LISTEN:
tcp_v4_rcv()
if (unlikely(sk->sk_state != TCP_LISTEN)) <- not taken
tcp_check_req() <- no listener re-check
tcp_v4_syn_recv_sock()
inet_csk_complete_hashdance()
inet_csk_reqsk_queue_add()
Under rskq_lock, inet_csk_reqsk_queue_add() still sees TCP_LISTEN and
queues the child:
} else {
req->sk = child;
req->dl_next = NULL;
...
queue->rskq_accept_tail = req;
sk_acceptq_added(sk);
}
__inet_inherit_port() can succeed here when the port was locked by an
explicit bind(). That is also the setup of the -EADDRINUSE example in
the commit message.
If get_port() or hash() then fails, the socket returns to TCP_CLOSE. The
req and child are still on rskq_accept_head, and sk_ack_backlog is not
zero.
Nothing seems to reclaim them after that:
- inet_csk_accept() rejects sockets that are not listening.
- __tcp_close(), tcp_disconnect() and tcp_abort() only call
inet_csk_listen_stop() from TCP_LISTEN.
- A later successful listen() overwrites rskq_accept_head and resets
sk_ack_backlog in reqsk_queue_alloc().
Wouldn't this leak the established child socket, its request_sock, and
the listener reference held through rsk_listener? That is the same kind
of leak this patch fixes for rskq_rst_head.
Could the error path run the full inet_csk_listen_stop() cleanup after
setting TCP_CLOSE? Alternatively, could TCP_LISTEN be set only after
get_port() and hash() succeed?
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261114111247.21995-1-edumazet%40kernel.org
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-11-15 21:17 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-14 11:12 [PATCH net] tcp: purge Fast Open RST queue when listen() fails Eric Dumazet
2026-11-14 16:14 ` Hyunwoo Kim
2026-11-14 17:06 ` Kuniyuki Iwashima
2026-11-15 21:17 ` netdev-bot+sashiko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox