From: Kuniyuki Iwashima <kuniyu@google.com>
To: david.lee@trailofbits.com
Cc: davem@davemloft.net, dominik.czarnota@trailofbits.com,
edumazet@google.com, horms@kernel.org, kuba@kernel.org,
kuniyu@google.com, kylebot@openai.com,
linux-kernel@vger.kernel.org, ncardwell@google.com,
netdev@vger.kernel.org, pabeni@redhat.com
Subject: Re: [PATCH] net: inet: prevent lhash2 lookups from escaping into ehash
Date: Tue, 4 Aug 2026 01:53:16 +0000 [thread overview]
Message-ID: <20260804015349.2353056-1-kuniyu@google.com> (raw)
In-Reply-To: <20260731140512.566464-1-david.lee@trailofbits.com>
From: David Lee <david.lee@trailofbits.com>
Date: Fri, 31 Jul 2026 14:05:12 +0000
> The lhash2 and ehash tables share sk_nulls_node. When a listening
> socket is unhashed and rehashed as a connected socket
This is only fuzzing scenario, and there is another series trying
to address the same class of issue by touching the fast path as well.
https://lore.kernel.org/netdev/20260803-sockmap-lookup-tcp-leak-v2-0-306e025bfe66@rbox.co/
> while an RCU
> listener lookup is walking it, the reader can follow the node's new
> next pointer into ehash. inet_lhash2_lookup() and
> inet6_lhash2_lookup() do not validate the terminal nulls marker, so
> they can return an ehash entry under the listener lookup's
> unreferenced ownership contract.
>
> In particular, returning a TIME_WAIT socket makes the TCP receive
> path consume a reference that the lookup never acquired. Repeated
> races can free the object while it remains linked in the hash tables.
>
> Skip non-listening sockets before scoring them. Also restart the walk
> when its terminal nulls marker does not match the expected lhash2 slot,
> as the established lookup already does.
>
> Fixes: cae3873c5b3a ("net: inet: Retire port only listening_hash")
> Bug found and triaged by OpenAI Security Research and
> validated by Trail of Bits.
>
> Assisted-by: Codex:gpt-5.6-sol gpt-5.5-cyber
> Signed-off-by: Kyle Zeng <kylebot@openai.com>
> ---
> Trail of Bits has a reproducer for this bug that triggers a
> KASAN use-after-free and can share if needed.
Can you test this diff ?
---8<---
diff --git a/include/net/inet_connection_sock.h b/include/net/inet_connection_sock.h
index 433c2df23076..903499581db7 100644
--- a/include/net/inet_connection_sock.h
+++ b/include/net/inet_connection_sock.h
@@ -94,6 +94,7 @@ struct inet_connection_sock {
u32 icsk_rto_max;
__u32 icsk_delack_max;
__u32 icsk_pmtu_cookie;
+ unsigned char unhashed_state;
const struct tcp_congestion_ops *icsk_ca_ops;
const struct inet_connection_sock_af_ops *icsk_af_ops;
const struct tcp_ulp_ops *icsk_ulp_ops;
diff --git a/net/ipv4/inet_hashtables.c b/net/ipv4/inet_hashtables.c
index ba0faa9ae2bb..3ed8d5833c3b 100644
--- a/net/ipv4/inet_hashtables.c
+++ b/net/ipv4/inet_hashtables.c
@@ -803,6 +803,14 @@ int inet_hash(struct sock *sk)
inet_init_ehash_secret();
WARN_ON(!sk_unhashed(sk));
+
+ if (unlikely(inet_csk(sk)->unhashed_state)) {
+ if (inet_csk(sk)->unhashed_state != TCP_LISTEN)
+ synchronize_rcu();
+
+ inet_csk(sk)->unhashed_state = 0;
+ }
+
ilb2 = inet_lhash2_bucket_sk(hashinfo, sk);
spin_lock(&ilb2->lock);
@@ -832,6 +840,9 @@ void inet_unhash(struct sock *sk)
return;
sock_rps_delete_flow(sk);
+
+ inet_csk(sk)->unhashed_state = sk->sk_state;
+
if (sk->sk_state == TCP_LISTEN) {
struct inet_listen_hashbucket *ilb2;
@@ -1058,6 +1069,13 @@ int __inet_hash_connect(struct inet_timewait_death_row *death_row,
int ret, i, low, high;
bool local_ports;
+ if (unlikely(inet_csk(sk)->unhashed_state)) {
+ if (inet_csk(sk)->unhashed_state == TCP_LISTEN)
+ synchronize_rcu();
+
+ inet_csk(sk)->unhashed_state = 0;
+ }
+
if (port) {
local_bh_disable();
ret = check_established(death_row, sk, port, NULL, false,
---8<---
prev parent reply other threads:[~2026-08-04 1:53 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-31 14:05 [PATCH] net: inet: prevent lhash2 lookups from escaping into ehash David Lee
2026-08-02 8:40 ` Sven Eckelmann
2026-08-04 1:53 ` Kuniyuki Iwashima [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260804015349.2353056-1-kuniyu@google.com \
--to=kuniyu@google.com \
--cc=davem@davemloft.net \
--cc=david.lee@trailofbits.com \
--cc=dominik.czarnota@trailofbits.com \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=kuba@kernel.org \
--cc=kylebot@openai.com \
--cc=linux-kernel@vger.kernel.org \
--cc=ncardwell@google.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.