Merge branch 'mptcp-misc-fixes-for-v7-2-rc6'

Matthieu Baerts says:

====================
mptcp: misc fixes for v7.2-rc6

Here are various unrelated fixes:

- Patches 1-3: harden incoming MPTCP suboptions parsing by rejecting
  non-combinable ones. Patch 3 removes unreachable code after patch 2
  added here for consistency, and to reduce comments from AI reviews.
  Fixes for v5.6.

- Patch 4: fix a data race in the ADD_ADDR timer callback. A fix for
  v5.13.

- Patch 5: correctly catch data corruption during the MPTCP join
  selftest by marking tests as failed, instead of only printing a
  warning. A fix for v5.18.

- Patch 6: fix a leak with the userspace ADD_ADDR list in case of race
  condition during teardown. A fix for v5.19.

- Patch 7: deal with MPTFO with a valid token, but no data in the SYN. A
  fix for v6.2.

- Patch 8: reclaim forward-allocated memory in case of error on the
  receive side. A fix for v6.19.
====================

Link: https://patch.msgid.link/20260803-net-mptcp-misc-fixes-7-2-rc6-v2-0-b8f496d71664@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2026-08-06 08:46:25 -07:00
commit c53900b3f1
8 changed files with 101 additions and 19 deletions

View File

@ -24,12 +24,13 @@ void mptcp_fastopen_subflow_synack_set_params(struct mptcp_subflow_context *subf
sk = subflow->conn;
tp = tcp_sk(ssk);
subflow->is_mptfo = 1;
/* A valid TFO cookie does not guarantee SYN data. */
skb = skb_peek(&ssk->sk_receive_queue);
if (WARN_ON_ONCE(!skb))
if (!skb)
return;
subflow->is_mptfo = 1;
/* dequeue the skb from sk receive queue */
__skb_unlink(skb, &ssk->sk_receive_queue);
skb_ext_reset(skb);

View File

@ -50,6 +50,14 @@ static void mptcp_parse_option(const struct sk_buff *skb,
}
}
/* Only the MPC + ACK can be used with a RM_ADDR */
if (subopt == OPTION_MPTCP_MPC_ACK) {
if ((mp_opt->suboptions & ~OPTION_MPTCP_RM_ADDR) != 0)
break;
} else if (mp_opt->suboptions != 0) {
break;
}
/* Cfr RFC 8684 Section 3.3.0:
* If a checksum is present but its use had
* not been negotiated in the MP_CAPABLE handshake, the receiver MUST
@ -122,6 +130,11 @@ static void mptcp_parse_option(const struct sk_buff *skb,
break;
case MPTCPOPT_MP_JOIN:
/* Can be used with a restricted number of other options */
if ((mp_opt->suboptions & ~(OPTION_MPTCP_RM_ADDR |
OPTION_MPTCP_PRIO)) != 0)
break;
if (opsize == TCPOLEN_MPTCP_MPJ_SYN) {
mp_opt->suboptions |= OPTION_MPTCP_MPJ_SYN;
mp_opt->backup = *ptr++ & MPTCPOPT_BACKUP;
@ -153,6 +166,14 @@ static void mptcp_parse_option(const struct sk_buff *skb,
break;
case MPTCPOPT_DSS:
/* Can be used with a restricted number of other options */
if ((mp_opt->suboptions & ~(OPTION_MPTCP_ADD_ADDR |
OPTION_MPTCP_RM_ADDR |
OPTION_MPTCP_PRIO |
OPTION_MPTCP_FASTCLOSE |
OPTION_MPTCP_FAIL)) != 0)
break;
pr_debug("DSS\n");
ptr++;
@ -188,8 +209,14 @@ static void mptcp_parse_option(const struct sk_buff *skb,
* RFC 8684 Section 3.3.0 checks later in subflow_data_ready
*/
if (opsize != expected_opsize &&
opsize != expected_opsize + TCPOLEN_MPTCP_DSS_CHECKSUM)
opsize != expected_opsize + TCPOLEN_MPTCP_DSS_CHECKSUM) {
mp_opt->dsn64 = 0;
mp_opt->use_map = 0;
mp_opt->ack64 = 0;
mp_opt->use_ack = 0;
mp_opt->data_fin = 0;
break;
}
mp_opt->suboptions |= OPTION_MPTCP_DSS;
if (mp_opt->use_ack) {
@ -234,6 +261,12 @@ static void mptcp_parse_option(const struct sk_buff *skb,
break;
case MPTCPOPT_ADD_ADDR:
/* Can be used with a restricted number of other options */
if ((mp_opt->suboptions & ~(OPTIONS_MPTCP_DSS |
OPTION_MPTCP_RM_ADDR |
OPTION_MPTCP_PRIO)) != 0)
break;
mp_opt->echo = (*ptr++) & MPTCP_ADDR_ECHO;
if (!mp_opt->echo) {
if (opsize == TCPOLEN_MPTCP_ADD_ADDR ||
@ -293,6 +326,14 @@ static void mptcp_parse_option(const struct sk_buff *skb,
break;
case MPTCPOPT_RM_ADDR:
/* Can be used with a restricted number of other options */
if ((mp_opt->suboptions & ~(OPTION_MPTCP_MPC_ACK |
OPTIONS_MPTCP_MPJ |
OPTIONS_MPTCP_DSS |
OPTION_MPTCP_ADD_ADDR |
OPTION_MPTCP_PRIO)) != 0)
break;
if (opsize < TCPOLEN_MPTCP_RM_ADDR_BASE + 1 ||
opsize > TCPOLEN_MPTCP_RM_ADDR_BASE + MPTCP_RM_IDS_MAX)
break;
@ -307,6 +348,13 @@ static void mptcp_parse_option(const struct sk_buff *skb,
break;
case MPTCPOPT_MP_PRIO:
/* Can be used with a restricted number of other options */
if ((mp_opt->suboptions & ~(OPTIONS_MPTCP_MPJ |
OPTIONS_MPTCP_DSS |
OPTION_MPTCP_ADD_ADDR |
OPTION_MPTCP_RM_ADDR)) != 0)
break;
if (opsize != TCPOLEN_MPTCP_PRIO)
break;
@ -316,6 +364,11 @@ static void mptcp_parse_option(const struct sk_buff *skb,
break;
case MPTCPOPT_MP_FASTCLOSE:
/* Can be used with a restricted number of other options */
if ((mp_opt->suboptions & ~(OPTIONS_MPTCP_DSS |
OPTION_MPTCP_RST)) != 0)
break;
if (opsize != TCPOLEN_MPTCP_FASTCLOSE)
break;
@ -327,6 +380,11 @@ static void mptcp_parse_option(const struct sk_buff *skb,
break;
case MPTCPOPT_RST:
/* Can be used with a restricted number of other options */
if ((mp_opt->suboptions & ~(OPTION_MPTCP_FAIL |
OPTION_MPTCP_FASTCLOSE)) != 0)
break;
if (opsize != TCPOLEN_MPTCP_RST)
break;
@ -342,6 +400,11 @@ static void mptcp_parse_option(const struct sk_buff *skb,
break;
case MPTCPOPT_MP_FAIL:
/* Can be used with a restricted number of other options */
if ((mp_opt->suboptions & ~(OPTIONS_MPTCP_DSS |
OPTION_MPTCP_RST)) != 0)
break;
if (opsize != TCPOLEN_MPTCP_FAIL)
break;
@ -1400,7 +1463,7 @@ void mptcp_write_options(struct tcphdr *th, __be32 *ptr, struct tcp_sock *tp,
* RM | C | C | C | P |------|------|------|------|
* PRIO | X | C | C | C | C |------|------|------|
* FAIL | X | X | C | X | X | X |------|------|
* FC | X | X | X | X | X | X | X |------|
* FC | X | X | P | X | X | X | X |------|
* RST | X | X | X | X | X | X | O | O |
* ------|------|------|------|------|------|------|------|------|
*

View File

@ -380,6 +380,7 @@ static void mptcp_pm_add_addr_timer(struct timer_list *timer)
struct mptcp_sock *msk = entry->sock;
struct sock *sk = (struct sock *)msk;
unsigned int timeout = 0;
bool retransmit;
pr_debug("msk=%p\n", msk);
@ -412,14 +413,15 @@ static void mptcp_pm_add_addr_timer(struct timer_list *timer)
entry->retrans_times++;
}
if (entry->retrans_times < ADD_ADDR_RETRANS_MAX)
retransmit = entry->retrans_times < ADD_ADDR_RETRANS_MAX;
if (retransmit)
timeout <<= entry->retrans_times;
else
timeout = 0;
spin_unlock_bh(&msk->pm.lock);
if (entry->retrans_times == ADD_ADDR_RETRANS_MAX)
if (!retransmit)
mptcp_pm_subflow_established(msk);
out:
@ -441,6 +443,9 @@ bool mptcp_pm_announced_alloc(struct mptcp_sock *msk,
lockdep_assert_held(&msk->pm.lock);
if (msk->pm.status & BIT(MPTCP_PM_DESTROYING))
return false;
add_entry = mptcp_pm_announced_lookup(msk, addr);
if (add_entry) {
if (WARN_ON_ONCE(mptcp_pm_is_kernel(msk)))
@ -1143,10 +1148,16 @@ void mptcp_pm_worker(struct mptcp_sock *msk)
void mptcp_pm_destroy(struct mptcp_sock *msk)
{
spin_lock_bh(&msk->pm.lock);
msk->pm.status |= BIT(MPTCP_PM_DESTROYING);
spin_unlock_bh(&msk->pm.lock);
mptcp_pm_free_announced_list(msk);
if (mptcp_pm_is_userspace(msk))
mptcp_userspace_pm_free_local_addr_list(msk);
/* Free the userspace local address list unconditionally: the socket
* can be reused (mptcp_disconnect()) and re-selected to a different PM
*/
mptcp_userspace_pm_free_local_addr_list(msk);
}
void mptcp_pm_data_reset(struct mptcp_sock *msk)

View File

@ -54,6 +54,10 @@ static int mptcp_userspace_pm_append_new_local_addr(struct mptcp_sock *msk,
bitmap_zero(id_bitmap, MPTCP_PM_MAX_ADDR_ID + 1);
spin_lock_bh(&msk->pm.lock);
if (msk->pm.status & BIT(MPTCP_PM_DESTROYING)) {
ret = -EINVAL;
goto append_err;
}
mptcp_for_each_userspace_pm_addr(msk, e) {
addr_match = mptcp_addresses_equal(&e->addr, &entry->addr, true);
if (addr_match && entry->addr.id == 0 && needs_id)

View File

@ -149,6 +149,12 @@ struct sock *__mptcp_nmpc_sk(struct mptcp_sock *msk)
static void mptcp_drop(struct sock *sk, struct sk_buff *skb)
{
/* The skb forward memory was already transferred to sk by
* mptcp_borrow_fwdmem(), even before setting the destructor.
*/
if (!skb->destructor)
sk_mem_reclaim(sk);
sk_drops_skbadd(sk, skb);
__kfree_skb(skb);
}

View File

@ -37,6 +37,7 @@
OPTION_MPTCP_MPC_ACK)
#define OPTIONS_MPTCP_MPJ (OPTION_MPTCP_MPJ_SYN | OPTION_MPTCP_MPJ_SYNACK | \
OPTION_MPTCP_MPJ_ACK)
#define OPTIONS_MPTCP_DSS (OPTION_MPTCP_DSS | OPTION_MPTCP_CSUMREQD)
/* MPTCP option subtypes */
#define MPTCPOPT_MP_CAPABLE 0
@ -189,9 +190,10 @@ enum mptcp_pm_status {
MPTCP_PM_ESTABLISHED,
MPTCP_PM_SUBFLOW_ESTABLISHED,
MPTCP_PM_ALREADY_ESTABLISHED, /* persistent status, set after ESTABLISHED event */
MPTCP_PM_MPC_ENDPOINT_ACCOUNTED /* persistent status, set after MPC local address is
* accounted int id_avail_bitmap
*/
MPTCP_PM_MPC_ENDPOINT_ACCOUNTED, /* persistent status, set after MPC local address is
* accounted int id_avail_bitmap
*/
MPTCP_PM_DESTROYING, /* To fence out PM list allocs */
};
enum mptcp_pm_type {

View File

@ -174,8 +174,6 @@ static int subflow_check_req(struct request_sock *req,
if (unlikely(listener->pm_listener))
return subflow_reset_req_endp(req, skb);
if (opt_mp_join)
return 0;
} else if (opt_mp_join) {
SUBFLOW_REQ_INC_STATS(req, MPTCP_MIB_JOINSYNRX);
@ -277,9 +275,6 @@ int mptcp_subflow_init_cookie_req(struct request_sock *req,
opt_mp_capable = !!(mp_opt.suboptions & OPTION_MPTCP_MPC_ACK);
opt_mp_join = !!(mp_opt.suboptions & OPTION_MPTCP_MPJ_ACK);
if (opt_mp_capable && opt_mp_join)
return -EINVAL;
if (opt_mp_capable && listener->request_mptcp) {
if (mp_opt.sndr_key == 0)
return -EINVAL;

View File

@ -584,7 +584,7 @@ check_transfer()
mv "$tmpfile" "$out"
tmpfile=""
fi
cmp -l "$in" "$out" | while read -r i a b; do
while read -r i a b; do
local sum=$((0${a} + 0${b}))
if [ $check_invert -eq 0 ] || [ $sum -ne $((0xff)) ]; then
fail_test "$what does not match (in, out):"
@ -595,7 +595,7 @@ check_transfer()
else
print_info "$what has inverted byte at ${i}"
fi
done
done < <(cmp -l "$in" "$out")
return 0
}