Re: [PATCH net] tcp: reset late connection after listening socket close
Kuniyuki Iwashima <[email protected]>
| Newsgroups | org.kernel.vger.stable,org.kernel.vger.linux-kernel,org.kernel.vger.netdev |
|---|---|
| Message-ID | <CAAVpQUD9RTxs742Ty-UQXJJwTbN2wrnFGQDffKN0eZL4udq3Ng@mail.gmail.com> |
On Fri, Aug 7, 2026 at 12:47 PM Asbjørn Sloth Tønnesen <[email protected]> wrote: > > In commit c82199061009 ("task_work: remove fifo ordering guarantee") > Eric removed the ordering guarantee, thereby changing it from a > guaranteed FIFO to currently LIFO ordering, in an effort to reduce > jitter. Could you elaborate a bit more on how c82199061009 introduced the issue? I didn't quite see the connection between the first and second paragraphs. The listener's state could change at any time on SMP. > > This allows for a TCP handshake to complete, after the listening socket > has been closed, and after inet_csk_listen_stop() has been run. > > In that case the client sees the connection as ESTABLISHED, however in > tcp_v{4,6}_syn_recv_sock() the call to __inet_inherit_port() returns > -ENOENT, and the new connection is dropped silently by put_and_exit. Note that if __inet_inherit_port() passes, inet_csk_reqsk_queue_add() calls inet_child_forget(). > > A client may therefore hang indefinitely on a blocking read() if the > used data communication protocol is initiated by the server, like SMTP > and the reporter[1]'s MariaDB protocol both are. > > Had the new connection been processed before the listening socket was > closed, it would have been in the accept queue, and been notified when > inet_csk_listen_stop() was run, and the client would have got a reset. > > This patch adds a check on the state of the listening socket, and > resets the new connection, before discarding it with put_and_exit. > The check is placed just before it would fail in __inet_inherit_port(), > and just after TCP MD5 and AO options have been copied to newsk. > > The blamed commit was identified by testing on ancient Debian stable > releases to get a rough scope of where to look, then identifying in > which release between v3.16 and v4.19 it broke, and finally bisecting > v4.2..v4.3 on a fresh Debian then-stable (jessie) VM with tooling from > that era, and confirmed by reverting it from v4.3, v5.10.y and net. > > Reproducer: > https://files.fiberby.net/ast/2026/kernel/socket_teardown_test.c > > Reported-by: Kristian Nielsen <[email protected]> > Link: https://lore.kernel.org/[email protected] # [1] > Fixes: c82199061009 ("task_work: remove fifo ordering guarantee") > Cc: <[email protected]> > Signed-off-by: Asbjørn Sloth Tønnesen <[email protected]> > --- > > I'm not submitting a selftest at this time, as I have only found an > efficient way to often detect the issue, not disprove it, and I would > need more test data from different systems, before I can reliably > disprove it with a low runtime budget, without getting false negatives. > > The check could also be "sk->sk_state != TCP_LISTEN". I have only > seen TCP_LISTEN and TCP_CLOSE at this location during my testing. > > IMHO socket migration is out of scope for this patch, see commit > 54b92e841937 ("tcp: Migrate TCP_ESTABLISHED/TCP_SYN_RECV sockets in > accept queues."), as it would complicate backporting to stable. > > I have not yet tested that MD5/AO works, I guess that would involve > extending my reproducer to place client and server in distinct netns. > > net/ipv4/tcp_ipv4.c | 5 +++++ > net/ipv6/tcp_ipv6.c | 5 +++++ > 2 files changed, 10 insertions(+) > > diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c > index b8887cdd66c57..45d9a7e40e951 100644 > --- a/net/ipv4/tcp_ipv4.c > +++ b/net/ipv4/tcp_ipv4.c > @@ -1756,6 +1756,11 @@ struct sock *tcp_v4_syn_recv_sock(const struct sock *sk, struct sk_buff *skb, > goto put_and_exit; /* OOM, release back memory */ > #endif > > + if (sk->sk_state == TCP_CLOSE) { > + tcp_v4_send_reset(newsk, skb, SK_RST_REASON_TCP_STATE); > + goto put_and_exit; > + } What happens if the state changes here ? > + > if (__inet_inherit_port(sk, newsk) < 0) > goto put_and_exit; -ENOENT should be checked to jump to a new label and call req->rsk_ops->send_reset() there. > *own_req = inet_ehash_nolisten(newsk, req_to_sk(req_unhash), > diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c > index 9e9155b1b3aa7..c7aa3c6b1c314 100644 > --- a/net/ipv6/tcp_ipv6.c > +++ b/net/ipv6/tcp_ipv6.c > @@ -1512,6 +1512,11 @@ static struct sock *tcp_v6_syn_recv_sock(const struct sock *sk, struct sk_buff * > goto put_and_exit; /* OOM */ > #endif > > + if (sk->sk_state == TCP_CLOSE) { > + tcp_v6_send_reset(newsk, skb, SK_RST_REASON_TCP_STATE); > + goto put_and_exit; > + } > + > if (__inet_inherit_port(sk, newsk) < 0) > goto put_and_exit; > *own_req = inet_ehash_nolisten(newsk, req_to_sk(req_unhash), > > base-commit: 594d905195024b228c962627ae5ae7c17bd582a4 > -- > 2.53.0 >