CVE-2026-45889
Description
In the Linux kernel, the following vulnerability has been resolved:
mptcp: do not account for OoO in mptcp_rcvbuf_grow()
MPTCP-level OoOs are physiological when multiple subflows are active concurrently and will not cause retransmissions nor are caused by drops.
Accounting for them in mptcp_rcvbuf_grow() causes the rcvbuf slowly drifting towards tcp_rmem[2].
Remove such accounting. Note that subflows will still account for TCP-level OoO when the MPTCP-level rcvbuf is propagated.
This also closes a subtle and very unlikely race condition with rcvspace init; active sockets with user-space holding the msk-level socket lock, could complete such initialization in the receive callback, after that the first OoO data reaches the rcvbuf and potentially triggering a divide by zero Oops.
Affected products
1Patches
6fb7bf00b04a6mptcp: do not account for OoO in mptcp_rcvbuf_grow()
1 file changed · +0 −7
net/mptcp/protocol.c+0 −7 modifieddiff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index e4bb7e2d7b19a0..15a70af7b776dc 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -224,9 +224,6 @@ static bool mptcp_rcvbuf_grow(struct sock *sk, u32 newval) do_div(grow, oldval); rcvwin += grow << 1; - if (!RB_EMPTY_ROOT(&msk->out_of_order_queue)) - rcvwin += MPTCP_SKB_CB(msk->ooo_last_skb)->end_seq - msk->ack_seq; - cap = READ_ONCE(net->ipv4.sysctl_tcp_rmem[2]); rcvbuf = min_t(u32, mptcp_space_from_win(sk, rcvwin), cap); @@ -350,9 +347,6 @@ merge_right: end: skb_condense(skb); skb_set_owner_r(skb, sk); - /* do not grow rcvbuf for not-yet-accepted or orphaned sockets. */ - if (sk->sk_socket) - mptcp_rcvbuf_grow(sk, msk->rcvq_space.space); } static void mptcp_init_skb(struct sock *ssk, struct sk_buff *skb, int offset, -- cgit 1.3-korg
6b329393502emptcp: do not account for OoO in mptcp_rcvbuf_grow()
1 file changed · +0 −7
net/mptcp/protocol.c+0 −7 modifieddiff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 9b8c51937eb2ac..758a6dcb1d7baa 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -224,9 +224,6 @@ static bool mptcp_rcvbuf_grow(struct sock *sk, u32 newval) do_div(grow, oldval); rcvwin += grow << 1; - if (!RB_EMPTY_ROOT(&msk->out_of_order_queue)) - rcvwin += MPTCP_SKB_CB(msk->ooo_last_skb)->end_seq - msk->ack_seq; - cap = READ_ONCE(net->ipv4.sysctl_tcp_rmem[2]); rcvbuf = min_t(u32, mptcp_space_from_win(sk, rcvwin), cap); @@ -350,9 +347,6 @@ merge_right: end: skb_condense(skb); skb_set_owner_r(skb, sk); - /* do not grow rcvbuf for not-yet-accepted or orphaned sockets. */ - if (sk->sk_socket) - mptcp_rcvbuf_grow(sk, msk->rcvq_space.space); } static void mptcp_init_skb(struct sock *ssk, struct sk_buff *skb, int offset, -- cgit 1.3-korg
400ee4854ademptcp: do not account for OoO in mptcp_rcvbuf_grow()
1 file changed · +0 −7
net/mptcp/protocol.c+0 −7 modifieddiff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 8d323366741810..cfa38bdaf2a924 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -224,9 +224,6 @@ static bool mptcp_rcvbuf_grow(struct sock *sk, u32 newval) do_div(grow, oldval); rcvwin += grow << 1; - if (!RB_EMPTY_ROOT(&msk->out_of_order_queue)) - rcvwin += MPTCP_SKB_CB(msk->ooo_last_skb)->end_seq - msk->ack_seq; - cap = READ_ONCE(net->ipv4.sysctl_tcp_rmem[2]); rcvbuf = min_t(u32, mptcp_space_from_win(sk, rcvwin), cap); @@ -350,9 +347,6 @@ merge_right: end: skb_condense(skb); skb_set_owner_r(skb, sk); - /* do not grow rcvbuf for not-yet-accepted or orphaned sockets. */ - if (sk->sk_socket) - mptcp_rcvbuf_grow(sk, msk->rcvq_space.space); } static void mptcp_init_skb(struct sock *ssk, struct sk_buff *skb, int offset, -- cgit 1.3-korg
fb7bf00b04a6mptcp: do not account for OoO in mptcp_rcvbuf_grow()
1 file changed · +0 −7
net/mptcp/protocol.c+0 −7 modifieddiff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index e4bb7e2d7b19a0..15a70af7b776dc 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -224,9 +224,6 @@ static bool mptcp_rcvbuf_grow(struct sock *sk, u32 newval) do_div(grow, oldval); rcvwin += grow << 1; - if (!RB_EMPTY_ROOT(&msk->out_of_order_queue)) - rcvwin += MPTCP_SKB_CB(msk->ooo_last_skb)->end_seq - msk->ack_seq; - cap = READ_ONCE(net->ipv4.sysctl_tcp_rmem[2]); rcvbuf = min_t(u32, mptcp_space_from_win(sk, rcvwin), cap); @@ -350,9 +347,6 @@ merge_right: end: skb_condense(skb); skb_set_owner_r(skb, sk); - /* do not grow rcvbuf for not-yet-accepted or orphaned sockets. */ - if (sk->sk_socket) - mptcp_rcvbuf_grow(sk, msk->rcvq_space.space); } static void mptcp_init_skb(struct sock *ssk, struct sk_buff *skb, int offset, -- cgit 1.3-korg
400ee4854ademptcp: do not account for OoO in mptcp_rcvbuf_grow()
1 file changed · +0 −7
net/mptcp/protocol.c+0 −7 modifieddiff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 8d323366741810..cfa38bdaf2a924 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -224,9 +224,6 @@ static bool mptcp_rcvbuf_grow(struct sock *sk, u32 newval) do_div(grow, oldval); rcvwin += grow << 1; - if (!RB_EMPTY_ROOT(&msk->out_of_order_queue)) - rcvwin += MPTCP_SKB_CB(msk->ooo_last_skb)->end_seq - msk->ack_seq; - cap = READ_ONCE(net->ipv4.sysctl_tcp_rmem[2]); rcvbuf = min_t(u32, mptcp_space_from_win(sk, rcvwin), cap); @@ -350,9 +347,6 @@ merge_right: end: skb_condense(skb); skb_set_owner_r(skb, sk); - /* do not grow rcvbuf for not-yet-accepted or orphaned sockets. */ - if (sk->sk_socket) - mptcp_rcvbuf_grow(sk, msk->rcvq_space.space); } static void mptcp_init_skb(struct sock *ssk, struct sk_buff *skb, int offset, -- cgit 1.3-korg
6b329393502emptcp: do not account for OoO in mptcp_rcvbuf_grow()
1 file changed · +0 −7
net/mptcp/protocol.c+0 −7 modifieddiff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c index 9b8c51937eb2ac..758a6dcb1d7baa 100644 --- a/net/mptcp/protocol.c +++ b/net/mptcp/protocol.c @@ -224,9 +224,6 @@ static bool mptcp_rcvbuf_grow(struct sock *sk, u32 newval) do_div(grow, oldval); rcvwin += grow << 1; - if (!RB_EMPTY_ROOT(&msk->out_of_order_queue)) - rcvwin += MPTCP_SKB_CB(msk->ooo_last_skb)->end_seq - msk->ack_seq; - cap = READ_ONCE(net->ipv4.sysctl_tcp_rmem[2]); rcvbuf = min_t(u32, mptcp_space_from_win(sk, rcvwin), cap); @@ -350,9 +347,6 @@ merge_right: end: skb_condense(skb); skb_set_owner_r(skb, sk); - /* do not grow rcvbuf for not-yet-accepted or orphaned sockets. */ - if (sk->sk_socket) - mptcp_rcvbuf_grow(sk, msk->rcvq_space.space); } static void mptcp_init_skb(struct sock *ssk, struct sk_buff *skb, int offset, -- cgit 1.3-korg
Vulnerability mechanics
Root cause
"Incorrectly accounting for MPTCP-level out-of-order data in `mptcp_rcvbuf_grow()` causes the receive buffer to inflate unnecessarily and introduces a race condition that can lead to a divide-by-zero Oops."
Attack vector
An attacker who can cause out-of-order (OoO) MPTCP data delivery — which is normal when multiple subflows are active concurrently — triggers the bug. Each OoO data arrival calls `mptcp_rcvbuf_grow()`, which adds the OoO gap size to the receive-window estimate, causing the receive buffer to slowly drift toward `tcp_rmem[2]` (the maximum). Additionally, a very unlikely race exists: if user-space holds the msk-level socket lock during `rcvspace` init, the first OoO data can reach the receive callback before initialization completes, potentially causing a divide-by-zero Oops [patch_id=2661647].
Affected code
The vulnerability is in `net/mptcp/protocol.c` within the `mptcp_rcvbuf_grow()` function and the receive-data path that calls it. Two code regions are removed by the patch: the OoO-queue size check in `mptcp_rcvbuf_grow()` (lines 227-228 of the old code) and the call to `mptcp_rcvbuf_grow()` at the end of the data-merge path (lines 350-352 of the old code) [patch_id=2661647][patch_id=2661648][patch_id=2661649][patch_id=2661644][patch_id=2661645][patch_id=2661646].
What the fix does
The patch removes two hunks from `net/mptcp/protocol.c`. First, it deletes the conditional that added the OoO gap (`end_seq - ack_seq`) to `rcvwin` inside `mptcp_rcvbuf_grow()`, because MPTCP-level OoO is physiological and should not inflate the buffer. Second, it removes the call to `mptcp_rcvbuf_grow()` from the data-merge path (the `end:` label), which was invoked on every received skb for non-orphaned sockets. Removing this call eliminates both the buffer drift and the race where `mptcp_rcvbuf_grow()` could run before `rcvspace` initialization completed, preventing the potential divide-by-zero Oops [patch_id=2661647][patch_id=2661648][patch_id=2661649][patch_id=2661644][patch_id=2661645][patch_id=2661646].
Preconditions
- configThe MPTCP socket must have multiple active subflows to generate MPTCP-level out-of-order data.
- authFor the divide-by-zero race: user-space must hold the msk-level socket lock during rcvspace initialization.
- networkNo authentication or special network position is required beyond being able to send data over the MPTCP connection.
Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
3News mentions
0No linked articles in our index yet.