Netdev List
 help / color / mirror / Atom feed
* [PATCH net 0/2] mptcp: misc fixes for v7.3-rc4
@ 2026-09-15 20:37 Matthieu Baerts (NGI0)
  2026-09-15 20:37 ` [PATCH net 1/2] mptcp: avoid unneeded actions on subflow reset Matthieu Baerts (NGI0)
  2026-09-15 20:37 ` [PATCH net 2/2] mptcp: close race between scheduler and state change Matthieu Baerts (NGI0)
  0 siblings, 2 replies; 7+ messages in thread
From: Matthieu Baerts (NGI0) @ 2026-09-15 20:37 UTC (permalink / raw)
  To: Mat Martineau, Geliang Tang, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman
  Cc: netdev, mptcp, Matthieu Baerts (NGI0), stable, Xinyang Ge,
	Shardul Bankar

Here are two unrelated fixes:

- Patch 1: avoid unneeded actions on subflow reset. A fix for another
  fix introduced in v6.12 and targeting a commit from v5.7.

- Patch 2: close a possible race when scheduling a closing path. A fix
  for another fix introduced in v6.0 and targeting v5.10.

Signed-off-by: Matthieu Baerts (NGI0) <matttbe@kernel.org>
---
Paolo Abeni (2):
      mptcp: avoid unneeded actions on subflow reset
      mptcp: close race between scheduler and state change

 net/mptcp/protocol.c | 4 +++-
 net/mptcp/protocol.h | 3 ++-
 net/mptcp/subflow.c  | 8 ++++++++
 3 files changed, 13 insertions(+), 2 deletions(-)
---
base-commit: 83a945a529d6e002dd7339c532288a931f463dba
change-id: 20260915-net-mptcp-misc-fixes-7-3-rc4-538b9d7ec047

Best regards,
--  
Matthieu Baerts (NGI0) <matttbe@kernel.org>


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH net 1/2] mptcp: avoid unneeded actions on subflow reset
  2026-09-15 20:37 [PATCH net 0/2] mptcp: misc fixes for v7.3-rc4 Matthieu Baerts (NGI0)
@ 2026-09-15 20:37 ` Matthieu Baerts (NGI0)
  2026-09-16 20:45   ` netdev-bot+sashiko
  2026-09-15 20:37 ` [PATCH net 2/2] mptcp: close race between scheduler and state change Matthieu Baerts (NGI0)
  1 sibling, 1 reply; 7+ messages in thread
From: Matthieu Baerts (NGI0) @ 2026-09-15 20:37 UTC (permalink / raw)
  To: Mat Martineau, Geliang Tang, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman
  Cc: netdev, mptcp, Matthieu Baerts (NGI0), stable, Xinyang Ge

From: Paolo Abeni <pabeni@redhat.com>

Once in a blue moon, the mptcp receive path can recursively call
mptcp_data_ready() via state change under unlucky error conditions, and
then try to hold the data lock again.

Break the recursion loop explicitly checking for the exceptional
condition.

Add a new flag instead of using an existing one like 'closing', to exit
early in subflow_state_change(). This avoids unneeded processing to
check for available data -- calling get_mapping_status() and more on a
dying subflow -- but also in error reporting and worker scheduling.

Fixes: e32d262c89e2 ("mptcp: handle consistently DSS corruption")
Cc: stable@vger.kernel.org
Reported-by: Xinyang Ge <xinyang@anthropic.com>
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>
---
 net/mptcp/protocol.h | 3 ++-
 net/mptcp/subflow.c  | 8 ++++++++
 2 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h
index 2b4c27426477..0384d6a023f9 100644
--- a/net/mptcp/protocol.h
+++ b/net/mptcp/protocol.h
@@ -585,7 +585,8 @@ struct mptcp_subflow_context {
 		is_mptfo : 1,	    /* subflow is doing TFO */
 		close_event_done : 1,       /* has done the post-closed part */
 		mpc_drop : 1,	    /* the MPC option has been dropped in a rtx */
-		__unused : 9;
+		resetting : 1,	    /* subflow is resetting */
+		__unused : 8;
 	bool	data_avail;
 	bool	scheduled;
 	bool	pm_listener;	    /* a listener managed by the kernel PM? */
diff --git a/net/mptcp/subflow.c b/net/mptcp/subflow.c
index 01db7edce18a..cbe227d21844 100644
--- a/net/mptcp/subflow.c
+++ b/net/mptcp/subflow.c
@@ -438,6 +438,7 @@ void mptcp_subflow_reset(struct sock *ssk)
 	/* must hold: tcp_done() could drop last reference on parent */
 	sock_hold(sk);
 
+	subflow->resetting = 1;
 	mptcp_send_active_reset_reason(ssk);
 	tcp_done(ssk);
 	if (!test_and_set_bit(MPTCP_WORK_CLOSE_SUBFLOW, &mptcp_sk(sk)->flags))
@@ -1883,6 +1884,13 @@ static void subflow_state_change(struct sock *sk)
 
 	__subflow_state_change(sk);
 
+	/* Rx queue processing is unneeded, error reporting will take place at
+	 * __mptcp_close_ssk() time and subflow reset can't happen in case of
+	 * fallback: subflow_sched_work_if_closed() would be a no-op.
+	 */
+	if (subflow->resetting)
+		return;
+
 	/* as recvmsg() does not acquire the subflow socket for ssk selection
 	 * a fin packet carrying a DSS can be unnoticed if we don't trigger
 	 * the data available machinery here.

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH net 2/2] mptcp: close race between scheduler and state change
  2026-09-15 20:37 [PATCH net 0/2] mptcp: misc fixes for v7.3-rc4 Matthieu Baerts (NGI0)
  2026-09-15 20:37 ` [PATCH net 1/2] mptcp: avoid unneeded actions on subflow reset Matthieu Baerts (NGI0)
@ 2026-09-15 20:37 ` Matthieu Baerts (NGI0)
  2026-09-16 20:45   ` netdev-bot+sashiko
  1 sibling, 1 reply; 7+ messages in thread
From: Matthieu Baerts (NGI0) @ 2026-09-15 20:37 UTC (permalink / raw)
  To: Mat Martineau, Geliang Tang, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman
  Cc: netdev, mptcp, Matthieu Baerts (NGI0), stable, Shardul Bankar,
	Xinyang Ge

From: Paolo Abeni <pabeni@redhat.com>

The mptcp scheduler may race with subflow sockets state change: data
transmission on the selected socket may fail and a later release could
try to use mss_now reset to 0 for a divide operation.

Address the issue by explicitly checking for the critical scenario.

Fixes: c886d70286bf ("mptcp: do not queue data on closed subflows")
Cc: stable@vger.kernel.org
Reported-by: Shardul Bankar <shardul.b@mpiricsoftware.com>
Reported-by: Xinyang Ge <xinyang@anthropic.com>
Closes: https://lore.kernel.org/20260525194828.1137119-1-shardul.b@mpiricsoftware.com
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>
---
 net/mptcp/protocol.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
index 0098e2830931..709610db3ce9 100644
--- a/net/mptcp/protocol.c
+++ b/net/mptcp/protocol.c
@@ -1664,7 +1664,9 @@ struct sock *mptcp_subflow_get_send(struct mptcp_sock *msk)
 
 static void mptcp_push_release(struct sock *ssk, struct mptcp_sendmsg_info *info)
 {
-	tcp_push(ssk, 0, info->mss_now, tcp_sk(ssk)->nonagle, info->size_goal);
+	if (info->mss_now)
+		tcp_push(ssk, 0, info->mss_now, tcp_sk(ssk)->nonagle,
+			 info->size_goal);
 	release_sock(ssk);
 }
 

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH net 1/2] mptcp: avoid unneeded actions on subflow reset
  2026-09-15 20:37 ` [PATCH net 1/2] mptcp: avoid unneeded actions on subflow reset Matthieu Baerts (NGI0)
@ 2026-09-16 20:45   ` netdev-bot+sashiko
  2026-09-17 11:14     ` Paolo Abeni
  0 siblings, 1 reply; 7+ messages in thread
From: netdev-bot+sashiko @ 2026-09-16 20:45 UTC (permalink / raw)
  To: matttbe
  Cc: martineau, geliang, davem, edumazet, kuba, pabeni, horms, netdev,
	mptcp, stable, shardul.b, xinyang

Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider:

Critical: 0 · High: 2 · Medium: 1 · Low: 0

- [High] The new unconditional `if (subflow->resetting) return;` in
  subflow_state_change() (net/mptcp/subflow.c:1891) removes the last…
- [High] mptcp: data race in bitfield resetting and close_event_done
- [Medium] The new early return in subflow_state_change()
  (net/mptcp/subflow.c:1891) is justified by an in-code comment claiming…

--

mptcp: avoid unneeded actions on subflow reset

This adds a new subflow->resetting bit, set in mptcp_subflow_reset() before
tcp_done(), and uses it to return early from subflow_state_change() so the
nested mptcp_data_ready() recursion is broken. The early return also skips
the data-available check, the error report and subflow_sched_work_if_closed().

> diff --git a/net/mptcp/subflow.c b/net/mptcp/subflow.c
> index 01db7edce18a6..cbe227d218449 100644
> --- a/net/mptcp/subflow.c
> +++ b/net/mptcp/subflow.c
> @@ -438,6 +438,7 @@ void mptcp_subflow_reset(struct sock *ssk)
>  	/* must hold: tcp_done() could drop last reference on parent */
>  	sock_hold(sk);
>  
> +	subflow->resetting = 1;
>  	mptcp_send_active_reset_reason(ssk);
>  	tcp_done(ssk);
>  	if (!test_and_set_bit(MPTCP_WORK_CLOSE_SUBFLOW, &mptcp_sk(sk)->flags))

[Severity: High]
The new bit is carved out of the same 32-bit bitfield word as
closeing/close_event_done/mpc_drop, and a C bitfield store is a
read-modify-write of that whole word, not of the single bit.  So the
question is whether that word is only ever written under one lock.

mptcp_subflow_reset() now writes subflow->resetting.  On the
mptcp_mp_fail_no_response() path the caller is mptcp_worker(), which
holds the msk socket lock and then lock_sock_fast(ssk), so both locks
are held.  But the DSS-corruption path -- mptcp_dss_corruption() ->
mptcp_subflow_reset() -- runs from get_mapping_status() /
mptcp_subflow_data_available() in the ssk receive path, i.e. with only
the subflow socket lock (and possibly in BH context).  Meanwhile
mptcp_close_ssk() writes subflow->close_event_done = true holding only
the msk socket lock.  Those two locks do not exclude each other, so
the two load/or/store sequences can interleave on the same word.

If the msk-side store wins, the resetting = 1 store is silently
dropped, subflow_state_change() takes the old path and we are back to
the recursive mptcp_data_ready() and the data lock re-acquisition this
patch is meant to break -- the failure mode is the same deadlock, just
rarer and harder to reproduce.  If the reset-side store wins,
close_event_done is lost and the post-close event handling runs twice.
KCSAN should be able to flag this write/write pair.

Is there serialization here that I am missing for the
mptcp_dss_corruption() caller?  If not, would you consider keeping
resetting out of the msk-owned bitfield word -- a plain bool next to
data_avail/scheduled, or a bit in a flags word updated with set_bit()
-- so that setting it under the ssk lock cannot clobber bits owned by
the msk lock?

[ ... ]

> @@ -1883,6 +1884,13 @@ static void subflow_state_change(struct sock *sk)
>  
>  	__subflow_state_change(sk);
>  
> +	/* Rx queue processing is unneeded, error reporting will take place at
> +	 * __mptcp_close_ssk() time and subflow reset can't happen in case of
> +	 * fallback: subflow_sched_work_if_closed() would be a no-op.
> +	 */
> +	if (subflow->resetting)
> +		return;
> +

[Severity: High]
Can a reset subflow with a non-empty ssk->sk_receive_queue still get
cleaned up after this early return?

mptcp_subflow_reset() calls tcp_done(ssk), and tcp_set_state(TCP_CLOSE)
unhashes the socket, so subflow_data_ready() cannot fire again for this
ssk.  That makes the tcp_done() -> sk_state_change() ->
subflow_state_change() path the last remaining entry into
mptcp_subflow_data_available() for this subflow, and the new return skips
it.

The flush that exists for exactly this case lives in validate_data_csum():

net/mptcp/subflow.c:validate_data_csum() {
	...
	if (skb_queue_is_last(&ssk->sk_receive_queue, skb)) {
		/* if this subflow is closed, the partial mapping
		 * will be never completed; flush the pending skbs, so
		 * that subflow_sched_work_if_closed() can kick in
		 */
		if (unlikely(ssk->sk_state == TCP_CLOSE))
			while ((skb = skb_peek(&ssk->sk_receive_queue)))
				sk_eat_skb(ssk, skb);

		/* not enough data to validate the csum */
		return MAPPING_EMPTY;
	}
	...
}

With the skbs left in place, does __mptcp_close_subflow() keep skipping
the subflow forever?

net/mptcp/protocol.c:__mptcp_close_subflow() {
	...
		/* 'subflow_data_ready' will re-sched once rx queue is empty */
		if (!skb_queue_empty_lockless(&ssk->sk_receive_queue))
			continue;

		mptcp_close_ssk(sk, ssk, subflow);
	...
}

mptcp_worker() has already cleared MPTCP_WORK_CLOSE_SUBFLOW at that point,
and recvmsg() only spools msk->backlog_list in mptcp_move_skbs() /
__mptcp_move_skbs(), so it never touches ssk->sk_receive_queue.

A concrete path with net.mptcp.checksum_enabled=1: a bad DSS csum arms
subflow->fail_tout in mptcp_subflow_fail(); a later partially received
mapping returns MAPPING_EMPTY leaving skbs queued on msk->first; the
MP_FAIL echo never arrives, so mptcp_worker() calls
mptcp_mp_fail_no_response():

net/mptcp/protocol.c:mptcp_mp_fail_no_response() {
	...
	slow = lock_sock_fast(ssk);
	mptcp_subflow_reset(ssk);
	WRITE_ONCE(mptcp_subflow_ctx(ssk)->fail_tout, 0);
	unlock_sock_fast(ssk, slow);
}

fail_tout is then cleared, and since __mptcp_close_ssk() is never reached,
mptcp_start_tout_timer() is never called either.  Does that leave the msk
in TCP_ESTABLISHED with its only, dead subflow, no pending timer, and a
reader blocked in mptcp_recvmsg() that is never woken with EOF or an
error?

The same early return also appears to leave reset join subflows (the
'reset:' label in check_fully_established() and __mptcp_flush_join_list())
with their context and queued skbs pinned on msk->conn_list.

For reference, the DSS-corruption path itself looks unaffected, because
__mptcp_move_skbs_from_subflow() re-calls mptcp_subflow_data_available(ssk)
in its own loop.

[Severity: Medium]
The comment states "subflow reset can't happen in case of fallback:
subflow_sched_work_if_closed() would be a no-op".  Is that premise
enforced anywhere?

mptcp_mp_fail_no_response() resets msk->first purely on fail_tout expiry,
with no __mptcp_check_fallback() test.  fail_tout is armed by
mptcp_subflow_fail() while the msk is not yet in fallback, and nothing
seems to clear it when a later fallback succeeds: __mptcp_try_fallback()
only touches allow_subflows / MPTCP_FALLBACK_DONE, and the pm.c side clears
fail_tout only on an MP_FAIL echo.

With net.mptcp.checksum_enabled=1: a middlebox mangles the MPTCP options,
MAPPING_BAD_CSUM with valid_csum_seen sends MP_FAIL and arms
fail_tout = jiffies + TCP_RTO_MAX; the echo never arrives; a subsequent
MAPPING_NODSS on the same first subflow makes mptcp_try_fallback() succeed,
so MPTCP_FALLBACK_DONE is set while fail_tout is still armed.  The tout
timer then runs mptcp_worker() -> mptcp_mp_fail_no_response() ->
mptcp_subflow_reset(msk->first) with __mptcp_check_fallback(msk) true.

In that state tcp_done() sets SHUTDOWN_MASK/TCP_CLOSE, so subflow_is_done(ssk)
is true and msk->first == ssk, which is precisely the branch that is not a
no-op:

net/mptcp/subflow.c:subflow_sched_work_if_closed() {
	...
	/* when the fallback subflow closes the rx side, trigger a 'dummy'
	 * ingress data fin, so that the msk state will follow along
	 */
	if (__mptcp_check_fallback(msk) && subflow_is_done(ssk) &&
	    msk->first == ssk &&
	    mptcp_update_rcv_data_fin(msk, subflow->map_seq +
				      subflow->map_data_len, true))
		mptcp_schedule_work(sk);
}

Skipping that call means mptcp_update_rcv_data_fin() is never invoked, the
msk never moves to CLOSE_WAIT/RCV_SHUTDOWN, and blocked readers get no EOF
until the close timeout (net.mptcp.close_timeout, 60 s by default) fires
and mptcp_do_fastclose() sets TCP_CLOSE and purges the backlog instead of
delivering a clean EOF.  Should the comment be qualified, or should the
check be narrowed so the fallback DATA_FIN injection still runs?

>  	/* as recvmsg() does not acquire the subflow socket for ssk selection
>  	 * a fin packet carrying a DSS can be unnoticed if we don't trigger
>  	 * the data available machinery here.

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260915-net-mptcp-misc-fixes-7-3-rc4-v1-0-a257797dd5d0%40kernel.org

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH net 2/2] mptcp: close race between scheduler and state change
  2026-09-15 20:37 ` [PATCH net 2/2] mptcp: close race between scheduler and state change Matthieu Baerts (NGI0)
@ 2026-09-16 20:45   ` netdev-bot+sashiko
  2026-09-17 11:16     ` Paolo Abeni
  0 siblings, 1 reply; 7+ messages in thread
From: netdev-bot+sashiko @ 2026-09-16 20:45 UTC (permalink / raw)
  To: matttbe
  Cc: martineau, geliang, davem, edumazet, kuba, pabeni, horms, netdev,
	mptcp, stable, shardul.b, xinyang

Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider:

Critical: 0 · High: 1 · Medium: 0 · Low: 0

- [High] Incomplete fix: the identical `mss_now == 0` divide-by-zero that
  the patch guards in `mptcp_push_release()` (net/mptcp/protocol.c:1667)…

--

mptcp: close race between scheduler and state change

The patch guards the tcp_push() call in mptcp_push_release() with a check
for info->mss_now being non-zero, so that a subflow which failed
transmission (leaving mss_now at 0) is not used for a divide operation.

Closes: https://lore.kernel.org/20260525194828.1137119-1-shardul.b@mpiricsoftware.com

> diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
> index 0098e28309318..709610db3ce9d 100644
> --- a/net/mptcp/protocol.c
> +++ b/net/mptcp/protocol.c
> @@ -1664,7 +1664,9 @@ struct sock *mptcp_subflow_get_send(struct mptcp_sock *msk)
>  
>  static void mptcp_push_release(struct sock *ssk, struct mptcp_sendmsg_info *info)
>  {
> -	tcp_push(ssk, 0, info->mss_now, tcp_sk(ssk)->nonagle, info->size_goal);
> +	if (info->mss_now)
> +		tcp_push(ssk, 0, info->mss_now, tcp_sk(ssk)->nonagle,
> +			 info->size_goal);
>  	release_sock(ssk);
>  }

[Severity: High]
Should the same guard also be added to the second tcp_push() call site, in
__mptcp_subflow_push_pending()?

That function starts with a zeroed info:

net/mptcp/protocol.c:__mptcp_subflow_push_pending() {
	struct mptcp_sendmsg_info info = {
		.data_lock_held = true,
	};
	...
	int copied = 0;

and info.mss_now is only ever assigned inside mptcp_sendmsg_frag(), after
the early return the commit message refers to:

	if (unlikely(!__tcp_can_send(ssk)))
		return -EAGAIN;

	/* compute send limit */
	...
	info->mss_now = tcp_send_mss(ssk, &info->size_goal, info->flags);

In the non-first iteration, the return value is accumulated into copied
without filtering negatives:

net/mptcp/protocol.c:__mptcp_subflow_push_pending() {
		if (READ_ONCE(subflow->scheduled)) {
			mptcp_subflow_set_scheduled(subflow, false);
			ret = __subflow_push_pending(sk, ssk, &info);
			if (ret <= 0)
				keep_pushing = false;
			copied += ret;
		}

__subflow_push_pending() passes mptcp_sendmsg_frag()'s -EAGAIN through
verbatim when the first frag fails, so copied becomes -EAGAIN, which is
non-zero, while info.mss_now is still 0:

out:
	if (copied) {
		tcp_push(ssk, 0, info.mss_now, tcp_sk(ssk)->nonagle,
			 info.size_goal);

Can that reach the divide the patch is trying to avoid?  With a non-empty
subflow write queue, tcp_push() does not take the autocork early return
(it requires skb->len < size_goal, i.e. skb->len < 0), so it calls
__tcp_push_pending_frames(sk, 0, nonagle), which only bails for TCP_CLOSE.
For FIN_WAIT1/FIN_WAIT2/CLOSING/LAST_ACK it continues into
tcp_write_xmit(sk, 0, ...) and tcp_tso_segs() -> tcp_tso_autosize():

net/ipv4/tcp_output.c:tcp_tso_autosize() {
	return max_t(u32, bytes / mss_now, min_tso_segs);

tcp_set_skb_tso_segs() has the same shape with
DIV_ROUND_UP(skb->len, mss_now).

Is the "scheduled but no longer sendable" state reachable on this path?
mptcp_sched_get_send() returns early without re-validating sendability if
any subflow already has the flag set:

net/mptcp/sched.c:mptcp_sched_get_send() {
	mptcp_for_each_subflow(msk, subflow) {
		if (READ_ONCE(subflow->scheduled))
			return 0;
	}

and __mptcp_subflow_push_pending() itself leaves the flag set on the
subflows it delegates via mptcp_subflow_delegate().  The tree also comments
on leftover flags elsewhere ("there could be left-over scheduled subflows;
clear them all or later xmit could use bad ones"), and
__mptcp_push_pending() already anticipates -EAGAIN together with
FIN_WAIT1/FIN_WAIT2 - which are exactly the states where
__tcp_push_pending_frames() does not bail out.

The entry point with first == false is __mptcp_check_push():

	if (!sock_owned_by_user(sk))
		__mptcp_subflow_push_pending(sk, ssk, false);

reached from ack_update_msk() in net/mptcp/options.c on incoming DSS acks,
so this runs in softirq context on peer-supplied input.

A second, smaller question on the same statement: since copied mixes byte
counts with negative errnos, can a positive count followed by an error that
sums to exactly zero (for example 11 bytes then -EAGAIN) make the
if (copied) test false and skip both the flush and
mptcp_reset_rtx_timer() even though data was queued?

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260915-net-mptcp-misc-fixes-7-3-rc4-v1-0-a257797dd5d0%40kernel.org

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH net 1/2] mptcp: avoid unneeded actions on subflow reset
  2026-09-16 20:45   ` netdev-bot+sashiko
@ 2026-09-17 11:14     ` Paolo Abeni
  0 siblings, 0 replies; 7+ messages in thread
From: Paolo Abeni @ 2026-09-17 11:14 UTC (permalink / raw)
  To: netdev-bot+sashiko, matttbe
  Cc: martineau, geliang, davem, edumazet, kuba, horms, netdev, mptcp,
	stable, shardul.b, xinyang

On 9/16/26 22:45, netdev-bot+sashiko@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 3 potential
> issue(s) to consider:
> 
> Critical: 0 · High: 2 · Medium: 1 · Low: 0
> 
> - [High] The new unconditional `if (subflow->resetting) return;` in
>    subflow_state_change() (net/mptcp/subflow.c:1891) removes the last…
> - [High] mptcp: data race in bitfield resetting and close_event_done
> - [Medium] The new early return in subflow_state_change()
>    (net/mptcp/subflow.c:1891) is justified by an in-code comment claiming…
> 
> --
> 
> mptcp: avoid unneeded actions on subflow reset
> 
> This adds a new subflow->resetting bit, set in mptcp_subflow_reset() before
> tcp_done(), and uses it to return early from subflow_state_change() so the
> nested mptcp_data_ready() recursion is broken. The early return also skips
> the data-available check, the error report and subflow_sched_work_if_closed().
> 
>> diff --git a/net/mptcp/subflow.c b/net/mptcp/subflow.c
>> index 01db7edce18a6..cbe227d218449 100644
>> --- a/net/mptcp/subflow.c
>> +++ b/net/mptcp/subflow.c
>> @@ -438,6 +438,7 @@ void mptcp_subflow_reset(struct sock *ssk)
>>   	/* must hold: tcp_done() could drop last reference on parent */
>>   	sock_hold(sk);
>>   
>> +	subflow->resetting = 1;
>>   	mptcp_send_active_reset_reason(ssk);
>>   	tcp_done(ssk);
>>   	if (!test_and_set_bit(MPTCP_WORK_CLOSE_SUBFLOW, &mptcp_sk(sk)->flags))
> 
> [Severity: High]
> The new bit is carved out of the same 32-bit bitfield word as
> closeing/close_event_done/mpc_drop, and a C bitfield store is a
> read-modify-write of that whole word, not of the single bit.  So the
> question is whether that word is only ever written under one lock.
> 
> mptcp_subflow_reset() now writes subflow->resetting.  On the
> mptcp_mp_fail_no_response() path the caller is mptcp_worker(), which
> holds the msk socket lock and then lock_sock_fast(ssk), so both locks
> are held.  But the DSS-corruption path -- mptcp_dss_corruption() ->
> mptcp_subflow_reset() -- runs from get_mapping_status() /
> mptcp_subflow_data_available() in the ssk receive path, i.e. with only
> the subflow socket lock (and possibly in BH context).  Meanwhile
> mptcp_close_ssk() writes subflow->close_event_done = true holding only
> the msk socket lock.  Those two locks do not exclude each other, so
> the two load/or/store sequences can interleave on the same word.
> 
> If the msk-side store wins, the resetting = 1 store is silently
> dropped, subflow_state_change() takes the old path and we are back to
> the recursive mptcp_data_ready() and the data lock re-acquisition this
> patch is meant to break -- the failure mode is the same deadlock, just
> rarer and harder to reproduce.  If the reset-side store wins,
> close_event_done is lost and the post-close event handling runs twice.
> KCSAN should be able to flag this write/write pair.
> 
> Is there serialization here that I am missing for the
> mptcp_dss_corruption() caller?  If not, would you consider keeping
> resetting out of the msk-owned bitfield word -- a plain bool next to
> data_avail/scheduled, or a bit in a flags word updated with set_bit()
> -- so that setting it under the ssk lock cannot clobber bits owned by
> the msk lock?

This is actually a pre-exiting issue, and quite unrelated. Could deserve 
a separate/later fix.

> [ ... ]
> 
>> @@ -1883,6 +1884,13 @@ static void subflow_state_change(struct sock *sk)
>>   
>>   	__subflow_state_change(sk);
>>   
>> +	/* Rx queue processing is unneeded, error reporting will take place at
>> +	 * __mptcp_close_ssk() time and subflow reset can't happen in case of
>> +	 * fallback: subflow_sched_work_if_closed() would be a no-op.
>> +	 */
>> +	if (subflow->resetting)
>> +		return;
>> +
> 
> [Severity: High]
> Can a reset subflow with a non-empty ssk->sk_receive_queue still get
> cleaned up after this early return?
> 
> mptcp_subflow_reset() calls tcp_done(ssk), and tcp_set_state(TCP_CLOSE)
> unhashes the socket, so subflow_data_ready() cannot fire again for this
> ssk.  That makes the tcp_done() -> sk_state_change() ->
> subflow_state_change() path the last remaining entry into
> mptcp_subflow_data_available() for this subflow, and the new return skips
> it.
> 
> The flush that exists for exactly this case lives in validate_data_csum():
> 
> net/mptcp/subflow.c:validate_data_csum() {
> 	...
> 	if (skb_queue_is_last(&ssk->sk_receive_queue, skb)) {
> 		/* if this subflow is closed, the partial mapping
> 		 * will be never completed; flush the pending skbs, so
> 		 * that subflow_sched_work_if_closed() can kick in
> 		 */
> 		if (unlikely(ssk->sk_state == TCP_CLOSE))
> 			while ((skb = skb_peek(&ssk->sk_receive_queue)))
> 				sk_eat_skb(ssk, skb);
> 
> 		/* not enough data to validate the csum */
> 		return MAPPING_EMPTY;
> 	}
> 	...
> }
> 
> With the skbs left in place, does __mptcp_close_subflow() keep skipping
> the subflow forever?
> 
> net/mptcp/protocol.c:__mptcp_close_subflow() {
> 	...
> 		/* 'subflow_data_ready' will re-sched once rx queue is empty */
> 		if (!skb_queue_empty_lockless(&ssk->sk_receive_queue))
> 			continue;
> 
> 		mptcp_close_ssk(sk, ssk, subflow);
> 	...
> }
> 
> mptcp_worker() has already cleared MPTCP_WORK_CLOSE_SUBFLOW at that point,
> and recvmsg() only spools msk->backlog_list in mptcp_move_skbs() /
> __mptcp_move_skbs(), so it never touches ssk->sk_receive_queue.
> 
> A concrete path with net.mptcp.checksum_enabled=1: a bad DSS csum arms
> subflow->fail_tout in mptcp_subflow_fail(); a later partially received
> mapping returns MAPPING_EMPTY leaving skbs queued on msk->first; the
> MP_FAIL echo never arrives, so mptcp_worker() calls
> mptcp_mp_fail_no_response():
> 
> net/mptcp/protocol.c:mptcp_mp_fail_no_response() {
> 	...
> 	slow = lock_sock_fast(ssk);
> 	mptcp_subflow_reset(ssk);
> 	WRITE_ONCE(mptcp_subflow_ctx(ssk)->fail_tout, 0);
> 	unlock_sock_fast(ssk, slow);
> }
> 
> fail_tout is then cleared, and since __mptcp_close_ssk() is never reached,
> mptcp_start_tout_timer() is never called either.  Does that leave the msk
> in TCP_ESTABLISHED with its only, dead subflow, no pending timer, and a
> reader blocked in mptcp_recvmsg() that is never woken with EOF or an
> error?
> 
> The same early return also appears to leave reset join subflows (the
> 'reset:' label in check_fully_established() and __mptcp_flush_join_list())
> with their context and queued skbs pinned on msk->conn_list.
> 
> For reference, the DSS-corruption path itself looks unaffected, because
> __mptcp_move_skbs_from_subflow() re-calls mptcp_subflow_data_available(ssk)
> in its own loop.

This may deserve to be fixed in the same series.

> [Severity: Medium]
> The comment states "subflow reset can't happen in case of fallback:
> subflow_sched_work_if_closed() would be a no-op".  Is that premise
> enforced anywhere?
> 
> mptcp_mp_fail_no_response() resets msk->first purely on fail_tout expiry,
> with no __mptcp_check_fallback() test.  fail_tout is armed by
> mptcp_subflow_fail() while the msk is not yet in fallback, and nothing
> seems to clear it when a later fallback succeeds: __mptcp_try_fallback()
> only touches allow_subflows / MPTCP_FALLBACK_DONE, and the pm.c side clears
> fail_tout only on an MP_FAIL echo.
> 
> With net.mptcp.checksum_enabled=1: a middlebox mangles the MPTCP options,
> MAPPING_BAD_CSUM with valid_csum_seen sends MP_FAIL and arms
> fail_tout = jiffies + TCP_RTO_MAX; the echo never arrives; a subsequent
> MAPPING_NODSS on the same first subflow makes mptcp_try_fallback() succeed,
> so MPTCP_FALLBACK_DONE is set while fail_tout is still armed.  The tout
> timer then runs mptcp_worker() -> mptcp_mp_fail_no_response() ->
> mptcp_subflow_reset(msk->first) with __mptcp_check_fallback(msk) true.
> 
> In that state tcp_done() sets SHUTDOWN_MASK/TCP_CLOSE, so subflow_is_done(ssk)
> is true and msk->first == ssk, which is precisely the branch that is not a
> no-op:
> 
> net/mptcp/subflow.c:subflow_sched_work_if_closed() {
> 	...
> 	/* when the fallback subflow closes the rx side, trigger a 'dummy'
> 	 * ingress data fin, so that the msk state will follow along
> 	 */
> 	if (__mptcp_check_fallback(msk) && subflow_is_done(ssk) &&
> 	    msk->first == ssk &&
> 	    mptcp_update_rcv_data_fin(msk, subflow->map_seq +
> 				      subflow->map_data_len, true))
> 		mptcp_schedule_work(sk);
> }
> 
> Skipping that call means mptcp_update_rcv_data_fin() is never invoked, the
> msk never moves to CLOSE_WAIT/RCV_SHUTDOWN, and blocked readers get no EOF
> until the close timeout (net.mptcp.close_timeout, 60 s by default) fires
> and mptcp_do_fastclose() sets TCP_CLOSE and purges the backlog instead of
> delivering a clean EOF.  Should the comment be qualified, or should the
> check be narrowed so the fallback DATA_FIN injection still runs?
Yeah sashiko does not like my wording, plus another unrelated race 
between fallback and mp_fail timeout.

/P


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH net 2/2] mptcp: close race between scheduler and state change
  2026-09-16 20:45   ` netdev-bot+sashiko
@ 2026-09-17 11:16     ` Paolo Abeni
  0 siblings, 0 replies; 7+ messages in thread
From: Paolo Abeni @ 2026-09-17 11:16 UTC (permalink / raw)
  To: matttbe
  Cc: martineau, geliang, davem, edumazet, kuba, horms, netdev,
	netdev-bot+sashiko, mptcp, stable, shardul.b, xinyang

On 9/16/26 22:45, netdev-bot+sashiko@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential
> issue(s) to consider:
> 
> Critical: 0 · High: 1 · Medium: 0 · Low: 0
> 
> - [High] Incomplete fix: the identical `mss_now == 0` divide-by-zero that
>    the patch guards in `mptcp_push_release()` (net/mptcp/protocol.c:1667)…
> 
> --
> 
> mptcp: close race between scheduler and state change
> 
> The patch guards the tcp_push() call in mptcp_push_release() with a check
> for info->mss_now being non-zero, so that a subflow which failed
> transmission (leaving mss_now at 0) is not used for a divide operation.
> 
> Closes: https://lore.kernel.org/20260525194828.1137119-1-shardul.b@mpiricsoftware.com
> 
>> diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
>> index 0098e28309318..709610db3ce9d 100644
>> --- a/net/mptcp/protocol.c
>> +++ b/net/mptcp/protocol.c
>> @@ -1664,7 +1664,9 @@ struct sock *mptcp_subflow_get_send(struct mptcp_sock *msk)
>>   
>>   static void mptcp_push_release(struct sock *ssk, struct mptcp_sendmsg_info *info)
>>   {
>> -	tcp_push(ssk, 0, info->mss_now, tcp_sk(ssk)->nonagle, info->size_goal);
>> +	if (info->mss_now)
>> +		tcp_push(ssk, 0, info->mss_now, tcp_sk(ssk)->nonagle,
>> +			 info->size_goal);
>>   	release_sock(ssk);
>>   }
> 
> [Severity: High]
> Should the same guard also be added to the second tcp_push() call site, in
> __mptcp_subflow_push_pending()?
> 
> That function starts with a zeroed info:
> 
> net/mptcp/protocol.c:__mptcp_subflow_push_pending() {
> 	struct mptcp_sendmsg_info info = {
> 		.data_lock_held = true,
> 	};
> 	...
> 	int copied = 0;
> 
> and info.mss_now is only ever assigned inside mptcp_sendmsg_frag(), after
> the early return the commit message refers to:
> 
> 	if (unlikely(!__tcp_can_send(ssk)))
> 		return -EAGAIN;
> 
> 	/* compute send limit */
> 	...
> 	info->mss_now = tcp_send_mss(ssk, &info->size_goal, info->flags);
> 
> In the non-first iteration, the return value is accumulated into copied
> without filtering negatives:
> 
> net/mptcp/protocol.c:__mptcp_subflow_push_pending() {
> 		if (READ_ONCE(subflow->scheduled)) {
> 			mptcp_subflow_set_scheduled(subflow, false);
> 			ret = __subflow_push_pending(sk, ssk, &info);
> 			if (ret <= 0)
> 				keep_pushing = false;
> 			copied += ret;
> 		}
> 
> __subflow_push_pending() passes mptcp_sendmsg_frag()'s -EAGAIN through
> verbatim when the first frag fails, so copied becomes -EAGAIN, which is
> non-zero, while info.mss_now is still 0:
This is a pre-existing issue but it could deserve to be fixed in the 
same series.

/P


^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-09-17 11:16 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-15 20:37 [PATCH net 0/2] mptcp: misc fixes for v7.3-rc4 Matthieu Baerts (NGI0)
2026-09-15 20:37 ` [PATCH net 1/2] mptcp: avoid unneeded actions on subflow reset Matthieu Baerts (NGI0)
2026-09-16 20:45   ` netdev-bot+sashiko
2026-09-17 11:14     ` Paolo Abeni
2026-09-15 20:37 ` [PATCH net 2/2] mptcp: close race between scheduler and state change Matthieu Baerts (NGI0)
2026-09-16 20:45   ` netdev-bot+sashiko
2026-09-17 11:16     ` Paolo Abeni

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox