Merge branch 'mptcp-misc-fixes-for-v7-1-rc7'

Matthieu Baerts says:

====================
mptcp: misc fixes for v7.1-rc7

Here are various unrelated fixes:

- Patch 1: fix missing wakeups when multiple threads are reading from
  the same fd. A fix for v5.7.

- Patch 2: fix retransmission loop when MPTCP checksum is enabled. A fix
  for v5.14.

- Patch 3: fix a TOCTOU race while computing rcv_wnd. A fix for v5.11.

- Patch 4: allow subflows receive window to shrink if needed. A fix for
  v5.19.

- Patches 5-6: avoid 'extra_subflows' to underflow with the userspace
  PM. A fix for v5.19.

- Patch 7: report errors if one subflow cannot set SO_TIMESTAMPING. A
  fix for v5.14.

- Patch 8: try to set TCP_MAXSEG on all subflows, before reporting
  errors, if any. A fix for v6.17.

- Patch 9: check desc->count in read_sock, to act as expected. A fix
  for v7.0.

- Patch 10: fix an uninit value in mptcp_established_options, reported
  by syzbot. A fix for v7.1-rc1.

- Patch 11: fix a similar issue than the previous patch, exposed by the
  same modification from v7.1-rc1, but was already causing issues since
  v5.15.
====================

Link: https://patch.msgid.link/20260602-net-mptcp-misc-fixes-7-1-rc7-v2-0-856831229976@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2026-06-03 19:04:46 -07:00
commit 11c31f8ee9
8 changed files with 81 additions and 70 deletions

View File

@ -27,7 +27,9 @@ struct mptcp_ext {
u32 subflow_seq;
u16 data_len;
__sum16 csum;
u8 use_map:1,
struct_group(flags,
u8 use_map:1,
dsn64:1,
data_fin:1,
use_ack:1,
@ -35,9 +37,10 @@ struct mptcp_ext {
mpc_map:1,
frozen:1,
reset_transient:1;
u8 reset_reason:4,
u8 reset_reason:4,
csum_reqd:1,
infinite_map:1;
); /* end of flags group */
};
#define MPTCPOPT_HMAC_LEN 20

View File

@ -566,12 +566,17 @@ static bool mptcp_established_options_dss(struct sock *sk, struct sk_buff *skb,
{
struct mptcp_subflow_context *subflow = mptcp_subflow_ctx(sk);
struct mptcp_sock *msk = mptcp_sk(subflow->conn);
struct tcp_sock *tp = tcp_sk(sk);
unsigned int dss_size = 0;
struct mptcp_ext *mpext;
unsigned int ack_size;
bool ret = false;
u64 ack_seq;
/* Zero `use_ack` and `use_map` flags with one shot. */
BUILD_BUG_ON(sizeof_field(struct mptcp_ext, flags) != sizeof(u16));
BUILD_BUG_ON(!IS_ALIGNED(offsetof(struct mptcp_ext, flags),
sizeof(u16)));
*(u16 *)&opts->ext_copy.flags = 0;
opts->csum_reqd = READ_ONCE(msk->csum_enabled);
mpext = skb ? mptcp_get_ext(skb) : NULL;
@ -595,20 +600,16 @@ static bool mptcp_established_options_dss(struct sock *sk, struct sk_buff *skb,
/* passive sockets msk will set the 'can_ack' after accept(), even
* if the first subflow may have the already the remote key handy
*/
opts->ext_copy.use_ack = 0;
if (!READ_ONCE(msk->can_ack)) {
*size = ALIGN(dss_size, 4);
return ret;
}
ack_seq = READ_ONCE(msk->ack_seq);
if (READ_ONCE(msk->use_64bit_ack)) {
ack_size = TCPOLEN_MPTCP_DSS_ACK64;
opts->ext_copy.data_ack = ack_seq;
opts->ext_copy.ack64 = 1;
} else {
ack_size = TCPOLEN_MPTCP_DSS_ACK32;
opts->ext_copy.data_ack32 = (uint32_t)ack_seq;
opts->ext_copy.ack64 = 0;
}
opts->ext_copy.use_ack = 1;
@ -618,6 +619,12 @@ static bool mptcp_established_options_dss(struct sock *sk, struct sk_buff *skb,
if (dss_size == 0)
ack_size += TCPOLEN_MPTCP_DSS_BASE;
/* The caller is __tcp_transmit_skb(), and will compute the new rcv
* wnd soon: ensure that the window can shrink.
*/
if (skb)
tp->rcv_wnd = tp->rcv_nxt - tp->rcv_wup;
dss_size += ack_size;
*size = ALIGN(dss_size, 4);
@ -658,7 +665,6 @@ static bool mptcp_established_options_add_addr(struct sock *sk, struct sk_buff *
{
struct mptcp_subflow_context *subflow = mptcp_subflow_ctx(sk);
struct mptcp_sock *msk = mptcp_sk(subflow->conn);
bool drop_other_suboptions = false;
unsigned int opt_size = *size;
struct mptcp_addr_info addr;
bool echo;
@ -669,36 +675,20 @@ static bool mptcp_established_options_add_addr(struct sock *sk, struct sk_buff *
*/
if (!mptcp_pm_should_add_signal(msk) ||
(opts->suboptions & (OPTION_MPTCP_MPJ_ACK | OPTION_MPTCP_MPC_ACK)) ||
!mptcp_pm_add_addr_signal(msk, skb, opt_size, remaining, &addr,
&echo, &drop_other_suboptions))
!skb || !skb_is_tcp_pure_ack(skb) ||
!mptcp_pm_add_addr_signal(msk, opt_size, remaining, &addr, &echo))
return false;
/*
* Later on, mptcp_write_options() will enforce mutually exclusion with
* DSS, bail out if such option is set and we can't drop it.
*/
if (drop_other_suboptions)
remaining += opt_size;
else if (opts->suboptions & OPTION_MPTCP_DSS)
return false;
remaining += opt_size;
len = mptcp_add_addr_len(addr.family, echo, !!addr.port);
if (remaining < len)
return false;
*size = len;
if (drop_other_suboptions) {
pr_debug("drop other suboptions\n");
opts->suboptions = 0;
/* note that e.g. DSS could have written into the memory
* aliased by ahmac, we must reset the field here
* to avoid appending the hmac even for ADD_ADDR echo
* options
*/
opts->ahmac = 0;
*size -= opt_size;
}
pr_debug("drop other suboptions\n");
opts->suboptions = 0;
*size -= opt_size;
opts->addr = addr;
opts->suboptions |= OPTION_MPTCP_ADD_ADDR;
if (!echo) {
@ -708,6 +698,7 @@ static bool mptcp_established_options_add_addr(struct sock *sk, struct sk_buff *
&opts->addr);
} else {
MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_ECHOADDTX);
opts->ahmac = 0;
}
pr_debug("addr_id=%d, ahmac=%llu, echo=%d, port=%d\n",
opts->addr.id, opts->ahmac, echo, ntohs(opts->addr.port));
@ -1297,19 +1288,14 @@ bool mptcp_incoming_options(struct sock *sk, struct sk_buff *skb)
return true;
}
static void mptcp_set_rwin(struct tcp_sock *tp, struct tcphdr *th)
static u64 mptcp_set_rwin(struct mptcp_sock *msk, struct tcp_sock *tp,
struct tcphdr *th, u64 ack_seq)
{
const struct sock *ssk = (const struct sock *)tp;
struct mptcp_subflow_context *subflow;
u64 ack_seq, rcv_wnd_old, rcv_wnd_new;
struct mptcp_sock *msk;
u64 rcv_wnd_old, rcv_wnd_new;
u32 new_win;
u64 win;
subflow = mptcp_subflow_ctx(ssk);
msk = mptcp_sk(subflow->conn);
ack_seq = READ_ONCE(msk->ack_seq);
rcv_wnd_new = ack_seq + tp->rcv_wnd;
rcv_wnd_old = atomic64_read(&msk->rcv_wnd_sent);
@ -1362,7 +1348,7 @@ static void mptcp_set_rwin(struct tcp_sock *tp, struct tcphdr *th)
update_wspace:
WRITE_ONCE(msk->old_wspace, tp->rcv_wnd);
subflow->rcv_wnd_sent = rcv_wnd_new;
return rcv_wnd_new;
}
static void mptcp_track_rwin(struct tcp_sock *tp)
@ -1474,13 +1460,25 @@ void mptcp_write_options(struct tcphdr *th, __be32 *ptr, struct tcp_sock *tp,
*ptr++ = mptcp_option(MPTCPOPT_DSS, len, 0, flags);
if (mpext->use_ack) {
struct mptcp_sock *msk;
u64 ack_seq;
/* DSS option is set only by mptcp_established_options,
* the caller is __tcp_transmit_skb() and ssk is always
* not NULL.
*/
subflow = mptcp_subflow_ctx(ssk);
msk = mptcp_sk(subflow->conn);
ack_seq = READ_ONCE(msk->ack_seq);
if (mpext->ack64) {
put_unaligned_be64(mpext->data_ack, ptr);
put_unaligned_be64(ack_seq, ptr);
ptr += 2;
} else {
put_unaligned_be32(mpext->data_ack32, ptr);
put_unaligned_be32(ack_seq, ptr);
ptr += 1;
}
subflow->rcv_wnd_sent = mptcp_set_rwin(msk, tp, th,
ack_seq);
}
if (mpext->use_map) {
@ -1708,9 +1706,6 @@ void mptcp_write_options(struct tcphdr *th, __be32 *ptr, struct tcp_sock *tp,
i += 4;
}
}
if (tp)
mptcp_set_rwin(tp, th);
}
__be32 mptcp_get_reset_option(const struct sk_buff *skb)

View File

@ -887,10 +887,9 @@ void mptcp_pm_mp_fail_received(struct sock *sk, u64 fail_seq)
}
}
bool mptcp_pm_add_addr_signal(struct mptcp_sock *msk, const struct sk_buff *skb,
unsigned int opt_size, unsigned int remaining,
struct mptcp_addr_info *addr, bool *echo,
bool *drop_other_suboptions)
bool mptcp_pm_add_addr_signal(struct mptcp_sock *msk, unsigned int opt_size,
unsigned int remaining,
struct mptcp_addr_info *addr, bool *echo)
{
bool skip_add_addr = false;
int ret = false;
@ -908,10 +907,7 @@ bool mptcp_pm_add_addr_signal(struct mptcp_sock *msk, const struct sk_buff *skb,
* plain dup-ack from TCP perspective. The other MPTCP-relevant info,
* if any, will be carried by the 'original' TCP ack
*/
if (skb && skb_is_tcp_pure_ack(skb)) {
remaining += opt_size;
*drop_other_suboptions = true;
}
remaining += opt_size;
*echo = mptcp_pm_should_add_signal_echo(msk);
if (*echo) {
@ -929,9 +925,6 @@ bool mptcp_pm_add_addr_signal(struct mptcp_sock *msk, const struct sk_buff *skb,
if (remaining < mptcp_add_addr_len(family, *echo, port)) {
struct net *net = sock_net((struct sock *)msk);
if (!*drop_other_suboptions)
goto out_unlock;
if (*echo) {
MPTCP_INC_STATS(net, MPTCP_MIB_ECHOADDTXDROP);
} else {

View File

@ -408,19 +408,21 @@ int mptcp_pm_nl_subflow_create_doit(struct sk_buff *skb, struct genl_info *info)
local.flags = entry.flags;
local.ifindex = entry.ifindex;
spin_lock_bh(&msk->pm.lock);
msk->pm.extra_subflows++;
spin_unlock_bh(&msk->pm.lock);
lock_sock(sk);
err = __mptcp_subflow_connect(sk, &local, &addr_r);
release_sock(sk);
if (err)
if (err) {
GENL_SET_ERR_MSG_FMT(info, "connect error: %d", err);
spin_lock_bh(&msk->pm.lock);
if (err)
spin_lock_bh(&msk->pm.lock);
mptcp_userspace_pm_delete_local_addr(msk, &entry);
else
msk->pm.extra_subflows++;
spin_unlock_bh(&msk->pm.lock);
spin_unlock_bh(&msk->pm.lock);
}
create_err:
sock_put(sk);

View File

@ -2276,6 +2276,10 @@ static bool mptcp_move_skbs(struct sock *sk)
mptcp_backlog_spooled(sk, moved, &skbs);
}
mptcp_data_unlock(sk);
if (enqueued && mptcp_epollin_ready(sk))
sk->sk_data_ready(sk);
return enqueued;
}
@ -2865,6 +2869,10 @@ static void __mptcp_retrans(struct sock *sk)
msk->bytes_retrans += len;
dfrag->already_sent = max(dfrag->already_sent, len);
/* With csum enabled retransmission can send new data. */
if (after64(dfrag->already_sent + dfrag->data_seq, msk->snd_nxt))
WRITE_ONCE(msk->snd_nxt, dfrag->already_sent + dfrag->data_seq);
reset_timer:
mptcp_check_and_set_pending(sk);
@ -4420,6 +4428,8 @@ static int __mptcp_read_sock(struct sock *sk, read_descriptor_t *desc,
}
mptcp_eat_recv_skb(sk, skb);
if (!desc->count)
break;
}
if (noack)

View File

@ -1229,10 +1229,9 @@ static inline int mptcp_rm_addr_len(const struct mptcp_rm_list *rm_list)
return TCPOLEN_MPTCP_RM_ADDR_BASE + roundup(rm_list->nr - 1, 4) + 1;
}
bool mptcp_pm_add_addr_signal(struct mptcp_sock *msk, const struct sk_buff *skb,
unsigned int opt_size, unsigned int remaining,
struct mptcp_addr_info *addr, bool *echo,
bool *drop_other_suboptions);
bool mptcp_pm_add_addr_signal(struct mptcp_sock *msk, unsigned int opt_size,
unsigned int remaining,
struct mptcp_addr_info *addr, bool *echo);
bool mptcp_pm_rm_addr_signal(struct mptcp_sock *msk, unsigned int remaining,
struct mptcp_rm_list *rm_list);
int mptcp_pm_get_local_id(struct mptcp_sock *msk, struct sock_common *skc);

View File

@ -241,15 +241,19 @@ static int mptcp_setsockopt_sol_socket_timestamping(struct mptcp_sock *msk,
mptcp_for_each_subflow(msk, subflow) {
struct sock *ssk = mptcp_subflow_tcp_sock(subflow);
int err;
lock_sock(ssk);
sock_set_timestamping(ssk, optname, timestamping);
err = sock_set_timestamping(ssk, optname, timestamping);
release_sock(ssk);
if (err < 0 && ret == 0)
ret = err;
}
release_sock(sk);
return 0;
return ret;
}
static int mptcp_setsockopt_sol_socket_linger(struct mptcp_sock *msk, sockptr_t optval,
@ -813,10 +817,11 @@ static int mptcp_setsockopt_all_sf(struct mptcp_sock *msk, int level,
mptcp_for_each_subflow(msk, subflow) {
struct sock *ssk = mptcp_subflow_tcp_sock(subflow);
int err;
ret = tcp_setsockopt(ssk, level, optname, optval, optlen);
if (ret)
break;
err = tcp_setsockopt(ssk, level, optname, optval, optlen);
if (err < 0 && ret == 0)
ret = err;
}
if (!ret)

View File

@ -4100,6 +4100,10 @@ userspace_tests()
chk_rm_nr 0 1
chk_mptcp_info subflows 0 subflows 0
chk_subflows_total 1 1
# check counters are not affected by errors at creation time
userspace_pm_add_sf $ns2 10.0.12.2 10 2>/dev/null
chk_mptcp_info subflows 0 subflows 0
chk_subflows_total 1 1
kill_events_pids
mptcp_lib_kill_group_wait $tests_pid
fi