* [PATCH net v2 0/2] inet: fix established lookup races
@ 2026-10-23 5:24 Hyunwoo Kim
2026-10-23 5:24 ` [PATCH net v2 1/2] inet: reject unhashed sockets during established lookups Hyunwoo Kim
` (2 more replies)
0 siblings, 3 replies; 9+ messages in thread
From: Hyunwoo Kim @ 2026-10-23 5:24 UTC (permalink / raw)
To: edumazet, ncardwell, kuniyu, davem, kuba, pabeni, horms
Cc: Hyunwoo Kim, martin.lau, john.fastabend, weiwan, sd, jiayuan.chen,
netdev
Fix two issues in lockless established socket lookups. Reject unhashed
sockets after taking a reference, and restore the acquire/release
ordering around refcount publication and acquisition.
Changes in v2:
- 1/2: split the unhashed checks and __reqsk_free() to reqsk_put()
conversions from the ordering changes, set the Fixes tag to commit
079096f103fa ("tcp/dccp: install syn_recv requests into ehash table"),
mention the TIME_WAIT hashdance and request replacement cases, clarify
request reference ownership, and drop the full socket race discussion
(Eric)
- 2/2: new, split out the acquire/release changes, set the Fixes tag to
commit fb041bb7c0a9 ("locking/refcount: Consolidate implementations of
refcount_t"), cover sk_clone(), and leave the already ordered TIME_WAIT
path unchanged (Eric)
- v1: https://lore.kernel.org/r/ath58Egc-oI6p3Jl@v4bel
Hyunwoo Kim (2):
inet: reject unhashed sockets during established lookups
inet: restore refcount ordering for established lookups
net/core/sock.c | 3 +--
net/ipv4/inet_connection_sock.c | 16 ++++++++++------
net/ipv4/inet_hashtables.c | 7 ++++---
net/ipv4/syncookies.c | 4 +++-
net/ipv4/tcp_fastopen.c | 3 +--
net/ipv4/tcp_input.c | 4 +++-
net/ipv6/inet6_hashtables.c | 6 ++++--
7 files changed, 26 insertions(+), 17 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH net v2 1/2] inet: reject unhashed sockets during established lookups
2026-10-23 5:24 [PATCH net v2 0/2] inet: fix established lookup races Hyunwoo Kim
@ 2026-10-23 5:24 ` Hyunwoo Kim
2026-10-23 16:39 ` Eric Dumazet
2026-10-27 5:55 ` netdev-bot+sashiko
2026-10-23 5:24 ` [PATCH net v2 2/2] inet: restore refcount ordering for " Hyunwoo Kim
2026-10-23 5:32 ` [PATCH net v2 0/2] inet: fix established lookup races netdev-bot+sinfo
2 siblings, 2 replies; 9+ messages in thread
From: Hyunwoo Kim @ 2026-10-23 5:24 UTC (permalink / raw)
To: edumazet, ncardwell, kuniyu, davem, kuba, pabeni, horms
Cc: Hyunwoo Kim, martin.lau, john.fastabend, weiwan, sd, jiayuan.chen,
netdev, stable
TCP established ehash lookups are lockless. The socket caches involved
use SLAB_TYPESAFE_BY_RCU, so memory can be reused before an RCU reader
that retained its old address has finished.
After a tuple match, __inet_lookup_established() and
__inet6_lookup_established() acquire a reference and repeat the tuple
check. They do not check whether the new object is hashed. If the
address is reused for a socket with the same tuple, the lookup can
acquire a reference to the new object and return it while its nulls node
is unhashed.
A PoC that drives reuse with a SYN cookie request socket reproduced the
crash. The new request socket is unhashed and has a NULL rsk_listener.
A lookup that had loaded the address of the old request socket acquired
the reference published by tcp_get_cookie_sock() and returned the new
object. tcp_v4_rcv() then read sk_state through the NULL listener.
The resulting crash was:
BUG: kernel NULL pointer dereference, address: 0000000000000012
#PF: supervisor read access in kernel mode
#PF: error_code(0x0000) - not-present page
...
Oops: Oops: 0000 [#1] SMP NOPTI
CPU: 4 UID: 0 PID: 0 Comm: swapper/4 Not tainted 7.3.0-00591-g0e135910deec #1 PREEMPT(lazy)
...
RIP: 0010:tcp_v4_rcv+0x44e/0x1280
...
R10: 0000000000000001 R11: 00000000024ca8c0 R12: 0000000000000000
...
CR2: 0000000000000012 CR3: 0000000103d50004 CR4: 0000000000770ef0
...
Call Trace:
<IRQ>
? raw_local_deliver+0xae/0x240
ip_protocol_deliver_rcu+0x2d/0x180
ip_local_deliver_finish+0x84/0x100
ip_sublist_rcv_finish+0x5f/0x70
ip_sublist_rcv+0x12a/0x1d0
ip_list_rcv+0xfd/0x130
__netif_receive_skb_list_core+0x223/0x250
netif_receive_skb_list_internal+0x1d2/0x2c0
? try_fill_recv+0x39/0x7b0
napi_complete_done+0x74/0x170
virtnet_poll+0x445/0x600
__napi_poll+0x27/0x1e0
net_rx_action+0x2fe/0x3b0
handle_softirqs+0xc2/0x2c0
__irq_exit_rcu+0x74/0xd0
common_interrupt+0x7f/0x90
</IRQ>
...
Kernel panic - not syncing: Fatal exception in interrupt
After taking a reference, check whether the nulls node is unhashed in
both IPv4 and IPv6 established lookups. If it is unhashed or the tuple
no longer matches, drop the reference and restart the lookup.
Lookups racing with the TIME_WAIT hashdance, or with a request being
replaced by its child, now restart and find the new socket instead of
returning the old one.
Request sockets that were never inserted into ehash can have a nonzero
refcount, and a stale lookup can hold a transient reference. Therefore,
when SYN cookie, Fast Open, and request migration paths fail after
setting a nonzero refcount, release the references held by those paths
with reqsk_put() instead of freeing the request directly.
Fixes: 079096f103fa ("tcp/dccp: install syn_recv requests into ehash table")
Cc: stable@vger.kernel.org
Signed-off-by: Hyunwoo Kim <imv4bel@gmail.com>
---
net/ipv4/inet_connection_sock.c | 10 +++++++---
net/ipv4/inet_hashtables.c | 5 +++--
net/ipv4/syncookies.c | 2 ++
net/ipv4/tcp_input.c | 4 +++-
net/ipv6/inet6_hashtables.c | 4 +++-
5 files changed, 18 insertions(+), 7 deletions(-)
diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c
index 6a30f1138454..cff02f4c5ac8 100644
--- a/net/ipv4/inet_connection_sock.c
+++ b/net/ipv4/inet_connection_sock.c
@@ -1136,10 +1136,14 @@ static void reqsk_timer_handler(struct timer_list *t)
*/
if (nreq) {
__NET_INC_STATS(net, LINUX_MIB_TCPMIGRATEREQFAILURE);
+ /* The timer was not armed, drop its reference here. */
+ reqsk_put(nreq);
no_ownership:
reqsk_migrate_reset(nreq);
reqsk_queue_removed(queue, nreq);
- __reqsk_free(nreq);
+ /* Drop the ehash and temporary references. */
+ reqsk_put(nreq);
+ reqsk_put(nreq);
}
drop:
@@ -1447,7 +1451,7 @@ struct sock *inet_csk_complete_hashdance(struct sock *sk, struct sock *child,
__NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPMIGRATEREQFAILURE);
reqsk_migrate_reset(nreq);
- __reqsk_free(nreq);
+ reqsk_put(nreq);
} else if (inet_csk_reqsk_queue_add(sk, req, child)) {
return child;
}
@@ -1502,7 +1506,7 @@ void inet_csk_listen_stop(struct sock *sk)
__NET_INC_STATS(sock_net(nsk),
LINUX_MIB_TCPMIGRATEREQFAILURE);
reqsk_migrate_reset(nreq);
- __reqsk_free(nreq);
+ reqsk_put(nreq);
}
rcu_read_unlock();
diff --git a/net/ipv4/inet_hashtables.c b/net/ipv4/inet_hashtables.c
index ba0faa9ae2bb..58d54965036a 100644
--- a/net/ipv4/inet_hashtables.c
+++ b/net/ipv4/inet_hashtables.c
@@ -552,8 +552,9 @@ struct sock *__inet_lookup_established(const struct net *net,
if (likely(inet_match(net, sk, acookie, ports, dif, sdif))) {
if (unlikely(!refcount_inc_not_zero(&sk->sk_refcnt)))
goto out;
- if (unlikely(!inet_match(net, sk, acookie,
- ports, dif, sdif))) {
+ if (unlikely(hlist_nulls_unhashed_lockless(&sk->sk_nulls_node) ||
+ !inet_match(net, sk, acookie, ports, dif,
+ sdif))) {
sock_gen_put(sk);
goto begin;
}
diff --git a/net/ipv4/syncookies.c b/net/ipv4/syncookies.c
index 73e129768184..644030561537 100644
--- a/net/ipv4/syncookies.c
+++ b/net/ipv4/syncookies.c
@@ -218,6 +218,8 @@ struct sock *tcp_get_cookie_sock(struct sock *sk, struct sk_buff *skb,
bh_unlock_sock(child);
sock_put(child);
+ reqsk_put(req);
+ return NULL;
}
__reqsk_free(req);
diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c
index 99baf14afdfd..3583867487d7 100644
--- a/net/ipv4/tcp_input.c
+++ b/net/ipv4/tcp_input.c
@@ -7777,7 +7777,9 @@ int tcp_conn_request(struct request_sock_ops *rsk_ops,
if (!inet_csk_reqsk_queue_add(sk, req, fastopen_sk)) {
bh_unlock_sock(fastopen_sk);
sock_put(fastopen_sk);
- goto drop_and_free;
+ /* tcp_disconnect() already dropped one reqsk ref. */
+ reqsk_put(req);
+ goto drop;
}
READ_ONCE(sk->sk_data_ready)(sk);
bh_unlock_sock(fastopen_sk);
diff --git a/net/ipv6/inet6_hashtables.c b/net/ipv6/inet6_hashtables.c
index b111b51d69fc..aa5fa1dd062f 100644
--- a/net/ipv6/inet6_hashtables.c
+++ b/net/ipv6/inet6_hashtables.c
@@ -108,7 +108,9 @@ struct sock *__inet6_lookup_established(const struct net *net,
if (unlikely(!refcount_inc_not_zero(&sk->sk_refcnt)))
goto out;
- if (unlikely(!inet6_match(net, sk, saddr, daddr, ports, dif, sdif))) {
+ if (unlikely(hlist_nulls_unhashed_lockless(&sk->sk_nulls_node) ||
+ !inet6_match(net, sk, saddr, daddr, ports, dif,
+ sdif))) {
sock_gen_put(sk);
goto begin;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH net v2 2/2] inet: restore refcount ordering for established lookups
2026-10-23 5:24 [PATCH net v2 0/2] inet: fix established lookup races Hyunwoo Kim
2026-10-23 5:24 ` [PATCH net v2 1/2] inet: reject unhashed sockets during established lookups Hyunwoo Kim
@ 2026-10-23 5:24 ` Hyunwoo Kim
2026-10-23 16:33 ` Eric Dumazet
2026-10-27 5:55 ` netdev-bot+sashiko
2026-10-23 5:32 ` [PATCH net v2 0/2] inet: fix established lookup races netdev-bot+sinfo
2 siblings, 2 replies; 9+ messages in thread
From: Hyunwoo Kim @ 2026-10-23 5:24 UTC (permalink / raw)
To: edumazet, ncardwell, kuniyu, davem, kuba, pabeni, horms
Cc: Hyunwoo Kim, martin.lau, john.fastabend, weiwan, sd, jiayuan.chen,
netdev, stable
When commit 3ab5aee7fe84 ("net: Convert TCP & DCCP hash tables to use
RCU / hlist_nulls") was written, atomic_inc_not_zero() provided full
memory ordering. Even after commit 41c6d650f653 ("net: convert
sock.sk_refcnt from atomic_t to refcount_t"), default builds used
atomic_add_unless() and retained that ordering.
Commit fb041bb7c0a9 ("locking/refcount: Consolidate implementations
of refcount_t") consolidated the refcount_t implementations in v5.5.
On weakly ordered architectures, this removed the ordering required
by validation after a successful reference acquisition.
Use refcount_inc_not_zero_acquire() in the IPv4 and IPv6 established
lookups. Use refcount_set_release() when setting initial refcounts in
sk_clone(), SYN cookie, and request migration paths. In the TCP Fast
Open path, move the refcount update after the required initialization
and use release ordering.
The TIME_WAIT path already has an smp_wmb() between refcount_set() and
hash publication, so leave it unchanged.
Fixes: fb041bb7c0a9 ("locking/refcount: Consolidate implementations of refcount_t")
Cc: stable@vger.kernel.org
Signed-off-by: Hyunwoo Kim <imv4bel@gmail.com>
---
net/core/sock.c | 3 +--
net/ipv4/inet_connection_sock.c | 6 +++---
net/ipv4/inet_hashtables.c | 2 +-
net/ipv4/syncookies.c | 2 +-
net/ipv4/tcp_fastopen.c | 3 +--
net/ipv6/inet6_hashtables.c | 2 +-
6 files changed, 8 insertions(+), 10 deletions(-)
diff --git a/net/core/sock.c b/net/core/sock.c
index 3c25d66ba31a..f3ef9601ab73 100644
--- a/net/core/sock.c
+++ b/net/core/sock.c
@@ -2622,8 +2622,7 @@ struct sock *sk_clone(const struct sock *sk, const gfp_t priority,
/* Before updating sk_refcnt, we must commit prior changes to memory
* (Documentation/RCU/rculist_nulls.rst for details)
*/
- smp_wmb();
- refcount_set(&newsk->sk_refcnt, 2);
+ refcount_set_release(&newsk->sk_refcnt, 2);
sk_set_socket(newsk, NULL);
sk_tx_queue_clear(newsk);
diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c
index cff02f4c5ac8..2d8e54d3a31b 100644
--- a/net/ipv4/inet_connection_sock.c
+++ b/net/ipv4/inet_connection_sock.c
@@ -1056,8 +1056,8 @@ static void reqsk_timer_handler(struct timer_list *t)
* hold another count to prevent use-after-free and
* call reqsk_put() just before return.
*/
- refcount_set(&nreq->rsk_refcnt, 2 + 1);
timer_setup(&nreq->rsk_timer, reqsk_timer_handler, TIMER_PINNED);
+ refcount_set_release(&nreq->rsk_refcnt, 2 + 1);
reqsk_queue_migrated(&inet_csk(nsk)->icsk_accept_queue, req);
req = nreq;
@@ -1441,7 +1441,7 @@ struct sock *inet_csk_complete_hashdance(struct sock *sk, struct sock *child,
goto child_put;
}
- refcount_set(&nreq->rsk_refcnt, 1);
+ refcount_set_release(&nreq->rsk_refcnt, 1);
if (inet_csk_reqsk_queue_add(sk, nreq, child)) {
__NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPMIGRATEREQSUCCESS);
reqsk_migrate_reset(req);
@@ -1494,7 +1494,7 @@ void inet_csk_listen_stop(struct sock *sk)
if (nsk) {
nreq = inet_reqsk_clone(req, nsk);
if (nreq) {
- refcount_set(&nreq->rsk_refcnt, 1);
+ refcount_set_release(&nreq->rsk_refcnt, 1);
rcu_read_lock();
if (inet_csk_reqsk_queue_add(nsk, nreq, child)) {
diff --git a/net/ipv4/inet_hashtables.c b/net/ipv4/inet_hashtables.c
index 58d54965036a..7ffa9171d6de 100644
--- a/net/ipv4/inet_hashtables.c
+++ b/net/ipv4/inet_hashtables.c
@@ -550,7 +550,7 @@ struct sock *__inet_lookup_established(const struct net *net,
if (sk->sk_hash != hash)
continue;
if (likely(inet_match(net, sk, acookie, ports, dif, sdif))) {
- if (unlikely(!refcount_inc_not_zero(&sk->sk_refcnt)))
+ if (unlikely(!refcount_inc_not_zero_acquire(&sk->sk_refcnt)))
goto out;
if (unlikely(hlist_nulls_unhashed_lockless(&sk->sk_nulls_node) ||
!inet_match(net, sk, acookie, ports, dif,
diff --git a/net/ipv4/syncookies.c b/net/ipv4/syncookies.c
index 644030561537..4b2d2713653b 100644
--- a/net/ipv4/syncookies.c
+++ b/net/ipv4/syncookies.c
@@ -205,7 +205,7 @@ struct sock *tcp_get_cookie_sock(struct sock *sk, struct sk_buff *skb,
child = icsk->icsk_af_ops->syn_recv_sock(sk, skb, req, dst,
NULL, &own_req, NULL);
if (child) {
- refcount_set(&req->rsk_refcnt, 1);
+ refcount_set_release(&req->rsk_refcnt, 1);
sock_rps_save_rxhash(child, skb);
if (rsk_drop_req(req)) {
diff --git a/net/ipv4/tcp_fastopen.c b/net/ipv4/tcp_fastopen.c
index 471c78be5513..2506f415f410 100644
--- a/net/ipv4/tcp_fastopen.c
+++ b/net/ipv4/tcp_fastopen.c
@@ -364,8 +364,6 @@ static struct sock *tcp_fastopen_create_child(struct sock *sk,
tcp_reset_xmit_timer(child, ICSK_TIME_RETRANS,
req->timeout, false);
- refcount_set(&req->rsk_refcnt, 2);
-
sk_mark_napi_id_set(child, skb);
/* Now finish processing the fastopen child socket. */
@@ -378,6 +376,7 @@ static struct sock *tcp_fastopen_create_child(struct sock *sk,
tcp_rsk(req)->rcv_nxt = tp->rcv_nxt;
tp->rcv_wup = tp->rcv_nxt;
tp->rcv_mwnd_seq = tp->rcv_wup + tp->rcv_wnd;
+ refcount_set_release(&req->rsk_refcnt, 2);
/* tcp_conn_request() is sending the SYNACK,
* and queues the child into listener accept queue.
*/
diff --git a/net/ipv6/inet6_hashtables.c b/net/ipv6/inet6_hashtables.c
index aa5fa1dd062f..1eea584d80df 100644
--- a/net/ipv6/inet6_hashtables.c
+++ b/net/ipv6/inet6_hashtables.c
@@ -105,7 +105,7 @@ struct sock *__inet6_lookup_established(const struct net *net,
continue;
if (!inet6_match(net, sk, saddr, daddr, ports, dif, sdif))
continue;
- if (unlikely(!refcount_inc_not_zero(&sk->sk_refcnt)))
+ if (unlikely(!refcount_inc_not_zero_acquire(&sk->sk_refcnt)))
goto out;
if (unlikely(hlist_nulls_unhashed_lockless(&sk->sk_nulls_node) ||
--
2.43.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH net v2 0/2] inet: fix established lookup races
2026-10-23 5:24 [PATCH net v2 0/2] inet: fix established lookup races Hyunwoo Kim
2026-10-23 5:24 ` [PATCH net v2 1/2] inet: reject unhashed sockets during established lookups Hyunwoo Kim
2026-10-23 5:24 ` [PATCH net v2 2/2] inet: restore refcount ordering for " Hyunwoo Kim
@ 2026-10-23 5:32 ` netdev-bot+sinfo
2 siblings, 0 replies; 9+ messages in thread
From: netdev-bot+sinfo @ 2026-10-23 5:32 UTC (permalink / raw)
To: Hyunwoo Kim
Cc: edumazet, ncardwell, kuniyu, davem, kuba, pabeni, horms,
martin.lau, john.fastabend, weiwan, sd, jiayuan.chen, netdev
Hi!
This is an automated message. This series looks like a fix, but its
commit messages seem to be missing some information:
- Whether the issue was actually triggered, or is only theoretical
(e.g. found by code inspection). If it was triggered please include
the symptoms, like the stack trace or error messages.
Please do not repost the series just to address the above. Instead,
reply to this email with the missing information, so that reviewers
can take it into account. If the series needs another revision for
other reasons, please include the information in the commit messages
then.
The evaluation is done by an LLM so it may be wrong, if you think
that is the case please reply and explain.
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH net v2 2/2] inet: restore refcount ordering for established lookups
2026-10-23 5:24 ` [PATCH net v2 2/2] inet: restore refcount ordering for " Hyunwoo Kim
@ 2026-10-23 16:33 ` Eric Dumazet
2026-10-23 18:07 ` Hyunwoo Kim
2026-10-27 5:55 ` netdev-bot+sashiko
1 sibling, 1 reply; 9+ messages in thread
From: Eric Dumazet @ 2026-10-23 16:33 UTC (permalink / raw)
To: Hyunwoo Kim
Cc: ncardwell, kuniyu, davem, kuba, pabeni, horms, martin.lau,
john.fastabend, weiwan, sd, jiayuan.chen, netdev, stable
Le ven. 23 oct. 2026 à 07:26, Hyunwoo Kim <imv4bel@gmail.com> a écrit :
>
> When commit 3ab5aee7fe84 ("net: Convert TCP & DCCP hash tables to use
> RCU / hlist_nulls") was written, atomic_inc_not_zero() provided full
> memory ordering. Even after commit 41c6d650f653 ("net: convert
> sock.sk_refcnt from atomic_t to refcount_t"), default builds used
> atomic_add_unless() and retained that ordering.
>
> Commit fb041bb7c0a9 ("locking/refcount: Consolidate implementations
> of refcount_t") consolidated the refcount_t implementations in v5.5.
> On weakly ordered architectures, this removed the ordering required
> by validation after a successful reference acquisition.
>
> Use refcount_inc_not_zero_acquire() in the IPv4 and IPv6 established
> lookups. Use refcount_set_release() when setting initial refcounts in
> sk_clone(), SYN cookie, and request migration paths. In the TCP Fast
> Open path, move the refcount update after the required initialization
> and use release ordering.
>
> The TIME_WAIT path already has an smp_wmb() between refcount_set() and
> hash publication, so leave it unchanged.
This is not accurate. The smp_wmb() in inet_twsk_hashdance_schedule()
orders the tw_refcnt store against the ehash publication, not the tw
identity stores done in inet_twsk_alloc() against the tw_refcnt store.
The latter is what a lookup holding a stale pointer needs. Today we get it
more or less by accident from the bhash spin_unlock() followed by
spin_lock() of the ehash lock.
Please either reword this paragraph, or simply use
refcount_set_release(&tw->tw_refcnt, 3) there; it is free under the lock
and makes the pairing explicit.
>
> Fixes: fb041bb7c0a9 ("locking/refcount: Consolidate implementations of refcount_t")
refcount_inc_not_zero_acquire() and refcount_set_release() were only
added in 7f8ceea0c580 ("refcount: provide ops for cases when object's
memory can be reused"), in v6.15.
> Cc: stable@vger.kernel.org
> Signed-off-by: Hyunwoo Kim <imv4bel@gmail.com>
> ---
> net/core/sock.c | 3 +--
> net/ipv4/inet_connection_sock.c | 6 +++---
> net/ipv4/inet_hashtables.c | 2 +-
> net/ipv4/syncookies.c | 2 +-
> net/ipv4/tcp_fastopen.c | 3 +--
> net/ipv6/inet6_hashtables.c | 2 +-
> 6 files changed, 8 insertions(+), 10 deletions(-)
>
> diff --git a/net/core/sock.c b/net/core/sock.c
> index 3c25d66ba31a..f3ef9601ab73 100644
> --- a/net/core/sock.c
> +++ b/net/core/sock.c
> @@ -2622,8 +2622,7 @@ struct sock *sk_clone(const struct sock *sk, const gfp_t priority,
> /* Before updating sk_refcnt, we must commit prior changes to memory
> * (Documentation/RCU/rculist_nulls.rst for details)
> */
> - smp_wmb();
> - refcount_set(&newsk->sk_refcnt, 2);
> + refcount_set_release(&newsk->sk_refcnt, 2);
This part is not a fix: the writer side only needs store->store ordering,
which smp_wmb() already provides.
I do not mind the conversion, but then reqsk_queue_hash_req() (which has
the same smp_wmb() + refcount_set() pattern) should be converted as well
for consistency. Otherwise please drop this hunk to keep the stable
backport minimal.
>
> sk_set_socket(newsk, NULL);
> sk_tx_queue_clear(newsk);
> diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c
> index cff02f4c5ac8..2d8e54d3a31b 100644
> --- a/net/ipv4/inet_connection_sock.c
> +++ b/net/ipv4/inet_connection_sock.c
> @@ -1056,8 +1056,8 @@ static void reqsk_timer_handler(struct timer_list *t)
> * hold another count to prevent use-after-free and
> * call reqsk_put() just before return.
> */
> - refcount_set(&nreq->rsk_refcnt, 2 + 1);
> timer_setup(&nreq->rsk_timer, reqsk_timer_handler, TIMER_PINNED);
> + refcount_set_release(&nreq->rsk_refcnt, 2 + 1);
> reqsk_queue_migrated(&inet_csk(nsk)->icsk_accept_queue, req);
>
> req = nreq;
> @@ -1441,7 +1441,7 @@ struct sock *inet_csk_complete_hashdance(struct sock *sk, struct sock *child,
> goto child_put;
> }
>
> - refcount_set(&nreq->rsk_refcnt, 1);
> + refcount_set_release(&nreq->rsk_refcnt, 1);
> if (inet_csk_reqsk_queue_add(sk, nreq, child)) {
> __NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPMIGRATEREQSUCCESS);
> reqsk_migrate_reset(req);
> @@ -1494,7 +1494,7 @@ void inet_csk_listen_stop(struct sock *sk)
> if (nsk) {
> nreq = inet_reqsk_clone(req, nsk);
> if (nreq) {
> - refcount_set(&nreq->rsk_refcnt, 1);
> + refcount_set_release(&nreq->rsk_refcnt, 1);
>
> rcu_read_lock();
> if (inet_csk_reqsk_queue_add(nsk, nreq, child)) {
> diff --git a/net/ipv4/inet_hashtables.c b/net/ipv4/inet_hashtables.c
> index 58d54965036a..7ffa9171d6de 100644
> --- a/net/ipv4/inet_hashtables.c
> +++ b/net/ipv4/inet_hashtables.c
> @@ -550,7 +550,7 @@ struct sock *__inet_lookup_established(const struct net *net,
> if (sk->sk_hash != hash)
> continue;
> if (likely(inet_match(net, sk, acookie, ports, dif, sdif))) {
> - if (unlikely(!refcount_inc_not_zero(&sk->sk_refcnt)))
> + if (unlikely(!refcount_inc_not_zero_acquire(&sk->sk_refcnt)))
> goto out;
> if (unlikely(hlist_nulls_unhashed_lockless(&sk->sk_nulls_node) ||
> !inet_match(net, sk, acookie, ports, dif,
> diff --git a/net/ipv4/syncookies.c b/net/ipv4/syncookies.c
> index 644030561537..4b2d2713653b 100644
> --- a/net/ipv4/syncookies.c
> +++ b/net/ipv4/syncookies.c
> @@ -205,7 +205,7 @@ struct sock *tcp_get_cookie_sock(struct sock *sk, struct sk_buff *skb,
> child = icsk->icsk_af_ops->syn_recv_sock(sk, skb, req, dst,
> NULL, &own_req, NULL);
> if (child) {
> - refcount_set(&req->rsk_refcnt, 1);
> + refcount_set_release(&req->rsk_refcnt, 1);
> sock_rps_save_rxhash(child, skb);
>
> if (rsk_drop_req(req)) {
> diff --git a/net/ipv4/tcp_fastopen.c b/net/ipv4/tcp_fastopen.c
> index 471c78be5513..2506f415f410 100644
> --- a/net/ipv4/tcp_fastopen.c
> +++ b/net/ipv4/tcp_fastopen.c
> @@ -364,8 +364,6 @@ static struct sock *tcp_fastopen_create_child(struct sock *sk,
> tcp_reset_xmit_timer(child, ICSK_TIME_RETRANS,
> req->timeout, false);
>
> - refcount_set(&req->rsk_refcnt, 2);
> -
> sk_mark_napi_id_set(child, skb);
>
> /* Now finish processing the fastopen child socket. */
> @@ -378,6 +376,7 @@ static struct sock *tcp_fastopen_create_child(struct sock *sk,
> tcp_rsk(req)->rcv_nxt = tp->rcv_nxt;
> tp->rcv_wup = tp->rcv_nxt;
> tp->rcv_mwnd_seq = tp->rcv_wup + tp->rcv_wnd;
> + refcount_set_release(&req->rsk_refcnt, 2);
> /* tcp_conn_request() is sending the SYNACK,
Moving this is harmless, but not needed. The lookup never looks at
rcv_nxt, and this req is never hashed, so after patch 1/2 a stale lookup
will reject it anyway. A plain conversion in place would be enough.
Let's keep code churn minimal, to lower risk of merge conflicts.
> * and queues the child into listener accept queue.
> */
> diff --git a/net/ipv6/inet6_hashtables.c b/net/ipv6/inet6_hashtables.c
> index aa5fa1dd062f..1eea584d80df 100644
> --- a/net/ipv6/inet6_hashtables.c
> +++ b/net/ipv6/inet6_hashtables.c
> @@ -105,7 +105,7 @@ struct sock *__inet6_lookup_established(const struct net *net,
> continue;
> if (!inet6_match(net, sk, saddr, daddr, ports, dif, sdif))
> continue;
> - if (unlikely(!refcount_inc_not_zero(&sk->sk_refcnt)))
> + if (unlikely(!refcount_inc_not_zero_acquire(&sk->sk_refcnt)))
> goto out;
>
> if (unlikely(hlist_nulls_unhashed_lockless(&sk->sk_nulls_node) ||
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH net v2 1/2] inet: reject unhashed sockets during established lookups
2026-10-23 5:24 ` [PATCH net v2 1/2] inet: reject unhashed sockets during established lookups Hyunwoo Kim
@ 2026-10-23 16:39 ` Eric Dumazet
2026-10-27 5:55 ` netdev-bot+sashiko
1 sibling, 0 replies; 9+ messages in thread
From: Eric Dumazet @ 2026-10-23 16:39 UTC (permalink / raw)
To: Hyunwoo Kim
Cc: ncardwell, kuniyu, davem, kuba, pabeni, horms, martin.lau,
john.fastabend, weiwan, sd, jiayuan.chen, netdev, stable
Le ven. 23 oct. 2026 à 07:26, Hyunwoo Kim <imv4bel@gmail.com> a écrit :
>
> TCP established ehash lookups are lockless. The socket caches involved
> use SLAB_TYPESAFE_BY_RCU, so memory can be reused before an RCU reader
> that retained its old address has finished.
>
> After a tuple match, __inet_lookup_established() and
> __inet6_lookup_established() acquire a reference and repeat the tuple
> check. They do not check whether the new object is hashed. If the
> address is reused for a socket with the same tuple, the lookup can
> acquire a reference to the new object and return it while its nulls node
> is unhashed.
>
> A PoC that drives reuse with a SYN cookie request socket reproduced the
> crash. The new request socket is unhashed and has a NULL rsk_listener.
> A lookup that had loaded the address of the old request socket acquired
> the reference published by tcp_get_cookie_sock() and returned the new
> object. tcp_v4_rcv() then read sk_state through the NULL listener.
>
> The resulting crash was:
>
> BUG: kernel NULL pointer dereference, address: 0000000000000012
> #PF: supervisor read access in kernel mode
> #PF: error_code(0x0000) - not-present page
> ...
> Oops: Oops: 0000 [#1] SMP NOPTI
> CPU: 4 UID: 0 PID: 0 Comm: swapper/4 Not tainted 7.3.0-00591-g0e135910deec #1 PREEMPT(lazy)
> ...
> RIP: 0010:tcp_v4_rcv+0x44e/0x1280
> ...
> R10: 0000000000000001 R11: 00000000024ca8c0 R12: 0000000000000000
> ...
> CR2: 0000000000000012 CR3: 0000000103d50004 CR4: 0000000000770ef0
> ...
> Call Trace:
> <IRQ>
> ? raw_local_deliver+0xae/0x240
> ip_protocol_deliver_rcu+0x2d/0x180
> ip_local_deliver_finish+0x84/0x100
> ip_sublist_rcv_finish+0x5f/0x70
> ip_sublist_rcv+0x12a/0x1d0
> ip_list_rcv+0xfd/0x130
> __netif_receive_skb_list_core+0x223/0x250
> netif_receive_skb_list_internal+0x1d2/0x2c0
> ? try_fill_recv+0x39/0x7b0
> napi_complete_done+0x74/0x170
> virtnet_poll+0x445/0x600
> __napi_poll+0x27/0x1e0
> net_rx_action+0x2fe/0x3b0
> handle_softirqs+0xc2/0x2c0
> __irq_exit_rcu+0x74/0xd0
> common_interrupt+0x7f/0x90
> </IRQ>
> ...
> Kernel panic - not syncing: Fatal exception in interrupt
>
> After taking a reference, check whether the nulls node is unhashed in
> both IPv4 and IPv6 established lookups. If it is unhashed or the tuple
> no longer matches, drop the reference and restart the lookup.
>
> Lookups racing with the TIME_WAIT hashdance, or with a request being
> replaced by its child, now restart and find the new socket instead of
> returning the old one.
>
> Request sockets that were never inserted into ehash can have a nonzero
> refcount, and a stale lookup can hold a transient reference. Therefore,
> when SYN cookie, Fast Open, and request migration paths fail after
> setting a nonzero refcount, release the references held by those paths
> with reqsk_put() instead of freeing the request directly.
>
> Fixes: 079096f103fa ("tcp/dccp: install syn_recv requests into ehash table")
> Cc: stable@vger.kernel.org
> Signed-off-by: Hyunwoo Kim <imv4bel@gmail.com>
> ---
> net/ipv4/inet_connection_sock.c | 10 +++++++---
> net/ipv4/inet_hashtables.c | 5 +++--
> net/ipv4/syncookies.c | 2 ++
> net/ipv4/tcp_input.c | 4 +++-
> net/ipv6/inet6_hashtables.c | 4 +++-
> 5 files changed, 18 insertions(+), 7 deletions(-)
>
> diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c
> index 6a30f1138454..cff02f4c5ac8 100644
> --- a/net/ipv4/inet_connection_sock.c
> +++ b/net/ipv4/inet_connection_sock.c
> @@ -1136,10 +1136,14 @@ static void reqsk_timer_handler(struct timer_list *t)
> */
> if (nreq) {
> __NET_INC_STATS(net, LINUX_MIB_TCPMIGRATEREQFAILURE);
> + /* The timer was not armed, drop its reference here. */
> + reqsk_put(nreq);
> no_ownership:
> reqsk_migrate_reset(nreq);
> reqsk_queue_removed(queue, nreq);
> - __reqsk_free(nreq);
> + /* Drop the ehash and temporary references. */
Lets change this comment to :
/* Drop the temporary reference and the one reserved for ehash. */
Reviewed-by: Eric Dumazet <edumazet@kernel.org>
> + reqsk_put(nreq);
> + reqsk_put(nreq);
> }
>
> drop:
> @@ -1447,7 +1451,7 @@ struct sock *inet_csk_complete_hashdance(struct sock *sk, struct sock *child,
>
> __NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPMIGRATEREQFAILURE);
> reqsk_migrate_reset(nreq);
> - __reqsk_free(nreq);
> + reqsk_put(nreq);
> } else if (inet_csk_reqsk_queue_add(sk, req, child)) {
> return child;
> }
> @@ -1502,7 +1506,7 @@ void inet_csk_listen_stop(struct sock *sk)
> __NET_INC_STATS(sock_net(nsk),
> LINUX_MIB_TCPMIGRATEREQFAILURE);
> reqsk_migrate_reset(nreq);
> - __reqsk_free(nreq);
> + reqsk_put(nreq);
> }
> rcu_read_unlock();
>
> diff --git a/net/ipv4/inet_hashtables.c b/net/ipv4/inet_hashtables.c
> index ba0faa9ae2bb..58d54965036a 100644
> --- a/net/ipv4/inet_hashtables.c
> +++ b/net/ipv4/inet_hashtables.c
> @@ -552,8 +552,9 @@ struct sock *__inet_lookup_established(const struct net *net,
> if (likely(inet_match(net, sk, acookie, ports, dif, sdif))) {
> if (unlikely(!refcount_inc_not_zero(&sk->sk_refcnt)))
> goto out;
> - if (unlikely(!inet_match(net, sk, acookie,
> - ports, dif, sdif))) {
> + if (unlikely(hlist_nulls_unhashed_lockless(&sk->sk_nulls_node) ||
> + !inet_match(net, sk, acookie, ports, dif,
> + sdif))) {
> sock_gen_put(sk);
> goto begin;
> }
> diff --git a/net/ipv4/syncookies.c b/net/ipv4/syncookies.c
> index 73e129768184..644030561537 100644
> --- a/net/ipv4/syncookies.c
> +++ b/net/ipv4/syncookies.c
> @@ -218,6 +218,8 @@ struct sock *tcp_get_cookie_sock(struct sock *sk, struct sk_buff *skb,
>
> bh_unlock_sock(child);
> sock_put(child);
> + reqsk_put(req);
> + return NULL;
> }
> __reqsk_free(req);
>
> diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c
> index 99baf14afdfd..3583867487d7 100644
> --- a/net/ipv4/tcp_input.c
> +++ b/net/ipv4/tcp_input.c
> @@ -7777,7 +7777,9 @@ int tcp_conn_request(struct request_sock_ops *rsk_ops,
> if (!inet_csk_reqsk_queue_add(sk, req, fastopen_sk)) {
> bh_unlock_sock(fastopen_sk);
> sock_put(fastopen_sk);
> - goto drop_and_free;
> + /* tcp_disconnect() already dropped one reqsk ref. */
> + reqsk_put(req);
> + goto drop;
> }
> READ_ONCE(sk->sk_data_ready)(sk);
> bh_unlock_sock(fastopen_sk);
> diff --git a/net/ipv6/inet6_hashtables.c b/net/ipv6/inet6_hashtables.c
> index b111b51d69fc..aa5fa1dd062f 100644
> --- a/net/ipv6/inet6_hashtables.c
> +++ b/net/ipv6/inet6_hashtables.c
> @@ -108,7 +108,9 @@ struct sock *__inet6_lookup_established(const struct net *net,
> if (unlikely(!refcount_inc_not_zero(&sk->sk_refcnt)))
> goto out;
>
> - if (unlikely(!inet6_match(net, sk, saddr, daddr, ports, dif, sdif))) {
> + if (unlikely(hlist_nulls_unhashed_lockless(&sk->sk_nulls_node) ||
> + !inet6_match(net, sk, saddr, daddr, ports, dif,
> + sdif))) {
> sock_gen_put(sk);
> goto begin;
> }
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH net v2 2/2] inet: restore refcount ordering for established lookups
2026-10-23 16:33 ` Eric Dumazet
@ 2026-10-23 18:07 ` Hyunwoo Kim
0 siblings, 0 replies; 9+ messages in thread
From: Hyunwoo Kim @ 2026-10-23 18:07 UTC (permalink / raw)
To: Eric Dumazet
Cc: ncardwell, kuniyu, davem, kuba, pabeni, horms, martin.lau,
john.fastabend, weiwan, sd, jiayuan.chen, netdev, stable, imv4bel
On Fri, Oct 23, 2026 at 06:33:53PM +0200, Eric Dumazet wrote:
> Le ven. 23 oct. 2026 à 07:26, Hyunwoo Kim <imv4bel@gmail.com> a écrit :
> >
> > When commit 3ab5aee7fe84 ("net: Convert TCP & DCCP hash tables to use
> > RCU / hlist_nulls") was written, atomic_inc_not_zero() provided full
> > memory ordering. Even after commit 41c6d650f653 ("net: convert
> > sock.sk_refcnt from atomic_t to refcount_t"), default builds used
> > atomic_add_unless() and retained that ordering.
> >
> > Commit fb041bb7c0a9 ("locking/refcount: Consolidate implementations
> > of refcount_t") consolidated the refcount_t implementations in v5.5.
> > On weakly ordered architectures, this removed the ordering required
> > by validation after a successful reference acquisition.
> >
> > Use refcount_inc_not_zero_acquire() in the IPv4 and IPv6 established
> > lookups. Use refcount_set_release() when setting initial refcounts in
> > sk_clone(), SYN cookie, and request migration paths. In the TCP Fast
> > Open path, move the refcount update after the required initialization
> > and use release ordering.
> >
> > The TIME_WAIT path already has an smp_wmb() between refcount_set() and
> > hash publication, so leave it unchanged.
>
> This is not accurate. The smp_wmb() in inet_twsk_hashdance_schedule()
> orders the tw_refcnt store against the ehash publication, not the tw
> identity stores done in inet_twsk_alloc() against the tw_refcnt store.
>
> The latter is what a lookup holding a stale pointer needs. Today we get it
> more or less by accident from the bhash spin_unlock() followed by
> spin_lock() of the ehash lock.
>
> Please either reword this paragraph, or simply use
> refcount_set_release(&tw->tw_refcnt, 3) there; it is free under the lock
> and makes the pairing explicit.
OK, I'll switch to refcount_set_release().
>
>
> >
> > Fixes: fb041bb7c0a9 ("locking/refcount: Consolidate implementations of refcount_t")
>
> refcount_inc_not_zero_acquire() and refcount_set_release() were only
> added in 7f8ceea0c580 ("refcount: provide ops for cases when object's
> memory can be reused"), in v6.15.
>
>
> > Cc: stable@vger.kernel.org
> > Signed-off-by: Hyunwoo Kim <imv4bel@gmail.com>
> > ---
> > net/core/sock.c | 3 +--
> > net/ipv4/inet_connection_sock.c | 6 +++---
> > net/ipv4/inet_hashtables.c | 2 +-
> > net/ipv4/syncookies.c | 2 +-
> > net/ipv4/tcp_fastopen.c | 3 +--
> > net/ipv6/inet6_hashtables.c | 2 +-
> > 6 files changed, 8 insertions(+), 10 deletions(-)
> >
> > diff --git a/net/core/sock.c b/net/core/sock.c
> > index 3c25d66ba31a..f3ef9601ab73 100644
> > --- a/net/core/sock.c
> > +++ b/net/core/sock.c
> > @@ -2622,8 +2622,7 @@ struct sock *sk_clone(const struct sock *sk, const gfp_t priority,
> > /* Before updating sk_refcnt, we must commit prior changes to memory
> > * (Documentation/RCU/rculist_nulls.rst for details)
> > */
> > - smp_wmb();
> > - refcount_set(&newsk->sk_refcnt, 2);
> > + refcount_set_release(&newsk->sk_refcnt, 2);
>
> This part is not a fix: the writer side only needs store->store ordering,
> which smp_wmb() already provides.
>
> I do not mind the conversion, but then reqsk_queue_hash_req() (which has
> the same smp_wmb() + refcount_set() pattern) should be converted as well
> for consistency. Otherwise please drop this hunk to keep the stable
> backport minimal.
>
>
> >
> > sk_set_socket(newsk, NULL);
> > sk_tx_queue_clear(newsk);
> > diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c
> > index cff02f4c5ac8..2d8e54d3a31b 100644
> > --- a/net/ipv4/inet_connection_sock.c
> > +++ b/net/ipv4/inet_connection_sock.c
> > @@ -1056,8 +1056,8 @@ static void reqsk_timer_handler(struct timer_list *t)
> > * hold another count to prevent use-after-free and
> > * call reqsk_put() just before return.
> > */
> > - refcount_set(&nreq->rsk_refcnt, 2 + 1);
> > timer_setup(&nreq->rsk_timer, reqsk_timer_handler, TIMER_PINNED);
> > + refcount_set_release(&nreq->rsk_refcnt, 2 + 1);
> > reqsk_queue_migrated(&inet_csk(nsk)->icsk_accept_queue, req);
> >
> > req = nreq;
> > @@ -1441,7 +1441,7 @@ struct sock *inet_csk_complete_hashdance(struct sock *sk, struct sock *child,
> > goto child_put;
> > }
> >
> > - refcount_set(&nreq->rsk_refcnt, 1);
> > + refcount_set_release(&nreq->rsk_refcnt, 1);
> > if (inet_csk_reqsk_queue_add(sk, nreq, child)) {
> > __NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPMIGRATEREQSUCCESS);
> > reqsk_migrate_reset(req);
> > @@ -1494,7 +1494,7 @@ void inet_csk_listen_stop(struct sock *sk)
> > if (nsk) {
> > nreq = inet_reqsk_clone(req, nsk);
> > if (nreq) {
> > - refcount_set(&nreq->rsk_refcnt, 1);
> > + refcount_set_release(&nreq->rsk_refcnt, 1);
> >
> > rcu_read_lock();
> > if (inet_csk_reqsk_queue_add(nsk, nreq, child)) {
> > diff --git a/net/ipv4/inet_hashtables.c b/net/ipv4/inet_hashtables.c
> > index 58d54965036a..7ffa9171d6de 100644
> > --- a/net/ipv4/inet_hashtables.c
> > +++ b/net/ipv4/inet_hashtables.c
> > @@ -550,7 +550,7 @@ struct sock *__inet_lookup_established(const struct net *net,
> > if (sk->sk_hash != hash)
> > continue;
> > if (likely(inet_match(net, sk, acookie, ports, dif, sdif))) {
> > - if (unlikely(!refcount_inc_not_zero(&sk->sk_refcnt)))
> > + if (unlikely(!refcount_inc_not_zero_acquire(&sk->sk_refcnt)))
> > goto out;
> > if (unlikely(hlist_nulls_unhashed_lockless(&sk->sk_nulls_node) ||
> > !inet_match(net, sk, acookie, ports, dif,
> > diff --git a/net/ipv4/syncookies.c b/net/ipv4/syncookies.c
> > index 644030561537..4b2d2713653b 100644
> > --- a/net/ipv4/syncookies.c
> > +++ b/net/ipv4/syncookies.c
> > @@ -205,7 +205,7 @@ struct sock *tcp_get_cookie_sock(struct sock *sk, struct sk_buff *skb,
> > child = icsk->icsk_af_ops->syn_recv_sock(sk, skb, req, dst,
> > NULL, &own_req, NULL);
> > if (child) {
> > - refcount_set(&req->rsk_refcnt, 1);
> > + refcount_set_release(&req->rsk_refcnt, 1);
> > sock_rps_save_rxhash(child, skb);
> >
> > if (rsk_drop_req(req)) {
> > diff --git a/net/ipv4/tcp_fastopen.c b/net/ipv4/tcp_fastopen.c
> > index 471c78be5513..2506f415f410 100644
> > --- a/net/ipv4/tcp_fastopen.c
> > +++ b/net/ipv4/tcp_fastopen.c
> > @@ -364,8 +364,6 @@ static struct sock *tcp_fastopen_create_child(struct sock *sk,
> > tcp_reset_xmit_timer(child, ICSK_TIME_RETRANS,
> > req->timeout, false);
> >
> > - refcount_set(&req->rsk_refcnt, 2);
> > -
> > sk_mark_napi_id_set(child, skb);
> >
> > /* Now finish processing the fastopen child socket. */
> > @@ -378,6 +376,7 @@ static struct sock *tcp_fastopen_create_child(struct sock *sk,
> > tcp_rsk(req)->rcv_nxt = tp->rcv_nxt;
> > tp->rcv_wup = tp->rcv_nxt;
> > tp->rcv_mwnd_seq = tp->rcv_wup + tp->rcv_wnd;
> > + refcount_set_release(&req->rsk_refcnt, 2);
> > /* tcp_conn_request() is sending the SYNACK,
>
> Moving this is harmless, but not needed. The lookup never looks at
> rcv_nxt, and this req is never hashed, so after patch 1/2 a stale lookup
> will reject it anyway. A plain conversion in place would be enough.
>
> Let's keep code churn minimal, to lower risk of merge conflicts.
I'll submit v3 with all unnecessary changes removed. Thanks for the review.
Best regards,
Hyunwoo Kim
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH net v2 1/2] inet: reject unhashed sockets during established lookups
2026-10-23 5:24 ` [PATCH net v2 1/2] inet: reject unhashed sockets during established lookups Hyunwoo Kim
2026-10-23 16:39 ` Eric Dumazet
@ 2026-10-27 5:55 ` netdev-bot+sashiko
1 sibling, 0 replies; 9+ messages in thread
From: netdev-bot+sashiko @ 2026-10-27 5:55 UTC (permalink / raw)
To: imv4bel
Cc: edumazet, ncardwell, kuniyu, davem, kuba, pabeni, horms,
martin.lau, john.fastabend, weiwan, sd, jiayuan.chen, netdev,
stable
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] At this commit, the new post-reference checks
hlist_nulls_unhashed_lockless(&sk->sk_nulls_node) and…
--
inet: reject unhashed sockets during established lookups
This commit adds an hlist_nulls_unhashed_lockless() check after the
reference is taken in __inet_lookup_established() and
__inet6_lookup_established(). A lookup that races with
SLAB_TYPESAFE_BY_RCU reuse of a socket now restarts instead of returning
an unhashed object. The SYN cookie, Fast Open and request migration
failure paths now drop references with reqsk_put() instead of calling
__reqsk_free() directly.
> diff --git a/net/ipv4/inet_hashtables.c b/net/ipv4/inet_hashtables.c
> index 5abcb0debb270..b0239d203f045 100644
> --- a/net/ipv4/inet_hashtables.c
> +++ b/net/ipv4/inet_hashtables.c
> @@ -552,8 +552,9 @@ struct sock *__inet_lookup_established(const struct net *net,
> if (likely(inet_match(net, sk, acookie, ports, dif, sdif))) {
> if (unlikely(!refcount_inc_not_zero(&sk->sk_refcnt)))
> goto out;
> - if (unlikely(!inet_match(net, sk, acookie,
> - ports, dif, sdif))) {
> + if (unlikely(hlist_nulls_unhashed_lockless(&sk->sk_nulls_node) ||
> + !inet_match(net, sk, acookie, ports, dif,
> + sdif))) {
[Severity: Low]
On weakly ordered architectures, are the new pprev load and the repeated
tuple check guaranteed to happen after the refcount increment?
refcount_inc_not_zero() is relaxed here. At this commit, the code that
publishes a nonzero refcount on never-hashed request sockets also uses
plain stores:
net/ipv4/syncookies.c:tcp_get_cookie_sock() {
...
if (child) {
refcount_set(&req->rsk_refcnt, 1);
...
}
net/ipv4/tcp_fastopen.c:tcp_fastopen_create_child() {
...
refcount_set(&req->rsk_refcnt, 2);
...
}
The inet_reqsk_clone() users do the same. They are reqsk_timer_handler()
(refcount_set(&nreq->rsk_refcnt, 2 + 1)), inet_csk_complete_hashdance()
and inet_csk_listen_stop() (refcount_set(&nreq->rsk_refcnt, 1)).
On arm64 or POWER, the hlist_nulls_unhashed_lockless() and inet_match()
loads could be satisfied before the cmpxchg in refcount_inc_not_zero().
If so, the lookup could still return the recycled, unhashed SYN cookie
reqsk with rsk_listener == NULL. That is the tcp_v4_rcv() crash described
in the commit message. The same applies to __inet6_lookup_established(),
which also uses a relaxed refcount_inc_not_zero() before the new check.
This ordering is older than this patch. It is fully fixed by the next
patch in the series, "inet: restore refcount ordering for established
lookups". That patch switches both lookups to
refcount_inc_not_zero_acquire(), and switches tcp_get_cookie_sock(),
tcp_fastopen_create_child() (moving the set after initialization),
reqsk_timer_handler(), inet_csk_complete_hashdance(),
inet_csk_listen_stop() and sk_clone() to refcount_set_release().
Both patches carry Cc: stable. Should they be marked so that they are
backported together, so that this one is not picked up alone?
> sock_gen_put(sk);
> goto begin;
> }
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261023052542.3505250-1-imv4bel%40gmail.com
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH net v2 2/2] inet: restore refcount ordering for established lookups
2026-10-23 5:24 ` [PATCH net v2 2/2] inet: restore refcount ordering for " Hyunwoo Kim
2026-10-23 16:33 ` Eric Dumazet
@ 2026-10-27 5:55 ` netdev-bot+sashiko
1 sibling, 0 replies; 9+ messages in thread
From: netdev-bot+sashiko @ 2026-10-27 5:55 UTC (permalink / raw)
To: imv4bel
Cc: edumazet, ncardwell, kuniyu, davem, kuba, pabeni, horms,
martin.lau, john.fastabend, weiwan, sd, jiayuan.chen, netdev,
stable
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 1 · 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] inet: premature refcount_set_release() in sk_clone() and
reqsk_timer_handler()
- [Medium] The patch converts only __inet_lookup_established() and
__inet6_lookup_established() to refcount_inc_not_zero_acquire().
--
inet: restore refcount ordering for established lookups
This patch converts the IPv4 and IPv6 established hash lookups to
refcount_inc_not_zero_acquire(). It also converts the code that sets
initial refcounts in sk_clone(), SYN cookies, request migration and TCP
Fast Open to refcount_set_release(). The goal is to restore the ordering
that weakly ordered architectures lost after the refcount_t
consolidation in v5.5.
> diff --git a/net/ipv4/inet_hashtables.c b/net/ipv4/inet_hashtables.c
> index b0239d203f045..860145d810ed4 100644
> --- a/net/ipv4/inet_hashtables.c
> +++ b/net/ipv4/inet_hashtables.c
> @@ -550,7 +550,7 @@ struct sock *__inet_lookup_established(const struct net *net,
> if (sk->sk_hash != hash)
> continue;
> if (likely(inet_match(net, sk, acookie, ports, dif, sdif))) {
> - if (unlikely(!refcount_inc_not_zero(&sk->sk_refcnt)))
> + if (unlikely(!refcount_inc_not_zero_acquire(&sk->sk_refcnt)))
> goto out;
[Severity: Medium]
This converts __inet_lookup_established() and
__inet6_lookup_established(). Other lockless readers of
SLAB_TYPESAFE_BY_RCU sockets take a reference and then revalidate the
object in the same way. Are those readers still using the relaxed
refcount_inc_not_zero()?
inet_twsk_purge() in net/ipv4/inet_timewait_sock.c walks the same ehash
chains and handles TIME_WAIT and NEW_SYN_RECV sockets:
net/ipv4/inet_timewait_sock.c:inet_twsk_purge() {
...
if (unlikely(!refcount_inc_not_zero(&sk->sk_refcnt)))
continue;
if (check_net(sock_net(sk))) {
sock_gen_put(sk);
goto restart;
}
...
}
The twsk and request sock slabs are created with prot->slab_flags. For
tcp_prot and tcpv6_prot, that is SLAB_TYPESAFE_BY_RCU. reqsk_timer_handler()
inserts nreq into ehash, and this patch changes it to set nreq's refcount
with refcount_set_release(). inet_twsk_purge() has no matching acquire.
On a weakly ordered CPU, the check_net() load can be satisfied before the
increment. In that case, could inet_twsk_purge() call
inet_twsk_deschedule_put() or inet_csk_reqsk_queue_drop_and_put() on a
recycled object that belongs to a live netns?
It also reads req->rsk_listener after rcu_read_unlock(), with no ordering
against the reference acquisition.
MPTCP has the same pattern on mptcp_sock. mptcp_prot also uses
SLAB_TYPESAFE_BY_RCU in net/mptcp/protocol.c:
net/mptcp/token.c:mptcp_token_get_sock() {
...
if (!refcount_inc_not_zero(&sk->sk_refcnt))
goto not_found;
if (READ_ONCE(msk->token) != token ||
!net_eq(sock_net(sk), net)) {
...
}
net/mptcp/token.c:mptcp_token_iter_next() {
...
if (!refcount_inc_not_zero(&sk->sk_refcnt))
continue;
if (!net_eq(sock_net(sk), net)) {
...
}
net/mptcp/subflow.c already uses refcount_inc_not_zero_acquire() for the
msk.
__refcount_add_not_zero() uses atomic_try_cmpxchg_relaxed(). The
refcount.h documentation says to use the acquire variant when the
increment is followed by validation on SLAB_TYPESAFE_BY_RCU objects.
Could mptcp_token_get_sock() return the wrong msk for a remote MP_JOIN
SYN? Could mptcp_token_iter_next() return an msk from another netns to
the PM or diag iterators? For the MP_JOIN case, the HMAC check would
most likely reject a wrong msk.
The commit message describes this class of bug as removing "the ordering
required by validation after a successful reference acquisition". Should
these readers also move to refcount_inc_not_zero_acquire(), either in
this patch or in a follow-up?
> if (unlikely(hlist_nulls_unhashed_lockless(&sk->sk_nulls_node) ||
> !inet_match(net, sk, acookie, ports, dif,
[ ... ]
> diff --git a/net/core/sock.c b/net/core/sock.c
> --- a/net/core/sock.c
> +++ b/net/core/sock.c
> @@ -2622,8 +2622,7 @@ struct sock *sk_clone(const struct sock *sk, const gfp_t priority,
> /* Before updating sk_refcnt, we must commit prior changes to memory
> * (Documentation/RCU/rculist_nulls.rst for details)
> */
> - smp_wmb();
> - refcount_set(&newsk->sk_refcnt, 2);
> + refcount_set_release(&newsk->sk_refcnt, 2);
>
> sk_set_socket(newsk, NULL);
> sk_tx_queue_clear(newsk);
[Severity: High]
Is this release placed late enough to cover the fields that the
established lookup revalidates?
sk_clone() publishes sk_refcnt here, but several stores still follow
it before the socket is linked into ehash. These include
sk_set_socket(newsk, NULL), sk_tx_queue_clear(), sk_rx_queue_clear()
and the sk_wq reset right below. They also include the tuple fields
(sk_daddr, sk_rcv_saddr, inet_dport, inet_num, sk_v6_daddr, ...) and
inet_sk_set_state(newsk, TCP_SYN_RECV) in inet_csk_clone_lock(). The
child setup in tcp_create_openreq_child() and tcp_v4_syn_recv_sock()
or tcp_v6_syn_recv_sock() comes after that.
Only then does inet_ehash_insert() link the socket. The pprev store in
__sk_nulls_add_node_rcu() or sk_nulls_replace_node_init_rcu() is a
plain WRITE_ONCE() and does not touch sk_refcnt.
Consider a reader in __inet_lookup_established() or
__inet6_lookup_established() that picked up the object pointer during
the object's previous life in the SLAB_TYPESAFE_BY_RCU cache. That
reader never traverses the newly published chain link:
refcount_inc_not_zero_acquire() succeeds and pairs with the
release in sk_clone()
hlist_nulls_unhashed_lockless() reads pprev with READ_ONCE()
inet_match() / inet6_match() read the tuple with relaxed loads
The acquire only orders the reader against stores made before the
release. The pprev and tuple values are written after the release,
so the reader can observe them with no ordering at all.
On a weakly ordered CPU, the lookup could then pass both checks and
return the socket while still seeing values that sock_copy() took
from the listener. Examples are sk_state == TCP_LISTEN, the
listener's sk_socket and sk_wq, or TCP fields that are not yet
initialized. Wouldn't tcp_v4_rcv() or tcp_v6_rcv() then process the
packet against a half-initialized socket?
reqsk_timer_handler() has the same problem. Moving the release after
timer_setup() helps, but several stores still run between
refcount_set_release(&nreq->rsk_refcnt, 2 + 1) and
inet_ehash_insert(req_to_sk(nreq), req_to_sk(oreq), NULL). These are
tcp_rtx_synack(), the accecn_fail_mode update, nreq->num_timeout++
and mod_timer(&nreq->rsk_timer, ...). A stale reader that sees nreq
hashed has no ordering against any of them.
The release cannot simply be moved past mod_timer() either. Once the
timer is armed, it can already drop references.
By contrast, the TIME_WAIT path that the commit message calls
correct does its smp_wmb() and refcount_set() after
inet_twsk_add_node_rcu(), if I read inet_twsk_hashdance_schedule()
correctly. There the refcount only becomes non-zero after the hash
linkage and every earlier store. Documentation/RCU/rculist_nulls.rst
likewise writes the key before the release on the refcount.
This gap already existed with the old smp_wmb() and refcount_set().
This patch sets out to restore the ordering that the lookup
revalidation depends on, though, so it looks incomplete.
How would you like to close the gap? One option is to make sk_refcnt
visible only after the socket is fully initialized and hashed.
Another is to pair an acquire load of pprev in the lookup with a
release store on the insert side. I'm not sure either option fits the
sk_clone() callers and the request timer's refcount requirements.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261023052542.3505250-1-imv4bel%40gmail.com
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-10-27 5:55 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-23 5:24 [PATCH net v2 0/2] inet: fix established lookup races Hyunwoo Kim
2026-10-23 5:24 ` [PATCH net v2 1/2] inet: reject unhashed sockets during established lookups Hyunwoo Kim
2026-10-23 16:39 ` Eric Dumazet
2026-10-27 5:55 ` netdev-bot+sashiko
2026-10-23 5:24 ` [PATCH net v2 2/2] inet: restore refcount ordering for " Hyunwoo Kim
2026-10-23 16:33 ` Eric Dumazet
2026-10-23 18:07 ` Hyunwoo Kim
2026-10-27 5:55 ` netdev-bot+sashiko
2026-10-23 5:32 ` [PATCH net v2 0/2] inet: fix established lookup races netdev-bot+sinfo
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox