From: sashiko-bot@kernel.org
To: "Geliang Tang" <geliang@kernel.org>, mptcp@lists.linux.dev
Subject: Re: [PATCH mptcp-next v18 4/5] mptcp: sync mptcp skb cb layout with tcp one
Date: Fri, 30 Oct 2026 09:19:17 +0000 [thread overview]
Message-ID: <sashiko-outbox-190753@kernel.org> (raw)
In-Reply-To: <ad478dff911f705f9d49c66a1a0ef1794b59c989.1793349369.git.geliang@kernel.org>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] mptcp: subtracting offset from map_seq64 in mptcp_init_skb() corrupts OFO data
- [High] mptcp: copied_seq desync when mptcp_move_skbs() runs before MPTCP_SYNC_SEQ
- [High] mptcp: 64-bit window wrap-around in mptcp_data_queue_ofo() when ack_seq > max_seq
--
commit 7989e41ea583368f6b9348be07447be36c4632d3
Author: Paolo Abeni <pabeni@redhat.com>
mptcp: sync mptcp skb cb layout with tcp one
This commit updates struct mptcp_skb_cb to store 32-bit map_seq and end_seq
fields alongside a 64-bit map_seq64 field so that the MPTCP control buffer
layout matches TCP's sequence number and flags offsets. It also converts the
out-of-order queue and receive queue helpers to use 32-bit sequence
arithmetic after window validation.
> diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
> index 792147b5279f9..7195da74d5c73 100644
> --- a/net/mptcp/protocol.c
> +++ b/net/mptcp/protocol.c
[ ... ]
> @@ -301,16 +303,21 @@ static void mptcp_data_queue_ofo(struct mptcp_sock *msk, struct sk_buff *skb)
> {
> struct sock *sk = (struct sock *)msk;
> struct rb_node **p, *parent;
> - u64 seq, end_seq, max_seq;
> + u64 end_seq, max_seq;
> struct sk_buff *skb1;
> + u32 seq;
>
> seq = MPTCP_SKB_CB(skb)->map_seq;
> - end_seq = MPTCP_SKB_CB(skb)->end_seq;
> + end_seq = MPTCP_SKB_CB(skb)->map_seq64 + skb->len;
> max_seq = atomic64_read(&msk->rcv_wnd_sent);
>
> - pr_debug("msk=%p seq=%llx limit=%llx empty=%d\n", msk, seq, max_seq,
> + pr_debug("msk=%p seq=%x limit=%llx empty=%d\n", msk, seq, max_seq,
> RB_EMPTY_ROOT(&msk->out_of_order_queue));
> - if (after64(end_seq, max_seq)) {
> + /* Use the full sequence space to perform the admission checks, to
> + * protect vs possible wrap-arounds.
> + */
> + if (after64(MPTCP_SKB_CB(skb)->map_seq64, max_seq) ||
> + after64(end_seq, max_seq)) {
[Severity: High]
Can a 64-bit sequence number wrap-around bypass this admission check when
msk->ack_seq has advanced past max_seq (msk->rcv_wnd_sent), allowing
out-of-window data to be queued and then dequeued via 32-bit truncation in
__mptcp_ofo_queue()?
In subflow_set_remote_key(), both msk->ack_seq and msk->rcv_wnd_sent are
initialized to subflow->iasn:
net/mptcp/subflow.c:subflow_set_remote_key() {
...
WRITE_ONCE(msk->ack_seq, subflow->iasn);
WRITE_ONCE(msk->can_ack, true);
atomic64_set(&msk->rcv_wnd_sent, subflow->iasn);
}
On an active MPTCP connection before the first outgoing DSS ACK updates
msk->rcv_wnd_sent via mptcp_set_rwin(), if in-order data of length N arrives
and is accepted by __mptcp_move_skb(), msk->ack_seq advances to
subflow->iasn + N while max_seq remains at subflow->iasn.
When msk->ack_seq > max_seq, a segment carrying a 64-bit data_seq in the
range [max_seq + 2^63 + 1, msk->ack_seq + 2^63 - 1] (for example,
subflow->iasn + 0x8000000000000000ULL + k with 1 <= k < N and k + len > N)
satisfies after64(map_seq64, msk->ack_seq) in __mptcp_move_skb(), but in
mptcp_data_queue_ofo() both after64(map_seq64, max_seq) and
after64(end_seq, max_seq) evaluate to false because max_seq - map_seq64 is
positive in signed 64-bit arithmetic.
Once queued into msk->out_of_order_queue, MPTCP_SKB_CB(skb)->map_seq and
end_seq are truncated to 32 bits, dropping bit 63 so that map_seq becomes
(u32)subflow->iasn + k. When __mptcp_ofo_queue() runs, after(map_seq, ack_seq)
is false and after(end_seq, ack_seq) is true, so the out-of-window SKB is
moved into sk->sk_receive_queue and advances msk->ack_seq.
Should mptcp_data_queue_ofo() clamp or validate max_seq against msk->ack_seq
(or check the distance from msk->ack_seq) before performing the window check?
[ ... ]
> @@ -421,11 +428,13 @@ static void mptcp_init_skb(struct sock *ssk, struct sk_buff *skb, int offset)
>
> /* the skb map_seq accounts for the skb offset:
> * mptcp_subflow_get_mapped_dsn() is based on the current tp->copied_seq
> - * value
> + * value; note that end seq number is only available in 32bits format.
> */
> - MPTCP_SKB_CB(skb)->map_seq = mptcp_subflow_get_mapped_dsn(subflow) -
> - offset;
> + MPTCP_SKB_CB(skb)->map_seq64 = mptcp_subflow_get_mapped_dsn(subflow) -
> + offset;
> + MPTCP_SKB_CB(skb)->map_seq = (u32)MPTCP_SKB_CB(skb)->map_seq64;
> MPTCP_SKB_CB(skb)->end_seq = MPTCP_SKB_CB(skb)->map_seq + skb->len;
[Severity: Critical]
As reported by Sashiko on v17 [1], does subtracting offset from map_seq64 and
map_seq without trimming the prefix bytes from skb corrupt the MPTCP data
stream when an SKB with offset > 0 carries an out-of-order mapping?
In __mptcp_move_skbs_from_subflow(), offset = tp->copied_seq - TCP_SKB_CB(skb)->seq
is non-zero when the first offset bytes of the head SKB on
ssk->sk_receive_queue were already consumed or discarded (for instance, when
a single subflow SKB spans two DSS mappings and mptcp_subflow_discard_data()
advances tp->copied_seq past the first mapping). Only the bytes in
[offset, skb->len) belong to the active DSS mapping starting at
mapped_dsn = mptcp_subflow_get_mapped_dsn(subflow).
When mapped_dsn > msk->ack_seq (out-of-order MPTCP data) and offset > 0,
subtracting offset in mptcp_init_skb() shifts map_seq64 and map_seq backwards
into the unreceived MPTCP sequence range [mapped_dsn - offset, mapped_dsn):
1. If mapped_dsn - offset <= msk->ack_seq, __mptcp_move_skb() treats the SKB
as in-sequence or partially overlapping msk->ack_seq, queues it directly
to sk->sk_receive_queue (or coalesces it into the tail SKB via
__mptcp_try_coalesce()), and advances msk->ack_seq across the unreceived
gap [msk->ack_seq, mapped_dsn), delivering the stale subflow prefix bytes
[0, offset) to user space.
2. If mapped_dsn - offset > msk->ack_seq, mptcp_data_queue_ofo() inserts the
SKB into out_of_order_queue covering [mapped_dsn - offset, mapped_dsn + len).
From there, __mptcp_try_coalesce() can merge the unpulled [0, offset)
prefix into a preceding OFO SKB ending at mapped_dsn - offset, subsequent
legitimate segments for [mapped_dsn - offset, mapped_dsn) are dropped as
duplicate data, and __mptcp_ofo_queue() dequeues the SKB as soon as
msk->ack_seq reaches mapped_dsn - offset rather than mapped_dsn.
> + MPTCP_SKB_CB(skb)->flags = 0;
> MPTCP_SKB_CB(skb)->has_rxtstamp = has_rxtstamp;
>
> __skb_unlink(skb, &ssk->sk_receive_queue);
> @@ -448,13 +457,14 @@ void __mptcp_sync_rcv_sequence(struct sock *sk)
> if (!skb)
> return;
>
> - MPTCP_SKB_CB(skb)->map_seq = mptcp_iasn(msk) - skb->len;
> + MPTCP_SKB_CB(skb)->map_seq64 = mptcp_iasn(msk) - skb->len;
> + MPTCP_SKB_CB(skb)->map_seq = (u32)MPTCP_SKB_CB(skb)->map_seq64;
> MPTCP_SKB_CB(skb)->end_seq = MPTCP_SKB_CB(skb)->map_seq + skb->len;
> }
[ ... ]
> @@ -2207,7 +2215,7 @@ static int __mptcp_recvmsg_mskq(struct sock *sk, struct msghdr *msg,
> int copied = 0;
>
> skb_queue_walk_safe(&sk->sk_receive_queue, skb, tmp) {
> - u64 offset = *seq - MPTCP_SKB_CB(skb)->map_seq;
> + u32 offset = (u32)(*seq) - MPTCP_SKB_CB(skb)->map_seq;
> u32 data_len = skb->len - offset;
> u32 count;
> int err;
> @@ -4618,7 +4626,7 @@ static struct sk_buff *mptcp_recv_skb(struct sock *sk, u32 *off)
> mptcp_move_skbs(sk);
>
> while ((skb = skb_peek(&sk->sk_receive_queue)) != NULL) {
> - offset = msk->copied_seq - MPTCP_SKB_CB(skb)->map_seq;
> + offset = (u32)msk->copied_seq - MPTCP_SKB_CB(skb)->map_seq;
> if (offset < skb->len) {
> *off = offset;
> return skb;
[Severity: High]
What happens in mptcp_recvmsg() and mptcp_recv_skb() if mptcp_move_skbs()
spools the backlog while MPTCP_SYNC_SEQ is still pending on msk->cb_flags?
When subflow_set_remote_key() runs in softirq while the MPTCP socket is owned
by user space (for example, on a passive TFO socket during mptcp_recvmsg() or
__mptcp_read_sock()), it sets msk->ack_seq to subflow->iasn immediately, but
defers updating msk->copied_seq by setting MPTCP_SYNC_SEQ in msk->cb_flags:
net/mptcp/subflow.c:subflow_set_remote_key() {
...
if (!sock_owned_by_user(sk)) {
msk->copied_seq += subflow->iasn;
__mptcp_sync_rcv_sequence(sk);
} else {
set_bit(MPTCP_SYNC_SEQ, &msk->cb_flags);
}
}
If data arrives in the 3rd ACK or immediately afterward while user space still
holds the socket lock, the incoming SKBs are queued to msk->backlog_list with
map_seq64 and map_seq in the subflow->iasn sequence space. Before
release_sock() runs mptcp_release_cb(), both mptcp_recvmsg() and
mptcp_recv_skb() call mptcp_move_skbs(sk):
1. Inside mptcp_move_skbs() -> __mptcp_move_skbs() -> __mptcp_move_skb(),
__mptcp_sync_rcv_sequence(sk) updates any remaining TFO SKB in
sk->sk_receive_queue to map_seq = (u32)(mptcp_iasn(msk) - skb->len) and
clears msk->rcvd_dummy_seq = false, while queuing the spooled SKBs with
IASN-based map_seq values into sk->sk_receive_queue. However,
msk->copied_seq is still in the pre-IASN sequence space.
2. In mptcp_recv_skb(), offset = (u32)msk->copied_seq - MPTCP_SKB_CB(skb)->map_seq
is then off by -(u32)iasn, so offset >= skb->len holds whenever
(u32)(-iasn) >= skb->len and the loop eats and drops every queued SKB via
mptcp_eat_recv_skb(). Follow-up commit 68366c7579c4 ("mptcp: defer
read_sock cleanup to mptcp_worker") later removes the mptcp_move_skbs()
call from mptcp_recv_skb(), but does not address mptcp_recvmsg() or
mptcp_release_cb().
3. In mptcp_recvmsg(), after mptcp_move_skbs(sk) returns true, the loop
continues into __mptcp_recvmsg_mskq() with *seq still unsynchronized,
causing u32 offset = (u32)(*seq) - MPTCP_SKB_CB(skb)->map_seq to skip
unread SKBs or copy from a corrupted offset.
4. Even when release_sock() runs mptcp_release_cb(), __mptcp_move_skbs() runs
before the test for MPTCP_SYNC_SEQ:
net/mptcp/protocol.c:mptcp_release_cb() {
for (;;) {
...
if (__mptcp_move_skbs(sk, &join_list, &moved))
...
if (__test_and_clear_bit(MPTCP_SYNC_SEQ, &msk->cb_flags)) {
msk->copied_seq += mptcp_iasn(msk);
__mptcp_sync_rcv_sequence(sk);
}
}
}
If the spooled backlog carries a DATA_FIN, __mptcp_move_skbs() calls
mptcp_check_data_fin(), which increments msk->ack_seq by 1 without
incrementing msk->bytes_received. When MPTCP_SYNC_SEQ then adds
mptcp_iasn(msk) (which is msk->ack_seq - msk->bytes_received +
msk->tfo_skb_len), msk->copied_seq is advanced by iasn + 1, skipping 1
byte of unread payload.
Should MPTCP_SYNC_SEQ be synchronized inside __mptcp_sync_rcv_sequence() (or
before spooling backlog SKBs in mptcp_move_skbs() and mptcp_release_cb()) so
that msk->copied_seq and the receive queue SKBs never end up in different
sequence spaces?
[1] https://lore.kernel.org/r/fe11356d3e5637de94fbe89540271d5f4ee381b4.1793326853.git.tanggeliang@kylinos.cn
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1793349369.git.geliang@kernel.org?part=4
next prev parent reply other threads:[~2026-10-30 9:19 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-30 8:41 [PATCH mptcp-next v18 0/5] Reduce the differences between TCP and MPTCP for TLS usage Geliang Tang
2026-10-30 8:41 ` [PATCH mptcp-next v18 1/5] mptcp: drop the mptcp_ooo_try_coalesce() helper Geliang Tang
2026-10-30 8:41 ` [PATCH mptcp-next v18 2/5] mptcp: drop the cant_coalesce CB field Geliang Tang
2026-10-30 9:02 ` sashiko-bot
2026-10-30 8:41 ` [PATCH mptcp-next v18 3/5] mptcp: remove CB offset field Geliang Tang
2026-10-30 9:18 ` sashiko-bot
2026-10-30 8:41 ` [PATCH mptcp-next v18 4/5] mptcp: sync mptcp skb cb layout with tcp one Geliang Tang
2026-10-30 9:19 ` sashiko-bot [this message]
2026-10-30 8:41 ` [PATCH mptcp-next v18 5/5] mptcp: defer read_sock cleanup to mptcp_worker Geliang Tang
2026-10-30 9:08 ` sashiko-bot
2026-10-30 10:45 ` [PATCH mptcp-next v18 0/5] Reduce the differences between TCP and MPTCP for TLS usage MPTCP CI
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=sashiko-outbox-190753@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=geliang@kernel.org \
--cc=mptcp@lists.linux.dev \
--cc=sashiko-reviews@lists.linux.dev \
/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