Re: [PATCH mptcp-next v3 5/5] Squash to "mptcp: pm: init and release mptcp_pm_ops"

Matthieu Baerts <[email protected]>
Newsgroups dev.linux.lists.mptcp,dev.linux.lists.sashiko-reviews
Organization NGI0 Core
Message-ID <[email protected]>
Hi Gang,

On 20/08/2026 08:36, [email protected] wrote:
> August 19, 2026 at 9:12 PM, [email protected] mailto:[email protected]  wrote:
> 
>> [Severity: Critical]
>> Since this series introduces mptcp_pm_ops lifetime handling and module
>> references, is there a race condition with concurrent MP_JOIN requests?
>>
>> When an incoming MP_JOIN request is processed in softirq context,
>> subflow_token_join_request() gets the socket via mptcp_token_get_sock(),
>> which doesn't hold lock_sock() or RCU. It then calls:
>>
>> net/mptcp/pm.c:mptcp_pm_get_local_id() {
>>  ...
>>  return msk->pm.ops->get_local_id(msk, &skc_local);
>> }
>>
>> Concurrently, if a local user closes the socket and triggers
>> mptcp_disconnect(msk), it calls mptcp_pm_ops_release() which clears the ops
>> and drops the module reference:
>>
>> net/mptcp/pm.c:mptcp_pm_ops_release() {
>>  ...
>>  msk->pm.ops = NULL;
>>  ...
>>  bpf_module_put(pm_ops, pm_ops->owner);
>> }
>>
>> Could this lead to a NULL pointer dereference or executing freed module code
>> if subflow_token_join_request() executes concurrently with a socket
>> disconnect?
>>
> 
> Hi Matt, Geliang
> 
> @Matt, Could you help me to trigger the CI?

It looks like GitHub was sick that day. I reset the tag manually.

> And I think it is a pre-existing bug: 
> 
> When it calls msk->pm.ops->get_local_id(msk, &skc_local) between
> 'mptcp_distroy_common' (which calls mptcp_pm_ops_release) and
> 'mptcp_pm_data_reset'. The pm.ops will be NULL and cause a NULL deref, right?

Can you first check if this can happen? I thought that
mptcp_destroy_common would first close all subflows, remove the token,
then release everything linked to the PM → so the PM will no longer be
called at that point for this msk, right? (I didn't check)

But maybe this will change when the BPF PM will be fully implemented? I
guess no because msk->pm.ops should only be called from events linked to
the network, so not after mptcp_destroy_common?

> If I'm right, I think we can using rcu to solve this based on this series, like:
> 
>   - Add a helper to get pm.ops, and then call the get_local_id/get_priority under rcu_lock:
> '''
> +static struct mptcp_pm_ops *mptcp_pm_deref(struct mptcp_sock *msk)
> +{
> +       struct mptcp_pm_ops *pm_ops;
> +
> +       pm_ops = rcu_dereference(msk->pm.ops);
> +       return pm_ops ? pm_ops : &mptcp_pm_kernel;
> +}
> +
> 
> bool mptcp_pm_is_backup(struct mptcp_sock *msk, struct sock_common *skc)
>  
>         mptcp_local_address((struct sock_common *)skc, &skc_local);
>  
> -       return msk->pm.ops->get_priority(msk, &skc_local);
> +       return mptcp_pm_deref(msk)->get_priority(msk, &skc_local);
>  }
>  
> So does the mptcp_pm_get_local_id like this.

I think you should then rename the helpers, to make it clear they need
to be used from a RCU read section, and to get a warning when
backporting code around that.

> static struct mptcp_sock *subflow_token_join_request(struct request_sock *req)
>                 return NULL;
>         }
>  
> +       rcu_read_lock();
>         local_id = mptcp_pm_get_local_id(msk, (struct sock_common *)req);
>         if (local_id < 0) {
>                 SUBFLOW_REQ_INC_STATS(req, MPTCP_MIB_MPJOINNOIDFOUND);
> +               rcu_read_unlock();
>                 sock_put((struct sock *)msk);
>                 return NULL;
>         }
>         subflow_req->local_id = local_id;
>         subflow_req->request_bkup = mptcp_pm_is_backup(msk, (struct sock_common *)req);
> +       rcu_read_unlock();
>  
> '''
> 
>   - Refactor the mptcp_pm_ops_init to support reuse socket, and put the
>     mptcp_pm_ops_release into mptcp_destroy:
> '''
>  static void mptcp_pm_ops_init(struct mptcp_sock *msk,
>                               struct mptcp_pm_ops *pm_ops)
>  {
> +       struct mptcp_pm_ops *old = msk->pm.ops;
> +
>         if (!pm_ops || !bpf_try_module_get(pm_ops, pm_ops->owner)) {
>                 pr_warn_once("pm %s fails, fallback to default pm", pm_ops->name);
>                 pm_ops = &mptcp_pm_kernel;
>         }
>  
> -       msk->pm.ops = pm_ops;
> +       if (old) {
> +               if (old == pm_ops) {
> +                       mptcp_pm_ops_release(msk);

Maybe return here, no need to re-init, right?

> +               } else {
> +                       rcu_assign_pointer(msk->pm.ops, pm_ops);
> +                       synchronize_rcu();
> +                       bpf_module_put(old, old->owner);
> +               }
> +       } else {
> +               rcu_assign_pointer(msk->pm.ops, pm_ops);
> +       }
> +
>         if (msk->pm.ops->init)
>                 msk->pm.ops->init(msk);
>  
> -       pr_debug("pm %s initialized\n", pm_ops->name);
> +       pr_debug("pm %s initialized\n", msk->pm.ops->name);
>  }
>  
> 
> 
> static void mptcp_destroy(struct sock *sk)
>         /* allow the following to close even the initial subflow */
>         msk->free_first = 1;
>         mptcp_destroy_common(msk);
> +       mptcp_pm_ops_release(msk);
>         sk_sockets_allocated_dec(sk);
>  }
> 
> '''
> 
> But I think it is a large fix, do you have any idea?
> 
> And as I said at the beginning, it seeems like not a issue attached to this issue,
> could you review the v3 code with ignoring it ?

Yes, the 4 first patches can be reviewed.

Cheers,
Matt
-- 
Sponsored by the NGI0 Core fund.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.