Thread (4 messages) 4 messages, 2 authors, 11h ago

RE: [PATCH net] tipc: raise the minimum bearer MTU to fit tunnel and crypto overhead

flat view

From: Tung Quang Nguyen <tung.quang.nguyen@est.tech>
Date: 2026-10-12 15:56:05
Subsystem: networking [general], the rest, tipc network layer · Maintainers: "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds, Jon Maloy, Tung Quang Nguyen

quoted hunk ↗ jump to hunk
Subject: [PATCH net] tipc: raise the minimum bearer MTU to fit tunnel and
crypto overhead

A peer can lower the link MTU to TIPC_MIN_BEARER_MTU (100 bytes, plus the
UDP encapsulation header) with the max_pkt field of a RESET or ACTIVATE
message; tipc_link_proto_rcv() only checks the lower bound returned by
tipc_bearer_min_mtu().  The same bound is applied to the device MTU when a
bearer is enabled.

That bound predates two later reductions of the link MSS:

 tipc_link_mss() = mtu - INT_H_SIZE                    (tunnel header)
                        - EMSG_OVERHEAD               (CONFIG_TIPC_CRYPTO)

With CONFIG_TIPC_CRYPTO=y, which is the default, an MTU of 100 gives an
MSS of 28.  Once the link comes up, tipc_named_node_up() calls
named_distribute(), which computes

 msg_dsz = ((tipc_node_get_mtu() - INT_H_SIZE) / ITEM_SIZE) * ITEM_SIZE;

For an MSS of 28 the subtraction is negative and the result truncates to
0xfffffff0; for an MSS between 40 and 59 it is 0.  named_prepare_buf() then
allocates a buffer with no room for any item (the u32 size wraps), and
msg_rem never reaches zero, so every cluster-scope publication on the node is
written with publ_to_item() past the end of that one skb, over
skb_shared_info and into adjacent memory.

Without crypto the MSS is at least 60, which still does not hold the first
fragment built by tipc_msg_build(): INT_H_SIZE fragment header plus a
message header of up to MAX_H_SIZE.  With pktmax below that, the header
copies run past the pktmax bytes allocated for the fragment.

Raise the minimum so the MSS can always hold INT_H_SIZE + MAX_H_SIZE,
which also leaves room for name table items: MAX_H_SIZE + 2 * INT_H_SIZE,
plus EMSG_OVERHEAD when crypto is enabled.  EMSG_OVERHEAD is defined
in crypto.h, which bearer.h cannot include, so the value is spelled out and
checked with BUILD_BUG_ON() in tipc_bearer_min_mtu().

Found by manual code review, assisted by an LLM, and reproduced with KASAN
using two network namespaces joined by a veth pair (see below).

Fixes: fc1b6d6de220 ("tipc: introduce TIPC encryption & authentication")
Fixes: 2320bcdae628 ("tipc: fix changeover issues due to large packet")
Signed-off-by: Joas Antonio dos Santos <redacted>
Assisted-by: Claude:claude-opus-5-5
---
Reproduced on 602042bf29f6 (arm64, QEMU, KASAN,
CONFIG_TIPC_CRYPTO=y) with two network namespaces joined by a veth pair.
Node A binds 40 cluster-scope services, node B enables its bearer on a veth
with MTU 100, so A negotiates a link MTU of 100 (a remote peer can do the
same with
max_pkt=100 in RESET/ACTIVATE).  As soon as the link comes up:

 BUG: KASAN: slab-out-of-bounds in publ_to_item+0x23c/0x288
 Write of size 4 at addr fff000001036ad00 by task ksoftirqd/1/22
  publ_to_item+0x23c/0x288
  tipc_named_node_up+0x2b0/0x714
  tipc_node_write_unlock+0x330/0x44c
  tipc_rcv+0x94c/0x2188
  tipc_l2_rcv_msg+0x118/0x1f4
 Allocated by task 22:
  ...
  tipc_buf_acquire+0x24/0xd4
  named_prepare_buf+0x3c/0x1dc
  tipc_named_node_up+0x528/0x714
 The buggy address is located 0 bytes to the right of
  allocated 704-byte region [fff000001036aa40, fff000001036ad00)
 tipc: Too large msg, purging xmit list 1 11 0 860 100!
 Unable to handle kernel paging request at virtual address 001fe000022e18a9
 pc : kfree_skb_list_reason+0xa4/0x32c

The final oops is the purge freeing the frag_list pointer that the overflow wrote
into skb_shared_info.

With the patch: MTU 100 is refused at bearer enable ("MTU too low for tipc
bearer"); MTU 172 and 1500 bring the link up with no KASAN report.
Builds cleanly with W=1 with and without CONFIG_TIPC_CRYPTO.
This was first reported to security@kernel.org; Tung Nguyen reproduced it with
the reproducer below and asked for the patch to go to netdev.

Reproducer (as root, CONFIG_TIPC=y, CONFIG_TIPC_CRYPTO=y; build with "gcc
-Wall -O2 -o pub pub.c", run "sh tipc_repro.sh"):

pub.c:

 /* Bind N TIPC services with cluster scope, then sleep. */
 #include <linux/tipc.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/socket.h>
 #include <unistd.h>

 int main(int argc, char **argv)
 {
 	int n = argc > 1 ? atoi(argv[1]) : 40;
 	for (int i = 0; i < n; i++) {
 		struct sockaddr_tipc a = {
 			.family = AF_TIPC,
 			.addrtype = TIPC_SERVICE_RANGE,
 			.scope = TIPC_CLUSTER_SCOPE,
 			.addr.nameseq = { .type = 1000 + i, .lower = 0, .upper =
0 },
 		};
 		int s = socket(AF_TIPC, SOCK_RDM, 0);
 		if (s < 0 || bind(s, (struct sockaddr *)&a, sizeof(a))) {
 			perror("tipc bind");
 			return 1;
 		}
 	}
 	printf("pub: bound %d cluster-scope services\n", n);
 	fflush(stdout);
 	pause();
 	return 0;
 }

tipc_repro.sh:

 #!/bin/sh
 # TIPC named_distribute() overflow reproducer.
 # Needs root, CONFIG_TIPC=y, CONFIG_TIPC_CRYPTO=y (default), KASAN
recommended.
 # Usage: ./tipc_repro.sh [peer_mtu]   (default 100; 100..131 trigger it)
 PEER_MTU=${1:-100}

 ip netns add A
 ip netns add B
 ip link add va type veth peer name vb
 ip link set va netns A
 ip link set vb netns B
 ip -n A link set lo up
 ip -n B link set lo up
 ip -n A link set va mtu 1500 up
 ip -n B link set vb mtu "$PEER_MTU" up

 # node A: 40 cluster-scope publications (any number >= 1 overflows;
 # more publications write further past the buffer)
 ip netns exec A ./pub 40 &
 sleep 2

 # node A uses MTU 1500, node B uses PEER_MTU; the link MTU is
 # negotiated down to PEER_MTU, the link MSS becomes PEER_MTU - 72
 ip netns exec A tipc bearer enable media eth dev va
 ip netns exec B tipc bearer enable media eth dev vb

 # the overflow happens in tipc_named_node_up() as soon as the link
 # comes up (a few seconds)
 sleep 20
 dmesg | grep -A30 -E "KASAN|BUG:|Oops"

This edits the lines next to the ones touched by Yunpeng Tian's
"tipc: bound the device MTU accepted ..." (upper bound, posted 2026-10-03).
The two are independent; I can rebase on top of it if that one is applied first.

tipc_msg_build() / named_distribute() could also get local sanity checks; I kept
this to the MTU floor so there is one fix point.

net/tipc/bearer.c |  4 ++++
net/tipc/bearer.h | 16 ++++++++++++++--
2 files changed, 18 insertions(+), 2 deletions(-)
diff --git a/net/tipc/bearer.c b/net/tipc/bearer.c index 05dcd2f9e..bcf031fdd
100644
--- a/net/tipc/bearer.c
+++ b/net/tipc/bearer.c
@@ -549,6 +549,10 @@ int tipc_bearer_min_mtu(struct net *net, u32
bearer_id)
	int mtu = TIPC_MIN_BEARER_MTU;
	struct tipc_bearer *b;

+#ifdef CONFIG_TIPC_CRYPTO
+	BUILD_BUG_ON(TIPC_MIN_BEARER_MTU !=
+		     MAX_H_SIZE + 2 * INT_H_SIZE + EMSG_OVERHEAD); #endif
	rcu_read_lock();
	b = bearer_get(net, bearer_id);
	if (b)
diff --git a/net/tipc/bearer.h b/net/tipc/bearer.h index 41eac1ee0..b99dcd23c
100644
--- a/net/tipc/bearer.h
+++ b/net/tipc/bearer.h
@@ -60,8 +60,20 @@
#define TIPC_MEDIA_TYPE_IB	2
#define TIPC_MEDIA_TYPE_UDP	3

-/* Minimum bearer MTU */
-#define TIPC_MIN_BEARER_MTU	(MAX_H_SIZE + INT_H_SIZE)
+/* Minimum bearer MTU
+ *
+ * The link MSS (see tipc_link_mss()) is the MTU minus a tunnel header
+ * (INT_H_SIZE) and, with encryption, minus EMSG_OVERHEAD (32 bytes,
+checked
+ * in tipc_bearer_min_mtu()).  The MSS must still hold a fragment
+header
+ * (INT_H_SIZE) followed by the largest message header (MAX_H_SIZE),
+which is
+ * what tipc_msg_build() puts in the first fragment, and leaves room
+for at
+ * least one name table item in named_distribute().
+ */
+#ifdef CONFIG_TIPC_CRYPTO
+#define TIPC_MIN_BEARER_MTU	(MAX_H_SIZE + 2 * INT_H_SIZE + 32)
This approach is not good because  it uses hard-coded size of TIPC crypto header (32).
The size should be calculated based on tipc_ehdr.
Could you please test below general approach ?

---
 net/tipc/bearer.c    | 16 ++++++++++++++++
 net/tipc/bearer.h    |  2 +-
 net/tipc/crypto.h    | 19 +++++++++++++++++++
 net/tipc/udp_media.h | 10 ++++++++--
 4 files changed, 44 insertions(+), 3 deletions(-)
diff --git a/net/tipc/bearer.c b/net/tipc/bearer.c
index 05dcd2f9e887..02b2e9f56cc5 100644
--- a/net/tipc/bearer.c
+++ b/net/tipc/bearer.c
@@ -433,7 +433,11 @@ int tipc_enable_l2_media(struct net *net, struct tipc_bearer *b,
        dev = dev_get_by_name(net, dev_name);
        if (!dev)
                return -ENODEV;
+#ifdef CONFIG_TIPC_CRYPTO
+       if (tipc_mtu_bad_crypto(dev)) {
+#else
        if (tipc_mtu_bad(dev)) {
+#endif
                dev_put(dev);
                return -EINVAL;
        }
@@ -546,7 +550,11 @@ int tipc_bearer_mtu(struct net *net, u32 bearer_id)

 int tipc_bearer_min_mtu(struct net *net, u32 bearer_id)
 {
+#ifdef CONFIG_TIPC_CRYPTO
+       int mtu = TIPC_MIN_BEARER_MTU_CRYPTO;
+#else
        int mtu = TIPC_MIN_BEARER_MTU;
+#endif
        struct tipc_bearer *b;

        rcu_read_lock();
@@ -711,7 +719,11 @@ static int tipc_l2_device_event(struct notifier_block *nb, unsigned long evt,
                test_and_set_bit_lock(0, &b->up);
                break;
        case NETDEV_CHANGEMTU:
+#ifdef CONFIG_TIPC_CRYPTO
+               if (tipc_mtu_bad_crypto(dev)) {
+#else
                if (tipc_mtu_bad(dev)) {
+#endif
                        bearer_disable(net, b);
                        break;
                }
@@ -1158,7 +1170,11 @@ int __tipc_nl_bearer_set(struct sk_buff *skb, struct genl_info *info)
                        }
 #ifdef CONFIG_TIPC_MEDIA_UDP
                        if (nla_get_u32(props[TIPC_NLA_PROP_MTU]) <
+#ifdef CONFIG_TIPC_CRYPTO
+                           b->encap_hlen + TIPC_MIN_BEARER_MTU_CRYPTO) {
+#else
                            b->encap_hlen + TIPC_MIN_BEARER_MTU) {
+#endif
                                NL_SET_ERR_MSG(info->extack,
                                               "MTU value is out-of-range");
                                return -EINVAL;
diff --git a/net/tipc/bearer.h b/net/tipc/bearer.h
index 41eac1ee0c09..1bf629f2e626 100644
--- a/net/tipc/bearer.h
+++ b/net/tipc/bearer.h
@@ -61,7 +61,7 @@
 #define TIPC_MEDIA_TYPE_UDP    3

 /* Minimum bearer MTU */
-#define TIPC_MIN_BEARER_MTU    (MAX_H_SIZE + INT_H_SIZE)
+#define TIPC_MIN_BEARER_MTU    (MAX_H_SIZE + 2 * INT_H_SIZE)

 /* Identifiers for distinguishing between broadcast/multicast and replicast
  */
diff --git a/net/tipc/crypto.h b/net/tipc/crypto.h
index ce7d4cc8a9e0..1d5a4839d639 100644
--- a/net/tipc/crypto.h
+++ b/net/tipc/crypto.h
@@ -156,6 +156,8 @@ struct tipc_ehdr {
 #define EMSG_OVERHEAD  (EHDR_SIZE + TIPC_AES_GCM_TAG_SIZE)
 } __packed;

+#define TIPC_MIN_BEARER_MTU_CRYPTO     (TIPC_MIN_BEARER_MTU + EMSG_OVERHEAD)
+
 int tipc_crypto_start(struct tipc_crypto **crypto, struct net *net,
                      struct tipc_node *node);
 void tipc_crypto_stop(struct tipc_crypto **crypto);
@@ -196,5 +198,22 @@ static inline void msg_set_key_mode(struct tipc_msg *m, u32 mode)
        msg_set_bits(m, 4, 0, 0xf, mode);
 }

+/**
+ * tipc_mtu_bad_crypto - check if device MTU is too low for encrypted headers
+ * @dev: the net device that its MTU needs to be checked
+ *
+ * Return:
+ * * false - valid MTU
+ * * true  - invalid MTU
+ *
+ */
+static inline bool tipc_mtu_bad_crypto(struct net_device *dev)
+{
+       if (dev->mtu >= TIPC_MIN_BEARER_MTU_CRYPTO)
+               return false;
+       netdev_warn(dev, "MTU too low for tipc bearer\n");
+       return true;
+}
+
 #endif /* _TIPC_CRYPTO_H */
 #endif
diff --git a/net/tipc/udp_media.h b/net/tipc/udp_media.h
index e7455cc73e16..43f895bec039 100644
--- a/net/tipc/udp_media.h
+++ b/net/tipc/udp_media.h
@@ -40,6 +40,7 @@

 #include <linux/ip.h>
 #include <linux/udp.h>
+#include "crypto.h"

 int tipc_udp_nl_bearer_add(struct tipc_bearer *b, struct nlattr *attr);
 int tipc_udp_nl_add_bearer_data(struct tipc_nl_msg *msg, struct tipc_bearer *b);
@@ -48,8 +49,13 @@ int tipc_udp_nl_dump_remoteip(struct sk_buff *skb, struct netlink_callback *cb);
 /* check if configured MTU is too low for tipc headers */
 static inline bool tipc_udp_mtu_bad(u32 mtu)
 {
-       if (mtu >= (TIPC_MIN_BEARER_MTU + sizeof(struct iphdr) +
-           sizeof(struct udphdr)))
+#ifdef CONFIG_TIPC_CRYPTO
+       int mtu_min = TIPC_MIN_BEARER_MTU_CRYPTO;
+#else
+       int mtu_min = TIPC_MIN_BEARER_MTU;
+#endif
+
+       if (mtu >= (mtu_min + sizeof(struct iphdr) + sizeof(struct udphdr)))
                return false;

        pr_warn("MTU too low for tipc bearer\n");
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help