From: Sasha Levin <sashal@kernel.org>
To: stable@vger.kernel.org
Cc: Ruide Cao <caoruide123@gmail.com>, Vega <vega@nebusec.ai>,
Sashiko <sashiko-bot@kernel.org>, Ren Wei <weir@nebusec.ai>,
"Matthieu Baerts (NGI0)" <matttbe@kernel.org>,
Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
Subject: [PATCH 5.15.y] mptcp: fix MP_CAPABLE token migration when cloning reqsk
Date: Tue, 10 Nov 2026 14:09:56 -0500 [thread overview]
Message-ID: <20261110190829.3442804-1-sashal@kernel.org> (raw)
In-Reply-To: <20261107060753-failed-5.15-2093bc7fa500@kernel.org>
From: Ruide Cao <caoruide123@gmail.com>
[ Upstream commit 2093bc7fa50056fef8620d2b82bcb2e9a0a5575d ]
TCP request migration clones pending request sockets with
inet_reqsk_clone(). For MPTCP MP_CAPABLE requests this byte-copies the
token_node hlist state into the clone even though the token table still
names the original request, and the following warning can be seen:
WARNING: net/mptcp/token.c:364 at mptcp_token_destroy_request+0x2b0/0x330, CPU#2: swapper/2/0
CPU: 2 UID: 0 PID: 0 Comm: swapper/2 Not tainted 7.2.0-rc5-00353-gc27e36054537 #10 PREEMPT(full)
RIP: 0010:mptcp_token_destroy_request (net/mptcp/token.c:364 (discriminator 1))
Call Trace:
<IRQ>
subflow_v4_req_destructor (net/mptcp/subflow.c:694)
__reqsk_free (net/ipv4/inet_connection_sock.c:906)
reqsk_timer_handler (include/net/request_sock.h:137 net/ipv4/inet_connection_sock.c:1147)
call_timer_fn (kernel/time/timer.c:1748)
Move token request ownership from the original request to the clone under
the token bucket lock. Make mptcp_token_accept() and
mptcp_token_destroy_request() re-check token_node under the same lock and
treat an already moved or removed request as a normal race instead of
warning. This also keeps token bucket chain_len accounting balanced.
If a passive MP_CAPABLE socket cannot claim the token, destroy the
provisional MPTCP socket and let the subflow fall back rather than
installing a socket with mismatched token ownership. Mark that path as a
passive handshake fallback so MPCapableFallbackACK records the downgrade.
If the speculative clone later loses the inet_ehash_insert() ownership
arbitration, the original request may find that its token was already moved
and fall back to plain TCP. The clone destructor then releases the token
reservation and keeps chain_len balanced. This trades an exceptionally
rare fallback for eliminating the warning and persistent accounting drift.
Fixes: c905dee62232 ("tcp: Migrate TCP_NEW_SYN_RECV requests at retransmitting SYN+ACKs.")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Reported-by: Sashiko <sashiko-bot@kernel.org>
Closes: https://sashiko.dev/#/patchset/86e2514b533bf4d55d4aa2fdbf1404022e8c9430.1776149210.git.caoruide123%40gmail.com
Signed-off-by: Ruide Cao <caoruide123@gmail.com>
Signed-off-by: Ren Wei <weir@nebusec.ai>
Reviewed-by: Matthieu Baerts (NGI0) <matttbe@kernel.org>
Link: https://patch.msgid.link/20261013-net-mptcp-misc-fixes-7-3-rc8-v1-4-932c9b182fc7@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
[ sashal: adapted token acceptance failure handling to subflow_syn_recv_sock() using the existing mptcp_force_close() cleanup ]
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
net/mptcp/protocol.h | 4 ++-
net/mptcp/subflow.c | 10 ++++++-
net/mptcp/token.c | 65 ++++++++++++++++++++++++++++++++++++++----
net/mptcp/token_test.c | 4 +--
4 files changed, 73 insertions(+), 10 deletions(-)
diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h
index 913d9b1474b05..7fd7ee90c0e50 100644
--- a/net/mptcp/protocol.h
+++ b/net/mptcp/protocol.h
@@ -712,9 +712,11 @@ static inline void mptcp_token_init_request(struct request_sock *req)
}
int mptcp_token_new_request(struct request_sock *req);
+void mptcp_token_move_request(struct request_sock *req,
+ struct request_sock *new_req);
void mptcp_token_destroy_request(struct request_sock *req);
int mptcp_token_new_connect(struct sock *sk);
-void mptcp_token_accept(struct mptcp_subflow_request_sock *r,
+bool mptcp_token_accept(struct mptcp_subflow_request_sock *r,
struct mptcp_sock *msk);
bool mptcp_token_exists(u32 token);
struct mptcp_sock *mptcp_token_get_sock(struct net *net, u32 token);
diff --git a/net/mptcp/subflow.c b/net/mptcp/subflow.c
index 73580bf40aca8..c0fb2400b907d 100644
--- a/net/mptcp/subflow.c
+++ b/net/mptcp/subflow.c
@@ -81,6 +81,8 @@ void mptcp_subflow_reqsk_clone(struct request_sock *req,
}
new_subflow_req->msk = msk;
+
+ mptcp_token_move_request(req, new_req);
}
static void subflow_generate_hmac(u64 key1, u64 key2, u32 nonce1, u32 nonce2,
@@ -779,6 +781,7 @@ static struct sock *subflow_syn_recv_sock(const struct sock *sk,
goto dispose_child;
}
+fallback:
if (fallback)
SUBFLOW_REQ_INC_STATS(req, MPTCP_MIB_MPCAPABLEPASSIVEFALLBACK);
@@ -803,13 +806,18 @@ static struct sock *subflow_syn_recv_sock(const struct sock *sk,
if (mp_opt.deny_join_id0)
WRITE_ONCE(mptcp_sk(new_msk)->pm.remote_deny_join_id0, true);
+ if (!mptcp_token_accept(subflow_req, mptcp_sk(new_msk))) {
+ WRITE_ONCE(mptcp_sk(new_msk)->first, NULL);
+ fallback = true;
+ goto fallback;
+ }
+
/* new mpc subflow takes ownership of the newly
* created mptcp socket
*/
new_msk->sk_destruct = mptcp_sock_destruct;
mptcp_sk(new_msk)->setsockopt_seq = ctx->setsockopt_seq;
mptcp_pm_new_connection(mptcp_sk(new_msk), child, 1);
- mptcp_token_accept(subflow_req, mptcp_sk(new_msk));
ctx->conn = new_msk;
new_msk = NULL;
diff --git a/net/mptcp/token.c b/net/mptcp/token.c
index e581b341c5beb..7a9dd2ade92b7 100644
--- a/net/mptcp/token.c
+++ b/net/mptcp/token.c
@@ -178,6 +178,43 @@ int mptcp_token_new_connect(struct sock *sk)
return 0;
}
+/**
+ * mptcp_token_move_request - move request token ownership to a clone
+ * @req: original request socket
+ * @new_req: cloned request socket
+ *
+ * Move the token hash entry from the original request to its clone.
+ */
+void mptcp_token_move_request(struct request_sock *req,
+ struct request_sock *new_req)
+{
+ struct mptcp_subflow_request_sock *subflow_req = mptcp_subflow_rsk(req);
+ struct mptcp_subflow_request_sock *new_subflow_req;
+ struct mptcp_subflow_request_sock *pos;
+ struct token_bucket *bucket;
+
+ new_subflow_req = mptcp_subflow_rsk(new_req);
+
+ if (hlist_nulls_unhashed_lockless(&subflow_req->token_node)) {
+ mptcp_token_init_request(new_req);
+ return;
+ }
+
+ bucket = token_bucket(subflow_req->token);
+ spin_lock_bh(&bucket->lock);
+ if (hlist_nulls_unhashed(&subflow_req->token_node)) {
+ mptcp_token_init_request(new_req);
+ } else {
+ pos = __token_lookup_req(bucket, subflow_req->token);
+ if (pos == subflow_req)
+ hlist_nulls_replace_init_rcu(&subflow_req->token_node,
+ &new_subflow_req->token_node);
+ else
+ mptcp_token_init_request(new_req);
+ }
+ spin_unlock_bh(&bucket->lock);
+}
+
/**
* mptcp_token_accept - replace a req sk with full sock in token hash
* @req: the request socket to be removed
@@ -185,22 +222,33 @@ int mptcp_token_new_connect(struct sock *sk)
*
* Called when a SYN packet creates a new logical connection, i.e.
* is not a join request.
+ *
+ * Return: true on success.
*/
-void mptcp_token_accept(struct mptcp_subflow_request_sock *req,
+bool mptcp_token_accept(struct mptcp_subflow_request_sock *req,
struct mptcp_sock *msk)
{
struct mptcp_subflow_request_sock *pos;
struct token_bucket *bucket;
+ bool ret = false;
bucket = token_bucket(req->token);
spin_lock_bh(&bucket->lock);
+ if (hlist_nulls_unhashed(&req->token_node))
+ goto unlock;
- /* pedantic lookup check for the moved token */
pos = __token_lookup_req(bucket, req->token);
- if (!WARN_ON_ONCE(pos != req))
- hlist_nulls_del_init_rcu(&req->token_node);
+ if (pos != req)
+ goto unlock;
+
+ hlist_nulls_del_init_rcu(&req->token_node);
__sk_nulls_add_node_rcu((struct sock *)msk, &bucket->msk_chain);
+ ret = true;
+
+unlock:
spin_unlock_bh(&bucket->lock);
+
+ return ret;
}
bool mptcp_token_exists(u32 token)
@@ -349,16 +397,21 @@ void mptcp_token_destroy_request(struct request_sock *req)
struct mptcp_subflow_request_sock *pos;
struct token_bucket *bucket;
- if (hlist_nulls_unhashed(&subflow_req->token_node))
+ if (hlist_nulls_unhashed_lockless(&subflow_req->token_node))
return;
bucket = token_bucket(subflow_req->token);
spin_lock_bh(&bucket->lock);
+ if (hlist_nulls_unhashed(&subflow_req->token_node))
+ goto unlock;
+
pos = __token_lookup_req(bucket, subflow_req->token);
- if (!WARN_ON_ONCE(pos != subflow_req)) {
+ if (pos == subflow_req) {
hlist_nulls_del_init_rcu(&pos->token_node);
bucket->chain_len--;
}
+
+unlock:
spin_unlock_bh(&bucket->lock);
}
diff --git a/net/mptcp/token_test.c b/net/mptcp/token_test.c
index 5d984bec1cd86..8fbf69b8172d3 100644
--- a/net/mptcp/token_test.c
+++ b/net/mptcp/token_test.c
@@ -91,7 +91,7 @@ static void mptcp_token_test_accept(struct kunit *test)
KUNIT_ASSERT_EQ(test, 0,
mptcp_token_new_request((struct request_sock *)req));
msk->token = req->token;
- mptcp_token_accept(req, msk);
+ KUNIT_EXPECT_TRUE(test, mptcp_token_accept(req, msk));
KUNIT_EXPECT_PTR_EQ(test, msk, mptcp_token_get_sock(&init_net, msk->token));
/* this is now a no-op */
@@ -114,7 +114,7 @@ static void mptcp_token_test_destroyed(struct kunit *test)
KUNIT_ASSERT_EQ(test, 0,
mptcp_token_new_request((struct request_sock *)req));
msk->token = req->token;
- mptcp_token_accept(req, msk);
+ KUNIT_EXPECT_TRUE(test, mptcp_token_accept(req, msk));
/* simulate race on removal */
refcount_set(&sk->sk_refcnt, 0);
--
2.53.0
prev parent reply other threads:[~2026-11-10 19:09 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-07 11:07 FAILED: patch "[PATCH] mptcp: fix MP_CAPABLE token migration when cloning reqsk" failed to apply to 5.15-stable tree Sasha Levin
2026-11-10 19:09 ` Sasha Levin [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=20261110190829.3442804-1-sashal@kernel.org \
--to=sashal@kernel.org \
--cc=caoruide123@gmail.com \
--cc=kuba@kernel.org \
--cc=matttbe@kernel.org \
--cc=sashiko-bot@kernel.org \
--cc=stable@vger.kernel.org \
--cc=vega@nebusec.ai \
--cc=weir@nebusec.ai \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox