]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
mptcp: plug races between subflow fail and subflow creation
authorPaolo Abeni <pabeni@redhat.com>
Mon, 28 Jul 2025 09:14:50 +0000 (11:14 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 1 Aug 2025 08:47:32 +0000 (09:47 +0100)
commit def5b7b2643ebba696fc60ddf675dca13f073486 upstream.

We have races similar to the one addressed by the previous patch between
subflow failing and additional subflow creation. They are just harder to
trigger.

The solution is similar. Use a separate flag to track the condition
'socket state prevent any additional subflow creation' protected by the
fallback lock.

The socket fallback makes such flag true, and also receiving or sending
an MP_FAIL option.

The field 'allow_infinite_fallback' is now always touched under the
relevant lock, we can drop the ONCE annotation on write.

Fixes: 478d770008b0 ("mptcp: send out MP_FAIL when data checksum fails")
Cc: stable@vger.kernel.org
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
Reviewed-by: Matthieu Baerts (NGI0) <matttbe@kernel.org>
Signed-off-by: Matthieu Baerts (NGI0) <matttbe@kernel.org>
Link: https://patch.msgid.link/20250714-net-mptcp-fallback-races-v1-2-391aff963322@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
[ Conflicts in subflow.c, because commit f1f26512a9bf ("mptcp: use plain
  bool instead of custom binary enum") and commit 46a5d3abedbe
  ("mptcp: fix typos in comments") are not in this version. Both are
  causing conflicts in the context, and the same modifications can still
  be applied. ]
Signed-off-by: Matthieu Baerts (NGI0) <matttbe@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
net/mptcp/pm.c
net/mptcp/protocol.c
net/mptcp/protocol.h
net/mptcp/subflow.c

index 157a574fab0ccf870b2f70f934e44587560d2ac3..29c167e5fc02555fec39b7e49638195d37cf041f 100644 (file)
@@ -304,8 +304,14 @@ void mptcp_pm_mp_fail_received(struct sock *sk, u64 fail_seq)
 
        pr_debug("fail_seq=%llu\n", fail_seq);
 
-       if (!READ_ONCE(msk->allow_infinite_fallback))
+       /* After accepting the fail, we can't create any other subflows */
+       spin_lock_bh(&msk->fallback_lock);
+       if (!msk->allow_infinite_fallback) {
+               spin_unlock_bh(&msk->fallback_lock);
                return;
+       }
+       msk->allow_subflows = false;
+       spin_unlock_bh(&msk->fallback_lock);
 
        if (!subflow->fail_tout) {
                pr_debug("send MP_FAIL response and infinite map\n");
index 2595ace615feb895fc5bd1cebf6d40cb73b75ca3..d95fc113d317752afa30fb044ef9e58a0e0360a0 100644 (file)
@@ -875,7 +875,7 @@ void mptcp_data_ready(struct sock *sk, struct sock *ssk)
 static void mptcp_subflow_joined(struct mptcp_sock *msk, struct sock *ssk)
 {
        mptcp_subflow_ctx(ssk)->map_seq = READ_ONCE(msk->ack_seq);
-       WRITE_ONCE(msk->allow_infinite_fallback, false);
+       msk->allow_infinite_fallback = false;
        mptcp_event(MPTCP_EVENT_SUB_ESTABLISHED, msk, ssk, GFP_ATOMIC);
 }
 
@@ -887,7 +887,7 @@ static bool __mptcp_finish_join(struct mptcp_sock *msk, struct sock *ssk)
                return false;
 
        spin_lock_bh(&msk->fallback_lock);
-       if (__mptcp_check_fallback(msk)) {
+       if (!msk->allow_subflows) {
                spin_unlock_bh(&msk->fallback_lock);
                return false;
        }
@@ -2688,7 +2688,7 @@ static void __mptcp_retrans(struct sock *sk)
                                len = max(copied, len);
                                tcp_push(ssk, 0, info.mss_now, tcp_sk(ssk)->nonagle,
                                         info.size_goal);
-                               WRITE_ONCE(msk->allow_infinite_fallback, false);
+                               msk->allow_infinite_fallback = false;
                        }
                        spin_unlock_bh(&msk->fallback_lock);
 
@@ -2819,7 +2819,8 @@ static void __mptcp_init_sock(struct sock *sk)
        WRITE_ONCE(msk->first, NULL);
        inet_csk(sk)->icsk_sync_mss = mptcp_sync_mss;
        WRITE_ONCE(msk->csum_enabled, mptcp_is_checksum_enabled(sock_net(sk)));
-       WRITE_ONCE(msk->allow_infinite_fallback, true);
+       msk->allow_infinite_fallback = true;
+       msk->allow_subflows = true;
        msk->recovery = false;
        msk->subflow_id = 1;
 
@@ -3624,7 +3625,7 @@ bool mptcp_finish_join(struct sock *ssk)
        /* active subflow, already present inside the conn_list */
        if (!list_empty(&subflow->node)) {
                spin_lock_bh(&msk->fallback_lock);
-               if (__mptcp_check_fallback(msk)) {
+               if (!msk->allow_subflows) {
                        spin_unlock_bh(&msk->fallback_lock);
                        return false;
                }
index 7e637f468065c974b0b86ffa4393ddf66cfbbaf5..c5f41cdb36c4b152766391f49462f11ad84a982b 100644 (file)
@@ -330,13 +330,15 @@ struct mptcp_sock {
                u64     rtt_us; /* last maximum rtt of subflows */
        } rcvq_space;
        u8              scaling_ratio;
+       bool            allow_subflows;
 
        u32             subflow_id;
        u32             setsockopt_seq;
        char            ca_name[TCP_CA_NAME_MAX];
 
-       spinlock_t      fallback_lock;  /* protects fallback and
-                                        * allow_infinite_fallback
+       spinlock_t      fallback_lock;  /* protects fallback,
+                                        * allow_infinite_fallback and
+                                        * allow_join
                                         */
 };
 
@@ -1113,6 +1115,7 @@ static inline bool __mptcp_try_fallback(struct mptcp_sock *msk)
                return false;
        }
 
+       msk->allow_subflows = false;
        set_bit(MPTCP_FALLBACK_DONE, &msk->flags);
        spin_unlock_bh(&msk->fallback_lock);
        return true;
index 5a8990536f8e3d25b301df1436afff3b68ab88a5..a01ea18283c726de79e78abae67fccc108dff4fe 100644 (file)
@@ -1257,20 +1257,29 @@ static void subflow_sched_work_if_closed(struct mptcp_sock *msk, struct sock *ss
                mptcp_schedule_work(sk);
 }
 
-static void mptcp_subflow_fail(struct mptcp_sock *msk, struct sock *ssk)
+static bool mptcp_subflow_fail(struct mptcp_sock *msk, struct sock *ssk)
 {
        struct mptcp_subflow_context *subflow = mptcp_subflow_ctx(ssk);
        unsigned long fail_tout;
 
+       /* we are really failing, prevent any later subflow join */
+       spin_lock_bh(&msk->fallback_lock);
+       if (!msk->allow_infinite_fallback) {
+               spin_unlock_bh(&msk->fallback_lock);
+               return false;
+       }
+       msk->allow_subflows = false;
+       spin_unlock_bh(&msk->fallback_lock);
+
        /* greceful failure can happen only on the MPC subflow */
        if (WARN_ON_ONCE(ssk != READ_ONCE(msk->first)))
-               return;
+               return false;
 
        /* since the close timeout take precedence on the fail one,
         * no need to start the latter when the first is already set
         */
        if (sock_flag((struct sock *)msk, SOCK_DEAD))
-               return;
+               return true;
 
        /* we don't need extreme accuracy here, use a zero fail_tout as special
         * value meaning no fail timeout at all;
@@ -1282,6 +1291,7 @@ static void mptcp_subflow_fail(struct mptcp_sock *msk, struct sock *ssk)
        tcp_send_ack(ssk);
 
        mptcp_reset_tout_timer(msk, subflow->fail_tout);
+       return true;
 }
 
 static bool subflow_check_data_avail(struct sock *ssk)
@@ -1342,12 +1352,11 @@ fallback:
                    (subflow->mp_join || subflow->valid_csum_seen)) {
                        subflow->send_mp_fail = 1;
 
-                       if (!READ_ONCE(msk->allow_infinite_fallback)) {
+                       if (!mptcp_subflow_fail(msk, ssk)) {
                                subflow->reset_transient = 0;
                                subflow->reset_reason = MPTCP_RST_EMIDDLEBOX;
                                goto reset;
                        }
-                       mptcp_subflow_fail(msk, ssk);
                        WRITE_ONCE(subflow->data_avail, MPTCP_SUBFLOW_DATA_AVAIL);
                        return true;
                }