* [PATCH mptcp-next 0/3] mptcp: options: MIB for invalid & drop suboptions
@ 2026-09-03 11:24 Matthieu Baerts (NGI0)
2026-09-03 11:24 ` [PATCH mptcp-next 1/3] mptcp: options: use a dedicated bit for csum reqd Matthieu Baerts (NGI0)
` (3 more replies)
0 siblings, 4 replies; 7+ messages in thread
From: Matthieu Baerts (NGI0) @ 2026-09-03 11:24 UTC (permalink / raw)
To: MPTCP Linux; +Cc: Matthieu Baerts (NGI0), Gang Yan
Here are a few improvements linked to the parsing of the MPTCP options:
- Patch 1: Move the only non-suboptions from the 'suboptions' field.
- Patch 2: Track invalid suboptions: unexpected suboptions combinations
or invalid sizes.
- Patch 3: drop any previously parsed suboptions in case of invalidity.
Signed-off-by: Matthieu Baerts (NGI0) <matttbe@kernel.org>
---
Matthieu Baerts (NGI0) (3):
mptcp: options: use a dedicated bit for csum reqd
mptcp: options: track invalid suboptions
mptcp: options: reset parsing in case of invalidity
net/mptcp/mib.c | 1 +
net/mptcp/mib.h | 1 +
net/mptcp/options.c | 92 ++++++++++++++++++++++++++++------------------------
net/mptcp/protocol.c | 2 +-
net/mptcp/protocol.h | 8 ++---
net/mptcp/subflow.c | 12 +++----
6 files changed, 61 insertions(+), 55 deletions(-)
---
base-commit: ead8d38ce308bbb7375717d5b791b403ee0c67d5
change-id: 20260729-mptcp-mib-inval-opt-ac1605020170
Best regards,
--
Matthieu Baerts (NGI0) <matttbe@kernel.org>
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH mptcp-next 1/3] mptcp: options: use a dedicated bit for csum reqd
2026-09-03 11:24 [PATCH mptcp-next 0/3] mptcp: options: MIB for invalid & drop suboptions Matthieu Baerts (NGI0)
@ 2026-09-03 11:24 ` Matthieu Baerts (NGI0)
2026-09-03 11:24 ` [PATCH mptcp-next 2/3] mptcp: options: track invalid suboptions Matthieu Baerts (NGI0)
` (2 subsequent siblings)
3 siblings, 0 replies; 7+ messages in thread
From: Matthieu Baerts (NGI0) @ 2026-09-03 11:24 UTC (permalink / raw)
To: MPTCP Linux; +Cc: Matthieu Baerts (NGI0)
Instead of mixing that with suboptions: that's the only non-suboptions
that is present in the 'suboptions' field.
This even cause a workaround with OPTIONS_MPTCP_DSS to exclude this
non-suboptions when checking which other ones are set.
Move it to a dedicated free bit, and adapt the corresponding code.
This is clearer like that.
Signed-off-by: Matthieu Baerts (NGI0) <matttbe@kernel.org>
---
net/mptcp/options.c | 23 +++++++++++------------
net/mptcp/protocol.c | 2 +-
net/mptcp/protocol.h | 6 ++----
net/mptcp/subflow.c | 2 +-
4 files changed, 15 insertions(+), 18 deletions(-)
diff --git a/net/mptcp/options.c b/net/mptcp/options.c
index f87707110c75..afda3f2ca935 100644
--- a/net/mptcp/options.c
+++ b/net/mptcp/options.c
@@ -93,9 +93,8 @@ static void mptcp_parse_option(const struct sk_buff *skb,
* In other words, the only way for checksums not to be used
* is if both hosts in their SYNs set A=0."
*/
- if ((flags & MPTCP_CAP_CHECKSUM_REQD) &&
- opsize < TCPOLEN_MPTCP_MPC_ACK_DATA)
- mp_opt->suboptions |= OPTION_MPTCP_CSUMREQD;
+ mp_opt->csum_reqd = (flags & MPTCP_CAP_CHECKSUM_REQD) &&
+ opsize < TCPOLEN_MPTCP_MPC_ACK_DATA;
mp_opt->deny_join_id0 = !!(flags & MPTCP_CAP_DENY_JOIN_ID0);
@@ -122,7 +121,7 @@ static void mptcp_parse_option(const struct sk_buff *skb,
}
if (opsize == TCPOLEN_MPTCP_MPC_ACK_DATA_CSUM) {
mp_opt->csum = get_unaligned((__force __sum16 *)ptr);
- mp_opt->suboptions |= OPTION_MPTCP_CSUMREQD;
+ mp_opt->csum_reqd = 1;
ptr += 2;
}
pr_debug("MP_CAPABLE version=%x, flags=%x, optlen=%d sndr=%llu, rcvr=%llu len=%d csum=%u\n",
@@ -248,14 +247,14 @@ static void mptcp_parse_option(const struct sk_buff *skb,
ptr += 2;
if (opsize == expected_opsize + TCPOLEN_MPTCP_DSS_CHECKSUM) {
- mp_opt->suboptions |= OPTION_MPTCP_CSUMREQD;
mp_opt->csum = get_unaligned((__force __sum16 *)ptr);
+ mp_opt->csum_reqd = 1;
ptr += 2;
}
pr_debug("data_seq=%llu subflow_seq=%u data_len=%u csum=%d:%u\n",
mp_opt->data_seq, mp_opt->subflow_seq,
- mp_opt->data_len, !!(mp_opt->suboptions & OPTION_MPTCP_CSUMREQD),
+ mp_opt->data_len, mp_opt->csum_reqd,
mp_opt->csum);
}
@@ -263,7 +262,7 @@ static void mptcp_parse_option(const struct sk_buff *skb,
case MPTCPOPT_ADD_ADDR:
/* Can be used with a restricted number of other options */
- if ((mp_opt->suboptions & ~(OPTIONS_MPTCP_DSS |
+ if ((mp_opt->suboptions & ~(OPTION_MPTCP_DSS |
OPTION_MPTCP_RM_ADDR |
OPTION_MPTCP_PRIO)) != 0)
break;
@@ -330,7 +329,7 @@ static void mptcp_parse_option(const struct sk_buff *skb,
/* 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_DSS |
OPTION_MPTCP_ADD_ADDR |
OPTION_MPTCP_PRIO)) != 0)
break;
@@ -351,7 +350,7 @@ static void mptcp_parse_option(const struct sk_buff *skb,
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_DSS |
OPTION_MPTCP_ADD_ADDR |
OPTION_MPTCP_RM_ADDR)) != 0)
break;
@@ -366,7 +365,7 @@ static void mptcp_parse_option(const struct sk_buff *skb,
case MPTCPOPT_MP_FASTCLOSE:
/* Can be used with a restricted number of other options */
- if ((mp_opt->suboptions & ~(OPTIONS_MPTCP_DSS |
+ if ((mp_opt->suboptions & ~(OPTION_MPTCP_DSS |
OPTION_MPTCP_RST)) != 0)
break;
@@ -402,7 +401,7 @@ static void mptcp_parse_option(const struct sk_buff *skb,
case MPTCPOPT_MP_FAIL:
/* Can be used with a restricted number of other options */
- if ((mp_opt->suboptions & ~(OPTIONS_MPTCP_DSS |
+ if ((mp_opt->suboptions & ~(OPTION_MPTCP_DSS |
OPTION_MPTCP_RST)) != 0)
break;
@@ -1349,7 +1348,7 @@ bool mptcp_incoming_options(struct sock *sk, struct sk_buff *skb)
}
mpext->data_len = mp_opt.data_len;
mpext->use_map = 1;
- mpext->csum_reqd = !!(mp_opt.suboptions & OPTION_MPTCP_CSUMREQD);
+ mpext->csum_reqd = mp_opt.csum_reqd;
if (mpext->csum_reqd)
mpext->csum = mp_opt.csum;
diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
index 0b24e0afedfb..0e4616054955 100644
--- a/net/mptcp/protocol.c
+++ b/net/mptcp/protocol.c
@@ -3771,7 +3771,7 @@ struct sock *mptcp_sk_clone_init(const struct sock *sk,
WRITE_ONCE(msk->token, subflow_req->token);
msk->in_accept_queue = 1;
WRITE_ONCE(msk->fully_established, false);
- if (mp_opt->suboptions & OPTION_MPTCP_CSUMREQD)
+ if (mp_opt->csum_reqd)
WRITE_ONCE(msk->csum_enabled, true);
WRITE_ONCE(msk->write_seq, subflow_req->idsn + 1);
diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h
index b3121c8c766b..c8d413e14064 100644
--- a/net/mptcp/protocol.h
+++ b/net/mptcp/protocol.h
@@ -31,13 +31,10 @@
#define OPTION_MPTCP_DSS BIT(11)
#define OPTION_MPTCP_FAIL BIT(12)
-#define OPTION_MPTCP_CSUMREQD BIT(13)
-
#define OPTIONS_MPTCP_MPC (OPTION_MPTCP_MPC_SYN | OPTION_MPTCP_MPC_SYNACK | \
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
@@ -165,7 +162,8 @@ struct mptcp_options_received {
echo:1,
backup:1,
deny_join_id0:1,
- __unused:2;
+ csum_reqd:1,
+ __unused:1;
);
u8 join_id;
u32 token;
diff --git a/net/mptcp/subflow.c b/net/mptcp/subflow.c
index 2d7ccb01d234..df1d18a4e906 100644
--- a/net/mptcp/subflow.c
+++ b/net/mptcp/subflow.c
@@ -553,7 +553,7 @@ static void subflow_finish_connect(struct sock *sk, const struct sk_buff *skb)
goto fallback;
}
- if (mp_opt.suboptions & OPTION_MPTCP_CSUMREQD)
+ if (mp_opt.csum_reqd)
WRITE_ONCE(msk->csum_enabled, true);
if (mp_opt.deny_join_id0)
WRITE_ONCE(msk->pm.remote_deny_join_id0, true);
--
2.55.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH mptcp-next 2/3] mptcp: options: track invalid suboptions
2026-09-03 11:24 [PATCH mptcp-next 0/3] mptcp: options: MIB for invalid & drop suboptions Matthieu Baerts (NGI0)
2026-09-03 11:24 ` [PATCH mptcp-next 1/3] mptcp: options: use a dedicated bit for csum reqd Matthieu Baerts (NGI0)
@ 2026-09-03 11:24 ` Matthieu Baerts (NGI0)
2026-09-03 11:24 ` [PATCH mptcp-next 3/3] mptcp: options: reset parsing in case of invalidity Matthieu Baerts (NGI0)
2026-09-03 12:28 ` [PATCH mptcp-next 0/3] mptcp: options: MIB for invalid & drop suboptions MPTCP CI
3 siblings, 0 replies; 7+ messages in thread
From: Matthieu Baerts (NGI0) @ 2026-09-03 11:24 UTC (permalink / raw)
To: MPTCP Linux; +Cc: Matthieu Baerts (NGI0), Gang Yan
Any unexpected suboptions combinations or use of invalid sizes are
suspicious: either it is coming from a buggy host, or an attacker.
Better to track that with a new MIB counter.
Note that receiving unsupported versions or extensions can happen, so
this part shouldn't be considered as invalid.
Co-developed-by: Gang Yan <yangang@kylinos.cn>
Signed-off-by: Gang Yan <yangang@kylinos.cn>
Signed-off-by: Matthieu Baerts (NGI0) <matttbe@kernel.org>
---
net/mptcp/mib.c | 1 +
net/mptcp/mib.h | 1 +
net/mptcp/options.c | 55 ++++++++++++++++++++++++++++++----------------------
net/mptcp/protocol.h | 2 +-
net/mptcp/subflow.c | 10 +++++-----
5 files changed, 40 insertions(+), 29 deletions(-)
diff --git a/net/mptcp/mib.c b/net/mptcp/mib.c
index 608cb568897c..c54a20f19cf9 100644
--- a/net/mptcp/mib.c
+++ b/net/mptcp/mib.c
@@ -95,6 +95,7 @@ static const struct snmp_mib mptcp_snmp_list[] = {
SNMP_MIB_ITEM("BacklogDrop", MPTCP_MIB_BACKLOGDROP),
SNMP_MIB_ITEM("RcvPruned", MPTCP_MIB_RCVPRUNED),
SNMP_MIB_ITEM("OFOPruned", MPTCP_MIB_OFOPRUNED),
+ SNMP_MIB_ITEM("OptInvalid", MPTCP_MIB_OPTINVALID),
};
/* mptcp_mib_alloc - allocate percpu mib counters
diff --git a/net/mptcp/mib.h b/net/mptcp/mib.h
index 1ebdb55e9534..d1ce731cb5b7 100644
--- a/net/mptcp/mib.h
+++ b/net/mptcp/mib.h
@@ -98,6 +98,7 @@ enum linux_mptcp_mib_field {
MPTCP_MIB_BACKLOGDROP, /* Backlog over memory limit */
MPTCP_MIB_RCVPRUNED, /* Dropped due to memory constraints */
MPTCP_MIB_OFOPRUNED, /* MPTCP-level OoO queue pruned */
+ MPTCP_MIB_OPTINVALID, /* Invalid MPTCP option */
__MPTCP_MIB_MAX
};
diff --git a/net/mptcp/options.c b/net/mptcp/options.c
index afda3f2ca935..aaaabcab0f8f 100644
--- a/net/mptcp/options.c
+++ b/net/mptcp/options.c
@@ -20,7 +20,7 @@ static bool mptcp_cap_flag_sha256(u8 flags)
return (flags & MPTCP_CAP_FLAG_MASK) == MPTCP_CAP_HMAC_SHA256;
}
-static void mptcp_parse_option(const struct sk_buff *skb,
+static void mptcp_parse_option(const struct sock *sk, const struct sk_buff *skb,
const unsigned char *ptr, int opsize,
struct mptcp_options_received *mp_opt)
{
@@ -53,9 +53,9 @@ 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;
+ goto invalid;
} else if (mp_opt->suboptions != 0) {
- break;
+ goto invalid;
}
/* Cfr RFC 8684 Section 3.3.0:
@@ -71,7 +71,7 @@ static void mptcp_parse_option(const struct sk_buff *skb,
if (opsize != expected_opsize &&
(expected_opsize != TCPOLEN_MPTCP_MPC_ACK_DATA ||
opsize != TCPOLEN_MPTCP_MPC_ACK_DATA_CSUM))
- break;
+ goto invalid;
/* try to be gentle vs future versions on the initial syn */
version = *ptr++ & MPTCP_VERSION_MASK;
@@ -133,7 +133,7 @@ static void mptcp_parse_option(const struct sk_buff *skb,
/* Can be used with a restricted number of other options */
if ((mp_opt->suboptions & ~(OPTION_MPTCP_RM_ADDR |
OPTION_MPTCP_PRIO)) != 0)
- break;
+ goto invalid;
if (opsize == TCPOLEN_MPTCP_MPJ_SYN) {
mp_opt->suboptions |= OPTION_MPTCP_MPJ_SYN;
@@ -162,6 +162,8 @@ static void mptcp_parse_option(const struct sk_buff *skb,
ptr += 2;
memcpy(mp_opt->hmac, ptr, MPTCPOPT_HMAC_LEN);
pr_debug("MP_JOIN hmac\n");
+ } else {
+ goto invalid;
}
break;
@@ -172,7 +174,7 @@ static void mptcp_parse_option(const struct sk_buff *skb,
OPTION_MPTCP_PRIO |
OPTION_MPTCP_FASTCLOSE |
OPTION_MPTCP_FAIL)) != 0)
- break;
+ goto invalid;
pr_debug("DSS\n");
ptr++;
@@ -215,7 +217,7 @@ static void mptcp_parse_option(const struct sk_buff *skb,
mp_opt->ack64 = 0;
mp_opt->use_ack = 0;
mp_opt->data_fin = 0;
- break;
+ goto invalid;
}
mp_opt->suboptions |= OPTION_MPTCP_DSS;
@@ -265,7 +267,7 @@ static void mptcp_parse_option(const struct sk_buff *skb,
if ((mp_opt->suboptions & ~(OPTION_MPTCP_DSS |
OPTION_MPTCP_RM_ADDR |
OPTION_MPTCP_PRIO)) != 0)
- break;
+ goto invalid;
mp_opt->echo = (*ptr++) & MPTCP_ADDR_ECHO;
if (!mp_opt->echo) {
@@ -278,7 +280,7 @@ static void mptcp_parse_option(const struct sk_buff *skb,
mp_opt->addr.family = AF_INET6;
#endif
else
- break;
+ goto invalid;
} else {
if (opsize == TCPOLEN_MPTCP_ADD_ADDR_BASE ||
opsize == TCPOLEN_MPTCP_ADD_ADDR_BASE_PORT)
@@ -289,7 +291,7 @@ static void mptcp_parse_option(const struct sk_buff *skb,
mp_opt->addr.family = AF_INET6;
#endif
else
- break;
+ goto invalid;
}
mp_opt->suboptions |= OPTION_MPTCP_ADD_ADDR;
@@ -332,11 +334,11 @@ static void mptcp_parse_option(const struct sk_buff *skb,
OPTION_MPTCP_DSS |
OPTION_MPTCP_ADD_ADDR |
OPTION_MPTCP_PRIO)) != 0)
- break;
+ goto invalid;
if (opsize < TCPOLEN_MPTCP_RM_ADDR_BASE + 1 ||
opsize > TCPOLEN_MPTCP_RM_ADDR_BASE + MPTCP_RM_IDS_MAX)
- break;
+ goto invalid;
ptr++;
@@ -353,10 +355,10 @@ static void mptcp_parse_option(const struct sk_buff *skb,
OPTION_MPTCP_DSS |
OPTION_MPTCP_ADD_ADDR |
OPTION_MPTCP_RM_ADDR)) != 0)
- break;
+ goto invalid;
if (opsize != TCPOLEN_MPTCP_PRIO)
- break;
+ goto invalid;
mp_opt->suboptions |= OPTION_MPTCP_PRIO;
mp_opt->backup = *ptr++ & MPTCP_PRIO_BKUP;
@@ -367,10 +369,10 @@ static void mptcp_parse_option(const struct sk_buff *skb,
/* Can be used with a restricted number of other options */
if ((mp_opt->suboptions & ~(OPTION_MPTCP_DSS |
OPTION_MPTCP_RST)) != 0)
- break;
+ goto invalid;
if (opsize != TCPOLEN_MPTCP_FASTCLOSE)
- break;
+ goto invalid;
ptr += 2;
mp_opt->rcvr_key = get_unaligned_be64(ptr);
@@ -383,10 +385,10 @@ static void mptcp_parse_option(const struct sk_buff *skb,
/* Can be used with a restricted number of other options */
if ((mp_opt->suboptions & ~(OPTION_MPTCP_FAIL |
OPTION_MPTCP_FASTCLOSE)) != 0)
- break;
+ goto invalid;
if (opsize != TCPOLEN_MPTCP_RST)
- break;
+ goto invalid;
if (!(TCP_SKB_CB(skb)->tcp_flags & TCPHDR_RST))
break;
@@ -403,10 +405,10 @@ static void mptcp_parse_option(const struct sk_buff *skb,
/* Can be used with a restricted number of other options */
if ((mp_opt->suboptions & ~(OPTION_MPTCP_DSS |
OPTION_MPTCP_RST)) != 0)
- break;
+ goto invalid;
if (opsize != TCPOLEN_MPTCP_FAIL)
- break;
+ goto invalid;
ptr += 2;
mp_opt->suboptions |= OPTION_MPTCP_FAIL;
@@ -415,11 +417,17 @@ static void mptcp_parse_option(const struct sk_buff *skb,
break;
default:
+ /* Not invalid: maybe used for experimentations */
break;
}
+
+ return;
+
+invalid:
+ MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_OPTINVALID);
}
-void mptcp_get_options(const struct sk_buff *skb,
+void mptcp_get_options(const struct sock *sk, const struct sk_buff *skb,
struct mptcp_options_received *mp_opt)
{
const struct tcphdr *th = tcp_hdr(skb);
@@ -454,7 +462,8 @@ void mptcp_get_options(const struct sk_buff *skb,
if (opsize > length)
return; /* don't parse partial options */
if (opcode == TCPOPT_MPTCP)
- mptcp_parse_option(skb, ptr, opsize, mp_opt);
+ mptcp_parse_option(sk, skb, ptr, opsize,
+ mp_opt);
ptr += opsize - 2;
length -= opsize;
}
@@ -1248,7 +1257,7 @@ bool mptcp_incoming_options(struct sock *sk, struct sk_buff *skb)
return !mptcp_over_limit(subflow->conn, sk, skb);
}
- mptcp_get_options(skb, &mp_opt);
+ mptcp_get_options(sk, skb, &mp_opt);
/* The subflow can be in close state only if check_fully_established()
* just sent a reset. If so, tell the caller to ignore the current packet.
diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h
index c8d413e14064..739f9006c1e1 100644
--- a/net/mptcp/protocol.h
+++ b/net/mptcp/protocol.h
@@ -925,7 +925,7 @@ struct sock *mptcp_sk_clone_init(const struct sock *sk,
const struct mptcp_options_received *mp_opt,
struct sock *ssk,
struct request_sock *req);
-void mptcp_get_options(const struct sk_buff *skb,
+void mptcp_get_options(const struct sock *sk, const struct sk_buff *skb,
struct mptcp_options_received *mp_opt);
void mptcp_finish_connect(struct sock *sk);
diff --git a/net/mptcp/subflow.c b/net/mptcp/subflow.c
index df1d18a4e906..2b4b2179fe03 100644
--- a/net/mptcp/subflow.c
+++ b/net/mptcp/subflow.c
@@ -167,7 +167,7 @@ static int subflow_check_req(struct request_sock *req,
}
#endif
- mptcp_get_options(skb, &mp_opt);
+ mptcp_get_options(sk_listener, skb, &mp_opt);
opt_mp_capable = !!(mp_opt.suboptions & OPTION_MPTCP_MPC_SYN);
opt_mp_join = !!(mp_opt.suboptions & OPTION_MPTCP_MPJ_SYN);
@@ -273,7 +273,7 @@ int mptcp_subflow_init_cookie_req(struct request_sock *req,
int err;
subflow_init_req(req, sk_listener);
- mptcp_get_options(skb, &mp_opt);
+ mptcp_get_options(sk_listener, skb, &mp_opt);
opt_mp_capable = !!(mp_opt.suboptions & OPTION_MPTCP_MPC_ACK);
opt_mp_join = !!(mp_opt.suboptions & OPTION_MPTCP_MPJ_ACK);
@@ -540,7 +540,7 @@ static void subflow_finish_connect(struct sock *sk, const struct sk_buff *skb)
subflow->ssn_offset = TCP_SKB_CB(skb)->seq;
pr_debug("subflow=%p synack seq=%x\n", subflow, subflow->ssn_offset);
- mptcp_get_options(skb, &mp_opt);
+ mptcp_get_options(sk, skb, &mp_opt);
if (subflow->request_mptcp) {
if (!(mp_opt.suboptions & OPTION_MPTCP_MPC_SYNACK)) {
if (!mptcp_try_fallback(sk,
@@ -842,13 +842,13 @@ static struct sock *subflow_syn_recv_sock(const struct sock *sk,
* reordered MPC will cause fallback, but we don't have other
* options.
*/
- mptcp_get_options(skb, &mp_opt);
+ mptcp_get_options(sk, skb, &mp_opt);
if (!(mp_opt.suboptions &
(OPTION_MPTCP_MPC_SYN | OPTION_MPTCP_MPC_ACK)))
fallback = true;
} else if (subflow_req->mp_join) {
- mptcp_get_options(skb, &mp_opt);
+ mptcp_get_options(sk, skb, &mp_opt);
if (!(mp_opt.suboptions & OPTION_MPTCP_MPJ_ACK))
fallback = true;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH mptcp-next 3/3] mptcp: options: reset parsing in case of invalidity
2026-09-03 11:24 [PATCH mptcp-next 0/3] mptcp: options: MIB for invalid & drop suboptions Matthieu Baerts (NGI0)
2026-09-03 11:24 ` [PATCH mptcp-next 1/3] mptcp: options: use a dedicated bit for csum reqd Matthieu Baerts (NGI0)
2026-09-03 11:24 ` [PATCH mptcp-next 2/3] mptcp: options: track invalid suboptions Matthieu Baerts (NGI0)
@ 2026-09-03 11:24 ` Matthieu Baerts (NGI0)
2026-09-03 11:38 ` sashiko-bot
2026-09-03 12:28 ` [PATCH mptcp-next 0/3] mptcp: options: MIB for invalid & drop suboptions MPTCP CI
3 siblings, 1 reply; 7+ messages in thread
From: Matthieu Baerts (NGI0) @ 2026-09-03 11:24 UTC (permalink / raw)
To: MPTCP Linux; +Cc: Matthieu Baerts (NGI0)
When an invalid option is detected -- any unexpected suboptions
combinations or use of invalid sizes -- the current behaviour is to
ignore the currently parsed option.
Receiving such invalid options is suspicious: either this is coming from
a buggy host, or an attacker. In these cases, it seems safer to drop any
previously parsed MPTCP option from such packets. This will likely lead
to a fallback to TCP or a subflow reject, which seems to be a good
reaction in these situations.
Signed-off-by: Matthieu Baerts (NGI0) <matttbe@kernel.org>
---
net/mptcp/options.c | 22 ++++++++++------------
1 file changed, 10 insertions(+), 12 deletions(-)
diff --git a/net/mptcp/options.c b/net/mptcp/options.c
index aaaabcab0f8f..b6d2e39f8464 100644
--- a/net/mptcp/options.c
+++ b/net/mptcp/options.c
@@ -20,7 +20,7 @@ static bool mptcp_cap_flag_sha256(u8 flags)
return (flags & MPTCP_CAP_FLAG_MASK) == MPTCP_CAP_HMAC_SHA256;
}
-static void mptcp_parse_option(const struct sock *sk, const struct sk_buff *skb,
+static bool mptcp_parse_option(const struct sock *sk, const struct sk_buff *skb,
const unsigned char *ptr, int opsize,
struct mptcp_options_received *mp_opt)
{
@@ -211,14 +211,8 @@ static void mptcp_parse_option(const struct sock *sk, 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) {
- mp_opt->dsn64 = 0;
- mp_opt->use_map = 0;
- mp_opt->ack64 = 0;
- mp_opt->use_ack = 0;
- mp_opt->data_fin = 0;
+ opsize != expected_opsize + TCPOLEN_MPTCP_DSS_CHECKSUM)
goto invalid;
- }
mp_opt->suboptions |= OPTION_MPTCP_DSS;
if (mp_opt->use_ack) {
@@ -421,10 +415,11 @@ static void mptcp_parse_option(const struct sock *sk, const struct sk_buff *skb,
break;
}
- return;
+ return true;
invalid:
MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_OPTINVALID);
+ return false;
}
void mptcp_get_options(const struct sock *sk, const struct sk_buff *skb,
@@ -461,9 +456,12 @@ void mptcp_get_options(const struct sock *sk, const struct sk_buff *skb,
return;
if (opsize > length)
return; /* don't parse partial options */
- if (opcode == TCPOPT_MPTCP)
- mptcp_parse_option(sk, skb, ptr, opsize,
- mp_opt);
+ if (opcode == TCPOPT_MPTCP &&
+ !mptcp_parse_option(sk, skb, ptr, opsize, mp_opt)) {
+ /* invalid option, drop previously parsed opt */
+ *(u32 *)&mp_opt->status = 0;
+ break;
+ }
ptr += opsize - 2;
length -= opsize;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH mptcp-next 3/3] mptcp: options: reset parsing in case of invalidity
2026-09-03 11:24 ` [PATCH mptcp-next 3/3] mptcp: options: reset parsing in case of invalidity Matthieu Baerts (NGI0)
@ 2026-09-03 11:38 ` sashiko-bot
2026-09-03 14:18 ` Matthieu Baerts
0 siblings, 1 reply; 7+ messages in thread
From: sashiko-bot @ 2026-09-03 11:38 UTC (permalink / raw)
To: Matthieu Baerts (NGI0); +Cc: mptcp
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The `break` statement inside `mptcp_get_options` incorrectly exits the `switch` statement instead of the `while` loop, causing `length` and `ptr` to become desynchronized, leading to an infinite loop, out-of-bounds reads, and MPTCP option smuggling.
--
commit 60e4bc5aa7cfc08049c3062323eeae263a824439
Author: Matthieu Baerts (NGI0) <matttbe@kernel.org>
mptcp: options: reset parsing in case of invalidity
When an invalid option is detected, it is safer to drop any previously
parsed MPTCP options. This ensures a fallback to TCP or a subflow reject
instead of just ignoring the currently parsed option.
> diff --git a/net/mptcp/options.c b/net/mptcp/options.c
> index aaaabcab0f8f9..b6d2e39f8464a 100644
> --- a/net/mptcp/options.c
> +++ b/net/mptcp/options.c
[ ... ]
> @@ -461,9 +456,12 @@ void mptcp_get_options(const struct sock *sk, const struct sk_buff *skb,
> return;
> if (opsize > length)
> return; /* don't parse partial options */
> - if (opcode == TCPOPT_MPTCP)
> - mptcp_parse_option(sk, skb, ptr, opsize,
> - mp_opt);
> + if (opcode == TCPOPT_MPTCP &&
> + !mptcp_parse_option(sk, skb, ptr, opsize, mp_opt)) {
> + /* invalid option, drop previously parsed opt */
> + *(u32 *)&mp_opt->status = 0;
> + break;
[Severity: High]
Does this break statement exit the switch rather than the while loop?
If this only exits the switch block, it will skip the pointer and length
updates that occur at the end of the default case:
ptr += opsize - 2;
length -= opsize;
Since length is not decremented when breaking here, the while (length > 0)
loop will continue indefinitely. The ptr keeps advancing by 2 bytes on each
subsequent iteration from reading opcode and opsize. Could this lead to an
infinite loop and out-of-bounds reads into the TCP payload?
Additionally, could this allow an attacker to smuggle MPTCP options by
placing them in the TCP payload after an invalid option that triggers this
break? Because length is never updated, the parser might treat the TCP payload
as an extension of the TCP options, bypassing inspection that only checks the
TCP header.
> + }
> ptr += opsize - 2;
> length -= opsize;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260903-mptcp-mib-inval-opt-v1-0-84a553c552b6@kernel.org?part=3
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH mptcp-next 0/3] mptcp: options: MIB for invalid & drop suboptions
2026-09-03 11:24 [PATCH mptcp-next 0/3] mptcp: options: MIB for invalid & drop suboptions Matthieu Baerts (NGI0)
` (2 preceding siblings ...)
2026-09-03 11:24 ` [PATCH mptcp-next 3/3] mptcp: options: reset parsing in case of invalidity Matthieu Baerts (NGI0)
@ 2026-09-03 12:28 ` MPTCP CI
3 siblings, 0 replies; 7+ messages in thread
From: MPTCP CI @ 2026-09-03 12:28 UTC (permalink / raw)
To: Matthieu Baerts; +Cc: mptcp
Hi Matthieu,
Thank you for your modifications, that's great!
Our CI did some validations and here is its report:
- KVM Validation: normal (except selftest_mptcp_join): Unstable: 1 failed test(s): selftest_simult_flows ⚠️
- KVM Validation: normal (only selftest_mptcp_join): Success! ✅
- KVM Validation: debug (except selftest_mptcp_join): Success! ✅
- KVM Validation: debug (only selftest_mptcp_join): Success! ✅
- KVM Validation: btf-normal (only bpftest_all): Success! ✅
- KVM Validation: btf-debug (only bpftest_all): Success! ✅
- Perf:
- Task: https://github.com/multipath-tcp/mptcp_net-next/actions/runs/33751121801
Initiator: Patchew Applier
Commits: https://github.com/multipath-tcp/mptcp_net-next/commits/4bac67106a0b
Patchwork: https://patchwork.kernel.org/project/mptcp/list/?series=1157018
If there are some issues, you can reproduce them using the same environment as
the one used by the CI thanks to a docker image, e.g.:
$ cd [kernel source code]
$ docker run -v "${PWD}:${PWD}:rw" -w "${PWD}" --privileged --rm -it \
--pull always mptcp/mptcp-upstream-virtme-docker:latest \
auto-normal
For more details:
https://github.com/multipath-tcp/mptcp-upstream-virtme-docker
Please note that despite all the efforts that have been already done to have a
stable tests suite when executed on a public CI like here, it is possible some
reported issues are not due to your modifications. Still, do not hesitate to
help us improve that ;-)
Cheers,
MPTCP GH Action bot
Bot operated by Matthieu Baerts (NGI0 Core)
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH mptcp-next 3/3] mptcp: options: reset parsing in case of invalidity
2026-09-03 11:38 ` sashiko-bot
@ 2026-09-03 14:18 ` Matthieu Baerts
0 siblings, 0 replies; 7+ messages in thread
From: Matthieu Baerts @ 2026-09-03 14:18 UTC (permalink / raw)
To: sashiko-reviews; +Cc: mptcp
On 03/09/2026 13:38, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [High] The `break` statement inside `mptcp_get_options` incorrectly exits the `switch` statement instead of the `while` loop, causing `length` and `ptr` to become desynchronized, leading to an infinite loop, out-of-bounds reads, and MPTCP option smuggling.
> --
>
> commit 60e4bc5aa7cfc08049c3062323eeae263a824439
> Author: Matthieu Baerts (NGI0) <matttbe@kernel.org>
>
> mptcp: options: reset parsing in case of invalidity
>
> When an invalid option is detected, it is safer to drop any previously
> parsed MPTCP options. This ensures a fallback to TCP or a subflow reject
> instead of just ignoring the currently parsed option.
>
>> diff --git a/net/mptcp/options.c b/net/mptcp/options.c
>> index aaaabcab0f8f9..b6d2e39f8464a 100644
>> --- a/net/mptcp/options.c
>> +++ b/net/mptcp/options.c
> [ ... ]
>> @@ -461,9 +456,12 @@ void mptcp_get_options(const struct sock *sk, const struct sk_buff *skb,
>> return;
>> if (opsize > length)
>> return; /* don't parse partial options */
>> - if (opcode == TCPOPT_MPTCP)
>> - mptcp_parse_option(sk, skb, ptr, opsize,
>> - mp_opt);
>> + if (opcode == TCPOPT_MPTCP &&
>> + !mptcp_parse_option(sk, skb, ptr, opsize, mp_opt)) {
>> + /* invalid option, drop previously parsed opt */
>> + *(u32 *)&mp_opt->status = 0;
>> + break;
>
> [Severity: High]
> Does this break statement exit the switch rather than the while loop?
Arf, it was supposed to be a break for the while loop, but I missed the
switch... Let's use 'return' here, then.
Cheers,
Matt
--
Sponsored by the NGI0 Core fund.
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-09-03 14:18 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-03 11:24 [PATCH mptcp-next 0/3] mptcp: options: MIB for invalid & drop suboptions Matthieu Baerts (NGI0)
2026-09-03 11:24 ` [PATCH mptcp-next 1/3] mptcp: options: use a dedicated bit for csum reqd Matthieu Baerts (NGI0)
2026-09-03 11:24 ` [PATCH mptcp-next 2/3] mptcp: options: track invalid suboptions Matthieu Baerts (NGI0)
2026-09-03 11:24 ` [PATCH mptcp-next 3/3] mptcp: options: reset parsing in case of invalidity Matthieu Baerts (NGI0)
2026-09-03 11:38 ` sashiko-bot
2026-09-03 14:18 ` Matthieu Baerts
2026-09-03 12:28 ` [PATCH mptcp-next 0/3] mptcp: options: MIB for invalid & drop suboptions MPTCP CI
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox