[PATCH net v3] tcp: reset late connection after listening socket close

Asbjørn Sloth Tønnesen <[email protected]>
Newsgroups org.kernel.vger.netdev,org.kernel.vger.linux-kernel,org.kernel.vger.stable
Message-ID <[email protected]>
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 <[email protected]>
Link: https://lore.kernel.org/[email protected] # [1]
Fixes: c2f34a65a61c ("tcp/dccp: fix potential NULL deref in __inet_inherit_port()")
Cc: <[email protected]>
Signed-off-by: Asbjørn Sloth Tønnesen <[email protected]>
---

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/[email protected]
  - 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/[email protected]

 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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.