* [PATCH] ipv6: raw: fix use-after-free in rawv6_send_hdrinc()
@ 2026-10-23 3:31 Zhou Qiuyue
2026-10-23 7:48 ` Zhou Qiuyue
2026-10-26 3:11 ` Hangbin Liu
0 siblings, 2 replies; 3+ messages in thread
From: Zhou Qiuyue @ 2026-10-23 3:31 UTC (permalink / raw)
To: netdev; +Cc: dsahern
rawv6_send_hdrinc() saves a pointer to the dst/rt6_info before calling
l3mdev_ip6_out():
skb_dst_set(skb, &rt->dst);
*dstp = NULL;
skb = l3mdev_ip6_out(sk, skb);
When the egress device is enslaved to a VRF with a non-default qdisc
(e.g. fq), l3mdev_ip6_out() takes the vrf_ip6_out_redirect() path,
which does:
skb_dst_drop(skb); // drops refcount on old dst -> call_rcu
skb_dst_set(skb, dst); // installs VRF's own rt6
The old dst entry (the one `rt` still points to) now has refcount 0 and
is queued for RCU-deferred destruction via dst_destroy_rcu(). After
l3mdev_ip6_out() returns, rawv6_send_hdrinc() continues to use the
stale `rt` pointer:
IP6_INC_STATS(net, rt->rt6i_idev, ...); // UAF read
err = NF_HOOK(..., rt->dst.dev, ...); // UAF read
With expedited RCU grace periods (rcupdate.rcu_expedited=1) and
CONFIG_PREEMPT=y, the RCU callback fires quickly enough to free the
object before the stale accesses, producing a slab-use-after-free
detectable by KASAN.
Fix this by reloading `rt` from skb_dst(skb) after l3mdev_ip6_out()
returns, so subsequent code uses the (possibly replaced) dst entry.
Fixes: a8e3e1a9f020 ("net: l3mdev: Add hook in output path")
Signed-off-by: Zhou Qiuyue <n0ps1edzz@gmail.com>
---
net/ipv6/raw.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/net/ipv6/raw.c b/net/ipv6/raw.c
index XXXXXXX..XXXXXXX 100644
--- a/net/ipv6/raw.c
+++ b/net/ipv6/raw.c
@@ -648,6 +648,13 @@ static int rawv6_send_hdrinc(struct sock *sk, struct msghdr *msg, int length,
if (unlikely(!skb))
return 0;
+ /*
+ * l3mdev_ip6_out() may have replaced skb's dst via
+ * vrf_ip6_out_redirect(), in which case the original dst has been
+ * released and may be freed asynchronously. Reload rt from the
+ * current skb dst.
+ */
+ rt = dst_rt6_info(skb_dst(skb));
+
/* Acquire rcu_read_lock() in case we need to use rt->rt6i_idev
* in the error path. Since skb has been freed, the dst could
* have been queued for deletion.
--
2.43.0
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH] ipv6: raw: fix use-after-free in rawv6_send_hdrinc()
2026-10-23 3:31 [PATCH] ipv6: raw: fix use-after-free in rawv6_send_hdrinc() Zhou Qiuyue
@ 2026-10-23 7:48 ` Zhou Qiuyue
2026-10-26 3:11 ` Hangbin Liu
1 sibling, 0 replies; 3+ messages in thread
From: Zhou Qiuyue @ 2026-10-23 7:48 UTC (permalink / raw)
To: netdev
Hello,
As a follow-up to my earlier patch, I performed some additional dynamic
validation of the reported stale-route use-after-free. I was able to reproduce
the same issue with KASAN on Linux 7.3, 7.2.12, and 6.18.58. I hope these
additional results are helpful.
I also found one correction to the affected-range analysis in my original
message. Commit a8e3e1a9f020 ("net: l3mdev: Add hook to output path") added
the generic L3-master output hook, but it did not by itself make this IPv6
VRF path reachable. The vulnerable combination was completed by:
4c1feac58e06 ("net: vrf: Flip IPv6 output path from FIB lookup hook to out hook")
That commit connected the IPv6 VRF output handler and added the destination
replacement which releases the original skb destination. Therefore, the
appropriate Fixes tag should be:
Fixes: 4c1feac58e06 ("net: vrf: Flip IPv6 output path from FIB lookup hook to out hook")
I apologize for the incorrect introduction commit in my original message.
Linux v4.8 does not contain the complete vulnerable path. A tag-by-tag source
check found the sequence in v4.9-rc1, every final upstream release from v4.9
through v7.3, and Torvalds' mainline commit
734bef548a96e8774d6eef5dfa2026c6edc4e80b (2026-10-22). This is a source-level
affected-range assessment; dynamic KASAN reproduction was performed only on
v6.18.58, v7.2.12, and v7.3. Vendor kernels may differ because of backports.
The common test environment was QEMU/KVM x86_64 with four vCPUs,
KASAN_GENERIC/KASAN_INLINE, CONFIG_NET_VRF=y, CONFIG_IPV6=y,
CONFIG_NET_SCH_FQ=y, and CONFIG_DUMMY=y. The kernels were booted with
preempt=full and rcupdate.rcu_expedited=1. The reproducer sent raw IPv6
IP_HDRINCL packets through a dummy interface enslaved to a VRF whose root
qdisc was fq.
For convenience, I have included the detailed KASAN reports inline below. The
KASAN sections are copied directly from the captured serial logs. The final
reproducer-summary line following each report is labeled separately. I
apologize for the length of the message, but I thought the complete diagnostics
might be useful.
======================================================================
Linux 7.3 (commit c03c1a14507dfaf8b856c2608f9ac216021fc222)
======================================================================
[ 18.595533] BUG: KASAN: slab-use-after-free in rawv6_sendmsg+0x2f12/0x35f0
[ 18.595669] Read of size 8 at addr ffff88800f91d9d0 by task repro/101
[ 18.595671]
[ 18.595784] CPU: 0 UID: 0 PID: 101 Comm: repro Tainted: G W 7.3.0 #1 PREEMPT(full)
[ 18.595791] Tainted: [W]=WARN
[ 18.595797] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.13.0-1ubuntu1.1 04/01/2014
[ 18.595810] Call Trace:
[ 18.595842] <TASK>
[ 18.595844] dump_stack_lvl+0x55/0x70
[ 18.595890] print_report+0x10d/0x5d0
[ 18.595934] ? rawv6_sendmsg+0x2f12/0x35f0
[ 18.595936] kasan_report+0x96/0xd0
[ 18.595938] ? rawv6_sendmsg+0x2f12/0x35f0
[ 18.595940] rawv6_sendmsg+0x2f12/0x35f0
[ 18.595948] ? update_load_avg+0x156/0x30e0
[ 18.595978] ? __pfx_rawv6_sendmsg+0x10/0x10
[ 18.595980] ? __switch_to+0x58a/0xdd0
[ 18.596004] ? __pfx_vrf_output6+0x10/0x10
[ 18.596043] ? __pfx_vrf_output6+0x10/0x10
[ 18.596045] ? __schedule+0x123d/0x3e60
[ 18.596055] ? __pfx_dst_output+0x10/0x10
[ 18.596057] ? update_se+0x33f/0x1380
[ 18.596059] ? __pfx_sock_has_perm+0x10/0x10
[ 18.596081] ? irqentry_exit+0x10e/0x6e0
[ 18.596083] ? timerqueue_linked_add+0x1fb/0x3f0
[ 18.596089] ? irqentry_exit+0x10e/0x6e0
[ 18.596091] ? selinux_socket_sendmsg+0x8a/0x290
[ 18.596097] ? __pfx_inet_sendmsg+0x10/0x10
[ 18.596131] ? __sock_sendmsg+0x135/0x180
[ 18.596154] ? __pfx_inet_sendmsg+0x10/0x10
[ 18.596155] __sock_sendmsg+0x135/0x180
[ 18.596157] __sys_sendto+0x1ed/0x2d0
[ 18.596159] ? __pfx___sys_sendto+0x10/0x10
[ 18.596160] ? finish_task_switch+0x1d4/0x9a0
[ 18.596169] ? __hrtimer_run_queues+0x2a5/0x580
[ 18.596191] ? __pfx___schedule+0x10/0x10
[ 18.596193] __x64_sys_sendto+0xdc/0x1b0
[ 18.596194] ? fpregs_assert_state_consistent+0x5e/0xf0
[ 18.596209] do_syscall_64+0xee/0x500
[ 18.596215] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 18.596221] RIP: 0033:0x4071a4
[ 18.596227] Code: f2 fc ff ff 44 8b 4c 24 2c 4c 8b 44 24 20 89 c5 44 8b 54 24 28 48 8b 54 24 18 b8 2c 00 00 00 48 8b 74 24 10 8b 7c 24 08 0f 05 <48> 3d 00 f0 ff ff 77 30 89 ef 48 89 44 24 08 e8 18 fd ff ff 48 8b
[ 18.596228] RSP: 002b:00007ffe0499c770 EFLAGS: 00000293 ORIG_RAX: 000000000000002c
[ 18.596257] RAX: ffffffffffffffda RBX: 0000000000000001 RCX: 00000000004071a4
[ 18.596258] RDX: 0000000000000028 RSI: 00007ffe0499c810 RDI: 0000000000000003
[ 18.596259] RBP: 0000000000000000 R08: 00007ffe0499c7f0 R09: 000000000000001c
[ 18.596260] R10: 0000000000000040 R11: 0000000000000293 R12: 00007ffe0499c7f0
[ 18.596266] R13: 0000000000000000 R14: 0000000000016762 R15: 00007ffe0499c810
[ 18.596268] </TASK>
[ 18.596281]
[ 18.596281] Allocated by task 101:
[ 18.596303] kasan_save_stack+0x24/0x50
[ 18.596310] kasan_save_track+0x14/0x30
[ 18.596311] __kasan_slab_alloc+0x59/0x70
[ 18.596312] kmem_cache_alloc_noprof+0x133/0x390
[ 18.596324] dst_alloc+0x75/0x150
[ 18.596335] ip6_dst_alloc+0x1e/0x90
[ 18.596344] ip6_rt_cache_alloc.isra.0+0x167/0x620
[ 18.596346] ip6_pol_route+0x8a0/0xae0
[ 18.596347] fib6_rule_action+0x22c/0x900
[ 18.596363] fib_rules_lookup+0x3a2/0xab0
[ 18.596381] fib6_rule_lookup+0x16c/0x610
[ 18.596382] ip6_route_output_flags+0x21a/0x360
[ 18.596385] ip6_dst_lookup_tail+0x26f/0xdf0
[ 18.596389] ip6_dst_lookup_flow+0xf5/0x1e0
[ 18.596391] rawv6_sendmsg+0xa10/0x35f0
[ 18.596392] __sock_sendmsg+0x135/0x180
[ 18.596394] __sys_sendto+0x1ed/0x2d0
[ 18.596395] __x64_sys_sendto+0xdc/0x1b0
[ 18.596395] do_syscall_64+0xee/0x500
[ 18.596397] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 18.596398]
[ 18.596398] Freed by task 102:
[ 18.596399] kasan_save_stack+0x24/0x50
[ 18.596400] kasan_save_track+0x14/0x30
[ 18.596401] kasan_save_free_info+0x3a/0x60
[ 18.596403] __kasan_slab_free+0x43/0x70
[ 18.596404] kmem_cache_free+0x1cf/0x3c0
[ 18.596406] dst_destroy+0x181/0x1f0
[ 18.596407] rcu_core+0x5af/0x1820
[ 18.596420] handle_softirqs+0x17f/0x5b0
[ 18.596432] irq_exit_rcu+0x69/0x150
[ 18.596433] sysvec_apic_timer_interrupt+0x39/0x80
[ 18.596435] asm_sysvec_apic_timer_interrupt+0x1a/0x20
[ 18.596436]
[ 18.596437] Last potentially related work creation:
[ 18.596438] kasan_save_stack+0x24/0x50
[ 18.596439] kasan_record_aux_stack+0x89/0xa0
[ 18.596440] __call_rcu_common.constprop.0+0x70/0x890
[ 18.596442] vrf_l3_out+0x950/0xb40
[ 18.596443] rawv6_sendmsg+0x2743/0x35f0
[ 18.596445] __sock_sendmsg+0x135/0x180
[ 18.596446] __sys_sendto+0x1ed/0x2d0
[ 18.596447] __x64_sys_sendto+0xdc/0x1b0
[ 18.596448] do_syscall_64+0xee/0x500
[ 18.596449] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 18.596451]
[ 18.596451] The buggy address belongs to the object at ffff88800f91d900
[ 18.596451] which belongs to the cache ip6_dst_cache of size 224
[ 18.596460] The buggy address is located 208 bytes inside of
[ 18.596460] freed 224-byte region [ffff88800f91d900, ffff88800f91d9e0)
[ 18.596462]
[ 18.596463] The buggy address belongs to the physical page:
[ 18.596478] page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0xf91c
[ 18.596491] head: order:1 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
[ 18.596492] flags: 0x100000000000040(head|node=0|zone=1)
[ 18.596507] page_type: f5(slab)
[ 18.596520] raw: 0100000000000040 ffff888006e00500 dead000000000100 dead000000000122
[ 18.596522] raw: 0000000000000000 0000000000190019 00000000f5000000 0000000000000000
[ 18.596527] head: 0100000000000040 ffff888006e00500 dead000000000100 dead000000000122
[ 18.596541] head: 0000000000000000 0000000000190019 00000000f5000000 0000000000000000
[ 18.596542] head: 0100000000000001 ffffffffffffff81 00000000ffffffff 00000000ffffffff
[ 18.596543] head: 0000000000000000 0000000000000000 00000000ffffffff 0000000000000000
[ 18.596544] page dumped because: kasan: bad access detected
[ 18.596544]
[ 18.596545] Memory state around the buggy address:
[ 18.596545] ffff88800f91d880: fb fb fb fb fc fc fc fc fc fc fc fc fc fc fc fc
[ 18.596546] ffff88800f91d900: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[ 18.596547] >ffff88800f91d980: fb fb fb fb fb fb fb fb fb fb fb fb fc fc fc fc
[ 18.596548] ^
[ 18.596549] ffff88800f91da00: fc fc fc fc fc fc fc fc fa fb fb fb fb fb fb fb
[ 18.596550] ffff88800f91da80: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[ 18.596550] ==================================================================
[ 18.616663] Disabling lock debugging due to kernel taint
Reproducer summary: sends=906284 errors=0 pulses=4214
======================================================================
Linux 7.2.12 (commit cc9de5d4b5ab6459f5774946aa3195ad51236fa8)
======================================================================
[ 6.731112] BUG: KASAN: slab-use-after-free in rawv6_sendmsg+0x324d/0x3630
[ 6.734083] Read of size 8 at addr ffff88800972fb10 by task repro/85
[ 6.737864]
[ 6.739614] CPU: 0 UID: 0 PID: 85 Comm: repro Not tainted 7.2.12 #1 PREEMPT(full)
[ 6.739672] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.13.0-1ubuntu1.1 04/01/2014
[ 6.739726] Call Trace:
[ 6.739787] <TASK>
[ 6.739791] dump_stack_lvl+0x55/0x70
[ 6.739815] print_report+0x10d/0x5d0
[ 6.739919] ? rawv6_sendmsg+0x324d/0x3630
[ 6.739950] kasan_report+0x96/0xd0
[ 6.739956] ? rawv6_sendmsg+0x324d/0x3630
[ 6.739963] rawv6_sendmsg+0x324d/0x3630
[ 6.739970] ? kvm_clock_read+0x14/0x30
[ 6.740047] ? __pfx_rawv6_sendmsg+0x10/0x10
[ 6.740054] ? __schedule+0x1238/0x3e60
[ 6.740081] ? __pfx_vrf_output6+0x10/0x10
[ 6.740181] ? __pfx_vrf_output6+0x10/0x10
[ 6.740189] ? preempt_schedule_irq+0x49/0x80
[ 6.740195] ? irqentry_exit+0x10e/0x6e0
[ 6.740217] ? __pfx_rawv6_sendmsg+0x10/0x10
[ 6.740224] ? __pfx_inet_sendmsg+0x10/0x10
[ 6.740288] ? __sock_sendmsg+0x135/0x180
[ 6.740385] ? __pfx_inet_sendmsg+0x10/0x10
[ 6.740390] __sock_sendmsg+0x135/0x180
[ 6.740398] __sys_sendto+0x1ed/0x2d0
[ 6.740404] ? __pfx___sys_sendto+0x10/0x10
[ 6.740410] ? __schedule+0x1238/0x3e60
[ 6.740414] ? __hrtimer_run_queues+0x26e/0x580
[ 6.740479] ? fdget_pos+0x392/0x4c0
[ 6.740516] ? fput+0x25/0x70
[ 6.740533] ? ksys_write+0x189/0x1c0
[ 6.740536] ? __pfx_ksys_write+0x10/0x10
[ 6.740538] __x64_sys_sendto+0xdc/0x1b0
[ 6.740541] ? fpregs_assert_state_consistent+0x5e/0xf0
[ 6.740581] do_syscall_64+0x104/0x5c0
[ 6.740586] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 6.740599] RIP: 0033:0x4071a4
[ 6.740634] Code: f2 fc ff ff 44 8b 4c 24 2c 4c 8b 44 24 20 89 c5 44 8b 54 24 28 48 8b 54 24 18 b8 2c 00 00 00 48 8b 74 24 10 8b 7c 24 08 0f 05 <48> 3d 00 f0 ff ff 77 30 89 ef 48 89 44 24 08 e8 18 fd ff ff 48 8b
[ 6.740636] RSP: 002b:00007ffe6facd9a0 EFLAGS: 00000293 ORIG_RAX: 000000000000002c
[ 6.740696] RAX: ffffffffffffffda RBX: 0000000000000001 RCX: 00000000004071a4
[ 6.740705] RDX: 0000000000000028 RSI: 00007ffe6facda40 RDI: 0000000000000003
[ 6.740708] RBP: 0000000000000000 R08: 00007ffe6facda20 R09: 000000000000001c
[ 6.740709] R10: 0000000000000040 R11: 0000000000000293 R12: 00007ffe6facda20
[ 6.740711] R13: 0000000000000000 R14: 0000000000000639 R15: 00007ffe6facda40
[ 6.740715] </TASK>
[ 6.740716]
[ 6.853371] Allocated by task 85:
[ 6.855389] kasan_save_stack+0x24/0x50
[ 6.856865] kasan_save_track+0x14/0x30
[ 6.859773] __kasan_slab_alloc+0x59/0x70
[ 6.863568] kmem_cache_alloc_noprof+0x130/0x370
[ 6.866803] dst_alloc+0x75/0x150
[ 6.869696] ip6_dst_alloc+0x1e/0x90
[ 6.871296] ip6_rt_cache_alloc.isra.0+0x167/0x620
[ 6.873879] ip6_pol_route+0x89f/0xae0
[ 6.875869] fib6_rule_action+0x22c/0x900
[ 6.878115] fib_rules_lookup+0x3a2/0xab0
[ 6.880533] fib6_rule_lookup+0x16c/0x610
[ 6.882609] ip6_route_output_flags+0x21a/0x360
[ 6.884676] ip6_dst_lookup_tail+0x26f/0xdf0
[ 6.886338] ip6_dst_lookup_flow+0xf5/0x1e0
[ 6.888399] rawv6_sendmsg+0xa09/0x3630
[ 6.889974] __sock_sendmsg+0x135/0x180
[ 6.892257] __sys_sendto+0x1ed/0x2d0
[ 6.894648] __x64_sys_sendto+0xdc/0x1b0
[ 6.896700] do_syscall_64+0x104/0x5c0
[ 6.898730] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 6.901488]
[ 6.902490] Freed by task 86:
[ 6.905179] kasan_save_stack+0x24/0x50
[ 6.908564] kasan_save_track+0x14/0x30
[ 6.910957] kasan_save_free_info+0x3a/0x60
[ 6.913588] __kasan_slab_free+0x43/0x70
[ 6.915634] kmem_cache_free+0x1cf/0x3b0
[ 6.917181] dst_destroy+0x181/0x1f0
[ 6.919826] rcu_core+0x56f/0x1740
[ 6.921735] handle_softirqs+0x187/0x5b0
[ 6.924720] irq_exit_rcu+0x67/0x140
[ 6.927768] sysvec_apic_timer_interrupt+0x39/0x80
[ 6.930184] asm_sysvec_apic_timer_interrupt+0x1a/0x20
[ 6.933566]
[ 6.934335] Last potentially related work creation:
[ 6.937187] kasan_save_stack+0x24/0x50
[ 6.940412] kasan_record_aux_stack+0x89/0xa0
[ 6.941939] __call_rcu_common.constprop.0+0x70/0x880
[ 6.944256] vrf_l3_out+0x625/0x7a0
[ 6.945714] rawv6_sendmsg+0x2680/0x3630
[ 6.947458] __sock_sendmsg+0x135/0x180
[ 6.949629] __sys_sendto+0x1ed/0x2d0
[ 6.951775] __x64_sys_sendto+0xdc/0x1b0
[ 6.953956] do_syscall_64+0x104/0x5c0
[ 6.957564] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 6.960614]
[ 6.961357] The buggy address belongs to the object at ffff88800972fa40
[ 6.961357] which belongs to the cache ip6_dst_cache of size 224
[ 6.966491] The buggy address is located 208 bytes inside of
[ 6.966491] freed 224-byte region [ffff88800972fa40, ffff88800972fb20)
[ 6.973695]
[ 6.974831] The buggy address belongs to the physical page:
[ 6.977004] page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x972e
[ 6.981375] head: order:1 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
[ 6.985797] flags: 0x100000000000040(head|node=0|zone=1)
[ 6.987956] page_type: f5(slab)
[ 6.989989] raw: 0100000000000040 ffff888004d8de00 dead000000000100 dead000000000122
[ 6.992064] raw: 0000000000000000 0000000000190019 00000000f5000000 0000000000000000
[ 6.996358] head: 0100000000000040 ffff888004d8de00 dead000000000100 dead000000000122
[ 6.999999] head: 0000000000000000 0000000000190019 00000000f5000000 0000000000000000
[ 7.004644] head: 0100000000000001 ffffffffffffff81 00000000ffffffff 00000000ffffffff
[ 7.007746] head: 0000000000000000 0000000000000000 00000000ffffffff 0000000000000000
[ 7.010232] page dumped because: kasan: bad access detected
[ 7.012455]
[ 7.013256] Memory state around the buggy address:
[ 7.015472] ffff88800972fa00: fc fc fc fc fc fc fc fc fa fb fb fb fb fb fb fb
[ 7.019411] ffff88800972fa80: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[ 7.023552] >ffff88800972fb00: fb fb fb fb fc fc fc fc fc fc fc fc fc fc fc fc
[ 7.026528] ^
[ 7.028532] ffff88800972fb80: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[ 7.032466] ffff88800972fc00: fb fb fb fb fb fb fb fb fb fb fb fb fc fc fc fc
[ 7.036695] ==================================================================
[ 7.060645] Disabling lock debugging due to kernel taint
Reproducer summary: sends=664388 errors=0 pulses=4245
======================================================================
Linux 6.18.58 (commit 2926d88bb7fc4608fd942453d929c39e4e5ec006)
======================================================================
[ 3.346892] BUG: KASAN: slab-use-after-free in rawv6_sendmsg+0x3148/0x3640
[ 3.348258] Read of size 8 at addr ffff888008d220d0 by task repro/84
[ 3.349556]
[ 3.350404] CPU: 0 UID: 0 PID: 84 Comm: repro Not tainted 6.18.58 #1 PREEMPT(full)
[ 3.350413] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.13.0-1ubuntu1.1 04/01/2014
[ 3.350424] Call Trace:
[ 3.350446] <TASK>
[ 3.350448] dump_stack_lvl+0x55/0x70
[ 3.350482] print_report+0xcf/0x610
[ 3.350522] ? rawv6_sendmsg+0x3148/0x3640
[ 3.350524] ? rawv6_sendmsg+0x3148/0x3640
[ 3.350526] kasan_report+0xb8/0xf0
[ 3.350528] ? rawv6_sendmsg+0x3148/0x3640
[ 3.350530] rawv6_sendmsg+0x3148/0x3640
[ 3.350533] ? finish_task_switch+0x139/0x710
[ 3.350563] ? __switch_to+0x5be/0xe50
[ 3.350587] ? switch_mm_irqs_off+0x43e/0x830
[ 3.350607] ? __pfx_rawv6_sendmsg+0x10/0x10
[ 3.350609] ? __pfx__raw_spin_lock_irqsave+0x10/0x10
[ 3.350626] ? n_tty_write+0x839/0x1020
[ 3.350659] ? sched_clock+0x10/0x30
[ 3.350669] ? __pfx_inet_sendmsg+0x10/0x10
[ 3.350686] ? __sock_sendmsg+0x135/0x180
[ 3.350713] __sock_sendmsg+0x135/0x180
[ 3.350715] __sys_sendto+0x201/0x2e0
[ 3.350721] ? __pfx___sys_sendto+0x10/0x10
[ 3.350723] ? fdget_pos+0x392/0x4c0
[ 3.350737] ? fput+0x25/0x70
[ 3.350742] ? ksys_write+0x189/0x1c0
[ 3.350748] ? __pfx_ksys_write+0x10/0x10
[ 3.350750] __x64_sys_sendto+0xdc/0x1b0
[ 3.350752] ? switch_fpu_return+0xdf/0x1e0
[ 3.350755] do_syscall_64+0xac/0x310
[ 3.350761] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 3.350768] RIP: 0033:0x4071a4
[ 3.350775] Code: f2 fc ff ff 44 8b 4c 24 2c 4c 8b 44 24 20 89 c5 44 8b 54 24 28 48 8b 54 24 18 b8 2c 00 00 00 48 8b 74 24 10 8b 7c 24 08 0f 05 <48> 3d 00 f0 ff ff 77 30 89 ef 48 89 44 24 08 e8 18 fd ff ff 48 8b
[ 3.350776] RSP: 002b:00007ffd69690bb0 EFLAGS: 00000293 ORIG_RAX: 000000000000002c
[ 3.350807] RAX: ffffffffffffffda RBX: 0000000000000001 RCX: 00000000004071a4
[ 3.350815] RDX: 0000000000000028 RSI: 00007ffd69690c50 RDI: 0000000000000003
[ 3.350816] RBP: 0000000000000000 R08: 00007ffd69690c30 R09: 000000000000001c
[ 3.350817] R10: 0000000000000040 R11: 0000000000000293 R12: 00007ffd69690c30
[ 3.350818] R13: 0000000000000000 R14: 0000000000000000 R15: 00007ffd69690c50
[ 3.350824] </TASK>
[ 3.350825]
[ 3.392704] Allocated by task 84:
[ 3.393375] kasan_save_stack+0x24/0x50
[ 3.394086] kasan_save_track+0x14/0x30
[ 3.395143] __kasan_slab_alloc+0x59/0x70
[ 3.395850] kmem_cache_alloc_noprof+0x114/0x4e0
[ 3.396785] dst_alloc+0x75/0x150
[ 3.397805] ip6_dst_alloc+0x1e/0x90
[ 3.398760] ip6_rt_cache_alloc.isra.0+0x167/0x620
[ 3.399676] ip6_pol_route+0x89f/0xae0
[ 3.400454] fib6_rule_action+0x22c/0x900
[ 3.401268] fib_rules_lookup+0x3a2/0xab0
[ 3.401866] fib6_rule_lookup+0x16c/0x610
[ 3.402493] ip6_route_output_flags+0x21a/0x360
[ 3.403285] ip6_dst_lookup_tail+0x26f/0xdf0
[ 3.404019] ip6_dst_lookup_flow+0x82/0x190
[ 3.404678] rawv6_sendmsg+0xab1/0x3640
[ 3.405768] __sock_sendmsg+0x135/0x180
[ 3.408038] __sys_sendto+0x201/0x2e0
[ 3.408739] __x64_sys_sendto+0xdc/0x1b0
[ 3.409399] do_syscall_64+0xac/0x310
[ 3.410049] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 3.410973]
[ 3.411360] Freed by task 85:
[ 3.412056] kasan_save_stack+0x24/0x50
[ 3.412857] kasan_save_track+0x14/0x30
[ 3.413500] __kasan_save_free_info+0x3a/0x60
[ 3.414532] __kasan_slab_free+0x43/0x70
[ 3.415230] kmem_cache_free+0x2ba/0x560
[ 3.416195] dst_destroy+0x181/0x1f0
[ 3.417005] rcu_core+0x642/0x19e0
[ 3.417610] handle_softirqs+0x16a/0x550
[ 3.418393] irq_exit_rcu+0xaf/0xe0
[ 3.418943] sysvec_apic_timer_interrupt+0x39/0x80
[ 3.420078] asm_sysvec_apic_timer_interrupt+0x1a/0x20
[ 3.420966]
[ 3.421367] Last potentially related work creation:
[ 3.422342] kasan_save_stack+0x24/0x50
[ 3.424121] kasan_record_aux_stack+0x89/0xa0
[ 3.424963] __call_rcu_common.constprop.0+0x70/0x800
[ 3.426010] vrf_l3_out+0x619/0x800
[ 3.426701] rawv6_sendmsg+0x25d2/0x3640
[ 3.427741] __sock_sendmsg+0x135/0x180
[ 3.428425] __sys_sendto+0x201/0x2e0
[ 3.428996] __x64_sys_sendto+0xdc/0x1b0
[ 3.429661] do_syscall_64+0xac/0x310
[ 3.430257] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 3.431259]
[ 3.431519] The buggy address belongs to the object at ffff888008d22000
[ 3.431519] which belongs to the cache ip6_dst_cache of size 224
[ 3.433503] The buggy address is located 208 bytes inside of
[ 3.433503] freed 224-byte region [ffff888008d22000, ffff888008d220e0)
[ 3.435379]
[ 3.435690] The buggy address belongs to the physical page:
[ 3.436706] page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x8d22
[ 3.437835] head: order:1 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
[ 3.440582] flags: 0x100000000000040(head|node=0|zone=1)
[ 3.441332] page_type: f5(slab)
[ 3.441936] raw: 0100000000000040 ffff888005d90f00 dead000000000122 0000000000000000
[ 3.443581] raw: 0000000000000000 0000000080190019 00000000f5000000 0000000000000000
[ 3.445077] head: 0100000000000040 ffff888005d90f00 dead000000000122 0000000000000000
[ 3.446780] head: 0000000000000000 0000000080190019 00000000f5000000 0000000000000000
[ 3.448036] head: 0100000000000001 ffffea0000234881 00000000ffffffff 00000000ffffffff
[ 3.449428] head: 0000000000000000 0000000000000000 00000000ffffffff 0000000000000000
[ 3.451070] page dumped because: kasan: bad access detected
[ 3.452250]
[ 3.452846] Memory state around the buggy address:
[ 3.453879] ffff888008d21f80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 3.456019] ffff888008d22000: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[ 3.457694] >ffff888008d22080: fb fb fb fb fb fb fb fb fb fb fb fb fc fc fc fc
[ 3.459558] ^
[ 3.460775] ffff888008d22100: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 3.462146] ffff888008d22180: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 3.463323] ==================================================================
[ 3.545228] Disabling lock debugging due to kernel taint
Reproducer summary: sends=995434 errors=0 pulses=4207
The reports show the same object lifetime on all three kernels. The route object
is allocated through the IPv6 route-cache lookup path, freed through
dst_destroy() in an RCU callback, and the related RCU work is created from
vrf_l3_out() before rawv6_sendmsg() performs the stale read. This is consistent
with the source-level issue described in the original patch: the VRF redirect
replaces and releases skb->dst while rawv6_send_hdrinc() retains and later
dereferences the original rt pointer.
These results confirm a real kernel use-after-free on all three tested versions.
They do not, by themselves, demonstrate an unprivileged trigger, privilege
escalation, or an arbitrary read/write primitive. The current reproducer uses
CAP_NET_ADMIN to configure the VRF and qdisc, CAP_NET_RAW to open the raw
socket, and CAP_SYS_NICE only for the scheduling amplifier used to make the
race reliable.
If I have misunderstood any part of the affected path or proposed fix, I would
be grateful for any corrections. Please also let me know if the kernel
configurations, standalone reproducer, or any additional validation would be
useful; I would be happy to provide them.
Thank you very much for your time and consideration.
Best regards,
Zhou Qiuyue
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH] ipv6: raw: fix use-after-free in rawv6_send_hdrinc()
2026-10-23 3:31 [PATCH] ipv6: raw: fix use-after-free in rawv6_send_hdrinc() Zhou Qiuyue
2026-10-23 7:48 ` Zhou Qiuyue
@ 2026-10-26 3:11 ` Hangbin Liu
1 sibling, 0 replies; 3+ messages in thread
From: Hangbin Liu @ 2026-10-26 3:11 UTC (permalink / raw)
To: Zhou Qiuyue; +Cc: netdev, dsahern
Hi Qiuyue,
On Thu, Oct 22, 2026 at 08:31:41PM -0700, Zhou Qiuyue wrote:
> rawv6_send_hdrinc() saves a pointer to the dst/rt6_info before calling
> l3mdev_ip6_out():
>
> skb_dst_set(skb, &rt->dst);
> *dstp = NULL;
> skb = l3mdev_ip6_out(sk, skb);
>
> When the egress device is enslaved to a VRF with a non-default qdisc
> (e.g. fq), l3mdev_ip6_out() takes the vrf_ip6_out_redirect() path,
> which does:
>
> skb_dst_drop(skb); // drops refcount on old dst -> call_rcu
> skb_dst_set(skb, dst); // installs VRF's own rt6
>
> The old dst entry (the one `rt` still points to) now has refcount 0 and
> is queued for RCU-deferred destruction via dst_destroy_rcu(). After
> l3mdev_ip6_out() returns, rawv6_send_hdrinc() continues to use the
> stale `rt` pointer:
>
> IP6_INC_STATS(net, rt->rt6i_idev, ...); // UAF read
> err = NF_HOOK(..., rt->dst.dev, ...); // UAF read
>
> With expedited RCU grace periods (rcupdate.rcu_expedited=1) and
> CONFIG_PREEMPT=y, the RCU callback fires quickly enough to free the
> object before the stale accesses, producing a slab-use-after-free
> detectable by KASAN.
>
> Fix this by reloading `rt` from skb_dst(skb) after l3mdev_ip6_out()
> returns, so subsequent code uses the (possibly replaced) dst entry.
>
> Fixes: a8e3e1a9f020 ("net: l3mdev: Add hook in output path")
> Signed-off-by: Zhou Qiuyue <n0ps1edzz@gmail.com>
You need add a target repo for patch subject. e.g. [PATCH net] ...
BTW, you patch doesn't apply to the current net tree.
Applying: ipv6: raw: fix use-after-free in rawv6_send_hdrinc()
Patch failed at 0001 ipv6: raw: fix use-after-free in rawv6_send_hdrinc()
error: corrupt patch at /net/.git/worktrees/tests/rebase-apply/patch:25
Please rebase you patch with latest code.
BTW, please remember cc all the needed maintainers with scripts/get_maintainer.pl.
> ---
> net/ipv6/raw.c | 7 +++++++
> 1 file changed, 7 insertions(+)
>
> diff --git a/net/ipv6/raw.c b/net/ipv6/raw.c
> index XXXXXXX..XXXXXXX 100644
> --- a/net/ipv6/raw.c
> +++ b/net/ipv6/raw.c
> @@ -648,6 +648,13 @@ static int rawv6_send_hdrinc(struct sock *sk, struct msghdr *msg, int length,
> if (unlikely(!skb))
> return 0;
>
> + /*
> + * l3mdev_ip6_out() may have replaced skb's dst via
> + * vrf_ip6_out_redirect(), in which case the original dst has been
> + * released and may be freed asynchronously. Reload rt from the
> + * current skb dst.
> + */
> + rt = dst_rt6_info(skb_dst(skb));
> +
> /* Acquire rcu_read_lock() in case we need to use rt->rt6i_idev
> * in the error path. Since skb has been freed, the dst could
> * have been queued for deletion.
> --
> 2.43.0
The change looks good to me.
Thanks
Hangbin
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-10-26 3:11 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-23 3:31 [PATCH] ipv6: raw: fix use-after-free in rawv6_send_hdrinc() Zhou Qiuyue
2026-10-23 7:48 ` Zhou Qiuyue
2026-10-26 3:11 ` Hangbin Liu
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox