* [PATCH net v3] tcp: free saved_syn after an RCU grace period
@ 2026-11-04 12:32 Yingjie Wang
2026-11-04 12:40 ` Eric Dumazet
2026-11-05 20:36 ` Kuniyuki Iwashima
0 siblings, 2 replies; 3+ messages in thread
From: Yingjie Wang @ 2026-11-04 12:32 UTC (permalink / raw)
To: Eric Dumazet, Neal Cardwell
Cc: Kuniyuki Iwashima, David S . Miller, Jakub Kicinski, Paolo Abeni,
Simon Horman, Daniel Borkmann, John Fastabend,
Stanislav Fomichev, Martin KaFai Lau, Alexei Starovoitov,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
Ihor Solodrai, netdev, bpf, linux-kernel, stable
tcp_reqsk_record_syn() allocates saved_syn separately from its request
socket. bpf_sock_ops_get_syn() can load that pointer while processing a
retransmitted SYN under RCU.
There are two ownership transfers which can let another CPU reclaim the
allocation while such a reader is still using it.
First, tcp_move_syn() transfers req->saved_syn to a newly created child
socket. The child can be closed and tcp_saved_syn_free() can reclaim the
allocation before a reader of the old request finishes.
Second, inet_reqsk_clone() shallow-copies request_sock during request
migration. After reqsk_migrate_reset() clears the old request pointer, the
migrated clone owns it. However, a reader which already loaded the pointer
from the old request can still be using it when the clone is released.
I found this through LLM-assisted concurrency fuzzing.
Mark the request-socket saved_syn pointer as RCU protected and use RCU
accessors for its reads and updates. Keep the full-socket pointer plain
because all of its accesses hold the socket lock. Embed an rcu_head in
struct saved_syn and use kfree_rcu() in both teardown paths. This lets a
request-side reader which loaded the object before an ownership transfer
finish.
This follows commit e3f33b0a1d89 ("ipv4: free inet_opt and ireq_opt
after an RCU grace period"), which fixed the same ownership-transfer
pattern for IPv4 options.
The issue was confirmed with KASAN on v7.3-16007-g36aad21d50d1 using
temporary kernel instrumentation to widen the reader and request-migration
window. KASAN reported a slab-use-after-free read in
bpf_sock_ops_get_syn(), with the allocation freed by __reqsk_free() from
reqsk_timer_handler(). The same explicit migration test completed without
a report with deferred reclamation. A patched kernel without the delay
instrumentation also completed a successful migration and seven saved-SYN
reads without a KASAN report.
Fixes: 0813a841566f ("bpf: tcp: Allow bpf prog to write and parse TCP header option")
Fixes: c905dee62232 ("tcp: Migrate TCP_NEW_SYN_RECV requests at retransmitting SYN+ACKs.")
Cc: stable@vger.kernel.org
Signed-off-by: Yingjie Wang <x-professor@outlook.com>
---
Changes in v3:
- Keep tcp_sock::saved_syn under the socket lock and apply RCU protection
only to request_sock::saved_syn, as suggested by Eric Dumazet.
- Use unrcu_pointer() when transferring or reclaiming the request-side
pointer.
- Describe the discovery as LLM-assisted concurrency fuzzing.
- Rebase onto net/main commit 241dec6f9ab7.
v2: https://lore.kernel.org/r/3dd45a687afdd0213ee82685c52777e5b0855e20.1793704772.git.x-professor@outlook.com
- Add RCU annotations and accessors following Kuniyuki Iwashima's review.
v1: https://lore.kernel.org/r/34f32e63b803f9f7faf0b0a5754219e48a986136.1793616950.git.x-professor@outlook.com
include/linux/tcp.h | 6 +++---
include/net/request_sock.h | 4 +++-
net/core/filter.c | 2 +-
net/ipv4/inet_connection_sock.c | 6 +++---
net/ipv4/tcp_input.c | 2 +-
5 files changed, 11 insertions(+), 9 deletions(-)
diff --git a/include/linux/tcp.h b/include/linux/tcp.h
index d18f7bc1e5d0..3ae57ae65ec4 100644
--- a/include/linux/tcp.h
+++ b/include/linux/tcp.h
@@ -618,13 +618,13 @@ static inline void fastopen_queue_tune(struct sock *sk, int backlog)
static inline void tcp_move_syn(struct tcp_sock *tp,
struct request_sock *req)
{
- tp->saved_syn = req->saved_syn;
- req->saved_syn = NULL;
+ tp->saved_syn = unrcu_pointer(req->saved_syn);
+ RCU_INIT_POINTER(req->saved_syn, NULL);
}
static inline void tcp_saved_syn_free(struct tcp_sock *tp)
{
- kfree(tp->saved_syn);
+ kfree_rcu(tp->saved_syn, rcu);
tp->saved_syn = NULL;
}
diff --git a/include/net/request_sock.h b/include/net/request_sock.h
index 5a9c826a7092..ddc134f1d15d 100644
--- a/include/net/request_sock.h
+++ b/include/net/request_sock.h
@@ -16,6 +16,7 @@
#include <linux/types.h>
#include <linux/bug.h>
#include <linux/refcount.h>
+#include <linux/rcupdate.h>
#include <net/sock.h>
#include <net/rstreason.h>
@@ -39,6 +40,7 @@ struct request_sock_ops {
};
struct saved_syn {
+ struct rcu_head rcu;
u32 mac_hdrlen;
u32 network_hdrlen;
u32 tcp_hdrlen;
@@ -68,7 +70,7 @@ struct request_sock {
struct timer_list rsk_timer;
const struct request_sock_ops *rsk_ops;
struct sock *sk;
- struct saved_syn *saved_syn;
+ struct saved_syn __rcu *saved_syn;
u32 secid;
u32 peer_secid;
u32 timeout;
diff --git a/net/core/filter.c b/net/core/filter.c
index d832d3332c6f..c5decaba928e 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -6125,7 +6125,7 @@ static int bpf_sock_ops_get_syn(struct bpf_sock_ops_kern *bpf_sock,
* not be available. It has to resort to
* saved_syn (if it is saved).
*/
- saved_syn = inet_reqsk(sk)->saved_syn;
+ saved_syn = rcu_dereference(inet_reqsk(sk)->saved_syn);
else
saved_syn = tcp_sk(sk)->saved_syn;
diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c
index e56beec43877..37cc8606d683 100644
--- a/net/ipv4/inet_connection_sock.c
+++ b/net/ipv4/inet_connection_sock.c
@@ -869,7 +869,7 @@ reqsk_alloc_noprof(const struct request_sock_ops *ops, struct sock *sk_listener,
req_to_sk(req)->sk_prot = sk_listener->sk_prot;
sk_node_init(&req_to_sk(req)->sk_node);
sk_tx_queue_clear(req_to_sk(req));
- req->saved_syn = NULL;
+ RCU_INIT_POINTER(req->saved_syn, NULL);
req->syncookie = 0;
req->num_timeout = 0;
req->num_retrans = 0;
@@ -908,7 +908,7 @@ void __reqsk_free(struct request_sock *req)
req->rsk_ops->destructor(req);
if (req->rsk_listener)
sock_put(req->rsk_listener);
- kfree(req->saved_syn);
+ kfree_rcu(unrcu_pointer(req->saved_syn), rcu);
kmem_cache_free(req->rsk_ops->slab, req);
}
@@ -979,7 +979,7 @@ static void reqsk_queue_migrated(struct request_sock_queue *queue,
static void reqsk_migrate_reset(struct request_sock *req)
{
- req->saved_syn = NULL;
+ RCU_INIT_POINTER(req->saved_syn, NULL);
#if IS_ENABLED(CONFIG_IPV6)
inet_rsk(req)->ipv6_opt = NULL;
inet_rsk(req)->pktopts = NULL;
diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c
index 73c5b4189866..4e87cddad6d9 100644
--- a/net/ipv4/tcp_input.c
+++ b/net/ipv4/tcp_input.c
@@ -7638,7 +7638,7 @@ static void tcp_reqsk_record_syn(const struct sock *sk,
saved_syn->network_hdrlen = skb_network_header_len(skb);
saved_syn->tcp_hdrlen = tcp_hdrlen(skb);
memcpy(saved_syn->data, base, len);
- req->saved_syn = saved_syn;
+ RCU_INIT_POINTER(req->saved_syn, saved_syn);
}
}
}
base-commit: 241dec6f9ab74d8c3ef246764f3611666b96b9e1
--
2.43.0
^ permalink raw reply [flat|nested] 3+ messages in thread* Re: [PATCH net v3] tcp: free saved_syn after an RCU grace period
2026-11-04 12:32 [PATCH net v3] tcp: free saved_syn after an RCU grace period Yingjie Wang
@ 2026-11-04 12:40 ` Eric Dumazet
2026-11-05 20:36 ` Kuniyuki Iwashima
1 sibling, 0 replies; 3+ messages in thread
From: Eric Dumazet @ 2026-11-04 12:40 UTC (permalink / raw)
To: Yingjie Wang
Cc: Neal Cardwell, Kuniyuki Iwashima, David S . Miller,
Jakub Kicinski, Paolo Abeni, Simon Horman, Daniel Borkmann,
John Fastabend, Stanislav Fomichev, Martin KaFai Lau,
Alexei Starovoitov, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Ihor Solodrai, netdev, bpf, linux-kernel,
stable
Le mer. 4 nov. 2026 à 13:32, Yingjie Wang <x-professor@outlook.com> a écrit :
>
> tcp_reqsk_record_syn() allocates saved_syn separately from its request
> socket. bpf_sock_ops_get_syn() can load that pointer while processing a
> retransmitted SYN under RCU.
>
> There are two ownership transfers which can let another CPU reclaim the
> allocation while such a reader is still using it.
>
> First, tcp_move_syn() transfers req->saved_syn to a newly created child
> socket. The child can be closed and tcp_saved_syn_free() can reclaim the
> allocation before a reader of the old request finishes.
>
> Second, inet_reqsk_clone() shallow-copies request_sock during request
> migration. After reqsk_migrate_reset() clears the old request pointer, the
> migrated clone owns it. However, a reader which already loaded the pointer
> from the old request can still be using it when the clone is released.
>
> I found this through LLM-assisted concurrency fuzzing.
>
> Mark the request-socket saved_syn pointer as RCU protected and use RCU
> accessors for its reads and updates. Keep the full-socket pointer plain
> because all of its accesses hold the socket lock. Embed an rcu_head in
> struct saved_syn and use kfree_rcu() in both teardown paths. This lets a
> request-side reader which loaded the object before an ownership transfer
> finish.
>
> This follows commit e3f33b0a1d89 ("ipv4: free inet_opt and ireq_opt
> after an RCU grace period"), which fixed the same ownership-transfer
> pattern for IPv4 options.
>
> The issue was confirmed with KASAN on v7.3-16007-g36aad21d50d1 using
> temporary kernel instrumentation to widen the reader and request-migration
> window. KASAN reported a slab-use-after-free read in
> bpf_sock_ops_get_syn(), with the allocation freed by __reqsk_free() from
> reqsk_timer_handler(). The same explicit migration test completed without
> a report with deferred reclamation. A patched kernel without the delay
> instrumentation also completed a successful migration and seven saved-SYN
> reads without a KASAN report.
>
> Fixes: 0813a841566f ("bpf: tcp: Allow bpf prog to write and parse TCP header option")
> Fixes: c905dee62232 ("tcp: Migrate TCP_NEW_SYN_RECV requests at retransmitting SYN+ACKs.")
> Cc: stable@vger.kernel.org
> Signed-off-by: Yingjie Wang <x-professor@outlook.com>
> ---
> Changes in v3:
> - Keep tcp_sock::saved_syn under the socket lock and apply RCU protection
> only to request_sock::saved_syn, as suggested by Eric Dumazet.
> - Use unrcu_pointer() when transferring or reclaiming the request-side
> pointer.
> - Describe the discovery as LLM-assisted concurrency fuzzing.
> - Rebase onto net/main commit 241dec6f9ab7.
>
> v2: https://lore.kernel.org/r/3dd45a687afdd0213ee82685c52777e5b0855e20.1793704772.git.x-professor@outlook.com
> - Add RCU annotations and accessors following Kuniyuki Iwashima's review.
>
> v1: https://lore.kernel.org/r/34f32e63b803f9f7faf0b0a5754219e48a986136.1793616950.git.x-professor@outlook.com
>
> include/linux/tcp.h | 6 +++---
> include/net/request_sock.h | 4 +++-
> net/core/filter.c | 2 +-
> net/ipv4/inet_connection_sock.c | 6 +++---
> net/ipv4/tcp_input.c | 2 +-
> 5 files changed, 11 insertions(+), 9 deletions(-)
>
> diff --git a/include/linux/tcp.h b/include/linux/tcp.h
> index d18f7bc1e5d0..3ae57ae65ec4 100644
> --- a/include/linux/tcp.h
> +++ b/include/linux/tcp.h
> @@ -618,13 +618,13 @@ static inline void fastopen_queue_tune(struct sock *sk, int backlog)
> static inline void tcp_move_syn(struct tcp_sock *tp,
> struct request_sock *req)
> {
> - tp->saved_syn = req->saved_syn;
> - req->saved_syn = NULL;
> + tp->saved_syn = unrcu_pointer(req->saved_syn);
> + RCU_INIT_POINTER(req->saved_syn, NULL);
> }
>
> static inline void tcp_saved_syn_free(struct tcp_sock *tp)
> {
> - kfree(tp->saved_syn);
> + kfree_rcu(tp->saved_syn, rcu);
> tp->saved_syn = NULL;
> }
>
> diff --git a/include/net/request_sock.h b/include/net/request_sock.h
> index 5a9c826a7092..ddc134f1d15d 100644
> --- a/include/net/request_sock.h
> +++ b/include/net/request_sock.h
> @@ -16,6 +16,7 @@
> #include <linux/types.h>
> #include <linux/bug.h>
> #include <linux/refcount.h>
> +#include <linux/rcupdate.h>
nit: this include is not needed.
Not worth a new version though.
Reviewed-by: Eric Dumazet <edumazet@kernel.org>
^ permalink raw reply [flat|nested] 3+ messages in thread* Re: [PATCH net v3] tcp: free saved_syn after an RCU grace period
2026-11-04 12:32 [PATCH net v3] tcp: free saved_syn after an RCU grace period Yingjie Wang
2026-11-04 12:40 ` Eric Dumazet
@ 2026-11-05 20:36 ` Kuniyuki Iwashima
1 sibling, 0 replies; 3+ messages in thread
From: Kuniyuki Iwashima @ 2026-11-05 20:36 UTC (permalink / raw)
To: Yingjie Wang
Cc: Eric Dumazet, Neal Cardwell, David S . Miller, Jakub Kicinski,
Paolo Abeni, Simon Horman, Daniel Borkmann, John Fastabend,
Stanislav Fomichev, Martin KaFai Lau, Alexei Starovoitov,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
Ihor Solodrai, netdev, bpf, linux-kernel, stable
On Wed, 04 Nov 2026 12:32:19 +0000, Yingjie Wang wrote:
> tcp_reqsk_record_syn() allocates saved_syn separately from its request
> socket. bpf_sock_ops_get_syn() can load that pointer while processing a
> retransmitted SYN under RCU.
>
> There are two ownership transfers which can let another CPU reclaim the
> allocation while such a reader is still using it.
>
> First, tcp_move_syn() transfers req->saved_syn to a newly created child
> socket. The child can be closed and tcp_saved_syn_free() can reclaim the
> allocation before a reader of the old request finishes.
>
> Second, inet_reqsk_clone() shallow-copies request_sock during request
> migration. After reqsk_migrate_reset() clears the old request pointer, the
> migrated clone owns it. However, a reader which already loaded the pointer
> from the old request can still be using it when the clone is released.
>
> I found this through LLM-assisted concurrency fuzzing.
>
> Mark the request-socket saved_syn pointer as RCU protected and use RCU
> accessors for its reads and updates. Keep the full-socket pointer plain
> because all of its accesses hold the socket lock. Embed an rcu_head in
> struct saved_syn and use kfree_rcu() in both teardown paths. This lets a
> request-side reader which loaded the object before an ownership transfer
> finish.
>
> This follows commit e3f33b0a1d89 ("ipv4: free inet_opt and ireq_opt
> after an RCU grace period"), which fixed the same ownership-transfer
> pattern for IPv4 options.
>
> The issue was confirmed with KASAN on v7.3-16007-g36aad21d50d1 using
> temporary kernel instrumentation to widen the reader and request-migration
> window. KASAN reported a slab-use-after-free read in
> bpf_sock_ops_get_syn(), with the allocation freed by __reqsk_free() from
> reqsk_timer_handler(). The same explicit migration test completed without
> a report with deferred reclamation. A patched kernel without the delay
> instrumentation also completed a successful migration and seven saved-SYN
> reads without a KASAN report.
>
> Fixes: 0813a841566f ("bpf: tcp: Allow bpf prog to write and parse TCP header option")
> Fixes: c905dee62232 ("tcp: Migrate TCP_NEW_SYN_RECV requests at retransmitting SYN+ACKs.")
> Cc: stable@vger.kernel.org
> Signed-off-by: Yingjie Wang <x-professor@outlook.com>
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-11-05 20:36 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-04 12:32 [PATCH net v3] tcp: free saved_syn after an RCU grace period Yingjie Wang
2026-11-04 12:40 ` Eric Dumazet
2026-11-05 20:36 ` Kuniyuki Iwashima
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®