[PATCH net] mptcp: hold msk reference when cloning request sockets

From: David Lee

Date: Tue Aug 04 2026 - 05:51:08 EST


From: Kyle Zeng <kylebot@xxxxxxxxxx>

An MP_JOIN request owns the reference stored in subflow_req->msk.
inet_reqsk_clone() byte-copies that pointer when migrating a request,
but does not acquire a reference for the clone. The original and cloned
request destructors can consequently drop the same reference, leaving
one request with a dangling msk pointer.

Let cloned MPTCP requests take their own msk reference. The source
request still owns its reference while it is being cloned, so sock_hold()
is safe. The clone's normal destructor balances the new reference on
both successful and failed migration paths.

Fixes: c905dee62232 ("tcp: Migrate TCP_NEW_SYN_RECV requests at retransmitting SYN+ACKs.")
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: Codex:gpt-5.6-sol Codex:gpt-5.5-cyber
Signed-off-by: Kyle Zeng <kylebot@xxxxxxxxxx>
Co-developed-by: David Lee <david.lee@xxxxxxxxxxxxxxx>
Signed-off-by: David Lee <david.lee@xxxxxxxxxxxxxxx>
---
Bug found and triaged by OpenAI Security Research and
validated by Trail of Bits.

Trail of Bits has a reproducer for this bug that triggers a
KASAN use-after-free and can share if needed.

include/net/mptcp.h | 5 +++++
net/ipv4/inet_connection_sock.c | 10 ++++++++--
net/mptcp/subflow.c | 8 ++++++++
3 files changed, 21 insertions(+), 2 deletions(-)

diff --git a/include/net/mptcp.h b/include/net/mptcp.h
index 71b9fc5a5..51d4b02f5 100644
--- a/include/net/mptcp.h
+++ b/include/net/mptcp.h
@@ -223,6 +223,7 @@ int mptcp_subflow_init_cookie_req(struct request_sock *req,
struct request_sock *mptcp_subflow_reqsk_alloc(const struct request_sock_ops *ops,
struct sock *sk_listener,
bool attach_listener);
+void mptcp_subflow_reqsk_clone(struct request_sock *req);

__be32 mptcp_get_reset_option(const struct sk_buff *skb);

@@ -309,6 +310,10 @@ static inline struct request_sock *mptcp_subflow_reqsk_alloc(const struct reques
return NULL;
}

+static inline void mptcp_subflow_reqsk_clone(struct request_sock *req)
+{
+}
+
static inline __be32 mptcp_reset_option(const struct sk_buff *skb) { return htonl(0u); }

static inline void mptcp_active_detect_blackhole(struct sock *sk, bool expired) { }
diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c
index 56902bba5..f3ef9c15a 100644
--- a/net/ipv4/inet_connection_sock.c
+++ b/net/ipv4/inet_connection_sock.c
@@ -17,6 +17,7 @@
#include <net/inet_timewait_sock.h>
#include <net/ip.h>
#include <net/route.h>
+#include <net/mptcp.h>
#include <net/tcp_states.h>
#include <net/xfrm.h>
#include <net/tcp.h>
@@ -946,8 +947,13 @@ static struct request_sock *inet_reqsk_clone(struct request_sock *req,
/* We need not acquire fastopenq->lock
* because the child socket is locked in inet_csk_listen_stop().
*/
- if (sk->sk_protocol == IPPROTO_TCP && tcp_rsk(nreq)->tfo_listener)
- rcu_assign_pointer(tcp_sk(nreq->sk)->fastopen_rsk, nreq);
+ if (sk->sk_protocol == IPPROTO_TCP) {
+ if (tcp_rsk(nreq)->tfo_listener)
+ rcu_assign_pointer(tcp_sk(nreq->sk)->fastopen_rsk, nreq);
+
+ if (rsk_is_mptcp(req))
+ mptcp_subflow_reqsk_clone(nreq);
+ }

return nreq;
}
diff --git a/net/mptcp/subflow.c b/net/mptcp/subflow.c
index 8e386899c..1b6aa5e8c 100644
--- a/net/mptcp/subflow.c
+++ b/net/mptcp/subflow.c
@@ -47,6 +47,14 @@ static void subflow_req_destructor(struct request_sock *req)
mptcp_token_destroy_request(req);
}

+void mptcp_subflow_reqsk_clone(struct request_sock *req)
+{
+ struct mptcp_subflow_request_sock *subflow_req = mptcp_subflow_rsk(req);
+
+ if (subflow_req->msk)
+ sock_hold((struct sock *)subflow_req->msk);
+}
+
static void subflow_generate_hmac(u64 key1, u64 key2, u32 nonce1, u32 nonce2,
void *hmac)
{
--
2.47.3