From: sashiko-bot@kernel.org
To: "Geliang Tang" <geliang@kernel.org>, mptcp@lists.linux.dev
Subject: Re: [PATCH mptcp-next v26 6/8] mptcp: remove CB offset field
Date: Fri, 13 Nov 2026 10:28:05 +0000 [thread overview]
Message-ID: <sashiko-outbox-212621@kernel.org> (raw)
In-Reply-To: <0bd74213de97c6d796b835f567e0f426bee89008.1794563432.git.tanggeliang@kylinos.cn>
Thank you for your contribution! Sashiko AI review found 6 potential
issues to consider:
- [High] mptcp: subflow sk_rmem_alloc leak in __mptcp_move_skb() on
rcv_data_fin drop
- [High] mptcp: rcv_data_fin guard bypass in __mptcp_move_skb()
underflows offset
- [High] mptcp: out-of-order queue corruption and stall on non-zero skb
offset
- [High] mptcp: wrong partial-packet copy_len in __mptcp_move_skb() when
offset > 0
- [High] mptcp: __mptcp_try_coalesce() clobbers encoded offset on target
skb
- [Medium] mptcp: commit message describes unimplemented
mptcp_init_skb() changes
--
commit c03fb1e5a208813143363f01640f8ace04366cfd
Author: Paolo Abeni <pabeni@redhat.com>
mptcp: remove CB offset field
This commit replaces the per-skb MPTCP_SKB_CB(skb)->offset field with an
msk-level copied_seq field to track the read offset in the MPTCP receive
queue.
> In mptcp_init_skb(), map_seq is computed as mptcp_subflow_get_mapped_dsn()
> minus offset. A mapping boundary check using before() for proper 32-bit
> modular arithmetic detects when the skb spans multiple DSS mappings
> (offset > 0 and skb starts before the current mapping). In that case,
> the consumed prefix bytes belong to an old mapping and must be removed
> before setting map_seq. The function now returns bool to indicate success.
>
> When removing the prefix, we handle linear and paged data separately.
> For offset within linear data where offset <= skb_headlen, use skb_pull()
> which is safe and doesn't modify truesize. For offset in paged data where
> offset > skb_headlen, use pskb_pull() which may expand the skb head. To
> avoid truesize corruption in pskb_expand_head(), we first orphan the skb
> to disconnect it from the socket, perform the pull, recompute truesize
> manually, then reattach with skb_set_owner_r(). This ensures proper memory
> accounting.
>
> When mptcp_init_skb() fails, the caller increments seq to skip the
> problematic skb, records the failure via MPTCP_MIB_RCVPRUNED, and jumps
> to cleanup to ensure tp->copied_seq and more_data_avail are properly
> updated. This maintains TCP/MPTCP state consistency, and MPTCP-level
> retransmission will fill any resulting gaps.
>
> When the skb stays within a single mapping and offset > 0, map_seq is
> rewound to cover the already-consumed prefix. A new MPTCP_SKB_CB_F_OFFSET
> flag marks such skbs. __mptcp_try_coalesce() refuses to coalesce when the
> from skb carries this flag, preventing an unvalidated prefix from being
> merged into a validated buffer. mptcp_data_queue_ofo() skips the
> rb_replace_node() replacement when the new skb covers an existing OFO
> entry and carries the flag, so that previously validated data is not
> overwritten by an unvalidated prefix; the new skb is inserted after the
> existing entry instead, and the prefix is naturally skipped when the OFO
> queue is later processed.
[Severity: Medium]
Does the commit message match the actual code changes in this patch?
mptcp_init_skb() still returns void and does not check mapping boundaries
or call skb_pull() / pskb_pull(), __mptcp_move_skbs_from_subflow() has no
failure handling or MPTCP_MIB_RCVPRUNED accounting, and
mptcp_data_queue_ofo() does not check MPTCP_SKB_CB_F_OFFSET before calling
rb_replace_node().
> diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
> index 41fc2429d2fed..99dfcd0488a45 100644
> --- a/net/mptcp/protocol.c
> +++ b/net/mptcp/protocol.c
> @@ -167,8 +167,9 @@ static bool __mptcp_try_coalesce(struct sock *sk, struct sk_buff *to,
> {
> int limit = READ_ONCE(sk->sk_rcvbuf);
>
> - if (MPTCP_SKB_CB(from)->map_seq != MPTCP_SKB_CB(to)->end_seq ||
> - MPTCP_SKB_CB(from)->offset ||
> + if (MPTCP_SKB_CB(from)->map_seq != MPTCP_SKB_CB(to)->map_seq +
> + to->len ||
> + MPTCP_SKB_CB(from)->flags & MPTCP_SKB_CB_F_OFFSET ||
> ((to->len + from->len) > (limit >> 3)) ||
> !skb_try_coalesce(to, from, fragstolen, delta))
> return false;
[Severity: High]
Does coalescing into a target skb that had offset > 0 lose its encoded
offset?
When to has offset > 0, mptcp_init_skb() sets to->map_seq = mapped_dsn -
offset and to->end_seq = to->map_seq + to->len - offset, so __mptcp_move_skb()
and __mptcp_ofo_queue() can recover offset from to->len - (to->end_seq -
to->map_seq). When from (with offset == 0 and from->end_seq ==
from->map_seq + from->len) is coalesced into to in __mptcp_add_backlog()
or mptcp_data_queue_ofo(), __mptcp_try_coalesce() does:
net/mptcp/protocol.c:__mptcp_try_coalesce() {
...
MPTCP_SKB_CB(to)->end_seq = MPTCP_SKB_CB(from)->end_seq;
return true;
}
That sets to->end_seq to to->map_seq + to->len, wiping out the offset difference.
When to is later spooled from the backlog or out-of-order queue, dsn is
computed as mapped_dsn - offset instead of mapped_dsn, causing the stale
prefix bytes in to->data[0..offset - 1] to be treated as valid unconsumed
payload.
> @@ -306,7 +307,7 @@ static void mptcp_data_queue_ofo(struct mptcp_sock *msk, struct sk_buff *skb)
> struct sk_buff *skb1;
>
> seq = MPTCP_SKB_CB(skb)->map_seq;
> - end_seq = MPTCP_SKB_CB(skb)->end_seq;
> + end_seq = MPTCP_SKB_CB(skb)->map_seq + skb->len;
> max_seq = atomic64_read(&msk->rcv_wnd_sent);
[ ... ]
> @@ -346,7 +347,8 @@ static void mptcp_data_queue_ofo(struct mptcp_sock *msk, struct sk_buff *skb)
> }
>
> /* Can avoid an rbtree lookup if we are adding skb after ooo_last_skb */
> - if (!before64(seq, MPTCP_SKB_CB(msk->ooo_last_skb)->end_seq)) {
> + if (!before64(seq, MPTCP_SKB_CB(msk->ooo_last_skb)->map_seq +
> + msk->ooo_last_skb->len)) {
> MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_OFOQUEUETAIL);
> parent = &msk->ooo_last_skb->rbnode;
> p = &parent->rb_right;
> @@ -362,8 +364,9 @@ static void mptcp_data_queue_ofo(struct mptcp_sock *msk, struct sk_buff *skb)
> p = &parent->rb_left;
> continue;
> }
> - if (before64(seq, MPTCP_SKB_CB(skb1)->end_seq)) {
> - if (!after64(end_seq, MPTCP_SKB_CB(skb1)->end_seq)) {
> + if (before64(seq, MPTCP_SKB_CB(skb1)->map_seq + skb1->len)) {
> + if (!after64(end_seq,
> + MPTCP_SKB_CB(skb1)->map_seq + skb1->len)) {
> /* All the bits are present. Drop. */
> mptcp_drop(sk, skb);
> MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_DUPDATA);
> @@ -400,7 +403,8 @@ static void mptcp_data_queue_ofo(struct mptcp_sock *msk, struct sk_buff *skb)
> merge_right:
> /* Remove other segments covered by skb. */
> while ((skb1 = skb_rb_next(skb)) != NULL) {
> - if (before64(end_seq, MPTCP_SKB_CB(skb1)->end_seq))
> + if (before64(end_seq,
> + MPTCP_SKB_CB(skb1)->map_seq + skb1->len))
> break;
> rb_erase(&skb1->rbnode, &msk->out_of_order_queue);
> mptcp_drop(sk, skb1);
> @@ -425,9 +429,10 @@ static void mptcp_init_skb(struct sock *ssk, struct sk_buff *skb, int offset,
> * mptcp_subflow_get_mapped_dsn() is based on the current tp->copied_seq
> * value
> */
> - MPTCP_SKB_CB(skb)->map_seq = mptcp_subflow_get_mapped_dsn(subflow);
> + MPTCP_SKB_CB(skb)->map_seq = mptcp_subflow_get_mapped_dsn(subflow) -
> + offset;
> MPTCP_SKB_CB(skb)->end_seq = MPTCP_SKB_CB(skb)->map_seq + copy_len;
> - MPTCP_SKB_CB(skb)->offset = offset;
> + MPTCP_SKB_CB(skb)->flags = offset ? MPTCP_SKB_CB_F_OFFSET : 0;
> MPTCP_SKB_CB(skb)->has_rxtstamp = has_rxtstamp;
[Severity: High]
Can an out-of-order skb with offset > 0 cause mptcp_data_queue_ofo() to
drop valid segments and stall the receive queue?
When offset > 0, mptcp_init_skb() rewinds MPTCP_SKB_CB(skb)->map_seq to
mapped_dsn - offset while the valid unconsumed payload only starts at
mapped_dsn. mptcp_data_queue_ofo() uses seq = MPTCP_SKB_CB(skb)->map_seq
(mapped_dsn - offset) to position skb and deduplicate overlapping ranges,
so any valid out-of-order segment skb1 covering [mapped_dsn - offset,
mapped_dsn) is either replaced via rb_replace_node() / merge_right if it
was queued first, or dropped as MPTCP_MIB_DUPDATA if it arrives after skb.
When msk->ack_seq later reaches mapped_dsn - offset, __mptcp_ofo_queue()
checks dsn = mapped_dsn > msk->ack_seq and stops draining the RB-tree
because the segment covering [mapped_dsn - offset, mapped_dsn) was
dropped, stalling the connection.
[ ... ]
> @@ -450,17 +455,24 @@ void __mptcp_sync_rcv_sequence(struct sock *sk)
[ ... ]
> static bool __mptcp_move_skb(struct sock *sk, struct sk_buff *skb)
> {
> u64 copy_len = MPTCP_SKB_CB(skb)->end_seq - MPTCP_SKB_CB(skb)->map_seq;
> + u64 dsn = MPTCP_SKB_CB(skb)->map_seq + skb->len - copy_len;
> struct mptcp_sock *msk = mptcp_sk(sk);
> struct sk_buff *tail;
>
> + if (READ_ONCE(msk->rcv_data_fin) &&
> + !before64(MPTCP_SKB_CB(skb)->map_seq,
> + READ_ONCE(msk->rcv_data_fin_seq))) {
> + mptcp_drop(sk, skb);
> + return false;
> + }
> +
> mptcp_borrow_fwdmem(sk, skb);
[Severity: High]
Does calling mptcp_drop() before mptcp_borrow_fwdmem() leak
ssk->sk_rmem_alloc on the subflow socket?
Both callers of __mptcp_move_skb() call mptcp_subflow_lend_fwdmem() first,
which clears skb->destructor = NULL while leaving skb->truesize charged to
ssk->sk_rmem_alloc until mptcp_borrow_fwdmem() transfers the forward
memory to sk and subtracts skb->truesize from ssk->sk_rmem_alloc.
Dropping skb before mptcp_borrow_fwdmem() skips that uncharge on ssk (so
closing the subflow triggers the sk_rmem_alloc warning in
inet_sock_destruct()) and calls sk_mem_reclaim(sk) without having credited
sk->sk_forward_alloc.
[Severity: High]
Can data arriving after DATA_FIN has been processed bypass this
rcv_data_fin check and underflow the receive offset?
Once mptcp_check_data_fin() processes the DATA_FIN, it increments
msk->ack_seq to rcv_data_fin_seq + 1 and clears msk->rcv_data_fin to 0:
net/mptcp/protocol.c:mptcp_check_data_fin() {
...
if (mptcp_pending_data_fin(sk, &rcv_data_fin_seq)) {
WRITE_ONCE(msk->ack_seq, msk->ack_seq + 1);
WRITE_ONCE(msk->rcv_data_fin, 0);
WRITE_ONCE(sk->sk_shutdown, sk->sk_shutdown | RCV_SHUTDOWN);
...
}
If a peer then sends a segment at dsn = rcv_data_fin_seq + 1 on an open
subflow, READ_ONCE(msk->rcv_data_fin) is 0, so __mptcp_move_skb() accepts
the skb as in-sequence (dsn == msk->ack_seq) and queues it with map_seq =
rcv_data_fin_seq + 1 while msk->copied_seq is at most rcv_data_fin_seq.
In __mptcp_recvmsg_mskq(), offset = *seq - MPTCP_SKB_CB(skb)->map_seq
underflows and skips the skb without consuming or freeing it. In
addition, while msk->rcv_data_fin is still set, checking map_seq instead
of map_seq + skb->len misses segments that straddle rcv_data_fin_seq (or
have map_seq < rcv_data_fin_seq <= dsn due to offset > 0) and lets them
advance msk->ack_seq past rcv_data_fin_seq.
[ ... ]
> @@ -495,10 +507,6 @@ static bool __mptcp_move_skb(struct sock *sk, struct sk_buff *skb)
> /* Partial packet */
> if (after64(MPTCP_SKB_CB(skb)->end_seq, msk->ack_seq)) {
> copy_len = MPTCP_SKB_CB(skb)->end_seq - msk->ack_seq;
> - MPTCP_SKB_CB(skb)->offset += msk->ack_seq -
> - MPTCP_SKB_CB(skb)->map_seq;
> - MPTCP_SKB_CB(skb)->map_seq += msk->ack_seq -
> - MPTCP_SKB_CB(skb)->map_seq;
> goto insert;
> }
[Severity: High]
Does the partial-packet check use the wrong end sequence when offset > 0?
In mptcp_init_skb(), MPTCP_SKB_CB(skb)->end_seq is set to map_seq +
copy_len (= dsn + copy_len - offset), which is offset bytes less than the
actual end of the segment (map_seq + skb->len). When before64(dsn,
msk->ack_seq) and offset > 0, using MPTCP_SKB_CB(skb)->end_seq here
falsely drops the skb as duplicate data if msk->ack_seq falls in
[end_seq, map_seq + skb->len), or under-increments msk->ack_seq and
msk->bytes_received by offset bytes while __mptcp_recvmsg_mskq() and
__mptcp_read_sock() still copy up to skb->len (advancing msk->copied_seq
past msk->ack_seq).
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1794563432.git.tanggeliang@kylinos.cn?part=6
next prev parent reply other threads:[~2026-11-13 10:28 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-13 9:58 [PATCH mptcp-next v26 0/8] Reduce the differences between TCP and MPTCP for TLS usage Geliang Tang
2026-11-13 9:58 ` [PATCH mptcp-net v26 1/8] mptcp: fix TFO SIOCINQ overflow in mptcp_inq_hint Geliang Tang
2026-11-13 9:58 ` [PATCH mptcp-next v26 2/8] selftests: mptcp: connect: check TFO SIOCINQ overflow Geliang Tang
2026-11-13 9:58 ` [PATCH mptcp-net v26 3/8] mptcp: fix fallback dummy mapping anchor Geliang Tang
2026-11-13 9:58 ` [PATCH mptcp-next v26 4/8] mptcp: drop the mptcp_ooo_try_coalesce() helper Geliang Tang
2026-11-13 9:58 ` [PATCH mptcp-next v26 5/8] mptcp: drop the cant_coalesce CB field Geliang Tang
2026-11-13 9:58 ` [PATCH mptcp-next v26 6/8] mptcp: remove CB offset field Geliang Tang
2026-11-13 10:28 ` sashiko-bot [this message]
2026-11-13 9:58 ` [PATCH mptcp-next v26 7/8] mptcp: sync mptcp skb cb layout with tcp one Geliang Tang
2026-11-13 10:30 ` sashiko-bot
2026-11-13 9:58 ` [PATCH mptcp-next v26 8/8] mptcp: defer read_sock cleanup to mptcp_worker Geliang Tang
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-212621@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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.