| 0/0 |
2026/06/15 00:02 |
flow |
assessment-security |
58m
Results: map[DenialOfService:false Explanation:**Analysis and Reasoning:**
The bug is a shift-out-of-bounds in `addr4_match()` reported by UBSAN. It occurs
when `prefixlen` is greater than 32.
This situation arises when an IPv6 XFRM selector (which can have a `prefixlen`
up to 128) is matched against an IPv4 flow. The `xfrm_selector_match()` function
uses the flow's address family (`AF_INET` for IPv4) to determine which matching
function to call. Consequently, it calls `__xfrm4_selector_match()` even if the
selector was originally configured for IPv6. This leads to `addr4_match()` being
called with a `prefixlen` > 32, resulting in a negative shift exponent (`32 -
prefixlen`).
In C, shifting by a negative amount is undefined behavior. However, on
architectures like x86, the CPU's shift instruction simply masks the shift count
(e.g., bitwise AND with 63), which results in a bogus mask value (frequently 0).
**Exploitability & Denial of Service:**
Since `addr4_match()` takes its address arguments by value (`__be32`), there is
no out-of-bounds memory access or memory corruption involved. The bug only
results in a logic error—specifically, an IPsec policy bypass where an IPv4
packet might be incorrectly accepted by an IPv6 selector. It does not cause
memory corruption, privilege escalation, or an information leak. Furthermore,
because this is a UBSAN report, it will not cause a kernel panic, warning, or
crash in production builds where UBSAN is disabled. The execution simply
continues with the bogus mask. Thus, it is neither exploitable nor a
Denial-of-Service (DoS) vector.
**Triggers:**
The bug is triggered during the processing of inbound IPsec/VTI network packets
that are decapsulated and checked against the XFRM policy. Thus, it is reachable
via an **Ethernet Network Trigger**.
To set up the prerequisite XFRM state with an IPv6 selector, a user needs
`CAP_NET_ADMIN` capabilities. While an unprivileged user cannot do this
directly, they can acquire the necessary capabilities by creating a new network
and user namespace. Therefore, the bug is accessible from **User Namespaces**. Exploitable:false FilesystemTrigger:false NetworkTrigger:true PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/06/15 00:02 |
action |
syz-repro-to-c-repro |
0m
Results:
|
| 2/1 |
2026/06/15 00:02 |
action |
kernel-checkouter |
1m
Results: map[KernelSrc:/app/workdir/cache/src/6d9309086d56b581ff799bc87c332100f162164d]
|
| 3/1 |
2026/06/15 00:03 |
action |
kernel-builder |
30m
Results: map[KernelObj:/app/workdir/cache/build/47f37d43e779b283ac9e05849aeac4af9a11e7d5]
|
| 4/1 |
2026/06/15 00:34 |
action |
codesearch-prepare |
24m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/06/15 00:58 |
agent |
expert |
2m
Model:
Results: map[DenialOfService:false 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:
------------[ cut here ]------------
UBSAN: shift-out-of-bounds in ./include/net/xfrm.h:970:23
shift exponent -96 is negative
CPU: 1 UID: 0 PID: 12115 Comm: syz.5.2221 Tainted: G L syzkaller #0 PREEMPT(full)
Tainted: [L]=SOFTLOCKUP
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 05/09/2026
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
ubsan_epilogue+0xa/0x30 lib/ubsan.c:233
__ubsan_handle_shift_out_of_bounds+0x36d/0x400 lib/ubsan.c:494
addr4_match include/net/xfrm.h:970 [inline]
__xfrm4_selector_match net/xfrm/xfrm_policy.c:222 [inline]
xfrm_selector_match+0xd5c/0x1140 net/xfrm/xfrm_policy.c:247
__xfrm_policy_check+0x5d1/0x37b0 net/xfrm/xfrm_policy.c:3715
__xfrm_policy_check2 include/net/xfrm.h:1302 [inline]
xfrm_policy_check+0x475/0x880 include/net/xfrm.h:1307
vti_rcv_cb+0x3b6/0x770 net/ipv4/ip_vti.c:135
tunnel4_rcv_cb+0xd6/0x230 net/ipv4/tunnel4.c:124
xfrm_rcv_cb+0x1c7/0x310 net/xfrm/xfrm_input.c:117
xfrm_input+0x4738/0x7760 net/xfrm/xfrm_input.c:729
vti_input+0x21f/0x330 net/ipv4/ip_vti.c:69
tunnel4_rcv+0xdd/0x2d0 net/ipv4/tunnel4.c:103
ip_protocol_deliver_rcu+0x2dc/0x440 net/ipv4/ip_input.c:207
ip_local_deliver_finish+0x3bb/0x6f0 net/ipv4/ip_input.c:241
NF_HOOK+0x336/0x3c0 include/linux/netfilter.h:318
NF_HOOK+0x336/0x3c0 include/linux/netfilter.h:318
__netif_receive_skb_one_core net/core/dev.c:6202 [inline]
__netif_receive_skb net/core/dev.c:6315 [inline]
netif_receive_skb_internal net/core/dev.c:6401 [inline]
netif_receive_skb+0x45b/0xbf0 net/core/dev.c:6460
tun_rx_batched+0x1de/0x790 drivers/net/tun.c:1487
tun_get_user+0x2b04/0x4350 drivers/net/tun.c:1955
tun_chr_write_iter+0x113/0x200 drivers/net/tun.c:2001
new_sync_write fs/read_write.c:595 [inline]
vfs_write+0x612/0xba0 fs/read_write.c:688
ksys_write+0x150/0x270 fs/read_write.c:740
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:0x7fca8819ce59
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:00007fca890f6028 EFLAGS: 00000246 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 00007fca88416090 RCX: 00007fca8819ce59
RDX: 0000000000000fce RSI: 0000200000000840 RDI: 0000000000000003
RBP: 00007fca88232d6f R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007fca88416128 R14: 00007fca88416090 R15: 00007fca8853fa48
</TASK>
---[ end trace ]---
Reply: **Analysis and Reasoning:**
The bug is a shift-out-of-bounds in `addr4_match()` reported by UBSAN. It occurs when `prefixlen` is greater than 32.
This situation arises when an IPv6 XFRM selector (which can have a `prefixlen` up to 128) is matched against an IPv4 flow. The `xfrm_selector_match()` function uses the flow's address family (`AF_INET` for IPv4) to determine which matching function to call. Consequently, it calls `__xfrm4_selector_match()` even if the selector was originally configured for IPv6. This leads to `addr4_match()` being called with a `prefixlen` > 32, resulting in a negative shift exponent (`32 - prefixlen`).
In C, shifting by a negative amount is undefined behavior. However, on architectures like x86, the CPU's shift instruction simply masks the shift count (e.g., bitwise AND with 63), which results in a bogus mask value (frequently 0).
**Exploitability & Denial of Service:**
Since `addr4_match()` takes its address arguments by value (`__be32`), there is no out-of-bounds memory access or memory corruption involved. The bug only results in a logic error—specifically, an IPsec policy bypass where an IPv4 packet might be incorrectly accepted by an IPv6 selector. It does not cause memory corruption, privilege escalation, or an information leak. Furthermore, because this is a UBSAN report, it will not cause a kernel panic, warning, or crash in production builds where UBSAN is disabled. The execution simply continues with the bogus mask. Thus, it is neither exploitable nor a Denial-of-Service (DoS) vector.
**Triggers:**
The bug is triggered during the processing of inbound IPsec/VTI network packets that are decapsulated and checked against the XFRM policy. Thus, it is reachable via an **Ethernet Network Trigger**.
To set up the prerequisite XFRM state with an IPv6 selector, a user needs `CAP_NET_ADMIN` capabilities. While an unprivileged user cannot do this directly, they can acquire the necessary capabilities by creating a new network and user namespace. Therefore, the bug is accessible from **User Namespaces**.
|
| 6/2 |
2026/06/15 00:58 |
llm |
expert |
0m
Model:
Tokens:
input: 7232
output: 36
thoughts: 69
Thoughts: Here's my analysis of the situation:
**Investigating a UBSAN Shift-Out-of-Bounds Error**
Alright, I've got a UBSAN report flagging a shift-out-of-bounds issue. The error is specifically in `include/net/xfrm.h` at line 970, and the root cause appears to be a negative shift exponent of -96. My immediate thought is to zero in on that section of the `xfrm.h` header and understand the context. A negative shift is a definite red flag; it should never happen, so there's likely a logic error in how the shift operation is being constructed or utilized. I need to figure out what variable is being shifted and by what, and then trace back the code to pinpoint where the negative value is coming from. Time to dig in and unravel this!
|
| 7/2 |
2026/06/15 00:58 |
tool |
read-file |
0m
Args: map[File:include/net/xfrm.h FirstLine:950 LineCount:50]
Results: map[Contents: 950: if (memcmp(a1, a2, pdw << 2))
951: return false;
952:
953: if (pbi) {
954: __be32 mask;
955:
956: mask = htonl((0xffffffff) << (32 - pbi));
957:
958: if ((a1[pdw] ^ a2[pdw]) & mask)
959: return false;
960: }
961:
962: return true;
963: }
964:
965: static inline bool addr4_match(__be32 a1, __be32 a2, u8 prefixlen)
966: {
967: /* C99 6.5.7 (3): u32 << 32 is undefined behaviour */
968: if (sizeof(long) == 4 && prefixlen == 0)
969: return true;
970: return !((a1 ^ a2) & htonl(~0UL << (32 - prefixlen)));
971: }
972:
973: static __inline__
974: __be16 xfrm_flowi_sport(const struct flowi *fl, const union flowi_uli *uli)
975: {
976: __be16 port;
977: switch(fl->flowi_proto) {
978: case IPPROTO_TCP:
979: case IPPROTO_UDP:
980: case IPPROTO_UDPLITE:
981: case IPPROTO_SCTP:
982: port = uli->ports.sport;
983: break;
984: case IPPROTO_ICMP:
985: case IPPROTO_ICMPV6:
986: port = htons(uli->icmpt.type);
987: break;
988: case IPPROTO_MH:
989: port = htons(uli->mht.type);
990: break;
991: case IPPROTO_GRE:
992: port = htons(ntohl(uli->gre_key) >> 16);
993: break;
994: default:
995: port = 0; /*XXX*/
996: }
997: return port;
998: }
999:
]
|
| 8/2 |
2026/06/15 00:58 |
llm |
expert |
0m
Model:
Tokens:
input: 8226
output: 39
thoughts: 185
Thoughts: **Debugging a Bizarre Prefix Length Issue**
Okay, so I'm looking at this line of code, line 970: `return !((a1 ^ a2) & htonl(~0UL << (32 - prefixlen)));`. The error message is screaming about a negative shift exponent, `-96`. That immediately flags something off; that suggests `32 - prefixlen` is equal to `-96`, meaning `prefixlen` is 128. That's clearly impossible, *prefixlen* is a `u8`, an unsigned 8-bit integer! It can't be greater than 255, let alone 128. It's obviously an integer overflow issue, given that 32 - 128 = -96 and we're dealing with unsigned numbers.
The function in question is `addr4_match(__be32 a1, __be32 a2, u8 prefixlen)`. And I know it's being called from `__xfrm4_selector_match` in `net/xfrm/xfrm_policy.c:222`.
Let's quickly check what's going on around line 222 in that file. I need to understand what's passing into this function and where the value for `prefixlen` is coming from. There's almost certainly a bug upstream, either in the calculation of `prefixlen` or in how it's being handled before this call. I'm willing to bet this is the tip of an iceberg.
|
| 9/2 |
2026/06/15 00:58 |
tool |
read-file |
0m
Args: map[File:net/xfrm/xfrm_policy.c FirstLine:200 LineCount:50]
Results: map[Contents: 200: u8 type, u16 family, u8 dir, u32 if_id);
201: static struct xfrm_policy *
202: xfrm_policy_insert_list(struct hlist_head *chain, struct xfrm_policy *policy,
203: bool excl);
204:
205: static bool
206: xfrm_policy_find_inexact_candidates(struct xfrm_pol_inexact_candidates *cand,
207: struct xfrm_pol_inexact_bin *b,
208: const xfrm_address_t *saddr,
209: const xfrm_address_t *daddr);
210:
211: static inline bool xfrm_pol_hold_rcu(struct xfrm_policy *policy)
212: {
213: return refcount_inc_not_zero(&policy->refcnt);
214: }
215:
216: static inline bool
217: __xfrm4_selector_match(const struct xfrm_selector *sel, const struct flowi *fl)
218: {
219: const struct flowi4 *fl4 = &fl->u.ip4;
220:
221: return addr4_match(fl4->daddr, sel->daddr.a4, sel->prefixlen_d) &&
222: addr4_match(fl4->saddr, sel->saddr.a4, sel->prefixlen_s) &&
223: !((xfrm_flowi_dport(fl, &fl4->uli) ^ sel->dport) & sel->dport_mask) &&
224: !((xfrm_flowi_sport(fl, &fl4->uli) ^ sel->sport) & sel->sport_mask) &&
225: (fl4->flowi4_proto == sel->proto || !sel->proto) &&
226: (fl4->flowi4_oif == sel->ifindex || !sel->ifindex);
227: }
228:
229: static inline bool
230: __xfrm6_selector_match(const struct xfrm_selector *sel, const struct flowi *fl)
231: {
232: const struct flowi6 *fl6 = &fl->u.ip6;
233:
234: return addr_match(&fl6->daddr, &sel->daddr, sel->prefixlen_d) &&
235: addr_match(&fl6->saddr, &sel->saddr, sel->prefixlen_s) &&
236: !((xfrm_flowi_dport(fl, &fl6->uli) ^ sel->dport) & sel->dport_mask) &&
237: !((xfrm_flowi_sport(fl, &fl6->uli) ^ sel->sport) & sel->sport_mask) &&
238: (fl6->flowi6_proto == sel->proto || !sel->proto) &&
239: (fl6->flowi6_oif == sel->ifindex || !sel->ifindex);
240: }
241:
242: bool xfrm_selector_match(const struct xfrm_selector *sel, const struct flowi *fl,
243: unsigned short family)
244: {
245: switch (family) {
246: case AF_INET:
247: return __xfrm4_selector_match(sel, fl);
248: case AF_INET6:
249: return __xfrm6_selector_match(sel, fl);
]
|
| 10/2 |
2026/06/15 00:58 |
llm |
expert |
0m
Model:
Tokens:
input: 9790
output: 40
thoughts: 203
Thoughts: **Debugging an IPv4 Prefix Length Issue**
Okay, so I've stumbled upon something a bit odd. The problem seems to stem from a mismatch between the address family and the prefix length within a selector, specifically with either `sel->prefixlen_s` or `sel->prefixlen_d` being set to 128 while the family is `AF_INET`, meaning IPv4. Now, for IPv4, a prefix length of 128 is completely nonsensical; it should never exceed 32. This leads to an immediate red flag: `32 - 128 = -96`, a negative value that's going to cause a shift operation to go haywire and likely results in an out-of-bounds access.
My immediate question is: how on earth did this IPv4 selector end up with a prefix length of 128? There are a couple of possibilities that spring to mind. First, it's very possible this selector was misconfigured from userspace, perhaps via netlink. The flexibility of `xfrm_selector` does allow configuration from userspace, so it's a likely culprit. Or, perhaps, it’s a case of an IPv6 selector incorrectly being matched against an IPv4 flow, or the other way around. This kind of cross-family confusion is something I've seen before.
Now, to dig deeper, I need to understand the call stack. Let's see... the `xfrm_selector_match` function, where this mismatch is likely causing issues, is being called. A quick check of the codebase reveals that `xfrm_selector_match` is invoked from `__xfrm_policy_check`, which is located in `net/xfrm/xfrm_policy.c` at line 3715. That should be a good place to start debugging, and tracing the origin of this selector data.
|
| 11/2 |
2026/06/15 00:58 |
tool |
read-file |
0m
Args: map[File:net/xfrm/xfrm_policy.c FirstLine:3700 LineCount:50]
Results: map[Contents:3700: XFRM_INC_STATS(net, LINUX_MIB_XFRMINHDRERROR);
3701: return 0;
3702: }
3703:
3704: nf_nat_decode_session(skb, &fl, family);
3705:
3706: /* First, check used SA against their selectors. */
3707: sp = skb_sec_path(skb);
3708: if (sp) {
3709: int i;
3710:
3711: for (i = sp->len - 1; i >= 0; i--) {
3712: struct xfrm_state *x = sp->xvec[i];
3713: int ret = 0;
3714:
3715: if (!xfrm_selector_match(&x->sel, &fl, family)) {
3716: ret = 1;
3717: if (x->props.flags & XFRM_STATE_ICMP &&
3718: xfrm_selector_inner_icmp_match(skb, family, &x->sel, &fl))
3719: ret = 0;
3720: if (ret) {
3721: XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATEMISMATCH);
3722: return 0;
3723: }
3724: }
3725: }
3726: }
3727:
3728: pol = NULL;
3729: sk = sk_to_full_sk(sk);
3730: if (sk && sk->sk_policy[dir]) {
3731: pol = xfrm_sk_policy_lookup(sk, dir, &fl, family, if_id);
3732: if (IS_ERR(pol)) {
3733: XFRM_INC_STATS(net, LINUX_MIB_XFRMINPOLERROR);
3734: return 0;
3735: }
3736: }
3737:
3738: if (!pol)
3739: pol = xfrm_policy_lookup(net, &fl, family, dir, if_id);
3740:
3741: if (IS_ERR(pol)) {
3742: XFRM_INC_STATS(net, LINUX_MIB_XFRMINPOLERROR);
3743: return 0;
3744: }
3745:
3746: if (!pol && dir == XFRM_POLICY_FWD)
3747: pol = xfrm_in_fwd_icmp(skb, &fl, family, if_id);
3748:
3749: if (!pol) {
]
|
| 12/2 |
2026/06/15 00:58 |
llm |
expert |
0m
Model:
Tokens:
input: 11211
output: 39
thoughts: 235
Thoughts: **Analyzing Potential IP Family Mismatches in XFRM Policy Checks**
Okay, so I'm looking at this `__xfrm_policy_check` function, and the `family` argument is bugging me. They're passing it in, and then it's used with `xfrm_selector_match` along with the `xfrm_state` selector. The `xfrm_state` comes from `skb_sec_path(skb)`. Now, the `family` value corresponds to the packet's IP family – `AF_INET` for IPv4, etc. But the selector, `x->sel`, of that `xfrm_state` seems potentially independent.
The question is, can we end up in a situation where we're matching an IPv4 packet against a state created for IPv6, or vice versa? For example, if the `xfrm_state` selector was built for an IPv6 prefix of length 128, could that somehow get applied to an IPv4 packet? I know the `xfrm_input` path in the network stack is where these `xfrm_state` structures get attached to the `skb`. So the next step is to examine the `xfrm_input` function in `net/xfrm/xfrm_input.c`. That's where I need to focus my attention to understand how the association between the packet's IP family and the `xfrm_state` is actually handled.
|
| 13/2 |
2026/06/15 00:58 |
tool |
read-file |
0m
Args: map[File:net/xfrm/xfrm_input.c FirstLine:700 LineCount:50]
Results: map[Contents: 700: if (err == -EINPROGRESS) {
701: if (async)
702: dev_put(skb->dev);
703: return 0;
704: } else if (err) {
705: XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATEMODEERROR);
706: goto drop;
707: }
708: resume_decapped:
709: if (x->outer_mode.flags & XFRM_MODE_FLAG_TUNNEL) {
710: decaps = 1;
711: break;
712: }
713:
714: /*
715: * We need the inner address. However, we only get here for
716: * transport mode so the outer address is identical.
717: */
718: daddr = &x->id.daddr;
719: family = x->props.family;
720:
721: err = xfrm_parse_spi(skb, nexthdr, &spi, &seq);
722: if (err < 0) {
723: XFRM_INC_STATS(net, LINUX_MIB_XFRMINHDRERROR);
724: goto drop;
725: }
726: crypto_done = false;
727: } while (!err);
728:
729: err = xfrm_rcv_cb(skb, family, x->type->proto, 0);
730: if (err)
731: goto drop;
732:
733: nf_reset_ct(skb);
734:
735: if (decaps) {
736: sp = skb_sec_path(skb);
737: if (sp)
738: sp->olen = 0;
739: if (skb_valid_dst(skb))
740: skb_dst_drop(skb);
741: if (async)
742: dev_put(skb->dev);
743: gro_cells_receive(&gro_cells, skb);
744: return 0;
745: } else {
746: xo = xfrm_offload(skb);
747: if (xo)
748: xfrm_gro = xo->flags & XFRM_GRO;
749:
]
|
| 14/2 |
2026/06/15 00:58 |
llm |
expert |
0m
Model:
Tokens:
input: 12526
output: 39
thoughts: 170
Thoughts: **Analyzing the Network Packet Processing Flow**
Okay, so I'm tracing through `xfrm_input`, and I see that it calls `xfrm_rcv_cb(skb, family, x->type->proto, 0)`. The `family` argument is the inner family of the packet, which is standard. But hold on, I recall that `family` is updated *within* `xfrm_input` itself. Let's look: `family = x->props.family;`. Ah, right, so `x->props.family` represents the family of the `xfrm_state`. If that state's family is, say, `AF_INET`, then `family` in the call to `xfrm_rcv_cb` *also* becomes `AF_INET`.
But then it clicks, doesn't it? If that is true, then `xfrm_rcv_cb` will call `tunnel4_rcv_cb` or `tunnel6_rcv_cb`. It seems there's some potential confusion on exactly how the `family` is used and then passed down the stack, so I'd better double-check the code in `xfrm_rcv_cb` to make sure I understand the full picture.
|
| 15/2 |
2026/06/15 00:58 |
tool |
read-file |
0m
Args: map[File:net/xfrm/xfrm_input.c FirstLine:100 LineCount:50]
Results: map[Contents: 100: afinfo = rcu_dereference(xfrm_input_afinfo[is_ipip][family]);
101: if (unlikely(!afinfo))
102: rcu_read_unlock();
103: return afinfo;
104: }
105:
106: static int xfrm_rcv_cb(struct sk_buff *skb, unsigned int family, u8 protocol,
107: int err)
108: {
109: bool is_ipip = (protocol == IPPROTO_IPIP || protocol == IPPROTO_IPV6);
110: const struct xfrm_input_afinfo *afinfo;
111: int ret;
112:
113: afinfo = xfrm_input_get_afinfo(family, is_ipip);
114: if (!afinfo)
115: return -EAFNOSUPPORT;
116:
117: ret = afinfo->callback(skb, protocol, err);
118: rcu_read_unlock();
119:
120: return ret;
121: }
122:
123: struct sec_path *secpath_set(struct sk_buff *skb)
124: {
125: struct sec_path *sp, *tmp = skb_ext_find(skb, SKB_EXT_SEC_PATH);
126:
127: sp = skb_ext_add(skb, SKB_EXT_SEC_PATH);
128: if (!sp)
129: return NULL;
130:
131: if (tmp) /* reused existing one (was COW'd if needed) */
132: return sp;
133:
134: /* allocated new secpath */
135: memset(sp->ovec, 0, sizeof(sp->ovec));
136: sp->olen = 0;
137: sp->len = 0;
138: sp->verified_cnt = 0;
139:
140: return sp;
141: }
142: EXPORT_SYMBOL(secpath_set);
143:
144: /* Fetch spi and seq from ipsec header */
145:
146: int xfrm_parse_spi(struct sk_buff *skb, u8 nexthdr, __be32 *spi, __be32 *seq)
147: {
148: int offset, offset_seq;
149: int hlen;
]
|
| 16/2 |
2026/06/15 00:58 |
llm |
expert |
0m
Model:
Tokens:
input: 13755
output: 37
thoughts: 69
Thoughts: Here's my take on those thoughts:
**Dissecting the XFRM Receive Path**
Okay, so `xfrm_rcv_cb` is the crucial function here, and it's calling `afinfo->callback`. That's the key. I need to figure out which callback gets invoked based on the address family. Since the prompt specifies `family == AF_INET`, it's going to be something like `xfrm4_input_afinfo`... likely a structure related to IPv4 input processing. Now I need to track this down, it could be a pointer to a struct. To be sure, I should really check the IPv4 tunnel code to see how these callbacks are set up; specifically, I need to investigate `net/ipv4/tunnel4.c` and hunt for `tunnel4_rcv_cb`. It might be set as the `callback` within a specific `afinfo` structure, and if so, what is calling it.
|
| 17/2 |
2026/06/15 00:58 |
tool |
read-file |
0m
Args: map[File:net/ipv4/tunnel4.c FirstLine:100 LineCount:50]
Results: map[Contents: 100: goto drop;
101:
102: for_each_tunnel_rcu(tunnel4_handlers, handler)
103: if (!handler->handler(skb))
104: return 0;
105:
106: icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
107:
108: drop:
109: kfree_skb(skb);
110: return 0;
111: }
112:
113: #if IS_ENABLED(CONFIG_INET_XFRM_TUNNEL)
114: static int tunnel4_rcv_cb(struct sk_buff *skb, u8 proto, int err)
115: {
116: struct xfrm_tunnel __rcu *head;
117: struct xfrm_tunnel *handler;
118: int ret;
119:
120: head = (proto == IPPROTO_IPIP) ? tunnel4_handlers : tunnel64_handlers;
121:
122: for_each_tunnel_rcu(head, handler) {
123: if (handler->cb_handler) {
124: ret = handler->cb_handler(skb, err);
125: if (ret <= 0)
126: return ret;
127: }
128: }
129:
130: return 0;
131: }
132:
133: static const struct xfrm_input_afinfo tunnel4_input_afinfo = {
134: .family = AF_INET,
135: .is_ipip = true,
136: .callback = tunnel4_rcv_cb,
137: };
138: #endif
139:
140: #if IS_ENABLED(CONFIG_IPV6)
141: static int tunnel64_rcv(struct sk_buff *skb)
142: {
143: struct xfrm_tunnel *handler;
144:
145: if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
146: goto drop;
147:
148: for_each_tunnel_rcu(tunnel64_handlers, handler)
149: if (!handler->handler(skb))
]
|
| 18/2 |
2026/06/15 00:58 |
llm |
expert |
0m
Model:
Tokens:
input: 14788
output: 39
thoughts: 121
Thoughts: **Analyzing the `tunnel4_rcv_cb` Callback and Its Handler**
Okay, so `tunnel4_rcv_cb` is calling `handler->cb_handler(skb, err)`. Standard callback pattern, nothing inherently alarming there. But I need to understand what's happening within that handler. Looking at the stack trace, I see `vti_rcv_cb` at `net/ipv4/ip_vti.c:135` is involved, and that's likely the key. The trace shows `tunnel4_rcv_cb` directly calling `vti_rcv_cb`. Let's dive into that `ip_vti.c` code at line 135 and understand what `vti_rcv_cb` is actually doing with the incoming `skb` and error code. I need to see what's being done in that handler to see what state it expects the `skb` and `err` to be in. Hopefully, there is more useful information there to work with.
|
| 19/2 |
2026/06/15 00:58 |
tool |
read-file |
0m
Args: map[File:net/ipv4/ip_vti.c FirstLine:115 LineCount:50]
Results: map[Contents: 115:
116: return 0;
117: }
118:
119: x = xfrm_input_state(skb);
120:
121: inner_mode = &x->inner_mode;
122:
123: if (x->sel.family == AF_UNSPEC) {
124: inner_mode = xfrm_ip2inner_mode(x, XFRM_MODE_SKB_CB(skb)->protocol);
125: if (inner_mode == NULL) {
126: XFRM_INC_STATS(dev_net(skb->dev),
127: LINUX_MIB_XFRMINSTATEMODEERROR);
128: return -EINVAL;
129: }
130: }
131:
132: family = inner_mode->family;
133:
134: skb->mark = be32_to_cpu(tunnel->parms.i_key);
135: ret = xfrm_policy_check(NULL, XFRM_POLICY_IN, skb, family);
136: skb->mark = orig_mark;
137:
138: if (!ret)
139: return -EPERM;
140:
141: skb_scrub_packet(skb, !net_eq(tunnel->net, dev_net(skb->dev)));
142: skb->dev = dev;
143: dev_sw_netstats_rx_add(dev, skb->len);
144:
145: return 0;
146: }
147:
148: static bool vti_state_check(const struct xfrm_state *x, __be32 dst, __be32 src)
149: {
150: xfrm_address_t *daddr = (xfrm_address_t *)&dst;
151: xfrm_address_t *saddr = (xfrm_address_t *)&src;
152:
153: /* if there is no transform then this tunnel is not functional.
154: * Or if the xfrm is not mode tunnel.
155: */
156: if (!x || x->props.mode != XFRM_MODE_TUNNEL ||
157: x->props.family != AF_INET)
158: return false;
159:
160: if (!dst)
161: return xfrm_addr_equal(saddr, &x->props.saddr, AF_INET);
162:
163: if (!xfrm_state_addr_check(x, daddr, saddr, AF_INET))
164: return false;
]
|
| 20/2 |
2026/06/15 00:58 |
llm |
expert |
0m
Model:
Tokens:
input: 15972
output: 39
thoughts: 546
Thoughts: **Analyzing a Potential Vulnerability in IPsec Processing**
Okay, so I'm looking at this `vti_rcv_cb` function and immediately I see `inner_mode->family` being used. My mind races. I need to understand where this value comes from and what it represents. It looks like `inner_mode` could be either `&x->inner_mode` or determined by `xfrm_ip2inner_mode`, depending on whether `x->sel.family` is `AF_UNSPEC` or something else. I realize the `inner_mode.family` is supposed to be the family of the *inner* packet. But here's where my alarm bells start ringing. What if there's a mismatch? What if `x->sel.family` is `AF_INET6` but `inner_mode->family` is `AF_INET`?
This is because, `xfrm_policy_check` is called with this `inner_mode->family`. Then, diving deeper, `__xfrm_policy_check` iterates through `sp->xvec`, calling `xfrm_selector_match` on each `x`. The `family` argument passed to `xfrm_selector_match` is the problematic `inner_mode->family` - the inner packet family, say, `AF_INET`. However, the `xfrm_state`'s selector (`x->sel`) can have a *different* family, maybe `AF_INET6`. This selector can have prefix lengths up to 128 (for IPv6).
Here's the problem. `xfrm_selector_match` uses the `family` argument to decide whether to call `__xfrm4_selector_match` or `__xfrm6_selector_match`. Since `family` is `AF_INET`, it calls `__xfrm4_selector_match`. However, the IPv6 selector's prefix lengths (up to 128) are *still* used inside `__xfrm4_selector_match`. This means that even if the code *thinks* it's working with IPv4, it's still using potentially huge prefix lengths from an IPv6 selector. This sounds like a classic integer overflow or shift-out-of-bounds vulnerability to me!
I need to confirm this definitively. Let me go back and re-examine the argument passing and verify that `xfrm_selector_match` indeed takes `family` as an argument. I suspect this is a critical flaw that needs to be addressed immediately.
|
| 21/2 |
2026/06/15 00:58 |
tool |
read-file |
0m
Args: map[File:net/xfrm/xfrm_policy.c FirstLine:242 LineCount:10]
Results: map[Contents: 242: bool xfrm_selector_match(const struct xfrm_selector *sel, const struct flowi *fl,
243: unsigned short family)
244: {
245: switch (family) {
246: case AF_INET:
247: return __xfrm4_selector_match(sel, fl);
248: case AF_INET6:
249: return __xfrm6_selector_match(sel, fl);
250: }
251: return false;
]
|
| 22/2 |
2026/06/15 00:58 |
llm |
expert |
0m
Model:
Tokens:
input: 17266
output: 39
thoughts: 975
Thoughts: **Analysis of a Potential Logic Bug in XFRM Selector Matching**
Okay, let's break this down. `xfrm_selector_match` is the function in question, and it's calling `__xfrm4_selector_match` when the packet family is `AF_INET`. The issue arises because `sel` (the selector) can have a different family like `AF_INET6`, which means `sel->prefixlen_s` could be 128. That's immediately a red flag.
`__xfrm4_selector_match` then calls `addr4_match`, which ultimately performs the troublesome calculation: `32 - prefixlen`. With `prefixlen` being 128, that's `32 - 128 = -96`. Right away, this screams "shift out of bounds" potential.
Now, let's consider the implications. In standard C, this is undefined behavior. The kernel, however, has certain compiler flags in place, such as `-fno-strict-overflow`. But on x86, it gets interesting. A shift out of bounds gets masked with 31 or 63. Specifically, for 64-bit, the shift amount will be masked with 63. So with `-96`, we have to consider the two's complement value. `-96` becomes `-96 % 64 = -32`. And `-32 & 63` is `32`.
So we're effectively doing `~0UL << 32`. On a 64-bit system, that results in `0xffffffff00000000`. But then, `htonl` comes into play, and it takes the lower 32 bits, truncating the result to `0`. So the end result is `htonl(0) = 0`. This means our mask is `0`.
This means `addr4_match` will *always* return true if `prefixlen = 128`. We have a logic bug! It's likely going to allow a packet to pass the policy check when it shouldn't.
The question is, is this exploitable? It's a policy bypass. A packet might get through when it should have been dropped. This isn't memory corruption or an information leak, so there are some limitations in severity. And because it's only a UBSAN report, it's not going to show up during production. The shift out of bounds is masked with 32.
Looking at the broader context, `x->sel.family` is `AF_INET6` but the packet is `AF_INET`. Now, I'm thinking, does `xfrm_selector_match` actually validate that the selector's family (`sel->family`) matches the packet's family? I'm not seeing that check right now. I need to confirm if it's considered elsewhere, such as when the state is added.
|
| 23/2 |
2026/06/15 00:59 |
tool |
read-file |
0m
Args: map[File:net/xfrm/xfrm_policy.c FirstLine:240 LineCount:20]
Results: map[Contents: 240: }
241:
242: bool xfrm_selector_match(const struct xfrm_selector *sel, const struct flowi *fl,
243: unsigned short family)
244: {
245: switch (family) {
246: case AF_INET:
247: return __xfrm4_selector_match(sel, fl);
248: case AF_INET6:
249: return __xfrm6_selector_match(sel, fl);
250: }
251: return false;
252: }
253:
254: static const struct xfrm_policy_afinfo *xfrm_policy_get_afinfo(unsigned short family)
255: {
256: const struct xfrm_policy_afinfo *afinfo;
257:
258: if (unlikely(family >= ARRAY_SIZE(xfrm_policy_afinfo)))
259: return NULL;
]
|
| 24/2 |
2026/06/15 00:59 |
llm |
expert |
0m
Model:
Tokens:
input: 19212
output: 23
thoughts: 2102
Thoughts: **Analysis of Potential IPsec Policy Bypass**
Okay, let's break this down. It seems there's a vulnerability in how the kernel handles IPv4 packets that are mistakenly routed through an IPv6 IPsec tunnel due to an overly permissive selector. The core issue centers around the matching of packet selectors in the `xfrm_state` structure, specifically when IPv6 selectors are used against IPv4 traffic within an IPsec tunnel.
First, the code doesn't explicitly verify that the family of the selector (`sel->family`) matches the packet's IP family. This allows a scenario where we have an `AF_INET6` selector but process an `AF_INET` packet. Critically, if `sel->family` is IPv6 and the packet is IPv4, the code ends up reading the first 4 bytes of an IPv6 address as the IPv4 address. Since the mask associated with the selector is zero, any IPv4 address will match an IPv6 selector with prefix length of 128 (default).
Now, the crucial insight: this policy check happens *after* the `xfrm_state` has been applied; the packet has already been decrypted/authenticated. The point of this check is to confirm that the packet which has been received via this xfrm_state actually matches the selector. So, if we find that the selectors *don't* match when they should, then this represents a *policy bypass* where an IPv4 packet is accepted into a supposedly IPv6-only IPsec tunnel. This is a tunnel mode SA, and the selector controls which inner packets are allowed. The logic error causes the IPv4 packets to be accepted through the SA that was configured for IPv6 packets, which is a significant problem.
This becomes a security issue because an attacker, with access to an IPsec tunnel, can bypass the intended policy of only allowing IPv6 packets to traverse it, and injecting crafted IPv4 packets instead.
However, does this mean privilege escalation or memory corruption? No, this is just a network policy bypass. It doesn't seem to lead to any memory access violations or overwrites, so it's not "exploitable" in the sense of causing those sorts of critical failures. It is purely a logic bug.
But, the analysis deepens. What if the prefix length is *not* 128? Calculating the mask with different prefix lengths (`prefixlen > 32`) introduces a shift-out-of-bounds error. This is a UBSAN violation, but UBSAN is typically disabled in production builds. If `prefixlen` is 96, then the mask value is calculated as 0xffffffff; in this case, the first 4 bytes of IPv6 will be strictly checked against the IPv4 address, which again leads to the bug.
Crucially, there are no out-of-bounds reads or writes during the addressing, so there's no direct memory corruption or information leak here, at least based on this logic. The shift-out-of-bounds is the core problem, as it can cause unexpected mask values, which leads to the policy bypass. In other words, the negative shift can lead to the mask calculation returning the wrong value, and that will make the comparison fail or pass depending on the value of the packet header.
This also doesn't cause a denial of service (DoS) in production because of the UBSAN behavior. There will just be a bogus mask value, but no crash or resource leak.
Looking at the prompts, the prompt specifies that exploitable = false since there is no memory corruption, elevated privileges, or info leak.
Tracing the code, the vulnerability is reached by receiving a network packet through the TUN device (which means it's possible to trigger this by a local user who has access to a TUN device, or receiving a packet from the network). However, the same code path can be reached from a real network interface receiving an IPsec/VTI packet, so the attacker can trigger this over the network. So, it is a network trigger.
Finally, to trigger this, one needs an `xfrm_state` configured with `sel.family = AF_INET6`, but this state is applied to an IPv4 packet. And to configure an `xfrm_state`, `CAP_NET_ADMIN` is required. An unprivileged user can have this capability within a user namespace, so an unprivileged user can trigger this.
|
| 25/2 |
2026/06/15 00:59 |
tool |
grepper |
0m
Args: map[Expression:xfrm_user_rcv_msg]
Results: map[Output:net/xfrm/xfrm_compat.c=412=static int xfrm_attr_cpy32(void *dst, size_t *pos, const struct nlattr *src,
--
net/xfrm/xfrm_compat.c-417-
net/xfrm/xfrm_compat.c:418: /* xfrm_user_rcv_msg_compat() relies on fact that 32-bit messages
net/xfrm/xfrm_compat.c-419- * have the same len or shorted than 64-bit ones.
--
net/xfrm/xfrm_compat.c=477=static int xfrm_xlate32(struct nlmsghdr *dst, const struct nlmsghdr *src,
--
net/xfrm/xfrm_compat.c-595-
net/xfrm/xfrm_compat.c:596:static struct nlmsghdr *xfrm_user_rcv_msg_compat(const struct nlmsghdr *h32,
net/xfrm/xfrm_compat.c-597- int maxtype, const struct nla_policy *policy,
--
net/xfrm/xfrm_compat.c=667=static struct xfrm_translator xfrm_translator = {
--
net/xfrm/xfrm_compat.c-669- .alloc_compat = xfrm_alloc_compat,
net/xfrm/xfrm_compat.c:670: .rcv_msg_compat = xfrm_user_rcv_msg_compat,
net/xfrm/xfrm_compat.c-671- .xlate_user_policy_sockptr = xfrm_user_policy_compat,
--
net/xfrm/xfrm_user.c=3423=static int xfrm_reject_unused_attr(int type, struct nlattr **attrs,
--
net/xfrm/xfrm_user.c-3454-
net/xfrm/xfrm_user.c:3455:static int xfrm_user_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh,
net/xfrm/xfrm_user.c-3456- struct netlink_ext_ack *extack)
--
net/xfrm/xfrm_user.c=3542=static void xfrm_netlink_rcv(struct sk_buff *skb)
--
net/xfrm/xfrm_user.c-3546- mutex_lock(&net->xfrm.xfrm_cfg_mutex);
net/xfrm/xfrm_user.c:3547: netlink_rcv_skb(skb, &xfrm_user_rcv_msg);
net/xfrm/xfrm_user.c-3548- mutex_unlock(&net->xfrm.xfrm_cfg_mutex);
]
|
| 26/2 |
2026/06/15 00:59 |
llm |
expert |
0m
Model:
Tokens:
input: 22991
output: 40
thoughts: 27
Thoughts: Here's my thought process, as if I were considering this myself:
**Investigating Namespace Capability Checks in `xfrm_user.c`**
Okay, so I need to quickly determine the security posture of `xfrm_user.c`, specifically related to its handling of namespaces and privilege escalation potential. My immediate thought is, let's drill down into how this module validates user permissions when creating or manipulating XFRM policies. The key question is whether it's using the more restrictive `ns_capable` check, which would take namespace context into account, or if it's lazily using the broader `capable` check, which, if misused, could present a significant vulnerability. A quick scan of the code in `xfrm_user.c` will give me that answer. I expect to be looking for functions that handle policy creation, deletion, modification, or any privileged operations on XFRM state, and specifically, the use of those capability-checking macros. I need to understand this to properly assess potential issues and ensure I am up to date on security.
|
| 27/2 |
2026/06/15 00:59 |
tool |
read-file |
0m
Args: map[File:net/xfrm/xfrm_user.c FirstLine:3440 LineCount:50]
Results: map[Contents:3440: case XFRM_MSG_NEWSA:
3441: case XFRM_MSG_UPDSA:
3442: case XFRM_MSG_ALLOCSPI:
3443: case XFRM_MSG_ACQUIRE:
3444:
3445: break;
3446: default:
3447: NL_SET_ERR_MSG(extack, "Invalid attribute SA_PCPU");
3448: return -EINVAL;
3449: }
3450: }
3451:
3452: return 0;
3453: }
3454:
3455: static int xfrm_user_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh,
3456: struct netlink_ext_ack *extack)
3457: {
3458: struct net *net = sock_net(skb->sk);
3459: struct nlattr *attrs[XFRMA_MAX+1];
3460: const struct xfrm_link *link;
3461: struct nlmsghdr *nlh64 = NULL;
3462: int type, err;
3463:
3464: type = nlh->nlmsg_type;
3465: if (type > XFRM_MSG_MAX)
3466: return -EINVAL;
3467:
3468: type -= XFRM_MSG_BASE;
3469: link = &xfrm_dispatch[type];
3470:
3471: /* All operations require privileges, even GET */
3472: if (!netlink_net_capable(skb, CAP_NET_ADMIN))
3473: return -EPERM;
3474:
3475: if (in_compat_syscall()) {
3476: struct xfrm_translator *xtr = xfrm_get_translator();
3477:
3478: if (!xtr)
3479: return -EOPNOTSUPP;
3480:
3481: nlh64 = xtr->rcv_msg_compat(nlh, link->nla_max,
3482: link->nla_pol, extack);
3483: xfrm_put_translator(xtr);
3484: if (IS_ERR(nlh64))
3485: return PTR_ERR(nlh64);
3486: if (nlh64)
3487: nlh = nlh64;
3488: }
3489:
]
|
| 28/2 |
2026/06/15 00:59 |
llm |
expert |
0m
Model:
Tokens:
input: 24019
output: 39
thoughts: 370
Thoughts: **Analyzing the IPv6 IPsec Tunnel Vulnerability**
Okay, so I'm looking at this code and it's using `netlink_net_capable(skb, CAP_NET_ADMIN)`. Alright, that's interesting. `netlink_net_capable` calls `ns_capable` with `sock_net(skb->sk)->user_ns`, which means this is definitely accessible from user namespaces. UserNamespace = True. And it requires `CAP_NET_ADMIN`, so Unprivileged = False. A normal user can't do this without creating their own user namespace and having the capability.
Wait a second… can this be triggered remotely? I remember something about IPv4 packets over IPv6 IPsec tunnels with IPv6 selectors… If an attacker can get a crafted IPv4 packet through that setup, it *might* be relevant. The attacker, of course, needs to know the IPsec keys unless the packet is coming from the other end of the tunnel. If *that* end is compromised, they *could* potentially send packets to exploit this.
But, hang on, this seems more like a policy bypass than a real exploit. Is it exploitable, really? It's not a memory corruption bug, definitely not a privilege escalation or an info leak. It's just a UBSAN shift-out-of-bounds issue. So, Exploitable = False. And it's not a denial of service either, so DenialOfService = False.
Okay, let me just double-check, because I'm not entirely satisfied. Are there *any* other consequences? Is there even a remote chance that `addr4_match` is used in a way that causes an out-of-bounds read? It takes `__be32 a1, __be32 a2, u8 prefixlen`. It doesn’t actually read memory, it just operates on the u32 values passed to it. So, no, definitely no out-of-bounds read.
What about `__xfrm4_selector_match`? I should probably check that one too.
|
| 29/2 |
2026/06/15 00:59 |
tool |
read-file |
0m
Args: map[File:net/xfrm/xfrm_policy.c FirstLine:216 LineCount:20]
Results: map[Contents: 216: static inline bool
217: __xfrm4_selector_match(const struct xfrm_selector *sel, const struct flowi *fl)
218: {
219: const struct flowi4 *fl4 = &fl->u.ip4;
220:
221: return addr4_match(fl4->daddr, sel->daddr.a4, sel->prefixlen_d) &&
222: addr4_match(fl4->saddr, sel->saddr.a4, sel->prefixlen_s) &&
223: !((xfrm_flowi_dport(fl, &fl4->uli) ^ sel->dport) & sel->dport_mask) &&
224: !((xfrm_flowi_sport(fl, &fl4->uli) ^ sel->sport) & sel->sport_mask) &&
225: (fl4->flowi4_proto == sel->proto || !sel->proto) &&
226: (fl4->flowi4_oif == sel->ifindex || !sel->ifindex);
227: }
228:
229: static inline bool
230: __xfrm6_selector_match(const struct xfrm_selector *sel, const struct flowi *fl)
231: {
232: const struct flowi6 *fl6 = &fl->u.ip6;
233:
234: return addr_match(&fl6->daddr, &sel->daddr, sel->prefixlen_d) &&
235: addr_match(&fl6->saddr, &sel->saddr, sel->prefixlen_s) &&
]
|
| 30/2 |
2026/06/15 00:59 |
llm |
expert |
0m
Model:
Tokens:
input: 25328
output: 36
thoughts: 116
Thoughts: **Analyzing Address Data in the Context of a Security Framework**
Okay, let's break this down. From what I'm seeing, `sel->daddr.a4` is simply reading the initial four bytes from the `sel->daddr` field. Makes perfect sense, and I understand this immediately given my experience in this area. No red flags there.
Now, I need to remind myself that `sel->daddr` is defined as an `xfrm_address_t`. I know this is a union, containing either a 32-bit IPv4 address (`a4`), a four-element array of 32-bit integers for an IPv6 address (`a6`), or an `in6_addr` struct for IPv6. I can verify this is safe, as the usage of `sel->daddr.a4` correctly adheres to the established structure. No issues.
My immediate question: what about `addr_match`? What is the function of that call in this flow? I need to analyze its implementation to ensure the address comparison is done correctly, handling potential IPv4/IPv6 mismatches and type safety.
|
| 31/2 |
2026/06/15 00:59 |
tool |
read-file |
0m
Args: map[File:include/net/xfrm.h FirstLine:940 LineCount:20]
Results: map[Contents: 940: {
941: const __be32 *a1 = token1;
942: const __be32 *a2 = token2;
943: unsigned int pdw;
944: unsigned int pbi;
945:
946: pdw = prefixlen >> 5; /* num of whole u32 in prefix */
947: pbi = prefixlen & 0x1f; /* num of bits in incomplete u32 in prefix */
948:
949: if (pdw)
950: if (memcmp(a1, a2, pdw << 2))
951: return false;
952:
953: if (pbi) {
954: __be32 mask;
955:
956: mask = htonl((0xffffffff) << (32 - pbi));
957:
958: if ((a1[pdw] ^ a2[pdw]) & mask)
959: return false;
]
|
| 32/2 |
2026/06/15 00:59 |
llm |
expert |
0m
Model:
Tokens:
input: 26030
output: 22
thoughts: 430
Thoughts: **My Analysis of the `addr_match` Vulnerability**
Okay, let's break this down. `addr_match` takes two addresses and a prefix length, `prefixlen`. The code does a right bit shift of `prefixlen` by 5 bits, so `pdw = prefixlen >> 5`. If `prefixlen` is 128, then `pdw` will be 4. Then we have a left bit shift, `pdw << 2` which evaluates to `4 << 2` or 16. It then compares 16 bytes. That looks safe.
The real problem lies within `addr4_match` when `prefixlen` exceeds 32. It's a classic UBSAN shift-out-of-bounds issue. This isn't likely to cause any memory corruption or a crash. So, it's not going to be exploitable, and it's definitely not a Denial of Service vulnerability.
Now, let's look at how it can be triggered. Can it be triggered over a network? Yes, through a packet received over an IPsec tunnel. Is it remote? No, it's Ethernet network trigger. Peripheral, filesystem, VM guest or host trigger? No, not applicable. Unprivileged? No, `CAP_NET_ADMIN` is required. User Namespace? Wait a minute, yes! I can see how, if you have `CAP_NET_ADMIN` within a user namespace, you can configure an XFRM state, and send a packet. So, UserNamespace is definitely true.
So, the next thing to check is, could it be a DoS? If UBSAN is enabled, it'll panic or warn, but in a production build, it just computes a wrong mask and continues without crashing. So no, no DoS.
Finally, I should make sure I haven't missed other instances of `addr4_match` that could create a problem.
|
| 33/2 |
2026/06/15 00:59 |
tool |
codesearch-find-references |
0m
Args:
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:__xfrm4_selector_match SourceFile:net/xfrm/xfrm_policy.c SourceLine:221] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:__xfrm4_selector_match SourceFile:net/xfrm/xfrm_policy.c SourceLine:222]] TruncatedOutput:false]
|
| 34/2 |
2026/06/15 00:59 |
llm |
expert |
0m
Model:
Tokens:
input: 27040
output: 26
thoughts: 33
Thoughts: Okay, here's my take on those thoughts, framed as my own:
**Digging into that `__xfrm4_selector_match` Function**
Right, so I'm honing in on `__xfrm4_selector_match`. Seems like that's the only place it's popping up so far, which is interesting. It could be significant.
Now, my next step is clear: I need to get a broader view. I'll need to check the entire codebase to make sure this function isn't referenced anywhere else. A deep dive is in order to understand how this little piece fits into the overall architecture. Gotta confirm where this code is actually being used. I need to know the bigger picture.
|
| 35/2 |
2026/06/15 00:59 |
tool |
codesearch-find-references |
0m
Args: map[Name:__xfrm4_selector_match]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:xfrm_selector_match SourceFile:net/xfrm/xfrm_policy.c SourceLine:247]] TruncatedOutput:false]
|
| 36/2 |
2026/06/15 00:59 |
llm |
expert |
0m
Model:
Tokens:
input: 27327
output: 24
thoughts: 29
Thoughts: Here's my take on the thought process, summarized as you requested:
**XFRM Selector Matching - Digging Deeper**
Okay, so I'm focusing in on `xfrm_selector_match`. My initial thought is that I need to really understand how this function is working. I'm pretty familiar with the XFRM subsystem, but I want to be *sure* I have a solid grasp of this specific part.
My next step is to perform a targeted code search. I need to confirm where else this function might be used. I will look for any place `xfrm_selector_match` is called. That will provide crucial context around its purpose and usage. I want to build a complete picture here. This is where I'll find my key to interpreting its operation.
|
| 37/2 |
2026/06/15 00:59 |
tool |
codesearch-find-references |
0m
Args: map[Name:xfrm_selector_match]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:__xfrm_policy_check SourceFile:net/xfrm/xfrm_policy.c SourceLine:3715] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:xfrm_policy_match SourceFile:net/xfrm/xfrm_policy.c SourceLine:1975] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:xfrm_selector_inner_icmp_match SourceFile:net/xfrm/xfrm_policy.c SourceLine:3609] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:xfrm_sk_policy_lookup SourceFile:net/xfrm/xfrm_policy.c SourceLine:2249] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:xfrm_state_look_at SourceFile:net/xfrm/xfrm_state.c SourceLine:1320] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:xfrm_state_look_at SourceFile:net/xfrm/xfrm_state.c SourceLine:1341]] TruncatedOutput:false]
|
| 38/2 |
2026/06/15 00:59 |
llm |
expert |
0m
Model:
Tokens:
input: 27909
output: 66
thoughts: 1340
Thoughts: **Analyzing the IPv4 Prefix Length Bug**
Okay, so I've been digging into this policy check vulnerability. My initial thought was that it's all about how these checks can be bypassed or denied, but I needed to understand the underlying mechanics to determine if it's actually exploitable. It's quickly apparent that these kinds of things are *not* memory corruption or DoS vectors, which is good.
My immediate focus was on the edge cases, especially the `prefixlen` values. If `prefixlen` is 255, the shift becomes `32 - 255 = -223`. Which, when ANDed with 63, becomes 33, which in turn leads to a shift count greater than 32, which is undefined behavior in C. But, on x86, it's just going to be ANDed with 63, meaning the actual shift is performed with 33, meaning the output is `0xfffffffe00000000`, the lower 32 bits of which get passed as 0, which gets passed to `htonl` which is 0.
What if `prefixlen` is 0? The code actually *has* a special check for that, so it matches everything, which is correct.
The real interesting case is `prefixlen` *greater* than 32. Okay, so a shift out of bounds can happen here. That sounds bad, but on x86, it's just going to AND the shift count with 63, so it's not going to lead to any memory corruption, or information disclosure.
So, here's the current summary:
* **Exploitable: false**
* **DenialOfService: false**
* **Unprivileged: false**
* **UserNamespace: true**
* **VMGuestTrigger: false**
* **VMHostTrigger: false**
* **NetworkTrigger: true**
* **RemoteTrigger: false**
* **PeripheralTrigger: false**
* **FilesystemTrigger: false**
But wait, hold on... does `prefixlen` being > 32 cause an out-of-bounds *read*? I can't be too careful. I need to make sure `addr4_match`'s arguments `__be32 a1, __be32 a2` are passed by value and there's no memory access. Okay, good, that's fine.
Let's double check *where* this `prefixlen` value is even used. In `xfrm_selector_match`, it just calls either `__xfrm4_selector_match` or `__xfrm6_selector_match`. In the IPv4 match, `__xfrm4_selector_match`, it's just calling `addr4_match`. Alright, nothing else.
I need to confirm if an unprivileged user can configure the XFRM state, which is required to trigger this vulnerability. Quick check on `xfrm_user_rcv_msg`, and yep, it requires `CAP_NET_ADMIN`. So, unprivileged is false. But, `CAP_NET_ADMIN` *is* allowed in a user namespace. Therefore, UserNamespace is true.
I had better double-check the triggers:
* **NetworkTrigger: true.** It's triggered by an IPsec packet over the network.
* **RemoteTrigger: false.** It's IPsec, typically ethernet/IP, not Wifi/Bluetooth etc.
* **VMGuestTrigger: false.** Not specific to a VM Guest.
* **VMHostTrigger: false.** Not specific to a VM Host.
* **PeripheralTrigger: false.** Not specific to a peripheral.
* **FilesystemTrigger: false.** Not specific to the file system.
Alright, final summary:
This is a shift-out-of-bounds in `addr4_match` when `prefixlen` > 32. This is triggered by matching an IPv6 XFRM selector against an IPv4 flow. `xfrm_selector_match` determines this based on `AF_INET`. This causes undefined behavior in C. x86, which is ANDed with a mask, and the net effect is a logic error: a bypass where an IPv4 packet could be accepted by an IPv6 selector. It doesn't cause memory corruption, escalation, or disclosure. Also, since it's a UBSAN report, production builds (where UBSAN is disabled) will not panic. Therefore, not exploitable. It doesn't cause DoS. Triggered by a crafted IPsec packet. Requires `CAP_NET_ADMIN`, which can be obtained within a user namespace.
|
| 39/2 |
2026/06/15 01:00 |
tool |
set-results |
0m
Args: map[DenialOfService:false Exploitable:false FilesystemTrigger:false NetworkTrigger:true PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:false Exploitable:false FilesystemTrigger:false NetworkTrigger:true PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
|
| 40/2 |
2026/06/15 01:00 |
llm |
expert |
0m
Model:
Tokens:
input: 30403
output: 499
thoughts: 13
Thoughts: Okay, here's the summary, rendered as a first-person thought process, aimed at an expert audience:
**Finalizing the Response**
Alright, the data's been crunched, the analysis is complete, and the interpretation has been rigorously checked. Everything’s in place, the variables have been accounted for, and any potential caveats have been addressed. Now it’s time to move beyond the internal review and prepare the final response. It's the point where I synthesize everything into a clear, concise, and defensible conclusion. No room for ambiguity here; this is the culmination of the process, the definitive statement based on solid evidence. I'm ready to present the final findings.
|
| 41/1 |
2026/06/15 01:00 |
action |
format-explanation |
0m
Results: map[Explanation:**Analysis and Reasoning:**
The bug is a shift-out-of-bounds in `addr4_match()` reported by UBSAN. It occurs
when `prefixlen` is greater than 32.
This situation arises when an IPv6 XFRM selector (which can have a `prefixlen`
up to 128) is matched against an IPv4 flow. The `xfrm_selector_match()` function
uses the flow's address family (`AF_INET` for IPv4) to determine which matching
function to call. Consequently, it calls `__xfrm4_selector_match()` even if the
selector was originally configured for IPv6. This leads to `addr4_match()` being
called with a `prefixlen` > 32, resulting in a negative shift exponent (`32 -
prefixlen`).
In C, shifting by a negative amount is undefined behavior. However, on
architectures like x86, the CPU's shift instruction simply masks the shift count
(e.g., bitwise AND with 63), which results in a bogus mask value (frequently 0).
**Exploitability & Denial of Service:**
Since `addr4_match()` takes its address arguments by value (`__be32`), there is
no out-of-bounds memory access or memory corruption involved. The bug only
results in a logic error—specifically, an IPsec policy bypass where an IPv4
packet might be incorrectly accepted by an IPv6 selector. It does not cause
memory corruption, privilege escalation, or an information leak. Furthermore,
because this is a UBSAN report, it will not cause a kernel panic, warning, or
crash in production builds where UBSAN is disabled. The execution simply
continues with the bogus mask. Thus, it is neither exploitable nor a
Denial-of-Service (DoS) vector.
**Triggers:**
The bug is triggered during the processing of inbound IPsec/VTI network packets
that are decapsulated and checked against the XFRM policy. Thus, it is reachable
via an **Ethernet Network Trigger**.
To set up the prerequisite XFRM state with an IPv6 selector, a user needs
`CAP_NET_ADMIN` capabilities. While an unprivileged user cannot do this
directly, they can acquire the necessary capabilities by creating a new network
and user namespace. Therefore, the bug is accessible from **User Namespaces**.]
|