[PATCH net v3] tcp: reset late connection after listening socket close
From: Asbjørn Sloth Tønnesen <hidden>
Date: 2026-08-11 21:10:41
Also in:
lkml, stable
Subsystem:
networking [general], networking [tcp], the rest · Maintainers:
"David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Neal Cardwell, Linus Torvalds
When __inet_inherit_port() returns -ENOENT, the new connection is
dropped silently.
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 by put_and_exit.
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 either have been added to the accept queue, or
inet_csk_reqsk_queue_add() should have sent RST.
The call to __inet_inherit_port() returns -ENOENT because
inet_csk(sk)->icsk_bind_hash is NULL, after inet_put_port() has been
called by tcp_set_state(sk, TCP_CLOSE).
This patch adds -ENOENT handling to both __inet_inherit_port() call
sites, and ensures that RST is sent before the connection is dropped.
Reproducer:
https://files.fiberby.net/ast/2026/kernel/socket_teardown_test.c
Reported-by: Kristian Nielsen <redacted>
Link: https://lore.kernel.org/87sf0ldk41.fsf@urd.knielsen-hq.org (local) # [1]
Fixes: c2f34a65a61c ("tcp/dccp: fix potential NULL deref in __inet_inherit_port()")
Cc: <redacted>
Signed-off-by: Asbjørn Sloth Tønnesen <redacted>
---
Changelog:
v3:
- Rewrite commit message around fixing commit c2f34a65a61c.
- Call tcp_v{4,6}_send_reset() directly again (but with sk, not newsk).
- Nest the two return value checks, and wrap in unlikely().
(Thanks again Kuniyuki)
v2: https://lore.kernel.org/20260810205642.1611338-1-ast@fiberby.net (local)
- Use return from __inet_inherit_port() to trigger send_reply()
- Use req->rsk_ops->send_reset.
- Clarity commit message, and update to reflect the changes.
(Thanks Kuniyuki)
v1: https://lore.kernel.org/20260807194513.1263310-1-ast@fiberby.net (local)
net/ipv4/tcp_ipv4.c | 9 ++++++++-
net/ipv6/tcp_ipv6.c | 9 ++++++++-
2 files changed, 16 insertions(+), 2 deletions(-)
diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
index b8887cdd66c5..9a14c2e56ec3 100644
--- a/net/ipv4/tcp_ipv4.c
+++ b/net/ipv4/tcp_ipv4.c@@ -1690,6 +1690,7 @@ struct sock *tcp_v4_syn_recv_sock(const struct sock *sk, struct sk_buff *skb, int l3index; #endif struct ip_options_rcu *inet_opt; + int ret; if (sk_acceptq_is_full(sk)) goto exit_overflow;
@@ -1756,8 +1757,12 @@ 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 (__inet_inherit_port(sk, newsk) < 0) + ret = __inet_inherit_port(sk, newsk); + if (unlikely(ret < 0)) { + if (ret == -ENOENT) + goto send_reset_and_exit; goto put_and_exit; + } *own_req = inet_ehash_nolisten(newsk, req_to_sk(req_unhash), &found_dup_sk); if (likely(*own_req)) {
@@ -1784,6 +1789,8 @@ struct sock *tcp_v4_syn_recv_sock(const struct sock *sk, struct sk_buff *skb, exit: tcp_listendrop(sk); return NULL; +send_reset_and_exit: + tcp_v4_send_reset(sk, skb, SK_RST_REASON_TCP_STATE); put_and_exit: newinet->inet_opt = NULL; inet_csk_prepare_forced_close(newsk);
diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c
index 9e9155b1b3aa..ecb0b405703c 100644
--- a/net/ipv6/tcp_ipv6.c
+++ b/net/ipv6/tcp_ipv6.c@@ -1400,6 +1400,7 @@ static struct sock *tcp_v6_syn_recv_sock(const struct sock *sk, struct sk_buff * int l3index; #endif struct flowi6 fl6; + int ret; if (skb->protocol == htons(ETH_P_IP)) return tcp_v4_syn_recv_sock(sk, skb, req, dst,
@@ -1512,8 +1513,12 @@ static struct sock *tcp_v6_syn_recv_sock(const struct sock *sk, struct sk_buff * goto put_and_exit; /* OOM */ #endif - if (__inet_inherit_port(sk, newsk) < 0) + ret = __inet_inherit_port(sk, newsk); + if (unlikely(ret < 0)) { + if (ret == -ENOENT) + goto send_reset_and_exit; goto put_and_exit; + } *own_req = inet_ehash_nolisten(newsk, req_to_sk(req_unhash), &found_dup_sk); if (*own_req) {
@@ -1547,6 +1552,8 @@ static struct sock *tcp_v6_syn_recv_sock(const struct sock *sk, struct sk_buff * exit: tcp_listendrop(sk); return NULL; +send_reset_and_exit: + tcp_v6_send_reset(sk, skb, SK_RST_REASON_TCP_STATE); put_and_exit: inet_csk_prepare_forced_close(newsk); tcp_done(newsk);
base-commit: cba9ccb47e9fa4cc77692fb896cc5ab57a667882 -- 2.55.0