From 4e2b3ef19e0e04adca38442f07dd6bc93c712bf1 Mon Sep 17 00:00:00 2001 From: Hyunwoo Kim Date: Fri, 30 Oct 2026 06:42:16 +0900 Subject: [PATCH 1/2] inet: reject unhashed sockets during established lookups 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: ? 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 ... 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 Reviewed-by: Eric Dumazet Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20261029214219.1035176-2-imv4bel@gmail.com Signed-off-by: Paolo Abeni --- 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 93b306d91b6d..4a507e5a2672 100644 --- a/net/ipv4/inet_connection_sock.c +++ b/net/ipv4/inet_connection_sock.c @@ -1141,10 +1141,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 temporary reference and the one reserved for ehash. */ + reqsk_put(nreq); + reqsk_put(nreq); } drop: @@ -1453,7 +1457,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; } @@ -1508,7 +1512,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 5abcb0debb27..b0239d203f04 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 8c9691569e01..349a3e9927eb 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 ebd2c12e42d5..73c5b4189866 100644 --- a/net/ipv4/tcp_input.c +++ b/net/ipv4/tcp_input.c @@ -7825,7 +7825,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; } From 5b5d8f351dd96f56a58bb516a80e2d4d742fe4e2 Mon Sep 17 00:00:00 2001 From: Hyunwoo Kim Date: Fri, 30 Oct 2026 06:42:17 +0900 Subject: [PATCH 2/2] net: restore refcount ordering for lockless socket access Before commit fb041bb7c0a9 ("locking/refcount: Consolidate implementations of refcount_t"), default builds implemented refcount_inc_not_zero() using atomic_add_unless(), which provided full memory ordering on success. The commit removed that ordering on weakly ordered architectures, allowing accesses after a successful reference acquisition to be reordered before it. Use refcount_inc_not_zero_acquire() in the IPv4 and IPv6 established lookups and in inet_twsk_purge(), where the socket is validated again after taking a reference. bpf_sk_assign() can receive a socket pointer obtained through BTF without a reference. Use acquire ordering when it takes its own reference for a refcounted socket. Use refcount_set_release() when setting refcounts in the SYN cookie, TCP Fast Open, request migration, and TIME_WAIT paths. Keep the existing TIME_WAIT smp_wmb(), which separately orders the refcount store before ehash publication. Fixes: fb041bb7c0a9 ("locking/refcount: Consolidate implementations of refcount_t") Fixes: 27e5203bd9c5 ("bpf: Change bpf_sk_assign to accept ARG_PTR_TO_BTF_ID_SOCK_COMMON") Cc: stable@vger.kernel.org Signed-off-by: Hyunwoo Kim Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20261029214219.1035176-3-imv4bel@gmail.com Signed-off-by: Paolo Abeni --- net/core/filter.c | 2 +- net/ipv4/inet_connection_sock.c | 6 +++--- net/ipv4/inet_hashtables.c | 2 +- net/ipv4/inet_timewait_sock.c | 4 ++-- net/ipv4/syncookies.c | 2 +- net/ipv4/tcp_fastopen.c | 2 +- net/ipv6/inet6_hashtables.c | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/net/core/filter.c b/net/core/filter.c index 09c60a58c82a..d832d3332c6f 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -8022,7 +8022,7 @@ BPF_CALL_3(bpf_sk_assign, struct sk_buff *, skb, struct sock *, sk, u64, flags) if (sk_unhashed(sk)) return -EOPNOTSUPP; if (sk_is_refcounted(sk) && - unlikely(!refcount_inc_not_zero(&sk->sk_refcnt))) + unlikely(!refcount_inc_not_zero_acquire(&sk->sk_refcnt))) return -ENOENT; skb_orphan(skb); diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c index 4a507e5a2672..e56beec43877 100644 --- a/net/ipv4/inet_connection_sock.c +++ b/net/ipv4/inet_connection_sock.c @@ -1061,8 +1061,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; @@ -1447,7 +1447,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); @@ -1500,7 +1500,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 b0239d203f04..860145d810ed 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/inet_timewait_sock.c b/net/ipv4/inet_timewait_sock.c index a1d86026aefb..e256c55ce9f2 100644 --- a/net/ipv4/inet_timewait_sock.c +++ b/net/ipv4/inet_timewait_sock.c @@ -143,7 +143,7 @@ void inet_twsk_hashdance_schedule(struct inet_timewait_sock *tw, * Also note that after this point, we lost our implicit reference * so we are not allowed to use tw anymore. */ - refcount_set(&tw->tw_refcnt, 3); + refcount_set_release(&tw->tw_refcnt, 3); /* Ensure tw_refcnt has been set before tw is published. * smp_wmb() provides the necessary memory barrier to enforce this @@ -330,7 +330,7 @@ void inet_twsk_purge(struct inet_hashinfo *hashinfo) if (check_net(sock_net(sk))) continue; - if (unlikely(!refcount_inc_not_zero(&sk->sk_refcnt))) + if (unlikely(!refcount_inc_not_zero_acquire(&sk->sk_refcnt))) continue; if (check_net(sock_net(sk))) { diff --git a/net/ipv4/syncookies.c b/net/ipv4/syncookies.c index 349a3e9927eb..4e0ff3b82062 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 6dfe40322fb5..33451f00c4e8 100644 --- a/net/ipv4/tcp_fastopen.c +++ b/net/ipv4/tcp_fastopen.c @@ -366,7 +366,7 @@ 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); + refcount_set_release(&req->rsk_refcnt, 2); sk_mark_napi_id_set(child, skb); 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) ||