| 0/0 |
2026/07/18 00:57 |
flow |
assessment-security |
3m
Results: map[DenialOfService:true Explanation:**Analysis of the Bug**
**Root Cause:**
The bug is a recursive locking deadlock in the IPv4 IGMP subsystem.
When the kernel needs to send an IGMPv3 state-change report (e.g., when a
multicast timer expires or in response to an IGMP query), `igmpv3_send_cr()` or
`igmpv3_send_report()` is called. These functions iterate over the multicast
groups and acquire a spinlock on the multicast group state
(`spin_lock_bh(&pmc->lock)`).
While holding this lock, the code calls `add_grec()` -> `add_grhead()` ->
`igmpv3_newpack()` to allocate and prepare the network packet.
`igmpv3_newpack()` performs a synchronous route lookup via
`ip_route_output_ports()`.
If an IPsec (XFRM) policy is configured in tunnel mode that matches this IGMP
traffic, the route lookup is intercepted by `xfrm_lookup_route()`. XFRM then
performs a secondary route lookup for the outer tunnel destination address. If
the user configured the XFRM tunnel destination to be the *same* multicast
address as the IGMP group, this secondary route lookup will eventually call
`ip_check_mc_rcu()` to validate the multicast source address.
Because the outer tunnel protocol is not recognized as `IPPROTO_IGMP` (or
because the protocol information is lost during the XFRM resolution),
`ip_check_mc_rcu()` attempts to acquire the multicast group lock
(`spin_lock_bh(&im->lock)`) to check the source filters. Since the lock is
already held by the exact same CPU in `igmpv3_send_cr()`, the CPU deadlocks,
spinning infinitely.
**Security Impact:**
* **Exploitable:** False. This is a pure recursive spinlock deadlock. It does
not cause memory corruption, out-of-bounds accesses, use-after-free, or
information leaks. It cannot be used for privilege escalation.
* **Denial Of Service:** True. The bug causes a CPU to hang in an infinite loop
with softirqs disabled, leading to a hard lockup and a system-wide Denial of
Service.
* **Accessible From Unprivileged Processes:** False. Triggering the bug requires
configuring an XFRM policy, which requires `CAP_NET_ADMIN` in the network
namespace.
* **Accessible From User Namespaces:** True. An unprivileged user can create a
new user namespace and a new network namespace. Within this namespace, they
possess `CAP_NET_ADMIN` and can configure the malicious XFRM policy, join a
multicast group, and trigger the deadlock to crash the host system.
* **Network Trigger:** True. If a system administrator has legitimately
configured an XFRM policy that tunnels multicast traffic to a multicast address,
an external attacker on the local network can send an IGMP query. This query
forces the victim's kernel to generate an IGMP report, hitting the XFRM policy
and triggering the deadlock remotely. Exploitable:false FilesystemTrigger:false NetworkTrigger:true PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/07/18 00:57 |
action |
syz-repro-to-c-repro |
0m
Results:
|
| 2/1 |
2026/07/18 00:57 |
action |
kernel-checkouter |
0m
Results: map[KernelSrc:/app/workdir/cache/src/c29781298d2bd891141914c908cacce3f6b834ea]
|
| 3/1 |
2026/07/18 00:57 |
action |
kernel-builder |
0m
Results: map[KernelObj:/app/workdir/cache/build/40f3db31fe643191bb91b7a80d77ed4608477ed5]
|
| 4/1 |
2026/07/18 00:57 |
action |
codesearch-prepare |
0m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/07/18 00:58 |
agent |
expert |
2m
Model:
Results: map[DenialOfService:true Exploitable:false FilesystemTrigger:false NetworkTrigger:true PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
Instruction: You are an experienced Linux kernel security engineer. Your task is to analyze given kernel bug report
and determine its security impact based on the following dimensions.
Use the provided tools to examine the source code, check for capability checks (e.g., capable(), ns_capable()),
and understand the nature of the bug. Analyze the given kernel build and configuration.
You can check the kernel config by grepping ".config" file; you can check kernel cmdline by grepping
".config" file for "CONFIG_CMDLINE=". Assume sysctl parameters have default values.
But analyze for the corresponding production build w/o debugging tools enabled (like KASAN, KMSAN, UBSAN).
Try different strategies when analyzing the bug:
- think of ways in which the vulnerable code is unreachable
- or the other way around: try to come up with different ideas of how an unprivileged user can reach the bug
If still unsure err on the side of the bug being non-exploitable/not-accessible.
In the final reply, provide a reasoning for your assessment.
Analysis dimensions:
* Exploitable:
Determine if the bug can result in memory corruption, elevated privileges, or an information leak.
Memory safety issues are almost always exploitable (KASAN or UBSAN reports for use-after-free, out-of-bounds;
refcounting issues, corrupted lists, etc). When kernel is crashing on a completely wild pointer access
(e.g. user-space address, or non-canonical address, but not on NULL or address corresponding to KASAN shadow
for NULL address), including both data accesses and control transfers, that also usually implies possibility
of exploitation. Such reports usually say "unable to handle kernel paging request".
Uses of uninitialized values detected by KMSAN may be exploitable b/c attacker frequently can affect uninit
values with spraying techniques. However, for these exploitability depends on how exactly the uninit value
is used in the code, and what it affects.
Information leaks are exploitable on their own and should be classified as such. A bug that copies kernel
memory contents to userspace (e.g. an out-of-bounds read whose result is returned to the caller, or
uninitialized stack/heap bytes written to a user buffer) is exploitable: it can reveal kernel pointer
values and defeat KASLR, expose sensitive data such as cryptographic keys or other processes' memory, and
serves as a necessary building block in most modern kernel privilege-escalation exploit chains. Do not classify
an information leak as non-exploitable solely because it does not directly cause a memory write or control-flow
hijack; the leak itself is the exploit primitive.
Think of what happens after the bug is triggered. Some bugs cause kernel panic and halt execution,
they are harder to exploit. For example, BUG reports halts the kernel. However, WARNING reports don't halt
execution in production builds. Debug bug detection tools (like KASAN, KMSAN, KCSAN, UBSAN) are also not enabled
in production builds, so attacker can freely exploit these bugs w/o being detected by these tools.
If you see an integer overflow, think how the overflowed value used later (if it's used as allocation size,
or an array index). If you see an out-of-bounds read, think if it's followed by an out-of-bounds write as well.
Some KCSAN data-races may be exploitable by skilled attackers as well. Think what data structures got corrupted
as the result of data races and how. However, note that kernel has lots of "benign" data races that don't lead
to any runtime misbehavior at all.
* Denial Of Service:
Determine if the bug can result in denial-of-service. Most bugs can, since they cause system crash,
hangs, deadlocks, or resource leaks. This is mostly applicable to WARNING bugs that won't cause system crash
in production. For these think what will be consequences of the violation of the kernel assumptions flagged
by the WARNING. In some cases the unexpected condition is also properly handled by the normal control flow
(e.g. with "if (WARN_ON(...))"), these won't cause denial-of-service. If the condition is not handled,
then it may or may not cause denial-of-service.
* Accessible From Unprivileged Processes:
Determine if the bug can be reached from a typical (non-root) user process that does NOT have any special capabilities
(like CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON) or access to device nodes restricted to root.
Assume that unprivileged_bpf_disabled=1, that is eBPF loading is not accessible. However, cBPF (classical BPF)
is still accessible to non-root processes.
Assume that user namespaces are not accessible, that is, the process cannot get the mentioned capabilities even
within a new user namespace (checked by ns_capable() function in the kernel sources).
* Accessible From User Namespaces:
Determine if the bug can be reached within a user-namespace where the process has all capabilities
(including CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON). Such capabilities are checked with ns_capable()
function in the kernel sources.
* VM Guest Trigger:
Determine if the bug can be triggered from the context of a typical KVM guest (e.g., set up by a QEMU VMM).
Consider accesses to standard Linux host paravirtualized features (virtio-blk, virtio-net, etc.),
and handling of VM exits in the KVM code.
* VM Host Trigger in The Confidential Computing Context:
Determine if the bug can be triggered in a confidential computing guest kernel from the context of a KVM host.
Consider access to standard Linux guest paravirtualized features (virtio-blk, virtio-net, etc.).
* Ethernet Network Trigger:
Determine if the bug can be triggered by processing ingress network Ethernet traffic, either directly (network stack)
or via drivers exposed to network data.
* Other Remote Trigger:
Determine if the bug can be triggered by processing remote traffic other than Ethernet (Wifi, Bluetooth, NFC, etc).
* Peripheral Trigger:
Determine if the bug can be triggered via an untrusted peripheral device that can be physically plugged
into a system, such as a USB device or a niche hardware driver handling external hardware inputs.
This is particularly important for mobile and desktop environments where users can plug in unknown devices.
* Malicious Filesystem Trigger:
Determine if the bug can be triggered by the kernel mounting and parsing a malicious filesystem image.
This is highly critical for Desktop and Mobile environments where external media or downloaded images
might be auto-mounted.
Don't make assumptions about the kernel source code (it may be different from what you assume it is).
Extensively use the provided code access tools (codesearch-*, git-*, grepper, etc)
to examine the actual source code, and confirm any assumptions.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt:
The kernel bug report is:
============================================
WARNING: possible recursive locking detected
syzkaller #0 Not tainted
--------------------------------------------
syz.4.3416/18916 is trying to acquire lock:
ffff888042dfe6b0 (&im->lock){+.-.}-{3:3}, at: spin_lock_bh include/linux/spinlock.h:348 [inline]
ffff888042dfe6b0 (&im->lock){+.-.}-{3:3}, at: ip_check_mc_rcu+0x448/0x670 net/ipv4/igmp.c:2869
but task is already holding lock:
ffff8880331d08b0 (&im->lock){+.-.}-{3:3}, at: spin_lock_bh include/linux/spinlock.h:348 [inline]
ffff8880331d08b0 (&im->lock){+.-.}-{3:3}, at: igmpv3_send_cr net/ipv4/igmp.c:727 [inline]
ffff8880331d08b0 (&im->lock){+.-.}-{3:3}, at: igmp_ifc_timer_expire+0x852/0x10b0 net/ipv4/igmp.c:843
other info that might help us debug this:
Possible unsafe locking scenario:
CPU0
----
lock(&im->lock);
lock(&im->lock);
*** DEADLOCK ***
May be due to missing lock nesting notation
9 locks held by syz.4.3416/18916:
#0: ffffffff9057afc0 (&ops->srcu#2){.+.+}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
#0: ffffffff9057afc0 (&ops->srcu#2){.+.+}-{0:0}, at: rcu_read_lock include/linux/rcupdate.h:840 [inline]
#0: ffffffff9057afc0 (&ops->srcu#2){.+.+}-{0:0}, at: rtnl_link_ops_get+0x23/0x250 net/core/rtnetlink.c:595
#1: ffffffff9002c240 (rtnl_mutex){+.+.}-{4:4}, at: rtnl_lock net/core/rtnetlink.c:80 [inline]
#1: ffffffff9002c240 (rtnl_mutex){+.+.}-{4:4}, at: rtnl_nets_lock net/core/rtnetlink.c:366 [inline]
#1: ffffffff9002c240 (rtnl_mutex){+.+.}-{4:4}, at: rtnl_newlink+0xc10/0x1c30 net/core/rtnetlink.c:4194
#2: ffffffff8eb4bfa0 (console_lock){+.+.}-{0:0}, at: console_trylock_spinning kernel/printk/printk.c:2026 [inline]
#2: ffffffff8eb4bfa0 (console_lock){+.+.}-{0:0}, at: vprintk_emit+0x2cf/0x560 kernel/printk/printk.c:2478
#3: ffffffff8ea33838 (console_srcu){....}-{0:0}, at: rcu_try_lock_acquire include/linux/rcupdate.h:305 [inline]
#3: ffffffff8ea33838 (console_srcu){....}-{0:0}, at: srcu_read_lock_nmisafe include/linux/srcu.h:428 [inline]
#3: ffffffff8ea33838 (console_srcu){....}-{0:0}, at: console_srcu_read_lock kernel/printk/printk.c:291 [inline]
#3: ffffffff8ea33838 (console_srcu){....}-{0:0}, at: console_flush_one_record kernel/printk/printk.c:3246 [inline]
#3: ffffffff8ea33838 (console_srcu){....}-{0:0}, at: console_flush_all+0x123/0xaf0 kernel/printk/printk.c:3343
#4: ffffc90000a08cc0 ((&in_dev->mr_ifc_timer)){+.-.}-{0:0}, at: call_timer_fn+0xd4/0x5e0 kernel/time/timer.c:1745
#5: ffffffff8eb59c60 (rcu_read_lock){....}-{1:3}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
#5: ffffffff8eb59c60 (rcu_read_lock){....}-{1:3}, at: rcu_read_lock include/linux/rcupdate.h:840 [inline]
#5: ffffffff8eb59c60 (rcu_read_lock){....}-{1:3}, at: igmpv3_send_cr net/ipv4/igmp.c:689 [inline]
#5: ffffffff8eb59c60 (rcu_read_lock){....}-{1:3}, at: igmp_ifc_timer_expire+0x28/0x10b0 net/ipv4/igmp.c:843
#6: ffff8880331d08b0 (&im->lock){+.-.}-{3:3}, at: spin_lock_bh include/linux/spinlock.h:348 [inline]
#6: ffff8880331d08b0 (&im->lock){+.-.}-{3:3}, at: igmpv3_send_cr net/ipv4/igmp.c:727 [inline]
#6: ffff8880331d08b0 (&im->lock){+.-.}-{3:3}, at: igmp_ifc_timer_expire+0x852/0x10b0 net/ipv4/igmp.c:843
#7: ffffffff8eb59c60 (rcu_read_lock){....}-{1:3}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
#7: ffffffff8eb59c60 (rcu_read_lock){....}-{1:3}, at: rcu_read_lock include/linux/rcupdate.h:840 [inline]
#7: ffffffff8eb59c60 (rcu_read_lock){....}-{1:3}, at: xfrm_policy_get_afinfo+0x30/0x1b0 net/xfrm/xfrm_policy.c:263
#8: ffffffff8eb59c60 (rcu_read_lock){....}-{1:3}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
#8: ffffffff8eb59c60 (rcu_read_lock){....}-{1:3}, at: rcu_read_lock include/linux/rcupdate.h:840 [inline]
#8: ffffffff8eb59c60 (rcu_read_lock){....}-{1:3}, at: ip_route_output_key_hash+0xd8/0x2a0 net/ipv4/route.c:2701
stack backtrace:
CPU: 1 UID: 0 PID: 18916 Comm: syz.4.3416 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 06/25/2026
Call Trace:
<IRQ>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_deadlock_bug+0x279/0x290 kernel/locking/lockdep.c:3041
check_deadlock kernel/locking/lockdep.c:3093 [inline]
validate_chain kernel/locking/lockdep.c:3895 [inline]
__lock_acquire+0x24df/0x2cf0 kernel/locking/lockdep.c:5237
lock_acquire+0x106/0x350 kernel/locking/lockdep.c:5868
__raw_spin_lock_bh include/linux/spinlock_api_smp.h:150 [inline]
_raw_spin_lock_bh+0x36/0x50 kernel/locking/spinlock.c:182
spin_lock_bh include/linux/spinlock.h:348 [inline]
ip_check_mc_rcu+0x448/0x670 net/ipv4/igmp.c:2869
__mkroute_output net/ipv4/route.c:2600 [inline]
ip_route_output_key_hash_rcu+0xe7f/0x2720 net/ipv4/route.c:2872
ip_route_output_key_hash+0x18d/0x2a0 net/ipv4/route.c:2702
xfrm4_get_saddr+0xb6/0x170 net/ipv4/xfrm4_policy.c:57
xfrm_get_saddr net/xfrm/xfrm_policy.c:2472 [inline]
xfrm_tmpl_resolve_one net/xfrm/xfrm_policy.c:2508 [inline]
xfrm_tmpl_resolve net/xfrm/xfrm_policy.c:2567 [inline]
xfrm_resolve_and_create_bundle+0x6df/0x3070 net/xfrm/xfrm_policy.c:2865
xfrm_bundle_lookup net/xfrm/xfrm_policy.c:3100 [inline]
xfrm_lookup_with_ifid+0x576/0x1b40 net/xfrm/xfrm_policy.c:3231
xfrm_lookup net/xfrm/xfrm_policy.c:3330 [inline]
xfrm_lookup_route+0x3c/0x1c0 net/xfrm/xfrm_policy.c:3341
ip_route_output_ports include/net/route.h:213 [inline]
igmpv3_newpack+0x2a7/0x1190 net/ipv4/igmp.c:402
add_grhead+0x70/0x340 net/ipv4/igmp.c:473
add_grec+0x1123/0x13e0 net/ipv4/igmp.c:607
igmpv3_send_cr net/ipv4/igmp.c:744 [inline]
igmp_ifc_timer_expire+0x93e/0x10b0 net/ipv4/igmp.c:843
call_timer_fn+0x192/0x5e0 kernel/time/timer.c:1748
expire_timers kernel/time/timer.c:1799 [inline]
__run_timers kernel/time/timer.c:2374 [inline]
__run_timer_base+0x652/0x8b0 kernel/time/timer.c:2386
run_timer_base kernel/time/timer.c:2395 [inline]
run_timer_softirq+0xb7/0x170 kernel/time/timer.c:2405
handle_softirqs+0x225/0x840 kernel/softirq.c:622
__do_softirq kernel/softirq.c:656 [inline]
invoke_softirq kernel/softirq.c:496 [inline]
__irq_exit_rcu+0xca/0x220 kernel/softirq.c:735
irq_exit_rcu+0x9/0x30 kernel/softirq.c:752
instr_sysvec_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1062 [inline]
sysvec_apic_timer_interrupt+0xa6/0xc0 arch/x86/kernel/apic/apic.c:1062
</IRQ>
<TASK>
asm_sysvec_apic_timer_interrupt+0x1a/0x20 arch/x86/include/asm/idtentry.h:674
RIP: 0010:console_flush_one_record arch/x86/include/asm/irqflags.h:-1 [inline]
RIP: 0010:console_flush_all+0x7d2/0xaf0 kernel/printk/printk.c:3343
Code: ff ff e8 71 2d 21 00 90 0f 0b 90 e9 9c fc ff ff e8 63 2d 21 00 e8 3e b0 29 0a 48 85 db 74 c0 e8 54 2d 21 00 fb 48 8b 5c 24 18 <48> 8b 44 24 28 42 80 3c 20 00 4d 8d 77 58 74 08 4c 89 f7 e8 e6 b9
RSP: 0018:ffffc90002fe6840 EFLAGS: 00000283
RAX: ffffffff81a544ec RBX: ffffc90002fe69a0 RCX: 0000000000080000
RDX: ffffc9000db64000 RSI: 0000000000043066 RDI: 0000000000043067
RBP: ffffc90002fe6950 R08: ffffffff905775f7 R09: 1ffffffff20aeebe
R10: dffffc0000000000 R11: fffffbfff20aeebf R12: dffffc0000000000
R13: 0000000000000001 R14: 0000000000000000 R15: ffffffff8f46ec20
__console_flush_and_unlock kernel/printk/printk.c:3373 [inline]
console_unlock+0xd1/0x1c0 kernel/printk/printk.c:3413
vprintk_emit+0x485/0x560 kernel/printk/printk.c:2479
_printk+0xdd/0x130 kernel/printk/printk.c:2504
vlan_vid0_add net/8021q/vlan.c:347 [inline]
vlan_device_event+0x13e6/0x1580 net/8021q/vlan.c:388
notifier_call_chain+0x1a5/0x3d0 kernel/notifier.c:85
call_netdevice_notifiers_extack net/core/dev.c:2291 [inline]
call_netdevice_notifiers net/core/dev.c:2305 [inline]
__dev_notify_flags+0x31e/0x510 net/core/dev.c:9798
rtnl_configure_link+0x2f5/0x510 net/core/rtnetlink.c:3712
rtnl_newlink_create+0x343/0x7b0 net/core/rtnetlink.c:3957
__rtnl_newlink net/core/rtnetlink.c:4078 [inline]
rtnl_newlink+0x166d/0x1c30 net/core/rtnetlink.c:4195
rtnetlink_rcv_msg+0x802/0xc00 net/core/rtnetlink.c:7112
netlink_rcv_skb+0x226/0x4a0 net/netlink/af_netlink.c:2556
netlink_unicast_kernel net/netlink/af_netlink.c:1319 [inline]
netlink_unicast+0x7bb/0x940 net/netlink/af_netlink.c:1345
netlink_sendmsg+0x813/0xb40 net/netlink/af_netlink.c:1900
sock_sendmsg_nosec+0x13a/0x180 net/socket.c:775
__sock_sendmsg net/socket.c:790 [inline]
____sys_sendmsg+0x54e/0x850 net/socket.c:2684
___sys_sendmsg+0x2a5/0x360 net/socket.c:2738
__sys_sendmsg net/socket.c:2770 [inline]
__do_sys_sendmsg net/socket.c:2775 [inline]
__se_sys_sendmsg net/socket.c:2773 [inline]
__x64_sys_sendmsg+0x1b1/0x290 net/socket.c:2773
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7ff1f559de59
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007ff1f37f6028 EFLAGS: 00000246 ORIG_RAX: 000000000000002e
RAX: ffffffffffffffda RBX: 00007ff1f5825fa0 RCX: 00007ff1f559de59
RDX: 0000000000000000 RSI: 0000200000000080 RDI: 0000000000000006
RBP: 00007ff1f5633e6f R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007ff1f5826038 R14: 00007ff1f5825fa0 R15: 00007ffdfbb2bc38
</TASK>
GRED: Unable to relocate VQ 0x0 after dequeue, screwing up backlog
----------------
Code disassembly (best guess), 1 bytes skipped:
0: ff ljmp (bad)
1: e8 71 2d 21 00 call 0x212d77
6: 90 nop
7: 0f 0b ud2
9: 90 nop
a: e9 9c fc ff ff jmp 0xfffffcab
f: e8 63 2d 21 00 call 0x212d77
14: e8 3e b0 29 0a call 0xa29b057
19: 48 85 db test %rbx,%rbx
1c: 74 c0 je 0xffffffde
1e: e8 54 2d 21 00 call 0x212d77
23: fb sti
24: 48 8b 5c 24 18 mov 0x18(%rsp),%rbx
* 29: 48 8b 44 24 28 mov 0x28(%rsp),%rax <-- trapping instruction
2e: 42 80 3c 20 00 cmpb $0x0,(%rax,%r12,1)
33: 4d 8d 77 58 lea 0x58(%r15),%r14
37: 74 08 je 0x41
39: 4c 89 f7 mov %r14,%rdi
3c: e8 .byte 0xe8
3d: e6 b9 out %al,$0xb9
Reply: **Analysis of the Bug**
**Root Cause:**
The bug is a recursive locking deadlock in the IPv4 IGMP subsystem.
When the kernel needs to send an IGMPv3 state-change report (e.g., when a multicast timer expires or in response to an IGMP query), `igmpv3_send_cr()` or `igmpv3_send_report()` is called. These functions iterate over the multicast groups and acquire a spinlock on the multicast group state (`spin_lock_bh(&pmc->lock)`).
While holding this lock, the code calls `add_grec()` -> `add_grhead()` -> `igmpv3_newpack()` to allocate and prepare the network packet. `igmpv3_newpack()` performs a synchronous route lookup via `ip_route_output_ports()`.
If an IPsec (XFRM) policy is configured in tunnel mode that matches this IGMP traffic, the route lookup is intercepted by `xfrm_lookup_route()`. XFRM then performs a secondary route lookup for the outer tunnel destination address. If the user configured the XFRM tunnel destination to be the *same* multicast address as the IGMP group, this secondary route lookup will eventually call `ip_check_mc_rcu()` to validate the multicast source address.
Because the outer tunnel protocol is not recognized as `IPPROTO_IGMP` (or because the protocol information is lost during the XFRM resolution), `ip_check_mc_rcu()` attempts to acquire the multicast group lock (`spin_lock_bh(&im->lock)`) to check the source filters. Since the lock is already held by the exact same CPU in `igmpv3_send_cr()`, the CPU deadlocks, spinning infinitely.
**Security Impact:**
* **Exploitable:** False. This is a pure recursive spinlock deadlock. It does not cause memory corruption, out-of-bounds accesses, use-after-free, or information leaks. It cannot be used for privilege escalation.
* **Denial Of Service:** True. The bug causes a CPU to hang in an infinite loop with softirqs disabled, leading to a hard lockup and a system-wide Denial of Service.
* **Accessible From Unprivileged Processes:** False. Triggering the bug requires configuring an XFRM policy, which requires `CAP_NET_ADMIN` in the network namespace.
* **Accessible From User Namespaces:** True. An unprivileged user can create a new user namespace and a new network namespace. Within this namespace, they possess `CAP_NET_ADMIN` and can configure the malicious XFRM policy, join a multicast group, and trigger the deadlock to crash the host system.
* **Network Trigger:** True. If a system administrator has legitimately configured an XFRM policy that tunnels multicast traffic to a multicast address, an external attacker on the local network can send an IGMP query. This query forces the victim's kernel to generate an IGMP report, hitting the XFRM policy and triggering the deadlock remotely.
|
| 6/2 |
2026/07/18 00:58 |
llm |
expert |
0m
Model:
Tokens:
input: 11344
output: 41
thoughts: 1082
Thoughts: **Recursive Locking Detected: Diving into `igmp.c`**
Okay, so I've got a "WARNING: possible recursive locking detected" in `igmp.c`. The lock in question is `&im->lock`. The report indicates that a task is attempting to acquire this lock within `ip_check_mc_rcu`, specifically through the `spin_lock_bh` call. But, the same task already holds that same lock, `&im->lock`, due to a `spin_lock_bh` call in `igmpv3_send_cr`. Time to dissect the stack traces.
Looking at the stack, I can see the lock is held at `igmp_ifc_timer_expire`, which calls `igmpv3_send_cr`, but also held inside of igmpv3_send_cr itself. The trace clearly shows `igmp_ifc_timer_expire` is the entrypoint. I can also see where the task is *trying* to re-acquire the lock within the `ip_check_mc_rcu` function, called through a chain of functions that's kind of typical: `ip_route_output_ports` -> `xfrm_lookup_route` -> `xfrm_lookup` -> ... ultimately leading back to `ip_check_mc_rcu`.
Aha! `igmp_ifc_timer_expire` acquires `im->lock`. Then, from within the same function calls flow is `igmpv3_send_cr` which does a spinlock bh on `im->lock`. Within `igmpv3_send_cr`, a cascade of function calls `add_grec`, `add_grhead`, `igmpv3_newpack`, which then calls `ip_route_output_ports`. That function in turn calls `xfrm_lookup_route`, which goes through `xfrm_lookup` and then down to `ip_route_output_key_hash` to call `__mkroute_output` which eventually, calls `ip_check_mc_rcu`. And *that* function tries to re-acquire `im->lock`, creating the recursive lock issue.
So, `igmp_ifc_timer_expire`'s flow is: acquire `im->lock` -> call `igmpv3_send_cr` -> `igmpv3_send_cr` does something that eventually calls `ip_check_mc_rcu`, where there is a second attempt to acquire the lock. This is the root cause. This confirms the initial warning. Now, I need to look closely at the relevant code in `igmp.c` and `route.c` to figure out why this is happening and determine how it needs to be fixed.
|
| 7/2 |
2026/07/18 00:58 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/ipv4/igmp.c Name:igmpv3_send_cr]
Results: map[SourceCode: 683: static void igmpv3_send_cr(struct in_device *in_dev)
684: {
685: struct ip_mc_list *pmc, *pmc_prev, *pmc_next;
686: struct sk_buff *skb = NULL;
687: int type, dtype;
688:
689: rcu_read_lock();
690: spin_lock_bh(&in_dev->mc_tomb_lock);
691:
692: /* deleted MCA's */
693: pmc_prev = NULL;
694: for (pmc = in_dev->mc_tomb; pmc; pmc = pmc_next) {
695: pmc_next = pmc->next;
696: if (pmc->sfmode == MCAST_INCLUDE) {
697: type = IGMPV3_BLOCK_OLD_SOURCES;
698: dtype = IGMPV3_BLOCK_OLD_SOURCES;
699: skb = add_grec(skb, pmc, type, 1, 0);
700: skb = add_grec(skb, pmc, dtype, 1, 1);
701: }
702: if (pmc->crcount) {
703: if (pmc->sfmode == MCAST_EXCLUDE) {
704: type = IGMPV3_CHANGE_TO_INCLUDE;
705: skb = add_grec(skb, pmc, type, 1, 0);
706: }
707: pmc->crcount--;
708: if (pmc->crcount == 0) {
709: igmpv3_clear_zeros(&pmc->tomb);
710: igmpv3_clear_zeros(&pmc->sources);
711: }
712: }
713: if (pmc->crcount == 0 && !pmc->tomb && !pmc->sources) {
714: if (pmc_prev)
715: pmc_prev->next = pmc_next;
716: else
717: in_dev->mc_tomb = pmc_next;
718: in_dev_put(pmc->interface);
719: kfree_pmc(pmc);
720: } else
721: pmc_prev = pmc;
722: }
723: spin_unlock_bh(&in_dev->mc_tomb_lock);
724:
725: /* change recs */
726: for_each_pmc_rcu(in_dev, pmc) {
727: spin_lock_bh(&pmc->lock);
728: if (pmc->sfcount[MCAST_EXCLUDE]) {
729: type = IGMPV3_BLOCK_OLD_SOURCES;
730: dtype = IGMPV3_ALLOW_NEW_SOURCES;
731: } else {
732: type = IGMPV3_ALLOW_NEW_SOURCES;
733: dtype = IGMPV3_BLOCK_OLD_SOURCES;
734: }
735: skb = add_grec(skb, pmc, type, 0, 0);
736: skb = add_grec(skb, pmc, dtype, 0, 1); /* deleted sources */
737:
738: /* filter mode changes */
739: if (pmc->crcount) {
740: if (pmc->sfmode == MCAST_EXCLUDE)
741: type = IGMPV3_CHANGE_TO_EXCLUDE;
742: else
743: type = IGMPV3_CHANGE_TO_INCLUDE;
744: skb = add_grec(skb, pmc, type, 0, 0);
745: pmc->crcount--;
746: }
747: spin_unlock_bh(&pmc->lock);
748: }
749: rcu_read_unlock();
750:
751: if (!skb)
752: return;
753: (void) igmpv3_sendpack(skb);
754: }
SourceFile:net/ipv4/igmp.c]
|
| 8/2 |
2026/07/18 00:58 |
llm |
expert |
0m
Model:
Tokens:
input: 14312
output: 40
thoughts: 97
Thoughts: **Analyzing the IGMPv3 Control Plane**
Okay, so I'm digging into `igmpv3_send_cr`, and it's grabbing `pmc->lock` with a spinlock at line 727, as expected for thread-safety. Nothing particularly surprising there. Now, the interesting part is the series of calls it makes: `add_grec` at three points β 735, 736, and 744. Clearly, this `add_grec` function is central to the control message generation process.
`add_grec` immediately leads us down a rabbit hole. It calls `add_grhead`, and that in turn calls `igmpv3_newpack`. Seems like this is where the meat of the IGMPv3 packet construction is happening. Let's shift our focus to `igmpv3_newpack` and see what's going on in there. It's time to understand how these control reports are actually built and what data is being packed into those IGMPv3 packets. This is where the crucial data structures are likely defined, and where the low-level formatting is occurring.
|
| 9/2 |
2026/07/18 00:58 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/ipv4/igmp.c Name:igmpv3_newpack]
Results: map[SourceCode: 378: static struct sk_buff *igmpv3_newpack(struct net_device *dev, unsigned int mtu)
379: {
380: struct sk_buff *skb;
381: struct rtable *rt;
382: struct iphdr *pip;
383: struct igmpv3_report *pig;
384: struct net *net = dev_net(dev);
385: struct flowi4 fl4;
386: int hlen = LL_RESERVED_SPACE(dev);
387: int tlen = dev->needed_tailroom;
388: unsigned int size;
389:
390: size = min(mtu, IP_MAX_MTU);
391: while (1) {
392: skb = alloc_skb(size + hlen + tlen,
393: GFP_ATOMIC | __GFP_NOWARN);
394: if (skb)
395: break;
396: size >>= 1;
397: if (size < 256)
398: return NULL;
399: }
400: skb->priority = TC_PRIO_CONTROL;
401:
402: rt = ip_route_output_ports(net, &fl4, NULL, IGMPV3_ALL_MCR, 0,
403: 0, 0,
404: IPPROTO_IGMP, 0, dev->ifindex);
405: if (IS_ERR(rt)) {
406: kfree_skb(skb);
407: return NULL;
408: }
409:
410: skb_dst_set(skb, &rt->dst);
411: skb->dev = dev;
412:
413: skb_reserve(skb, hlen);
414: skb_tailroom_reserve(skb, mtu, tlen);
415:
416: skb_reset_network_header(skb);
417: pip = ip_hdr(skb);
418: skb_put(skb, sizeof(struct iphdr) + 4);
419:
420: pip->version = 4;
421: pip->ihl = (sizeof(struct iphdr)+4)>>2;
422: pip->tos = 0xc0;
423: pip->frag_off = htons(IP_DF);
424: pip->ttl = 1;
425: pip->daddr = fl4.daddr;
426:
427: rcu_read_lock();
428: pip->saddr = igmpv3_get_srcaddr(dev, &fl4);
429: rcu_read_unlock();
430:
431: pip->protocol = IPPROTO_IGMP;
432: pip->tot_len = 0; /* filled in later */
433: ip_select_ident(net, skb, NULL);
434: ((u8 *)&pip[1])[0] = IPOPT_RA;
435: ((u8 *)&pip[1])[1] = 4;
436: ((u8 *)&pip[1])[2] = 0;
437: ((u8 *)&pip[1])[3] = 0;
438:
439: skb->transport_header = skb->network_header + sizeof(struct iphdr) + 4;
440: skb_put(skb, sizeof(*pig));
441: pig = igmpv3_report_hdr(skb);
442: pig->type = IGMPV3_HOST_MEMBERSHIP_REPORT;
443: pig->resv1 = 0;
444: pig->csum = 0;
445: pig->resv2 = 0;
446: pig->ngrec = 0;
447: return skb;
448: }
SourceFile:net/ipv4/igmp.c]
|
| 10/2 |
2026/07/18 00:58 |
llm |
expert |
0m
Model:
Tokens:
input: 15879
output: 38
thoughts: 227
Thoughts: **Digging into the IGMPv3 Packet Processing Path**
Okay, so I'm tracing the flow of an IGMPv3 packet and hit a bit of a snag. `igmpv3_newpack`, specifically at line 402 in `net/ipv4/igmp.c`, calls `ip_route_output_ports`. Now, `ip_route_output_ports` is interesting because it, in turn, calls `ip_route_output_flow`. And that ultimately leads to `__ip_route_output_key` and then `ip_route_output_key_hash`. Standard routing lookup path, which is fine, but... there's something else.
Looking at the stack trace, I see that right after `ip_route_output_ports` (again, at include/net/route.h:213 - inline), we hit `igmpv3_newpack` as expected. But then, *after* that, I see `xfrm_lookup_route` being called from `xfrm_policy.c` at line 3341. I need to be careful here, as I'm thinking back, and this is where it's a bit unclear.
Aha, that's it! `ip_route_output_ports` doesn't *directly* call `xfrm_lookup_route`. It calls `ip_route_output_flow`, and *that* one calls `xfrm_lookup_route`. My memory needs a refresh. Time to carefully examine the implementation of `ip_route_output_ports` and the flow.
|
| 11/2 |
2026/07/18 00:58 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:include/net/route.h Name:ip_route_output_ports]
Results: map[SourceCode: 201: static inline struct rtable *ip_route_output_ports(struct net *net, struct flowi4 *fl4,
202: const struct sock *sk,
203: __be32 daddr, __be32 saddr,
204: __be16 dport, __be16 sport,
205: __u8 proto, __u8 tos, int oif)
206: {
207: flowi4_init_output(fl4, oif, sk ? READ_ONCE(sk->sk_mark) : 0, tos,
208: sk ? ip_sock_rt_scope(sk) : RT_SCOPE_UNIVERSE,
209: proto, sk ? inet_sk_flowi_flags(sk) : 0,
210: daddr, saddr, dport, sport, sock_net_uid(net, sk));
211: if (sk)
212: security_sk_classify_flow(sk, flowi4_to_flowi_common(fl4));
213: return ip_route_output_flow(net, fl4, sk);
214: }
SourceFile:include/net/route.h]
|
| 12/2 |
2026/07/18 00:58 |
llm |
expert |
0m
Model:
Tokens:
input: 16843
output: 39
thoughts: 19
Thoughts: Okay, here's my interpretation of that thought process, from an expert's perspective:
**Diving into the Packet Routing Depths**
Right, so we're talking about the core of IP routing here. `ip_route_output_flow`β¦ that's the function that kicks off the process of figuring out *where* a packet needs to go when it's leaving the host. It's the point of no return, where all the pre-routing setup is done and the system decides on the output interface. And, within that incredibly important process, a key element stands out: `xfrm_lookup_route`.
Aha! `xfrm_lookup_route`. Now we're getting to the intersection of routing and the IPsec/IPsec-related security infrastructure. It's essentially the place where the *route* that's being determined needs to integrate with the security policies defined in the system. The `xfrm` framework is crucial for handling things like encryption, authentication, and tunneling. So, when the route lookup calls `xfrm_lookup_route`, it's confirming that the chosen path adheres to the security policy. This is about making sure packets are appropriately handled based on the configured security associations (SAs). It's also an indicator that the route lookup can trigger an IKE negotiation if needed.
|
| 13/2 |
2026/07/18 00:58 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/ipv4/route.c Name:ip_route_output_flow]
Results: map[SourceCode:2926: struct rtable *ip_route_output_flow(struct net *net, struct flowi4 *flp4,
2927: const struct sock *sk)
2928: {
2929: struct rtable *rt = __ip_route_output_key(net, flp4);
2930:
2931: if (IS_ERR(rt))
2932: return rt;
2933:
2934: if (flp4->flowi4_proto) {
2935: flp4->flowi4_oif = rt->dst.dev->ifindex;
2936: rt = dst_rtable(xfrm_lookup_route(net, &rt->dst,
2937: flowi4_to_flowi(flp4),
2938: sk, 0));
2939: }
2940:
2941: return rt;
2942: }
SourceFile:net/ipv4/route.c]
|
| 14/2 |
2026/07/18 00:58 |
llm |
expert |
0m
Model:
Tokens:
input: 17479
output: 41
thoughts: 307
Thoughts: **Tracing the `xfrm_lookup_route` Call Chain**
Okay, so I see `xfrm_lookup_route` kicking things off. Naturally, it then uses `xfrm_lookup` to do some more fundamental work. `xfrm_lookup` delegates to `xfrm_lookup_with_ifid`, which, in turn, hands off the bulk of the lookup to `xfrm_bundle_lookup`. I can see this is heading toward resolving something. `xfrm_bundle_lookup` seems to be the critical point, calling `xfrm_resolve_and_create_bundle` to, well, resolve and create.
This then triggers `xfrm_tmpl_resolve`, which likely handles template-based lookups. That resolves down to `xfrm_tmpl_resolve_one`. I can see that this eventually calls `xfrm_get_saddr` to get a source address. And that calls `xfrm4_get_saddr` presumably for IPv4.
Here's the interesting part: `xfrm4_get_saddr` then calls `ip_route_output_key_hash`. This clearly indicates a route lookup based on the key constructed from the input. And `ip_route_output_key_hash` then, logically, calls `ip_route_output_key_hash_rcu`. Which subsequently calls `__mkroute_output`. That's where we get to the route creation.
And finally, `__mkroute_output` calls `ip_check_mc_rcu`. Ah, and *that* calls `spin_lock_bh(&im->lock)`. I'll have to investigate that function in more detail. That's a spinlock, inside an RCU protected area. This is going to involve multicasting and concurrency control, and I'll need to check how it all integrates.
|
| 15/2 |
2026/07/18 00:58 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/ipv4/igmp.c Name:ip_check_mc_rcu]
Results: map[SourceCode:2841: /* called with rcu_read_lock() */
2842: int ip_check_mc_rcu(struct in_device *in_dev, __be32 mc_addr, __be32 src_addr, u8 proto)
2843: {
2844: struct ip_mc_list *im;
2845: struct ip_mc_list __rcu **mc_hash;
2846: struct ip_sf_list *psf;
2847: int rv = 0;
2848:
2849: mc_hash = rcu_dereference(in_dev->mc_hash);
2850: if (mc_hash) {
2851: u32 hash = hash_32((__force u32)mc_addr, MC_HASH_SZ_LOG);
2852:
2853: for (im = rcu_dereference(mc_hash[hash]);
2854: im != NULL;
2855: im = rcu_dereference(im->next_hash)) {
2856: if (im->multiaddr == mc_addr)
2857: break;
2858: }
2859: } else {
2860: for_each_pmc_rcu(in_dev, im) {
2861: if (im->multiaddr == mc_addr)
2862: break;
2863: }
2864: }
2865: if (im && proto == IPPROTO_IGMP) {
2866: rv = 1;
2867: } else if (im) {
2868: if (src_addr) {
2869: spin_lock_bh(&im->lock);
2870: for (psf = im->sources; psf; psf = psf->sf_next) {
2871: if (psf->sf_inaddr == src_addr)
2872: break;
2873: }
2874: if (psf)
2875: rv = psf->sf_count[MCAST_INCLUDE] ||
2876: psf->sf_count[MCAST_EXCLUDE] !=
2877: im->sfcount[MCAST_EXCLUDE];
2878: else
2879: rv = im->sfcount[MCAST_EXCLUDE] != 0;
2880: spin_unlock_bh(&im->lock);
2881: } else
2882: rv = 1; /* unspecified source; tentatively allow */
2883: }
2884: return rv;
2885: }
SourceFile:net/ipv4/igmp.c]
|
| 16/2 |
2026/07/18 00:58 |
llm |
expert |
0m
Model:
Tokens:
input: 18977
output: 44
thoughts: 399
Thoughts: **Digging into the IGMPv3 Code Path**
Okay, so I'm tracing the execution flow here, specifically concerning `ip_check_mc_rcu`. The logic is clear enough, it acquires `spin_lock_bh(&im->lock)` when `src_addr` is non-zero and the protocol isn't IGMP. But then I'm thinking, isn't the protocol IGMP in `igmpv3_newpack`? Let's verify.
Ah, yes. Diving back into the `igmpv3_newpack` function, I can see that `proto` is indeed set to `IPPROTO_IGMP` when calling `ip_route_output_ports`. That seems to be the critical call. `ip_route_output_ports` then feeds into `ip_route_output_flow`, and that leads us to `__ip_route_output_key`.
From there, it's a series of calls: `xfrm_lookup_route`, `xfrm_lookup`, `xfrm_lookup_with_ifid`, `xfrm_bundle_lookup`, `xfrm_resolve_and_create_bundle`, `xfrm_tmpl_resolve`, `xfrm_tmpl_resolve_one`, and finally, `xfrm_get_saddr`. The journey to find the source address ultimately boils down to `xfrm4_get_saddr`, which relies on `ip_route_output_key_hash`.
This suggests that the routing decision, which affects the locking in `ip_check_mc_rcu`, is fundamentally influenced by the `xfrm` subsystem, particularly its handling of security policies and transforms. I'll need to look deeper into the interplay between these functions and the routing cache, to understand the conditions under which that lock is contended. This is shaping up to be a routing-related performance issue, I'll need to trace further.
|
| 17/2 |
2026/07/18 00:58 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/ipv4/xfrm4_policy.c Name:xfrm4_get_saddr]
Results: map[SourceCode: 51: static int xfrm4_get_saddr(xfrm_address_t *saddr,
52: const struct xfrm_dst_lookup_params *params)
53: {
54: struct dst_entry *dst;
55: struct flowi4 fl4;
56:
57: dst = __xfrm4_dst_lookup(&fl4, params);
58: if (IS_ERR(dst))
59: return -EHOSTUNREACH;
60:
61: saddr->a4 = fl4.saddr;
62: dst_release(dst);
63: return 0;
64: }
SourceFile:net/ipv4/xfrm4_policy.c]
|
| 18/2 |
2026/07/18 00:58 |
llm |
expert |
0m
Model:
Tokens:
input: 20058
output: 44
thoughts: 14
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience, presented in the first person:
**Diving into `__xfrm4_dst_lookup`**
Alright, time to get to the core of this. Let's start with `__xfrm4_dst_lookup`. This is the function I need to understand better. Itβs likely the starting point for IPv4 destination lookup within the XFRM framework. I'm going to focus on how it handles the different elements within its processing of the lookup. Specifically, I'm thinking about the data structures and algorithms involved. I need to refresh my understanding of how this handles cache hits and misses, and the potential performance implications of each scenario. I'll need to remember any potential bottlenecks or areas prone to fragmentation. I want to check the specific algorithms to see how efficient they are, and whether any improvements are possible.
|
| 19/2 |
2026/07/18 00:58 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/ipv4/xfrm4_policy.c Name:__xfrm4_dst_lookup]
Results: map[SourceCode: 21: static struct dst_entry *__xfrm4_dst_lookup(struct flowi4 *fl4,
22: const struct xfrm_dst_lookup_params *params)
23: {
24: struct rtable *rt;
25:
26: memset(fl4, 0, sizeof(*fl4));
27: fl4->daddr = params->daddr->a4;
28: fl4->flowi4_dscp = params->dscp;
29: fl4->flowi4_l3mdev = l3mdev_master_ifindex_by_index(params->net,
30: params->oif);
31: fl4->flowi4_mark = params->mark;
32: if (params->saddr)
33: fl4->saddr = params->saddr->a4;
34: fl4->flowi4_proto = params->ipproto;
35: fl4->uli = params->uli;
36:
37: rt = __ip_route_output_key(params->net, fl4);
38: if (!IS_ERR(rt))
39: return &rt->dst;
40:
41: return ERR_CAST(rt);
42: }
SourceFile:net/ipv4/xfrm4_policy.c]
|
| 20/2 |
2026/07/18 00:58 |
llm |
expert |
0m
Model:
Tokens:
input: 20693
output: 43
thoughts: 64
Thoughts: Okay, let me break this down.
**Diving into the `xfrm4_dst_lookup` Rabbit Hole**
Alright, so I'm tracing this packet flow and I've hit `__xfrm4_dst_lookup`. It's calling `__ip_route_output_key`, which is expected, but the interesting part is how it's setting up the routing key. Specifically, it's passing `fl4->flowi4_proto = params->ipproto`. I need to figure out what `params->ipproto` actually represents here. It seems critical for determining the protocol used in the routing lookup.
Okay, so I follow the breadcrumbs. It looks like `params->ipproto` is coming from `xfrm_tmpl_resolve_one`. Time to back up and see what's happening there to understand how the protocol type is set. That should provide the essential context for this part of the route lookup. This will then dictate the overall behavior and effectiveness of the routing.
|
| 21/2 |
2026/07/18 00:58 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/xfrm/xfrm_policy.c Name:xfrm_tmpl_resolve_one]
Results: map[SourceCode:2479: static int
2480: xfrm_tmpl_resolve_one(struct xfrm_policy *policy, const struct flowi *fl,
2481: struct xfrm_state **xfrm, unsigned short family)
2482: {
2483: struct net *net = xp_net(policy);
2484: int nx;
2485: int i, error;
2486: xfrm_address_t *daddr = xfrm_flowi_daddr(fl, family);
2487: xfrm_address_t *saddr = xfrm_flowi_saddr(fl, family);
2488: xfrm_address_t tmp;
2489:
2490: for (nx = 0, i = 0; i < policy->xfrm_nr; i++) {
2491: struct xfrm_state *x;
2492: xfrm_address_t *remote = daddr;
2493: xfrm_address_t *local = saddr;
2494: struct xfrm_tmpl *tmpl = &policy->xfrm_vec[i];
2495:
2496: if (tmpl->mode == XFRM_MODE_TUNNEL ||
2497: tmpl->mode == XFRM_MODE_IPTFS ||
2498: tmpl->mode == XFRM_MODE_BEET) {
2499: remote = &tmpl->id.daddr;
2500: local = &tmpl->saddr;
2501: if (xfrm_addr_any(local, tmpl->encap_family)) {
2502: struct xfrm_dst_lookup_params params;
2503:
2504: memset(¶ms, 0, sizeof(params));
2505: params.net = net;
2506: params.oif = fl->flowi_oif;
2507: params.daddr = remote;
2508: error = xfrm_get_saddr(tmpl->encap_family, &tmp,
2509: ¶ms);
2510: if (error)
2511: goto fail;
2512: local = &tmp;
2513: }
2514: }
2515:
2516: x = xfrm_state_find(remote, local, fl, tmpl, policy, &error,
2517: family, policy->if_id);
2518: if (x && x->dir && x->dir != XFRM_SA_DIR_OUT) {
2519: XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTSTATEDIRERROR);
2520: xfrm_state_put(x);
2521: error = -EINVAL;
2522: goto fail;
2523: }
2524:
2525: if (x && x->km.state == XFRM_STATE_VALID) {
2526: xfrm[nx++] = x;
2527: daddr = remote;
2528: saddr = local;
2529: continue;
2530: }
2531: if (x) {
2532: error = (x->km.state == XFRM_STATE_ERROR ?
2533: -EINVAL : -EAGAIN);
2534: xfrm_state_put(x);
2535: } else if (error == -ESRCH) {
2536: error = -EAGAIN;
2537: }
2538:
2539: if (!tmpl->optional)
2540: goto fail;
2541: }
2542: return nx;
2543:
2544: fail:
2545: for (nx--; nx >= 0; nx--)
2546: xfrm_state_put(xfrm[nx]);
2547: return error;
2548: }
SourceFile:net/xfrm/xfrm_policy.c]
|
| 22/2 |
2026/07/18 00:58 |
llm |
expert |
0m
Model:
Tokens:
input: 22139
output: 37
thoughts: 171
Thoughts: **Digging into a Potential Routing Issue**
Okay, so I'm tracing through this `xfrm_tmpl_resolve_one` function. It looks like `params.ipproto` isn't getting initialized, which means it's defaulting to zero. That's a red flag. Now, further down the line, `__xfrm4_dst_lookup` calls `__ip_route_output_key`, and the problem becomes apparent. The `fl4->flowi4_proto` also has a value of 0.
Let's see, `__ip_route_output_key` then calls `ip_route_output_key_hash`. Okay, makes sense. Following the call chain down, `ip_route_output_key_hash` calls `ip_route_output_key_hash_rcu`, and finally that function leads to `__mkroute_output`. Hmm, interesting. And now, `__mkroute_output` calls `ip_check_mc_rcu`. I think I need to dive deeper into what's happening within `__mkroute_output` to see if the issue is propagated. Let's examine that function to understand where this 0 value may be causing a problem.
|
| 23/2 |
2026/07/18 00:58 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/ipv4/route.c Name:__mkroute_output]
Results: map[SourceCode:2558: /* called with rcu_read_lock() */
2559: static struct rtable *__mkroute_output(const struct fib_result *res,
2560: const struct flowi4 *fl4, int orig_oif,
2561: struct net_device *dev_out,
2562: unsigned int flags)
2563: {
2564: struct fib_info *fi = res->fi;
2565: struct fib_nh_exception *fnhe;
2566: struct in_device *in_dev;
2567: u16 type = res->type;
2568: struct rtable *rth;
2569: bool do_cache;
2570:
2571: in_dev = __in_dev_get_rcu(dev_out);
2572: if (!in_dev)
2573: return ERR_PTR(-EINVAL);
2574:
2575: if (likely(!IN_DEV_ROUTE_LOCALNET(in_dev)))
2576: if (ipv4_is_loopback(fl4->saddr) &&
2577: !(dev_out->flags & IFF_LOOPBACK) &&
2578: !netif_is_l3_master(dev_out))
2579: return ERR_PTR(-EINVAL);
2580:
2581: if (ipv4_is_lbcast(fl4->daddr)) {
2582: type = RTN_BROADCAST;
2583:
2584: /* reset fi to prevent gateway resolution */
2585: fi = NULL;
2586: } else if (ipv4_is_multicast(fl4->daddr)) {
2587: type = RTN_MULTICAST;
2588: } else if (ipv4_is_zeronet(fl4->daddr)) {
2589: return ERR_PTR(-EINVAL);
2590: }
2591:
2592: if (dev_out->flags & IFF_LOOPBACK)
2593: flags |= RTCF_LOCAL;
2594:
2595: do_cache = true;
2596: if (type == RTN_BROADCAST) {
2597: flags |= RTCF_BROADCAST | RTCF_LOCAL;
2598: } else if (type == RTN_MULTICAST) {
2599: flags |= RTCF_MULTICAST | RTCF_LOCAL;
2600: if (!ip_check_mc_rcu(in_dev, fl4->daddr, fl4->saddr,
2601: fl4->flowi4_proto))
2602: flags &= ~RTCF_LOCAL;
2603: else
2604: do_cache = false;
2605: /* If multicast route do not exist use
2606: * default one, but do not gateway in this case.
2607: * Yes, it is hack.
2608: */
2609: if (fi && res->prefixlen < 4)
2610: fi = NULL;
2611: } else if ((type == RTN_LOCAL) && (orig_oif != 0) &&
2612: (orig_oif != dev_out->ifindex)) {
2613: /* For local routes that require a particular output interface
2614: * we do not want to cache the result. Caching the result
2615: * causes incorrect behaviour when there are multiple source
2616: * addresses on the interface, the end result being that if the
2617: * intended recipient is waiting on that interface for the
2618: * packet he won't receive it because it will be delivered on
2619: * the loopback interface and the IP_PKTINFO ipi_ifindex will
2620: * be set to the loopback interface as well.
2621: */
2622: do_cache = false;
2623: }
2624:
2625: fnhe = NULL;
2626: do_cache &= fi != NULL;
2627: if (fi) {
2628: struct fib_nh_common *nhc = FIB_RES_NHC(*res);
2629: struct rtable __rcu **prth;
2630:
2631: fnhe = find_exception(nhc, fl4->daddr);
2632: if (!do_cache)
2633: goto add;
2634: if (fnhe) {
2635: prth = &fnhe->fnhe_rth_output;
2636: } else {
2637: if (unlikely(fl4->flowi4_flags &
2638: FLOWI_FLAG_KNOWN_NH &&
2639: !(nhc->nhc_gw_family &&
2640: nhc->nhc_scope == RT_SCOPE_LINK))) {
2641: do_cache = false;
2642: goto add;
2643: }
2644: prth = raw_cpu_ptr(nhc->nhc_pcpu_rth_output);
2645: }
2646: rth = rcu_dereference(*prth);
2647: if (rt_cache_valid(rth) && dst_hold_safe(&rth->dst))
2648: return rth;
2649: }
2650:
2651: add:
2652: rth = rt_dst_alloc(dev_out, flags, type,
2653: IN_DEV_ORCONF(in_dev, NOXFRM));
2654: if (!rth)
2655: return ERR_PTR(-ENOBUFS);
2656:
2657: rth->rt_iif = orig_oif;
2658:
2659: RT_CACHE_STAT_INC(out_slow_tot);
2660:
2661: if (flags & (RTCF_BROADCAST | RTCF_MULTICAST)) {
2662: if (flags & RTCF_LOCAL &&
2663: !(dev_out->flags & IFF_LOOPBACK)) {
2664: rth->dst.output = ip_mc_output;
2665: RT_CACHE_STAT_INC(out_slow_mc);
2666: }
2667: #ifdef CONFIG_IP_MROUTE
2668: if (type == RTN_MULTICAST) {
2669: if (IN_DEV_MFORWARD(in_dev) &&
2670: !ipv4_is_local_multicast(fl4->daddr)) {
2671: rth->dst.input = ip_mr_input;
2672: rth->dst.output = ip_mr_output;
2673: }
2674: }
2675: #endif
2676: }
2677:
2678: rt_set_nexthop(rth, fl4->daddr, res, fnhe, fi, type, 0, do_cache);
2679: lwtunnel_set_redirect(&rth->dst);
2680:
2681: return rth;
2682: }
SourceFile:net/ipv4/route.c]
|
| 24/2 |
2026/07/18 00:58 |
llm |
expert |
0m
Model:
Tokens:
input: 24660
output: 44
thoughts: 933
Thoughts: **Deadlock Detected: XFRM, IGMPv3, and Multicast - A Kernel Vulnerability**
Okay, let me break this down. It seems I've stumbled upon a potential deadlock scenario within the Linux kernel, specifically related to IPsec (XFRM), IGMPv3, and multicast traffic. I'm seeing a classic recursive locking problem, which could lead to a denial-of-service vulnerability.
The core of the issue lies in how multicast traffic is routed when XFRM policies are in place. The process starts with `__mkroute_output`, which eventually calls `ip_check_mc_rcu`. The problem arises because, during XFRM route lookup (`__xfrm4_dst_lookup`), the protocol (`fl4->flowi4_proto`) gets inadvertently set to zero. This happens through the `params` struct, whose protocol field isn't explicitly set during initialization and defaults to zero.
This seemingly innocuous zero value triggers a specific code path within `ip_check_mc_rcu` that attempts to acquire a spinlock (`im->lock`). Hereβs where it gets interesting: this same lock (`im->lock`) is already *held* by `igmpv3_send_cr`. `igmpv3_send_cr` calls `ip_route_output_ports` (among other things) which winds up calling `ip_check_mc_rcu` again, trying to grab the lock it already has! This nested call chain is happening because multicast route lookup calls `xfrm_lookup_route` which, when a relevant XFRM policy exists, ends up calling `ip_check_mc_rcu` again.
Wait, are we talking about the same lock? Yes, it turns out that `im` and `pmc` are both `ip_mc_list` objects. This is key. The deadlock materializes if the destination address of the XFRM template (`tmpl->id.daddr`) is the same as the multicast address managed by the IGMPv3 code.
So, to summarize:
1. `igmpv3_send_cr` holds `pmc->lock` (which is the same as `im->lock`).
2. Then, due to XFRM policy lookups, we get `ip_check_mc_rcu` being called, which tries to get `im->lock`.
3. This becomes a deadlock, as it's the same lock!
Is this exploitable? Yes, a kernel deadlock can be leveraged for a Denial of Service. To trigger it, an unprivileged user would need to create a specific scenario:
1. An XFRM policy (`tmpl->mode` being TUNNEL, IPTFS, or BEET) needs to be configured.
2. The XFRM template's destination address (`tmpl->id.daddr`) must be a multicast address.
3. The XFRM template's source address (`tmpl->saddr`) should be `INADDR_ANY`.
4. IGMPv3 needs to be active and sending a report for this multicast address.
The critical question now is, can an unprivileged user set up these XFRM policies? Policies are configured via netlink (`NETLINK_XFRM`), which usually requires `CAP_NET_ADMIN` privileges in the initial network namespace, or potentially in a user namespace if supported. I need to check the exact behavior of XFRM netlink in user namespaces to definitively determine the exploitability. This is a vulnerability that needs further investigation.
|
| 25/2 |
2026/07/18 00:59 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/xfrm/xfrm_user.c Name:xfrm_user_rcv_msg]
Results: map[SourceCode:3818: static int xfrm_user_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh,
3819: struct netlink_ext_ack *extack)
3820: {
3821: struct net *net = sock_net(skb->sk);
3822: struct nlattr *attrs[XFRMA_MAX+1];
3823: const struct xfrm_link *link;
3824: struct nlmsghdr *nlh64 = NULL;
3825: int type, err;
3826:
3827: type = nlh->nlmsg_type;
3828: if (type > XFRM_MSG_MAX)
3829: return -EINVAL;
3830:
3831: type -= XFRM_MSG_BASE;
3832: link = &xfrm_dispatch[type];
3833:
3834: /* All operations require privileges, even GET */
3835: if (!netlink_net_capable(skb, CAP_NET_ADMIN))
3836: return -EPERM;
3837:
3838: if (IS_ENABLED(CONFIG_COMPAT_FOR_U64_ALIGNMENT) && in_compat_syscall()) {
3839: struct xfrm_translator *xtr = xfrm_get_translator();
3840:
3841: if (!xtr)
3842: return -EOPNOTSUPP;
3843:
3844: nlh64 = xtr->rcv_msg_compat(nlh, link->nla_max,
3845: link->nla_pol, extack);
3846: xfrm_put_translator(xtr);
3847: if (IS_ERR(nlh64))
3848: return PTR_ERR(nlh64);
3849: if (nlh64)
3850: nlh = nlh64;
3851: }
3852:
3853: if ((type == (XFRM_MSG_GETSA - XFRM_MSG_BASE) ||
3854: type == (XFRM_MSG_GETPOLICY - XFRM_MSG_BASE)) &&
3855: (nlh->nlmsg_flags & NLM_F_DUMP)) {
3856: struct netlink_dump_control c = {
3857: .start = link->start,
3858: .dump = link->dump,
3859: .done = link->done,
3860: };
3861:
3862: if (link->dump == NULL) {
3863: err = -EINVAL;
3864: goto err;
3865: }
3866:
3867: err = netlink_dump_start(xfrm_net_nlsk(net, skb), skb, nlh, &c);
3868: goto err;
3869: }
3870:
3871: err = nlmsg_parse_deprecated(nlh, xfrm_msg_min[type], attrs,
3872: link->nla_max ? : XFRMA_MAX,
3873: link->nla_pol ? : xfrma_policy, extack);
3874: if (err < 0)
3875: goto err;
3876:
3877: if (!link->nla_pol || link->nla_pol == xfrma_policy) {
3878: err = xfrm_reject_unused_attr((type + XFRM_MSG_BASE), attrs, extack);
3879: if (err < 0)
3880: goto err;
3881: }
3882:
3883: if (link->doit == NULL) {
3884: err = -EINVAL;
3885: goto err;
3886: }
3887:
3888: err = link->doit(skb, nlh, attrs, extack);
3889:
3890: /* We need to free skb allocated in xfrm_alloc_compat() before
3891: * returning from this function, because consume_skb() won't take
3892: * care of frag_list since netlink destructor sets
3893: * sbk->head to NULL. (see netlink_skb_destructor())
3894: */
3895: if (skb_has_frag_list(skb)) {
3896: kfree_skb(skb_shinfo(skb)->frag_list);
3897: skb_shinfo(skb)->frag_list = NULL;
3898: }
3899:
3900: err:
3901: kvfree(nlh64);
3902: return err;
3903: }
SourceFile:net/xfrm/xfrm_user.c]
|
| 26/2 |
2026/07/18 00:59 |
llm |
expert |
0m
Model:
Tokens:
input: 27798
output: 41
thoughts: 49
Thoughts: Okay, here's my interpretation of this check, rendered as my own internal thought process:
**Diving into Netlink Capability Checks**
Alright, so I'm seeing a `netlink_net_capable(skb, CAP_NET_ADMIN)` call here. My immediate thought is, "Ah, right, we're verifying the permissions of the sender." The function is designed to see if the message's origin has the `CAP_NET_ADMIN` capability. It's crucial, obviously, because this implies a fairly high level of network management privileges. The fact that it specifies the *namespace* of the socket is also important for security, isolating administrative tasks to appropriate contexts. Before I make any assumptions about what's going on, I should probably take a look at the function's definition. Let's see what `netlink_net_capable` is actually doing behind the scenes.
|
| 27/2 |
2026/07/18 00:59 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/netlink/af_netlink.c Name:netlink_net_capable]
Results: map[SourceCode: 889: /**
890: * netlink_net_capable - Netlink network namespace message capability test
891: * @skb: socket buffer holding a netlink command from userspace
892: * @cap: The capability to use
893: *
894: * Test to see if the opener of the socket we received the message
895: * from had when the netlink socket was created and the sender of the
896: * message has the capability @cap over the network namespace of
897: * the socket we received the message from.
898: */
899: bool netlink_net_capable(const struct sk_buff *skb, int cap)
900: {
901: return netlink_ns_capable(skb, sock_net(skb->sk)->user_ns, cap);
902: }
SourceFile:net/netlink/af_netlink.c]
|
| 28/2 |
2026/07/18 00:59 |
llm |
expert |
0m
Model:
Tokens:
input: 28350
output: 44
thoughts: 1062
Thoughts: **Analyzing the Network Namespace XFRM Deadlock**
Okay, so I've been digging into this potential vulnerability. It seems an unprivileged user can create new user and network namespaces and then configure XFRM policies, which is the starting point. The core of the issue lies in the network stack and involves local configuration combined with IGMP timers. The good news (or bad, depending on your perspective) is that it doesn't require any special hardware or physical access β it's all about how the network is set up within the user's namespace.
Essentially, this is a recursive locking deadlock, which is a classic issue. In production builds, with `CONFIG_PROVE_LOCKING` disabled, the kernel will simply deadlock. The CPU will get stuck in an infinite loop trying to acquire a spinlock that it already holds. This will cause a hard lockup on that CPU since it is a spinlock and softirqs are disabled, which means we will spin forever within the `_raw_spin_lock_bh` which calls `__raw_spin_lock`. It's a textbook Denial of Service (DoS) vulnerability. However, it's *not* a privilege escalation or memory corruption issue β it's just a deadlock.
Let me break down the code flow. First, I need to confirm that `ip_check_mc_rcu` is actually called with `proto == 0`. In `xfrm_tmpl_resolve_one`, `params.ipproto` is initialized to 0, which gets passed down. We're going from `xfrm_tmpl_resolve_one` to `xfrm_get_saddr`, then `xfrm4_get_saddr`, then `__xfrm4_dst_lookup`. `__xfrm4_dst_lookup` sets `fl4->flowi4_proto` to 0. That eventually leads to `ip_route_output_key_hash`, which calls `ip_route_output_key_hash_rcu`, then `__mkroute_output`, which finally calls `ip_check_mc_rcu` with `fl4->flowi4_proto` which is zero.
The critical path in `ip_check_mc_rcu` has this block:
```c
if (im && proto == IPPROTO_IGMP) {
rv = 1;
} else if (im) {
if (src_addr) {
spin_lock_bh(&im->lock);
```
So I see that `src_addr` is critical, and it's equal to `fl4->saddr`. But wait, in `__xfrm4_dst_lookup`:
```c
if (params->saddr)
fl4->saddr = params->saddr->a4;
```
It's important to realize that `params.saddr` is *not* set in `xfrm_tmpl_resolve_one`. Thus, if `params.saddr` isn't set, `fl4->saddr` will be 0. So, `src_addr` which is `fl4->saddr` will be 0. So when we get to `ip_check_mc_rcu`:
```c
} else if (im) {
if (src_addr) {
spin_lock_bh(&im->lock);
...
} else
rv = 1; /* unspecified source; tentatively allow */
}
```
Wait a second! If `src_addr` is 0, the lock *isn't* taken! This changes everything! That could mean the deadlock isn't reachable with this path. Okay, I need to revisit the `__xfrm4_dst_lookup` code flow. This is crucial for understanding the whole thing! I need to ensure that the code is working as intended, and that there are no gaps in the logic.
|
| 29/2 |
2026/07/18 00:59 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/ipv4/xfrm4_policy.c Name:__xfrm4_dst_lookup]
Results: map[SourceCode: 21: static struct dst_entry *__xfrm4_dst_lookup(struct flowi4 *fl4,
22: const struct xfrm_dst_lookup_params *params)
23: {
24: struct rtable *rt;
25:
26: memset(fl4, 0, sizeof(*fl4));
27: fl4->daddr = params->daddr->a4;
28: fl4->flowi4_dscp = params->dscp;
29: fl4->flowi4_l3mdev = l3mdev_master_ifindex_by_index(params->net,
30: params->oif);
31: fl4->flowi4_mark = params->mark;
32: if (params->saddr)
33: fl4->saddr = params->saddr->a4;
34: fl4->flowi4_proto = params->ipproto;
35: fl4->uli = params->uli;
36:
37: rt = __ip_route_output_key(params->net, fl4);
38: if (!IS_ERR(rt))
39: return &rt->dst;
40:
41: return ERR_CAST(rt);
42: }
SourceFile:net/ipv4/xfrm4_policy.c]
|
| 30/2 |
2026/07/18 00:59 |
llm |
expert |
0m
Model:
Tokens:
input: 30701
output: 41
thoughts: 73
Thoughts: Okay, let me break this down.
**IP Address Route Key Examination**
Alright, so I'm looking at this code and specifically at the handling of source addresses (`saddr`). The logic seems straightforward: if the input parameters (`params->saddr`) are missing (i.e., `NULL`), then the associated `fl4->saddr` should be initialized to zero. Seems logical enough for an uninitialized or default scenario.
But then, that little voice in the back of my head chimes in, "Hang on... could some intermediate function potentially *modify* `fl4->saddr` after the initial assignment?" My immediate thought goes to the `__ip_route_output_key` function. I have to quickly verify if this function has the potential to overwrite the intended value.
Ah, right! I remember now. `__ip_route_output_key` isn't some black box; it's a wrapper. It simply acts as an entry point for `ip_route_output_key_hash`. This clarifies things, I am confident that `__ip_route_output_key` won't be silently modifying `fl4->saddr`. Now, I can confidently understand how the routing key functions are working.
|
| 31/2 |
2026/07/18 00:59 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/ipv4/route.c Name:ip_route_output_key_hash]
Results: map[SourceCode:2688: struct rtable *ip_route_output_key_hash(struct net *net, struct flowi4 *fl4,
2689: const struct sk_buff *skb)
2690: {
2691: struct fib_result res = {
2692: .type = RTN_UNSPEC,
2693: .fi = NULL,
2694: .table = NULL,
2695: .tclassid = 0,
2696: };
2697: struct rtable *rth;
2698:
2699: fl4->flowi4_iif = LOOPBACK_IFINDEX;
2700:
2701: rcu_read_lock();
2702: rth = ip_route_output_key_hash_rcu(net, fl4, &res, skb);
2703: rcu_read_unlock();
2704:
2705: return rth;
2706: }
SourceFile:net/ipv4/route.c]
|
| 32/2 |
2026/07/18 00:59 |
llm |
expert |
0m
Model:
Tokens:
input: 31388
output: 44
thoughts: 18
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Diving into `ip_route_output_key_hash_rcu`**
Alright, time to get my hands dirty. My initial thought is to dive straight into `ip_route_output_key_hash_rcu`. This isn't just a casual glance; I'm going in with a specific purpose, a goal in mind. I'm assuming, given the context, that this is a critical function within the Linux networking stack, most likely related to route lookup and potentially utilizing RCU for concurrency. Knowing that the routing subsystem is often a bottleneck, particularly under load, my focus will be on the performance characteristics. Iβll be dissecting the implementation, looking at its hash function, any potential lock contention points, RCU usage patterns (read-side critical sections, write-side handling), and its interaction with other critical data structures like the routing cache. I need to understand how efficiently it handles route lookups, especially the worst-case scenarios and potential for collisions within the hash table. Are there optimizations for common prefixes? How does it handle route updates? I need to analyze its implications for scalability and overall throughput. I'm going to start by pulling up the code and letting it inform the rest of my work.
|
| 33/2 |
2026/07/18 00:59 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/ipv4/route.c Name:ip_route_output_key_hash_rcu]
Results: map[SourceCode:2709: struct rtable *ip_route_output_key_hash_rcu(struct net *net, struct flowi4 *fl4,
2710: struct fib_result *res,
2711: const struct sk_buff *skb)
2712: {
2713: struct net_device *dev_out = NULL;
2714: int orig_oif = fl4->flowi4_oif;
2715: unsigned int flags = 0;
2716: struct rtable *rth;
2717: int err;
2718:
2719: if (fl4->saddr) {
2720: if (ipv4_is_multicast(fl4->saddr) ||
2721: ipv4_is_lbcast(fl4->saddr)) {
2722: rth = ERR_PTR(-EINVAL);
2723: goto out;
2724: }
2725:
2726: rth = ERR_PTR(-ENETUNREACH);
2727:
2728: /* I removed check for oif == dev_out->oif here.
2729: * It was wrong for two reasons:
2730: * 1. ip_dev_find(net, saddr) can return wrong iface, if saddr
2731: * is assigned to multiple interfaces.
2732: * 2. Moreover, we are allowed to send packets with saddr
2733: * of another iface. --ANK
2734: */
2735:
2736: if (fl4->flowi4_oif == 0 &&
2737: (ipv4_is_multicast(fl4->daddr) ||
2738: ipv4_is_lbcast(fl4->daddr))) {
2739: /* It is equivalent to inet_addr_type(saddr) == RTN_LOCAL */
2740: dev_out = __ip_dev_find(net, fl4->saddr, false);
2741: if (!dev_out)
2742: goto out;
2743:
2744: /* Special hack: user can direct multicasts
2745: * and limited broadcast via necessary interface
2746: * without fiddling with IP_MULTICAST_IF or IP_PKTINFO.
2747: * This hack is not just for fun, it allows
2748: * vic,vat and friends to work.
2749: * They bind socket to loopback, set ttl to zero
2750: * and expect that it will work.
2751: * From the viewpoint of routing cache they are broken,
2752: * because we are not allowed to build multicast path
2753: * with loopback source addr (look, routing cache
2754: * cannot know, that ttl is zero, so that packet
2755: * will not leave this host and route is valid).
2756: * Luckily, this hack is good workaround.
2757: */
2758:
2759: fl4->flowi4_oif = dev_out->ifindex;
2760: goto make_route;
2761: }
2762:
2763: if (!(fl4->flowi4_flags & FLOWI_FLAG_ANYSRC)) {
2764: /* It is equivalent to inet_addr_type(saddr) == RTN_LOCAL */
2765: if (!__ip_dev_find(net, fl4->saddr, false))
2766: goto out;
2767: }
2768: }
2769:
2770:
2771: if (fl4->flowi4_oif) {
2772: dev_out = dev_get_by_index_rcu(net, fl4->flowi4_oif);
2773: rth = ERR_PTR(-ENODEV);
2774: if (!dev_out)
2775: goto out;
2776:
2777: /* RACE: Check return value of inet_select_addr instead. */
2778: if (!(dev_out->flags & IFF_UP) || !__in_dev_get_rcu(dev_out)) {
2779: rth = ERR_PTR(-ENETUNREACH);
2780: goto out;
2781: }
2782: if (ipv4_is_local_multicast(fl4->daddr) ||
2783: ipv4_is_lbcast(fl4->daddr) ||
2784: fl4->flowi4_proto == IPPROTO_IGMP) {
2785: if (!fl4->saddr)
2786: fl4->saddr = inet_select_addr(dev_out, 0,
2787: RT_SCOPE_LINK);
2788: goto make_route;
2789: }
2790: if (!fl4->saddr) {
2791: if (ipv4_is_multicast(fl4->daddr))
2792: fl4->saddr = inet_select_addr(dev_out, 0,
2793: fl4->flowi4_scope);
2794: else if (!fl4->daddr)
2795: fl4->saddr = inet_select_addr(dev_out, 0,
2796: RT_SCOPE_HOST);
2797: }
2798: }
2799:
2800: if (!fl4->daddr) {
2801: fl4->daddr = fl4->saddr;
2802: if (!fl4->daddr)
2803: fl4->daddr = fl4->saddr = htonl(INADDR_LOOPBACK);
2804: dev_out = net->loopback_dev;
2805: fl4->flowi4_oif = LOOPBACK_IFINDEX;
2806: res->type = RTN_LOCAL;
2807: flags |= RTCF_LOCAL;
2808: goto make_route;
2809: }
2810:
2811: err = fib_lookup(net, fl4, res, 0);
2812: if (err) {
2813: res->fi = NULL;
2814: res->table = NULL;
2815: if (fl4->flowi4_oif &&
2816: (ipv4_is_multicast(fl4->daddr) || !fl4->flowi4_l3mdev)) {
2817: /* Apparently, routing tables are wrong. Assume,
2818: * that the destination is on link.
2819: *
2820: * WHY? DW.
2821: * Because we are allowed to send to iface
2822: * even if it has NO routes and NO assigned
2823: * addresses. When oif is specified, routing
2824: * tables are looked up with only one purpose:
2825: * to catch if destination is gatewayed, rather than
2826: * direct. Moreover, if MSG_DONTROUTE is set,
2827: * we send packet, ignoring both routing tables
2828: * and ifaddr state. --ANK
2829: *
2830: *
2831: * We could make it even if oif is unknown,
2832: * likely IPv6, but we do not.
2833: */
2834:
2835: if (fl4->saddr == 0)
2836: fl4->saddr = inet_select_addr(dev_out, 0,
2837: RT_SCOPE_LINK);
2838: res->type = RTN_UNICAST;
2839: goto make_route;
2840: }
2841: rth = ERR_PTR(err);
2842: goto out;
2843: }
2844:
2845: if (res->type == RTN_LOCAL) {
2846: if (!fl4->saddr) {
2847: if (res->fi->fib_prefsrc)
2848: fl4->saddr = res->fi->fib_prefsrc;
2849: else
2850: fl4->saddr = fl4->daddr;
2851: }
2852:
2853: /* L3 master device is the loopback for that domain */
2854: dev_out = l3mdev_master_dev_rcu(FIB_RES_DEV(*res)) ? :
2855: net->loopback_dev;
2856:
2857: /* make sure orig_oif points to fib result device even
2858: * though packet rx/tx happens over loopback or l3mdev
2859: */
2860: orig_oif = FIB_RES_OIF(*res);
2861:
2862: fl4->flowi4_oif = dev_out->ifindex;
2863: flags |= RTCF_LOCAL;
2864: goto make_route;
2865: }
2866:
2867: fib_select_path(net, res, fl4, skb);
2868:
2869: dev_out = FIB_RES_DEV(*res);
2870:
2871: make_route:
2872: rth = __mkroute_output(res, fl4, orig_oif, dev_out, flags);
2873:
2874: out:
2875: return rth;
2876: }
SourceFile:net/ipv4/route.c]
|
| 34/2 |
2026/07/18 00:59 |
llm |
expert |
0m
Model:
Tokens:
input: 34421
output: 26
thoughts: 3913
Thoughts: **Deadlock Deep Dive in IP Multicast Routing**
Okay, so I've been tracing through this `ip_route_output_key_hash_rcu` function, and it's pretty clear where this deadlock is occurring. It all boils down to how multicast routes are handled, particularly with IGMP and XFRM. If a source address (`fl4->saddr`) is zero, it sets it based on the output interface. That seems fine initially. The key is that `ip_check_mc_rcu` is called from `__mkroute_output` which gets called in a few places including the route lookup code.
The situation is this: when an IGMP packet hits an XFRM policy, it does a recursive route lookup. This lookup can trigger `ip_check_mc_rcu`, and if that happens *while* the `im->lock` (the multicast membership lock) is already held, we have a deadlock. The `im->lock` is held inside the IGMP code when a new IGMP message has to be sent out (e.g. `igmpv3_send_cr`).
The actual exploit involves user namespaces and a little bit of network configuration. An unprivileged user can create a user namespace, giving them `CAP_NET_ADMIN` privileges within that namespace. They can then configure an XFRM policy that's triggered by IGMP packets and encapsulates them in a tunnel to a multicast address. Then when they join a multicast group this starts the IGMP process. Then `igmpv3_send_cr` does its work and calls `ip_route_output_ports` while holding the `im->lock`. The XFRM policy then triggers a route lookup, which calls `ip_check_mc_rcu` and attempts to acquire `im->lock` again.
So, this is a clear denial-of-service, a classic deadlock. It's not exploitable for privilege escalation or memory corruption. It just hangs the CPU when the lock is held in a specific context. An unprivileged user _can_ trigger it, thanks to user namespaces giving `CAP_NET_ADMIN`. No, we're not talking about triggering this from a VM guest. I'm labeling it a network trigger since a malicious query could be sent. It's a bit specific, as an attacker would have to know if a relevant XFRM policy is set up.
The only way to hit this is through XFRM, because it's the only place a route lookup is initiated synchronously in the critical path. The other possible paths (e.g. eBPF) are not reachable due to the lack of privileges or because they are not synchronous.
|
| 35/2 |
2026/07/18 01:00 |
tool |
codesearch-find-references |
0m
Args: map[Name:ip_check_mc_rcu]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:__mkroute_output SourceFile:net/ipv4/route.c SourceLine:2600] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:ip_route_input_rcu SourceFile:net/ipv4/route.c SourceLine:2514] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:ip_route_input_rcu SourceFile:net/ipv4/route.c SourceLine:2523] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:udp_v4_early_demux SourceFile:net/ipv4/udp.c SourceLine:2786]] TruncatedOutput:false]
|
| 36/2 |
2026/07/18 01:00 |
llm |
expert |
0m
Model:
Tokens:
input: 39171
output: 41
thoughts: 1988
Thoughts: **Analysis of a Potential Kernel Deadlock in IGMP Handling**
Okay, let's break this down. We've got a potential deadlock situation here within the Linux kernel, specifically related to IGMP (Internet Group Management Protocol) traffic and XFRM (ipsec/netfilter). The root cause seems to stem from a specific sequence of events triggered by how IGMP reports are handled in conjunction with IPsec policies.
I see that calls to `ip_check_mc_rcu` from `ip_route_input_rcu` and `udp_v4_early_demux` aren't relevant, and that the problematic call path goes through `ip_route_output_flow` and down a chain of function calls involving XFRM: `xfrm_lookup_route`, `xfrm_tmpl_resolve_one`, `xfrm_get_saddr`, `__xfrm4_dst_lookup`, `__ip_route_output_key`, `ip_route_output_key_hash_rcu`, `__mkroute_output`, eventually ending up at `ip_check_mc_rcu`.
The crux of the issue appears to be how the `params.ipproto` field gets handled within `xfrm_tmpl_resolve_one`. It's getting zeroed out via `memset`, which means the protocol is lost during the XFRM route lookup. This is vital, because when `params.ipproto` is zero, `fl4->flowi4_proto` becomes zero, and that's when `ip_check_mc_rcu` is called with `proto = 0`. Because the protocol is not IGMP, the lock is taken.
Now, that's where the potential deadlock comes in. Since the call to `ip_check_mc_rcu` is done in the XFRM path, that's where the problem is.
Now, how can this occur? An IGMP query triggers an IGMP report. The IGMP report hits an XFRM policy. This forces an XFRM route lookup, which, because of the zeroed protocol, potentially leads to a call to `ip_check_mc_rcu` with `proto = 0`. The lock can be taken there. The lock is probably the `pmc->lock` in `ip_check_mc_rcu`.
So, can this be triggered? The answer is yes. Since `spin_lock_bh` disables softirqs, the system will experience a hard lockup that will be detected by the watchdog and then panic the system. This becomes a system-wide denial-of-service, a pretty nasty bug.
We've got to consider the triggers. A network trigger is definitely possible, an attacker can trigger this by sending an IGMP query.
Is this exploitable remotely? No, it's just a deadlock. Is it denial of service? Yes, the system will panic. Is it unprivileged? No, `CAP_NET_ADMIN` is required to configure XFRM policies. Can this be triggered in a user namespace? Yes, with `CAP_NET_ADMIN` in the user namespace, an unprivileged user can create a network namespace and configure XFRM. Is this a network or remote trigger? Yes, any network interface receiving the IGMP query and configured with the XFRM policy will trigger the issue.
What's a realistic XFRM configuration that might be affected? Tunneling multicast traffic over IPsec is a known use case. If such a policy is configured, any received IGMP query will trigger an IGMP report and cause a DoS.
So we now know the exploitability characteristics:
* Exploitable: False
* DenialOfService: True
* Unprivileged: False
* UserNamespace: True
* VMGuestTrigger: False
* VMHostTrigger: False
* NetworkTrigger: True
* RemoteTrigger: False
* PeripheralTrigger: False
* FilesystemTrigger: False
One might wonder if this could occur with IPIP tunnels instead of XFRM, but it's not the same, since the call to `ip_check_mc_rcu` does not happen while `pmc->lock` is held. So we need the recursive route lookup *inside* `ip_route_output_ports`. XFRM is the only one.
|
| 37/2 |
2026/07/18 01:00 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/ipv4/igmp.c Name:igmpv3_send_report]
Results: map[SourceCode: 618: static int igmpv3_send_report(struct in_device *in_dev, struct ip_mc_list *pmc)
619: {
620: struct sk_buff *skb = NULL;
621: struct net *net = dev_net(in_dev->dev);
622: int type;
623:
624: if (!pmc) {
625: rcu_read_lock();
626: for_each_pmc_rcu(in_dev, pmc) {
627: if (pmc->multiaddr == IGMP_ALL_HOSTS)
628: continue;
629: if (ipv4_is_local_multicast(pmc->multiaddr) &&
630: !READ_ONCE(net->ipv4.sysctl_igmp_llm_reports))
631: continue;
632: spin_lock_bh(&pmc->lock);
633: if (pmc->sfcount[MCAST_EXCLUDE])
634: type = IGMPV3_MODE_IS_EXCLUDE;
635: else
636: type = IGMPV3_MODE_IS_INCLUDE;
637: skb = add_grec(skb, pmc, type, 0, 0);
638: spin_unlock_bh(&pmc->lock);
639: }
640: rcu_read_unlock();
641: } else {
642: spin_lock_bh(&pmc->lock);
643: if (pmc->sfcount[MCAST_EXCLUDE])
644: type = IGMPV3_MODE_IS_EXCLUDE;
645: else
646: type = IGMPV3_MODE_IS_INCLUDE;
647: skb = add_grec(skb, pmc, type, 0, 0);
648: spin_unlock_bh(&pmc->lock);
649: }
650: if (!skb)
651: return 0;
652: return igmpv3_sendpack(skb);
653: }
SourceFile:net/ipv4/igmp.c]
|
| 38/2 |
2026/07/18 01:00 |
llm |
expert |
0m
Model:
Tokens:
input: 42786
output: 66
thoughts: 1370
Thoughts: **Deadlock Analysis in IGMPv3 Report Generation**
Okay, so here's the breakdown as I see it. It looks like `igmpv3_send_report` holds `pmc->lock` when calling `add_grec`, and we already knew that `igmpv3_send_cr` does the same. The critical path here is that `add_grec` calls `add_grhead` which calls `igmpv3_newpack`, and within `igmpv3_newpack`, we have a route lookup. Since we're holding `pmc->lock` during this route lookup, any IGMPv3 report generation can trigger a deadlock *if* XFRM policy is configured. We generate these reports when a user joins a group, a timer expires, or an IGMP query is received.
So, yes, a remote attacker *could* trigger this with a crafted IGMP query if the XFRM policy is present. A local user, with `CAP_NET_ADMIN` (or within a user namespace with those capabilities), could join a multicast group and cause this too.
The syzbot report confirms it can be triggered by a timer expiration. However, `igmpv3_send_report` could also trigger it.
The interesting part is that `ip_check_mc_rcu` *only* takes a lock when the protocol isn't IGMP. It checks `flowi4_proto`. If XFRM preserved the IGMP protocol during the route lookup, `ip_check_mc_rcu` wouldn't take the lock, and the deadlock wouldn't happen. But, here's the rub - if we're tunneling an IGMP packet with XFRM, the outer packet will have a protocol like ESP/AH/UDP (not IGMP). This means `ip_check_mc_rcu` will *always* take the lock during the outer packet's route lookup! And since the tunnel destination is the same multicast address as the original IGMP packet, we end up with the same `im` object being accessed and a deadlock.
Wait, the fact the tunnel destination is the *same* multicast address as the IGMP packet means someone has configured an XFRM tunnel *to* a multicast address, right? Is that even valid? Yep, it is. So an IGMP report for a multicast address is tunneled *to that same multicast address*! The route lookup for the tunnel destination ends up calling `ip_check_mc_rcu`, which tries to take `im->lock`. But, `im->lock` is already held by the IGMP report sending process. Boom! Deadlock!
The fundamental problem is that `igmpv3_newpack` is doing a route lookup *while* holding `pmc->lock`, and that lookup can trigger XFRM, which triggers another route lookup (and thus, a lock). This is a classic lock inversion/recursive locking scenario.
As for exploitability, no privilege escalation is possible β it's just a CPU spinlock. No memory corruption, no information leak. Exploitable is False.
However, it *is* a Denial of Service: a hard CPU lockup. It requires `CAP_NET_ADMIN` so it's not unprivileged. User namespaces with `CAP_NET_ADMIN` are sufficient. No VM guest or host trigger, and it can be triggered over the network by an attacker with crafted IGMP queries if such an XFRM policy is configured. No remote, peripheral, or filesystem triggers are involved. My assessment is sound.
|
| 39/2 |
2026/07/18 01:00 |
tool |
set-results |
0m
Args: map[DenialOfService:true Exploitable:false FilesystemTrigger:false NetworkTrigger:true PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:true Exploitable:false FilesystemTrigger:false NetworkTrigger:true PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
|
| 40/2 |
2026/07/18 01:00 |
llm |
expert |
0m
Model:
Tokens:
input: 45064
output: 654
thoughts: 357
Thoughts: **My Analysis of the IGMPv3 Deadlock**
Alright, the results are in, and it's time to craft the final response. My understanding of this issue is as follows:
The root cause centers around a recursive spinlock deadlock. Specifically, the function `igmpv3_send_cr` (or `igmpv3_send_report`) holds `pmc->lock`, which is equivalent to `im->lock`, while subsequently calling a chain of functions: `add_grec` -> `add_grhead` -> `igmpv3_newpack`. This is the starting point.
The critical piece here is that `igmpv3_newpack` then performs a route lookup using `ip_route_output_ports`. This is where the potential for conflict arises.
If an XFRM policy, particularly one configured in tunnel mode (likely IPsec), is active, this route lookup triggers XFRM policy resolution, specifically `xfrm_lookup_route`. XFRM then proceeds to perform a *secondary* route lookup for the tunnel destination address.
Now, if that tunnel destination happens to be the same multicast address, `__mkroute_output` gets called, which, in turn, calls `ip_check_mc_rcu` on that multicast address. Here's where it all falls apart.
`ip_check_mc_rcu` attempts to acquire `im->lock` again to check source filters. This is where the recursive locking occurs: `im->lock` is already held by `igmpv3_send_cr`, causing the spinlock deadlock.
The end result is a hard lockup on the CPU, a denial of service. While it's bad news, at least it doesn't appear to be exploitable for privilege escalation or memory corruption.
From a practical perspective, an unprivileged user can't trigger this directly, but they *can* create a user/network namespace to gain `CAP_NET_ADMIN` privileges. With those, they could configure the specific XFRM policy that leads to the deadlock.
Interestingly, this *can* also be triggered remotely if a system administrator has, inadvertently or intentionally, configured such an XFRM policy. An incoming IGMP query would cause the kernel to generate an IGMP report, which would then trigger the deadlock. Thus, an attack is possible given the right conditions.
|
| 41/1 |
2026/07/18 01:01 |
action |
format-explanation |
0m
Results: map[Explanation:**Analysis of the Bug**
**Root Cause:**
The bug is a recursive locking deadlock in the IPv4 IGMP subsystem.
When the kernel needs to send an IGMPv3 state-change report (e.g., when a
multicast timer expires or in response to an IGMP query), `igmpv3_send_cr()` or
`igmpv3_send_report()` is called. These functions iterate over the multicast
groups and acquire a spinlock on the multicast group state
(`spin_lock_bh(&pmc->lock)`).
While holding this lock, the code calls `add_grec()` -> `add_grhead()` ->
`igmpv3_newpack()` to allocate and prepare the network packet.
`igmpv3_newpack()` performs a synchronous route lookup via
`ip_route_output_ports()`.
If an IPsec (XFRM) policy is configured in tunnel mode that matches this IGMP
traffic, the route lookup is intercepted by `xfrm_lookup_route()`. XFRM then
performs a secondary route lookup for the outer tunnel destination address. If
the user configured the XFRM tunnel destination to be the *same* multicast
address as the IGMP group, this secondary route lookup will eventually call
`ip_check_mc_rcu()` to validate the multicast source address.
Because the outer tunnel protocol is not recognized as `IPPROTO_IGMP` (or
because the protocol information is lost during the XFRM resolution),
`ip_check_mc_rcu()` attempts to acquire the multicast group lock
(`spin_lock_bh(&im->lock)`) to check the source filters. Since the lock is
already held by the exact same CPU in `igmpv3_send_cr()`, the CPU deadlocks,
spinning infinitely.
**Security Impact:**
* **Exploitable:** False. This is a pure recursive spinlock deadlock. It does
not cause memory corruption, out-of-bounds accesses, use-after-free, or
information leaks. It cannot be used for privilege escalation.
* **Denial Of Service:** True. The bug causes a CPU to hang in an infinite loop
with softirqs disabled, leading to a hard lockup and a system-wide Denial of
Service.
* **Accessible From Unprivileged Processes:** False. Triggering the bug requires
configuring an XFRM policy, which requires `CAP_NET_ADMIN` in the network
namespace.
* **Accessible From User Namespaces:** True. An unprivileged user can create a
new user namespace and a new network namespace. Within this namespace, they
possess `CAP_NET_ADMIN` and can configure the malicious XFRM policy, join a
multicast group, and trigger the deadlock to crash the host system.
* **Network Trigger:** True. If a system administrator has legitimately
configured an XFRM policy that tunnels multicast traffic to a multicast address,
an external attacker on the local network can send an IGMP query. This query
forces the victim's kernel to generate an IGMP report, hitting the XFRM policy
and triggering the deadlock remotely.]
|