| 0/0 |
2026/05/30 22:33 |
flow |
assessment-security |
50m
Results: map[DenialOfService:true Explanation:Based on the analysis of the bug report and the kernel source code, here is the
assessment of the vulnerability:
### Bug Mechanism
The bug is a race condition in the Soft-RoCE (RXE) driver that leads to a double
free of a `struct socket` and its associated `struct sock`.
When an RXE device is deleted, `rxe_net_del()` is called to clean up the
underlying UDP tunnel sockets. It retrieves the shared socket from the
per-network namespace storage (`rxe_ns_pernet_sk4`) and calls `rxe_sock_put()`.
`rxe_sock_put()` checks if the socket's refcount is greater than
`SK_REF_FOR_TUNNEL` (which is hardcoded to 2). For a single RXE device, the
socket's refcount is 1, so the condition `1 > 2` is false. It then proceeds to
the `else` branch, calling `rxe_release_udp_tunnel()`, which ultimately calls
`sock_release()` and frees the socket.
The critical flaw is that `rxe_net_del()` lacks any locking or synchronization.
If it is called concurrently for the same deviceβfor example, via two concurrent
`rdma link del` netlink commands, or a concurrent `rdma link del` and a netdev
unregister event handled by `rxe_notify()`βboth threads can retrieve the same
socket pointer from `rxe_ns_pernet_sk4()`. Both threads will observe a refcount
of 1, and both will call `rxe_release_udp_tunnel()` on the same socket. The
first thread frees the socket, and the second thread uses the freed socket and
attempts to free it again, resulting in a double free and the observed
`refcount_t: underflow; use-after-free` warning.
### Security Impact
* **Exploitable:** **Yes.** A double free of a complex kernel object like
`struct sock` (which contains function pointers such as `sk_destruct`) is highly
exploitable. An attacker can use heap spraying techniques to overlap the freed
object with controlled data and hijack the control flow when the second free or
subsequent uses occur, leading to Local Privilege Escalation (LPE).
* **Denial Of Service:** **Yes.** If not successfully exploited for LPE, the
double free will corrupt kernel memory and cause a system crash (kernel panic).
* **Accessible From User Namespaces:** **Yes.** The RDMA netlink commands (`rdma
link add` / `rdma link del`) require `CAP_NET_ADMIN`, but this capability is
only checked against the user namespace that owns the network namespace of the
netlink socket. An unprivileged user can create a new user namespace and a new
network namespace to gain `CAP_NET_ADMIN`. They can then trigger the
auto-loading of the `rdma_rxe` module, create a dummy network device, attach an
RXE link to it, and concurrently delete it to trigger the double free.
* **Accessible From Unprivileged Processes:** **False.** Without the ability to
create a user namespace to gain `CAP_NET_ADMIN`, an unprivileged user cannot
send the required RDMA netlink commands or manipulate network devices.
### Conclusion
This is a highly critical, locally exploitable vulnerability (LPE/DoS) reachable
by unprivileged users on systems where user namespaces are enabled. Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/05/30 22:33 |
action |
syz-repro-to-c-repro |
0m
Results:
|
| 2/1 |
2026/05/30 22:33 |
action |
kernel-checkouter |
1m
Results: map[KernelSrc:/app/workdir/cache/src/479804c1071eb2bf357c7ee5deeda81aeeecb0bc]
|
| 3/1 |
2026/05/30 22:35 |
action |
kernel-builder |
17m
Results: map[KernelObj:/app/workdir/cache/build/7228175be883b0a14384cf2516068f42a29cae53]
|
| 4/1 |
2026/05/30 22:53 |
action |
codesearch-prepare |
13m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/05/30 23:06 |
agent |
expert |
17m
Model:
Results: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false 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 or elevated privileges.
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.
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 ]------------
refcount_t: underflow; use-after-free.
WARNING: lib/refcount.c:28 at refcount_warn_saturate+0xf4/0x130 lib/refcount.c:28, CPU#3: syz.0.1764/12782
Modules linked in:
CPU: 3 UID: 0 PID: 12782 Comm: syz.0.1764 Tainted: G L syzkaller #0 PREEMPT(full)
Tainted: [L]=SOFTLOCKUP
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
RIP: 0010:refcount_warn_saturate+0xf4/0x130 lib/refcount.c:28
Code: cc e8 70 f9 0e fd 48 8d 3d 59 b9 e8 0b 67 48 0f b9 3a e8 5f f9 0e fd 5b 5d e9 d8 93 94 06 e8 53 f9 0e fd 48 8d 3d 4c b9 e8 0b <67> 48 0f b9 3a e8 42 f9 0e fd 5b 5d c3 cc cc cc cc e8 36 f9 0e fd
RSP: 0018:ffffc900054ff120 EFLAGS: 00010293
RAX: 0000000000000000 RBX: ffff88803bf3ef00 RCX: ffffffff84f9b6bb
RDX: ffff88802cdc4a00 RSI: ffffffff84f9b74d RDI: ffffffff90e270a0
RBP: 0000000000000003 R08: 0000000000000005 R09: 0000000000000004
R10: 0000000000000003 R11: 0000000000000000 R12: ffff88803bf3ef00
R13: ffffffff8a05bb60 R14: 0000000000000000 R15: 0000000000000018
FS: 00007f7276fc56c0(0000) GS:ffff8880d666a000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007f7276fc4ff8 CR3: 0000000039e81000 CR4: 0000000000352ef0
Call Trace:
<TASK>
__refcount_sub_and_test include/linux/refcount.h:400 [inline]
__refcount_dec_and_test include/linux/refcount.h:432 [inline]
refcount_dec_and_test include/linux/refcount.h:450 [inline]
sock_put include/net/sock.h:2009 [inline]
sk_common_release+0x260/0x370 net/core/sock.c:4024
inet_release+0xed/0x200 net/ipv4/af_inet.c:442
__sock_release net/socket.c:722 [inline]
sock_release+0x91/0x1c0 net/socket.c:750
rxe_release_udp_tunnel drivers/infiniband/sw/rxe/rxe_net.c:294 [inline]
rxe_sock_put+0xae/0x130 drivers/infiniband/sw/rxe/rxe_net.c:639
rxe_net_del+0x83/0x120 drivers/infiniband/sw/rxe/rxe_net.c:660
rxe_dellink+0x15/0x20 drivers/infiniband/sw/rxe/rxe.c:254
nldev_dellink+0x289/0x3c0 drivers/infiniband/core/nldev.c:1849
rdma_nl_rcv_msg+0x392/0x6f0 drivers/infiniband/core/netlink.c:195
rdma_nl_rcv_skb.constprop.0.isra.0+0x2cb/0x410 drivers/infiniband/core/netlink.c:239
netlink_unicast_kernel net/netlink/af_netlink.c:1318 [inline]
netlink_unicast+0x585/0x850 net/netlink/af_netlink.c:1344
netlink_sendmsg+0x8b0/0xda0 net/netlink/af_netlink.c:1894
sock_sendmsg_nosec net/socket.c:787 [inline]
__sock_sendmsg net/socket.c:802 [inline]
____sys_sendmsg+0x9e1/0xb70 net/socket.c:2698
___sys_sendmsg+0x190/0x1e0 net/socket.c:2752
__sys_sendmsg+0x170/0x220 net/socket.c:2784
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x10b/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f727619ce59
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:00007f7276fc5028 EFLAGS: 00000246 ORIG_RAX: 000000000000002e
RAX: ffffffffffffffda RBX: 00007f7276416090 RCX: 00007f727619ce59
RDX: 0000000000000004 RSI: 0000200000004680 RDI: 0000000000000003
RBP: 00007f7276232d6f R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007f7276416128 R14: 00007f7276416090 R15: 00007ffe765f7028
</TASK>
----------------
Code disassembly (best guess):
0: cc int3
1: e8 70 f9 0e fd call 0xfd0ef976
6: 48 8d 3d 59 b9 e8 0b lea 0xbe8b959(%rip),%rdi # 0xbe8b966
d: 67 48 0f b9 3a ud1 (%edx),%rdi
12: e8 5f f9 0e fd call 0xfd0ef976
17: 5b pop %rbx
18: 5d pop %rbp
19: e9 d8 93 94 06 jmp 0x69493f6
1e: e8 53 f9 0e fd call 0xfd0ef976
23: 48 8d 3d 4c b9 e8 0b lea 0xbe8b94c(%rip),%rdi # 0xbe8b976
* 2a: 67 48 0f b9 3a ud1 (%edx),%rdi <-- trapping instruction
2f: e8 42 f9 0e fd call 0xfd0ef976
34: 5b pop %rbx
35: 5d pop %rbp
36: c3 ret
37: cc int3
38: cc int3
39: cc int3
3a: cc int3
3b: e8 36 f9 0e fd call 0xfd0ef976
Reply: Based on the analysis of the bug report and the kernel source code, here is the assessment of the vulnerability:
### Bug Mechanism
The bug is a race condition in the Soft-RoCE (RXE) driver that leads to a double free of a `struct socket` and its associated `struct sock`.
When an RXE device is deleted, `rxe_net_del()` is called to clean up the underlying UDP tunnel sockets. It retrieves the shared socket from the per-network namespace storage (`rxe_ns_pernet_sk4`) and calls `rxe_sock_put()`.
`rxe_sock_put()` checks if the socket's refcount is greater than `SK_REF_FOR_TUNNEL` (which is hardcoded to 2). For a single RXE device, the socket's refcount is 1, so the condition `1 > 2` is false. It then proceeds to the `else` branch, calling `rxe_release_udp_tunnel()`, which ultimately calls `sock_release()` and frees the socket.
The critical flaw is that `rxe_net_del()` lacks any locking or synchronization. If it is called concurrently for the same deviceβfor example, via two concurrent `rdma link del` netlink commands, or a concurrent `rdma link del` and a netdev unregister event handled by `rxe_notify()`βboth threads can retrieve the same socket pointer from `rxe_ns_pernet_sk4()`. Both threads will observe a refcount of 1, and both will call `rxe_release_udp_tunnel()` on the same socket. The first thread frees the socket, and the second thread uses the freed socket and attempts to free it again, resulting in a double free and the observed `refcount_t: underflow; use-after-free` warning.
### Security Impact
* **Exploitable:** **Yes.** A double free of a complex kernel object like `struct sock` (which contains function pointers such as `sk_destruct`) is highly exploitable. An attacker can use heap spraying techniques to overlap the freed object with controlled data and hijack the control flow when the second free or subsequent uses occur, leading to Local Privilege Escalation (LPE).
* **Denial Of Service:** **Yes.** If not successfully exploited for LPE, the double free will corrupt kernel memory and cause a system crash (kernel panic).
* **Accessible From User Namespaces:** **Yes.** The RDMA netlink commands (`rdma link add` / `rdma link del`) require `CAP_NET_ADMIN`, but this capability is only checked against the user namespace that owns the network namespace of the netlink socket. An unprivileged user can create a new user namespace and a new network namespace to gain `CAP_NET_ADMIN`. They can then trigger the auto-loading of the `rdma_rxe` module, create a dummy network device, attach an RXE link to it, and concurrently delete it to trigger the double free.
* **Accessible From Unprivileged Processes:** **False.** Without the ability to create a user namespace to gain `CAP_NET_ADMIN`, an unprivileged user cannot send the required RDMA netlink commands or manipulate network devices.
### Conclusion
This is a highly critical, locally exploitable vulnerability (LPE/DoS) reachable by unprivileged users on systems where user namespaces are enabled.
|
| 6/2 |
2026/05/30 23:06 |
llm |
expert |
0m
Model:
Tokens:
input: 8116
output: 50
thoughts: 769
Thoughts: **Analyzing a Refcount Underflow in the InfiniBand RXE Driver**
Okay, so I've got a bug report here, a classic `refcount_t` underflow, leading to a use-after-free situation. The kernel's warning points directly to `lib/refcount.c:28` within `refcount_warn_saturate`, indicating a negative refcount. The call trace immediately jumps out, highlighting the sequence: `sock_put` is the critical function, and specifically it occurs in the context of `rxe_release_udp_tunnel`.
A refcount underflow suggests we're decrementing a reference counter beyond its valid minimum, typically zero. This usually means a double-free, or a premature release of a resource. The call stack gives me a clear path to trace the error.
My analysis needs to focus on `rxe_release_udp_tunnel` and `rxe_sock_put`, since `sock_put` is being called there. I need to understand how the socket's reference count is managed, how it's incremented, and, critically, *why* it's being decremented too many times. Is there a race condition? Is there a logic error in how the reference is being held, or released? Is the reference being mistakenly decremented in a different path? I need to meticulously review the code within those functions and any associated locking or synchronization mechanisms to identify the point where the refcount is being mishandled. The fact that this is in the RXE driver suggests it could be related to UDP tunnel handling, so the nuances of that area are particularly important.
|
| 7/2 |
2026/05/30 23:06 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/infiniband/sw/rxe/rxe_net.c Name:rxe_release_udp_tunnel]
Results: map[SourceCode: 291: static void rxe_release_udp_tunnel(struct socket *sk)
292: {
293: if (sk)
294: udp_tunnel_sock_release(sk);
295: }
SourceFile:drivers/infiniband/sw/rxe/rxe_net.c]
|
| 8/2 |
2026/05/30 23:06 |
llm |
expert |
0m
Model:
Tokens:
input: 9390
output: 48
thoughts: 21
Thoughts: Here's my take on those thoughts:
**Analyzing `rxe_sock_put` and `rxe_net_del`**
Alright, let's dive into this. I'm thinking, "Okay, we need to examine `rxe_sock_put` and `rxe_net_del` closely." My initial thought process is to understand the interplay between these two functions in the RXE (RDMA over Ethernet) stack. I need to figure out the scenarios where these functions are critical and what their impact is. What socket resources are being released or managed within `rxe_sock_put`? And how does `rxe_net_del` relate to overall network teardown or resource deallocation when dealing with RXE connections? I'm anticipating that these are likely core components for socket and network resource management. I'll need to consider potential race conditions or locking issues, especially if these functions are used within interrupt contexts or multi-threaded environments. I need to identify the exact data structures each one modifies and the implications of those modifications. My focus is on understanding how they contribute to the lifecycle of an RXE connection, from establishment to teardown.
|
| 9/2 |
2026/05/30 23:06 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/infiniband/sw/rxe/rxe_net.c Name:rxe_sock_put]
Results: map[SourceCode: 632: static void rxe_sock_put(struct sock *sk,
633: void (*set_sk)(struct net *, struct sock *),
634: struct net *net)
635: {
636: if (refcount_read(&sk->sk_refcnt) > SK_REF_FOR_TUNNEL) {
637: __sock_put(sk);
638: } else {
639: rxe_release_udp_tunnel(sk->sk_socket);
640: sk = NULL;
641: set_sk(net, sk);
642: }
643: }
SourceFile:drivers/infiniband/sw/rxe/rxe_net.c]
|
| 10/2 |
2026/05/30 23:06 |
llm |
expert |
1m
Model:
Tokens:
input: 9928
output: 48
thoughts: 107
Thoughts: **Analyzing `rxe_sock_put` and its Implications**
Okay, so I'm looking at `rxe_sock_put` and trying to grasp its logic. The crucial part seems to be this conditional check on the socket's reference count. If the reference count is above `SK_REF_FOR_TUNNEL`, it simply calls `__sock_put(sk)`. That's a standard socket release, nothing particularly exotic there. But... if the reference count is *not* above that threshold, it executes this `rxe_release_udp_tunnel(sk->sk_socket)` function, sets the socket pointer (`sk`) to `NULL`, and then calls `set_sk(net, sk)`. So, it looks like this is a special path for UDP tunnels, where the socket isn't just released normally. Instead, something specific to the tunnel is released. Hmm, interesting. Now, I need to understand what `rxe_release_udp_tunnel` actually *does* to get the bigger picture. Next step is definitely to dive into how `rxe_net_del` factors into all of this. I bet it has some of the key components.
|
| 11/2 |
2026/05/30 23:07 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/infiniband/sw/rxe/rxe_net.c Name:rxe_net_del]
Results: map[SourceCode: 645: void rxe_net_del(struct ib_device *dev)
646: {
647: struct rxe_dev *rxe = container_of(dev, struct rxe_dev, ib_dev);
648: struct net_device *ndev;
649: struct sock *sk;
650: struct net *net;
651:
652: ndev = rxe_ib_device_get_netdev(&rxe->ib_dev);
653: if (!ndev)
654: return;
655:
656: net = dev_net(ndev);
657:
658: sk = rxe_ns_pernet_sk4(net);
659: if (sk)
660: rxe_sock_put(sk, rxe_ns_pernet_set_sk4, net);
661:
662: sk = rxe_ns_pernet_sk6(net);
663: if (sk)
664: rxe_sock_put(sk, rxe_ns_pernet_set_sk6, net);
665:
666: dev_put(ndev);
667: }
SourceFile:drivers/infiniband/sw/rxe/rxe_net.c]
|
| 12/2 |
2026/05/30 23:07 |
llm |
expert |
0m
Model:
Tokens:
input: 10729
output: 52
thoughts: 56
Thoughts: Okay, here's my interpretation of those thoughts, framed as my own:
**Decoding This Tunnel Reference**
Alright, the first question buzzing around my head is, what exactly *is* this `SK_REF_FOR_TUNNEL` thing? It's immediately followed by a check on `sk_refcnt`, which is intriguing. I need to understand the connection. My brain is already starting to piece together how the kernel's reference counting system is being used in this context.
Now, to drill down and hopefully unlock the mystery, my focus needs to shift to `rxe_ns_pernet_sk4` and `rxe_ns_pernet_set_sk4`. These functions likely hold the key. They *must* be connected to the handling of this tunnel reference, likely concerning the creation or management of the tunnel's sockets or network namespaces. I'll need to trace the code paths of those functions and see what they tell me about how `SK_REF_FOR_TUNNEL` is used and how it interacts with the reference counter. I bet there is some important context to derive here.
|
| 13/2 |
2026/05/30 23:07 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/infiniband/sw/rxe/rxe_net.c Name:rxe_ns_pernet_sk4]
Results: map[SourceCode: 74: struct sock *rxe_ns_pernet_sk4(struct net *net)
75: {
76: struct rxe_ns_sock *ns_sk = net_generic(net, rxe_pernet_id);
77: struct sock *sk;
78:
79: rcu_read_lock();
80: sk = rcu_dereference(ns_sk->rxe_sk4);
81: rcu_read_unlock();
82:
83: return sk;
84: }
SourceFile:drivers/infiniband/sw/rxe/rxe_ns.c]
|
| 14/2 |
2026/05/30 23:07 |
llm |
expert |
0m
Model:
Tokens:
input: 11268
output: 48
thoughts: 167
Thoughts: **Concurrent Socket Lifecycle Concerns**
Okay, let me think this through. I'm seeing `rxe_sock_put` operating on a socket (`sk`) obtained from `rxe_ns_pernet_sk4`. The interesting thing is that `rxe_ns_pernet_sk4` uses `rcu_dereference` β it's *not* taking a reference to the socket itself. It just returns the raw pointer, which means there's no inherent protection here.
Then, inside `rxe_sock_put`, there's this check: `refcount_read(&sk->sk_refcnt) > SK_REF_FOR_TUNNEL`. And *if* that condition is met, it proceeds to call `__sock_put(sk)`. That's where the alarm bells are going off. If `rxe_sock_put` is potentially called concurrently from multiple contexts β which is entirely possible given the lack of reference-counting in the lookup β then we could have a race condition where `__sock_put` is called more than once on the same socket. That would be really bad, leading to all sorts of double-free and use-after-free scenarios.
To understand this better, I need to see how these sockets are *created* and stored in the first place. Let's dig into `rxe_net_add` and trace the socket lifecycle from there. We need to confirm how the socket reference count is initialized and whether there are any synchronization mechanisms preventing these concurrent calls.
|
| 15/2 |
2026/05/30 23:07 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/infiniband/sw/rxe/rxe_net.c Name:rxe_net_add]
Results: map[SourceCode: 612: int rxe_net_add(const char *ibdev_name, struct net_device *ndev)
613: {
614: int err;
615: struct rxe_dev *rxe = NULL;
616:
617: rxe = ib_alloc_device(rxe_dev, ib_dev);
618: if (!rxe)
619: return -ENOMEM;
620:
621: ib_mark_name_assigned_by_user(&rxe->ib_dev);
622:
623: err = rxe_add(rxe, ndev->mtu, ibdev_name, ndev);
624: if (err) {
625: ib_dealloc_device(&rxe->ib_dev);
626: return err;
627: }
628:
629: return 0;
630: }
SourceFile:drivers/infiniband/sw/rxe/rxe_net.c]
|
| 16/2 |
2026/05/30 23:07 |
llm |
expert |
0m
Model:
Tokens:
input: 12122
output: 46
thoughts: 27
Thoughts: Here's my thought process, as if I were analyzing that code:
**Diving into the Network Addition**
Okay, so `rxe_net_add` calls `rxe_add`. That's a good starting point. My first instinct, given what I know about this architecture, is to understand what `rxe_add` is actually *doing*. It's likely the core function responsible for registering or initializing something related to the network interface or connection within the RXE subsystem. I need to understand the specifics of how it interacts with the underlying hardware, how it handles resource allocation, and, crucially, what kind of data structures it's working with. This is going to be key to tracing the path of a new network interface entering the system. I'll need to look at the internals of `rxe_add` next.
|
| 17/2 |
2026/05/30 23:07 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/infiniband/sw/rxe/rxe_net.c Name:rxe_add]
Results: map[SourceCode: 207: /* called by ifc layer to create new rxe device.
208: * The caller should allocate memory for rxe by calling ib_alloc_device.
209: */
210: int rxe_add(struct rxe_dev *rxe, unsigned int mtu, const char *ibdev_name,
211: struct net_device *ndev)
212: {
213: rxe_init(rxe, ndev);
214: rxe_set_mtu(rxe, mtu);
215: rxe->ib_dev.link_ops = &rxe_link_ops;
216:
217: return rxe_register_device(rxe, ibdev_name, ndev);
218: }
SourceFile:drivers/infiniband/sw/rxe/rxe.c]
|
| 18/2 |
2026/05/30 23:07 |
llm |
expert |
0m
Model:
Tokens:
input: 12626
output: 50
thoughts: 20
Thoughts: Here's a first-person summary, keeping in mind the audience is an expert:
**Tracing Socket Creation in the UDP Tunnel**
Okay, so I need to pinpoint the exact location where the UDP socket is instantiated within the `rxe` framework. My initial thought is to jump directly into `rxe_setup_udp_tunnel`. That function's name strongly suggests it's the likely spot for socket creation, but let's confirm. I'll need to carefully examine the code within that function to ensure socket calls like `socket()` and related options and settings are happening there, or if that routine simply acts as a helper or wrapper. Time to dive into the source and see what's what.
|
| 19/2 |
2026/05/30 23:07 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/infiniband/sw/rxe/rxe_net.c Name:rxe_setup_udp_tunnel]
Results: map[SourceCode: 259: static struct socket *rxe_setup_udp_tunnel(struct net *net, __be16 port,
260: bool ipv6)
261: {
262: int err;
263: struct socket *sock;
264: struct udp_port_cfg udp_cfg = { };
265: struct udp_tunnel_sock_cfg tnl_cfg = { };
266:
267: if (ipv6) {
268: udp_cfg.family = AF_INET6;
269: udp_cfg.ipv6_v6only = 1;
270: } else {
271: udp_cfg.family = AF_INET;
272: }
273:
274: udp_cfg.local_udp_port = port;
275:
276: /* Create UDP socket */
277: err = udp_sock_create(net, &udp_cfg, &sock);
278: if (err < 0)
279: return ERR_PTR(err);
280: rxe_reclassify_recv_socket(sock);
281:
282: tnl_cfg.encap_type = 1;
283: tnl_cfg.encap_rcv = rxe_udp_encap_recv;
284:
285: /* Setup UDP tunnel */
286: setup_udp_tunnel_sock(net, sock, &tnl_cfg);
287:
288: return sock;
289: }
SourceFile:drivers/infiniband/sw/rxe/rxe_net.c]
|
| 20/2 |
2026/05/30 23:07 |
llm |
expert |
0m
Model:
Tokens:
input: 13335
output: 26
thoughts: 13
Thoughts: Okay, here's my interpretation of that question, rendered as a first-person thought process, keeping in mind my expert understanding:
**Digging into the `rxe_setup_udp_tunnel` Call Stack**
Alright, that's a key question. `rxe_setup_udp_tunnel`β¦ right, that's the function in the RDMA over Converged Ethernet (RoCE) implementation that's responsible for setting up the UDP tunnel encapsulation. My immediate thought is, *who's initiating this process*? Understanding the caller is critical for understanding the overall data flow and how the RoCE stack is being configured.
I need to trace back from that function call. I need to know *where* it's being invoked. Is it called from the user space through some sort of system call, or is it triggered within the kernel in response to a network event? Is it a part of the initial setup, perhaps on device initialization, or is it dynamically created as needed? Knowing the calling function and how it got invoked tells me what triggers RoCE configuration.
Knowing the call path will help me to determine which part of the system is configuring the RDMA network and the UDP encapsulation. Is this a system where the tunnels are pre-configured, or is this being dynamically built in response to network events? Is it triggered by some higher level orchestration layer, like a container orchestrator, or is this more of a manual configuration? I need to understand the control flow. The context of the call is the key. Knowing who calls `rxe_setup_udp_tunnel` will help me understand its purpose and how it fits into the bigger picture. Time to dive into the code and start looking at the cross-references.
|
| 21/2 |
2026/05/30 23:07 |
tool |
codesearch-find-references |
0m
Args: map[Name:rxe_setup_udp_tunnel]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:rxe_net_ipv4_init SourceFile:drivers/infiniband/sw/rxe/rxe_net.c SourceLine:766] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:rxe_net_ipv6_init SourceFile:drivers/infiniband/sw/rxe/rxe_net.c SourceLine:788]] TruncatedOutput:false]
|
| 22/2 |
2026/05/30 23:07 |
llm |
expert |
0m
Model:
Tokens:
input: 13883
output: 51
thoughts: 15
Thoughts: Okay, let's dive into this. Here's my take on those thoughts, as if they're my own:
**Initial Assessment of `rxe_net_ipv4_init`**
Alright, let's take a look at `rxe_net_ipv4_init`. Immediately, my brain starts running through the implications. This function's name suggests it's the initialization routine for IPv4 networking within the RXE (RDMA over Converged Ethernet) context. Knowing RXE, I'm expecting this function to be critical for the network fabric's functionality, and handling interactions with the underlying RDMA hardware. It's likely involved in setting up network interfaces, configuring IP addresses, and ensuring the proper communication paths for data transfer over the RDMA network are established. My mind's automatically considering the potential interactions with other networking layers and how this initialization ties into the broader RXE architecture. What are the key data structures this function manipulates? What error checking is in place? I need to look into these questions.
|
| 23/2 |
2026/05/30 23:07 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/infiniband/sw/rxe/rxe_net.c Name:rxe_net_ipv4_init]
Results: map[SourceCode: 755: static int rxe_net_ipv4_init(struct net *net)
756: {
757: struct sock *sk;
758: struct socket *sock;
759:
760: sk = rxe_ns_pernet_sk4(net);
761: if (sk) {
762: sock_hold(sk);
763: return 0;
764: }
765:
766: sock = rxe_setup_udp_tunnel(net, htons(ROCE_V2_UDP_DPORT), false);
767: if (IS_ERR(sock)) {
768: pr_err("Failed to create IPv4 UDP tunnel\n");
769: return -1;
770: }
771: rxe_ns_pernet_set_sk4(net, sock->sk);
772:
773: return 0;
774: }
SourceFile:drivers/infiniband/sw/rxe/rxe_net.c]
|
| 24/2 |
2026/05/30 23:07 |
llm |
expert |
0m
Model:
Tokens:
input: 14488
output: 24
thoughts: 169
Thoughts: **Investigating the RXE Network Initialization and Socket Release**
Okay, let's see, `rxe_net_ipv4_init` first checks for an existing socket `sk` using `rxe_ns_pernet_sk4`. If one exists, it holds a reference and returns. Seems straightforward. But, what happens when it's time to release this socket? That's where `rxe_sock_put` comes in.
Now, here's where my interest peaks: The code checks the reference count, but not just any reference count. It compares it against `SK_REF_FOR_TUNNEL`. And *that's* the crucial piece I need to understand. What does that value represent? What triggers a release versus a tunnel-specific action? Why is there a distinction? I need to dig into what's happening within this socket's lifecycle to understand the role of `SK_REF_FOR_TUNNEL`. What conditions lead to `rxe_release_udp_tunnel` being called? This all hinges on that comparison.
|
| 25/2 |
2026/05/30 23:07 |
tool |
grepper |
0m
Args: map[Expression:#define SK_REF_FOR_TUNNEL]
Results: map[Output:drivers/infiniband/sw/rxe/rxe_net.c-22-#ifndef SK_REF_FOR_TUNNEL
drivers/infiniband/sw/rxe/rxe_net.c:23:#define SK_REF_FOR_TUNNEL 2
drivers/infiniband/sw/rxe/rxe_net.c-24-#endif
]
|
| 26/2 |
2026/05/30 23:07 |
llm |
expert |
1m
Model:
Tokens:
input: 15010
output: 43
thoughts: 96
Thoughts: **Analyzing UDP Tunnel Socket Refcounts**
Okay, so I'm seeing `SK_REF_FOR_TUNNEL` is defined as 2. That's our baseline. Now, when `rxe_setup_udp_tunnel` calls the socket creation routine, specifically `udp_sock_create`, I need to nail down the initial reference count. Right, `udp_sock_create` creates the socket, and as I recall from my previous dives into the kernel, a freshly minted socket would have an `sk_refcnt` of 1.
But, hang on a second...it just hit me; `setup_udp_tunnel_sock`, might be taking *another* reference. That's the crucial step I'm missing. We need to check the code there, specifically `setup_udp_tunnel_sock`, to see if a reference is added and if so when.
|
| 27/2 |
2026/05/30 23:09 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/ipv4/udp_tunnel_core.c Name:setup_udp_tunnel_sock]
Results: map[SourceCode: 71: void setup_udp_tunnel_sock(struct net *net, struct socket *sock,
72: struct udp_tunnel_sock_cfg *cfg)
73: {
74: struct sock *sk = sock->sk;
75:
76: /* Disable multicast loopback */
77: inet_clear_bit(MC_LOOP, sk);
78:
79: /* Enable CHECKSUM_UNNECESSARY to CHECKSUM_COMPLETE conversion */
80: inet_inc_convert_csum(sk);
81:
82: rcu_assign_sk_user_data(sk, cfg->sk_user_data);
83:
84: udp_sk(sk)->encap_type = cfg->encap_type;
85: udp_sk(sk)->encap_rcv = cfg->encap_rcv;
86: udp_sk(sk)->encap_err_rcv = cfg->encap_err_rcv;
87: udp_sk(sk)->encap_err_lookup = cfg->encap_err_lookup;
88: udp_sk(sk)->encap_destroy = cfg->encap_destroy;
89: udp_sk(sk)->gro_receive = cfg->gro_receive;
90: udp_sk(sk)->gro_complete = cfg->gro_complete;
91:
92: udp_tunnel_encap_enable(sk);
93:
94: udp_tunnel_update_gro_rcv(sk, true);
95:
96: if (!sk->sk_dport && !sk->sk_bound_dev_if && sk_saddr_any(sk) &&
97: sk->sk_kern_sock)
98: udp_tunnel_update_gro_lookup(net, sk, true);
99: }
SourceFile:net/ipv4/udp_tunnel_core.c]
|
| 28/2 |
2026/05/30 23:09 |
llm |
expert |
0m
Model:
Tokens:
input: 15873
output: 27
thoughts: 147
Thoughts: **My Current Reasoning Process**
Okay, so I'm looking at this code and trying to figure out how a socket is being handled. Initially, it looked like this socket might be floating around without any references, which seemed strange. But, hang on a second, a newly created socket has `sk->sk_refcnt` set to 1, so there is at least a base reference. Alright, so I need to track down where this socket is actually instantiated.
Let's see⦠I'm noticing that `rxe_net_ipv4_init` calls `rxe_setup_udp_tunnel`, and *that* function is the one creating the socket, bingo! Then, `rxe_ns_pernet_set_sk4` is setting it up. Great, it's starting to make sense. So, if `rxe_net_ipv4_init` gets called, the socket gets created.
Now, where do we go from there? Okay, `rxe_net_add` calls `rxe_add`, which then calls `rxe_register_device`. Iβm following the lifecycle of this socket and it appears to be well-defined. But, I am still missing the root cause. My question now is, what is actually calling `rxe_net_ipv4_init` in the first place? I need to understand that to get the complete picture.
|
| 29/2 |
2026/05/30 23:09 |
tool |
codesearch-find-references |
0m
Args: map[Name:rxe_net_ipv4_init]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:rxe_net_init SourceFile:drivers/infiniband/sw/rxe/rxe_net.c SourceLine:831]] TruncatedOutput:false]
|
| 30/2 |
2026/05/30 23:09 |
llm |
expert |
0m
Model:
Tokens:
input: 16428
output: 48
thoughts: 12
Thoughts: Okay, let's dive into this. Here's my thought process summarized, assuming I'm already pretty familiar with network initialization routines:
**Initial Assessment of `rxe_net_init`**
Alright, I'm thinking, "Let's check `rxe_net_init`." That's the starting point. I know this is the function responsible for the initialization of the RoCE (RDMA over Converged Ethernet) network stack, likely for a specific RDMA context or device. My immediate questions are: What's the scope? What's it setting up? What resources is it allocating? Are there any potential concurrency issues I need to keep in mind? I need to understand its role in the bigger picture of the driver's initialization sequence. Is it called from the device probe function? Is there any interaction with the networking stack above it? Okay, let's dig into the code.
|
| 31/2 |
2026/05/30 23:09 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/infiniband/sw/rxe/rxe_net.c Name:rxe_net_init]
Results: map[SourceCode: 823: int rxe_net_init(struct net_device *ndev)
824: {
825: struct net *net;
826: struct sock *sk;
827: int err;
828:
829: net = dev_net(ndev);
830:
831: err = rxe_net_ipv4_init(net);
832: if (err)
833: return err;
834:
835: err = rxe_net_ipv6_init(net);
836: if (err)
837: goto err_out;
838:
839: return 0;
840:
841: err_out:
842: /* If ipv6 error, release ipv4 resource */
843: sk = rxe_ns_pernet_sk4(net);
844: if (sk)
845: rxe_sock_put(sk, rxe_ns_pernet_set_sk4, net);
846:
847: return err;
848: }
SourceFile:drivers/infiniband/sw/rxe/rxe_net.c]
|
| 32/2 |
2026/05/30 23:09 |
llm |
expert |
0m
Model:
Tokens:
input: 17053
output: 24
thoughts: 11
Thoughts: Okay, here's my attempt at summarizing that thought process, tailored for an expert audience:
**Investigating the `rxe_net_init` Caller**
Alright, so `rxe_net_init`. That's the function that initializes the RXE (RDMA over Converged Ethernet) network interface, right? My immediate thought is, "Okay, who's actually calling this thing?" It's a critical initialization function, likely setting up the necessary structures, perhaps allocating resources, and getting the RXE network stack ready to roll. Understanding its call graph is *essential* for comprehending the RXE bring-up sequence, potential dependencies, and troubleshooting any initialization issues. Is it called directly by the kernel? During a specific device probe, maybe? Or is it part of some more complex, layered initialization process? I need to figure out the entry point into the RXE subsystem to truly understand how the architecture functions and troubleshoot any startup errors. I need to trace this back.
|
| 33/2 |
2026/05/30 23:09 |
tool |
codesearch-find-references |
0m
Args:
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:rxe_newlink SourceFile:drivers/infiniband/sw/rxe/rxe.c SourceLine:239]] TruncatedOutput:false]
|
| 34/2 |
2026/05/30 23:09 |
llm |
expert |
0m
Model:
Tokens:
input: 17372
output: 45
thoughts: 11
Thoughts: Okay, here's my interpretation of "Let's see `rxe_newlink`," tailored as requested for an expert audience.
**Diving into `rxe_newlink`**
Alright, so the call for "Let's see `rxe_newlink`" immediately triggers a specific mental map. I'm assuming we're dealing with the context of a new link creation, probably within the realm of RDMA or similar high-performance networking, given the "rxe" prefix which strongly suggests Reliable eXpress or a related RXE/RDMA implementation. My initial thought process is focusing on several key areas:
* **Underlying Architecture:** What architecture are we dealing with? Is this kernel space, user space, or a hybrid? Understanding the environment profoundly impacts how `rxe_newlink` would function. Memory mapping, device access, and synchronization all shift depending on the context. I'm looking for clues in related code, headers, or documentation.
* **Data Structures:** What data structures are involved? `rxe_newlink` almost certainly interacts with structures representing the physical NIC, the virtual link itself, connection management, potentially even queue pairs, and of course, any associated configuration data. I'm mentally assembling the likely core structures: `rxe_dev`, `rxe_link`, `rxe_qp`, and their relationships.
* **Resource Allocation:** This function is a prime candidate for resource allocation. It will probably handle allocating and initializing memory for link structures, potentially requesting resources from the underlying NIC, and setting up initial states. I'm anticipating that we'll be dealing with memory allocation and, very probably, some kind of locking or synchronization mechanism to handle concurrent access.
* **Error Handling:** "Newlink" functions are often crucial, and thus prone to failure. I'm immediately looking for how error conditions are managed. What are the potential failure points? Resource exhaustion, configuration errors, hardware issues... and how are these errors reported and handled? A robust `rxe_newlink` must have comprehensive error handling to ensure stability.
* **Configuration:** The function almost definitely will take a configuration structure as input. I'll need to know the typical configuration parameters, e.g., MTU size, QKey, port details, routing information, etc. How are these parameters validated and propagated? Are there any sanity checks being done before the link is made active?
* **Security Context:** This is important. What are the security considerations? Depending on the architecture, there might be user-provided data, and any kind of vulnerabilities that may stem from that.
In short, I'm mentally preparing to scrutinize `rxe_newlink` for its role in establishing a new link, focusing on resource management, error resilience, and ensuring a stable and efficient connection. I expect to be drilling down into the details of data structure initialization, error codes, and configuration parameters. I'll need to see how it fits within the larger network stack and what dependencies it has.
|
| 35/2 |
2026/05/30 23:09 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/infiniband/sw/rxe/rxe.c Name:rxe_newlink]
Results: map[SourceCode: 220: static int rxe_newlink(const char *ibdev_name, struct net_device *ndev)
221: {
222: struct rxe_dev *rxe;
223: int err = 0;
224:
225: if (is_vlan_dev(ndev)) {
226: rxe_err("rxe creation allowed on top of a real device only\n");
227: err = -EPERM;
228: goto err;
229: }
230:
231: rxe = rxe_get_dev_from_net(ndev);
232: if (rxe) {
233: ib_device_put(&rxe->ib_dev);
234: rxe_err_dev(rxe, "already configured on %s\n", ndev->name);
235: err = -EEXIST;
236: goto err;
237: }
238:
239: err = rxe_net_init(ndev);
240: if (err)
241: return err;
242:
243: err = rxe_net_add(ibdev_name, ndev);
244: if (err) {
245: rxe_err("failed to add %s\n", ndev->name);
246: goto err;
247: }
248: err:
249: return err;
250: }
SourceFile:drivers/infiniband/sw/rxe/rxe.c]
|
| 36/2 |
2026/05/30 23:09 |
llm |
expert |
0m
Model:
Tokens:
input: 18545
output: 45
thoughts: 168
Thoughts: **My Concerns Regarding Socket Management in the RXE Network Initialization**
Okay, let me think this through. `rxe_newlink` starts by calling `rxe_net_init` and then `rxe_net_add`. My immediate worry is: what happens if `rxe_net_add` fails? It returns an error code, `err`, but I don't see any explicit cleanup of the socket that `rxe_net_init` created! I need to be sure this is handled correctly to avoid resource leaks.
Hang on⦠`rxe_net_init` is the one creating the socket, and crucially, it's placing the socket into `rxe_ns_pernet_sk4`. So, if `rxe_net_add` fails, the socket just sits there in `rxe_ns_pernet_sk4`, unmanaged. That's a definite problem. I need to understand the lifecycle of this socket.
On the other hand, `rxe_net_add` returns 0 on success, which is good. But the critical question is still unanswered: When is this socket properly released? Where's the matching cleanup function?
Ah! I see that `rxe_dellink` calls `rxe_net_del`. I'll need to go through the code, particularly `rxe_net_del`, to confirm it correctly deals with the socket in `rxe_ns_pernet_sk4`. I need to ensure that it's handling both successful and unsuccessful link additions. There might be a race condition, or a simple memory leak, that I need to look for.
|
| 37/2 |
2026/05/30 23:09 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/infiniband/sw/rxe/rxe.c Name:rxe_dellink]
Results: map[SourceCode: 252: static int rxe_dellink(struct ib_device *dev)
253: {
254: rxe_net_del(dev);
255:
256: return 0;
257: }
SourceFile:drivers/infiniband/sw/rxe/rxe.c]
|
| 38/2 |
2026/05/30 23:09 |
llm |
expert |
0m
Model:
Tokens:
input: 19221
output: 41
thoughts: 212
Thoughts: **Reflections on Socket Reference Counting in the RXE Network Stack**
Okay, so I'm digging into how the RXE network stack handles socket management, specifically around link creation and deletion. The first thing that jumps out is the relationship between `rxe_net_del` and `rxe_sock_put`. That seems straightforward enough; deletion should naturally involve releasing the socket. Now, I have to consider the nuances of multiple invocations of `rxe_newlink` for the same network namespace. Looking at the code for `rxe_net_init`, this is where things get interesting. It appears that it checks for an existing socket (`rxe_ns_pernet_sk4`). If one exists, `sock_hold` is called.
Aha! `sock_hold` is the critical function to examine. It increases `sk_refcnt`. So, if `rxe_newlink` is invoked multiple timesβlet's say *N* timesβfor the same netns, then the socket's reference count will become *N*, and the initial socket reference count would be N - 1, plus the initial one. This is because the code holds a socket, if it already exists, and so, the refcount will be incremented. That makes sense.
Now, let's step back and consider how sockets are *initially* created. I have to look at `udp_sock_create`. That's where it all begins. It is where a new socket is created. That would mean that a socket's reference count would begin at 1. `udp_sock_create` also calls `sock_create_kern`, which allocates the socket.
I should check the `udp_sock_create` function to make sure that the refcount is set as expected, because the whole process is dependent on this function.
|
| 39/2 |
2026/05/30 23:09 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/ipv4/udp_tunnel_core.c Name:udp_sock_create]
Results: map[SourceCode: 58: static inline int udp_sock_create(struct net *net,
59: struct udp_port_cfg *cfg,
60: struct socket **sockp)
61: {
62: if (cfg->family == AF_INET)
63: return udp_sock_create4(net, cfg, sockp);
64:
65: if (cfg->family == AF_INET6)
66: return udp_sock_create6(net, cfg, sockp);
67:
68: return -EPFNOSUPPORT;
69: }
SourceFile:include/net/udp_tunnel.h]
|
| 40/2 |
2026/05/30 23:09 |
llm |
expert |
0m
Model:
Tokens:
input: 20063
output: 42
thoughts: 12
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Diving into `udp_sock_create4`**
Okay, time to dig into `udp_sock_create4`. Given the context, I'm assuming we're dealing with IPv4 UDP socket creation, as the name explicitly indicates. My immediate questions revolve around the implementation details: What are the key data structures involved? Is it allocating a new `sock` structure, initializing it, and then linking it into the appropriate protocol hash tables or lists? Are there any subtle nuances regarding socket options or address family handling I need to be aware of? Are there potential race conditions in this path that could require careful synchronization? I'm particularly interested in how the socket is integrated with the larger networking stack and how it interacts with the underlying protocol handlers. What are the error code paths? I'm looking for anything unusual or unexpected; this is where interesting bugs often hide. I need to understand the memory allocation strategy and any associated reference counting to avoid memory leaks or double frees. Letβs start tracing the code.
|
| 41/2 |
2026/05/30 23:09 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/ipv4/udp_tunnel_core.c Name:udp_sock_create4]
Results: map[SourceCode: 12: int udp_sock_create4(struct net *net, struct udp_port_cfg *cfg,
13: struct socket **sockp)
14: {
15: int err;
16: struct socket *sock = NULL;
17: struct sockaddr_in udp_addr;
18:
19: err = sock_create_kern(net, AF_INET, SOCK_DGRAM, 0, &sock);
20: if (err < 0)
21: goto error;
22:
23: if (cfg->bind_ifindex) {
24: err = sock_bindtoindex(sock->sk, cfg->bind_ifindex, true);
25: if (err < 0)
26: goto error;
27: }
28:
29: udp_addr.sin_family = AF_INET;
30: udp_addr.sin_addr = cfg->local_ip;
31: udp_addr.sin_port = cfg->local_udp_port;
32: err = kernel_bind(sock, (struct sockaddr_unsized *)&udp_addr,
33: sizeof(udp_addr));
34: if (err < 0)
35: goto error;
36:
37: if (cfg->peer_udp_port) {
38: udp_addr.sin_family = AF_INET;
39: udp_addr.sin_addr = cfg->peer_ip;
40: udp_addr.sin_port = cfg->peer_udp_port;
41: err = kernel_connect(sock, (struct sockaddr_unsized *)&udp_addr,
42: sizeof(udp_addr), 0);
43: if (err < 0)
44: goto error;
45: }
46:
47: sock->sk->sk_no_check_tx = !cfg->use_udp_checksums;
48:
49: *sockp = sock;
50: return 0;
51:
52: error:
53: if (sock) {
54: kernel_sock_shutdown(sock, SHUT_RDWR);
55: sock_release(sock);
56: }
57: *sockp = NULL;
58: return err;
59: }
SourceFile:net/ipv4/udp_tunnel_core.c]
|
| 42/2 |
2026/05/30 23:09 |
llm |
expert |
0m
Model:
Tokens:
input: 21034
output: 43
thoughts: 329
Thoughts: **Analyzing Socket Reference Counting in the RXE Kernel Module**
Okay, let's break this down. My initial thought is that `sock_create_kern` should create a socket and set the `sk_refcnt` of the associated `struct sock` to 1. That's the baseline, the initial reference. Then, I see `rxe_sock_put` come into play, and it's checking `refcount_read(&sk->sk_refcnt) > SK_REF_FOR_TUNNEL`. And `SK_REF_FOR_TUNNEL` is defined as 2. That's the first red flag. If we just created the socket, shouldn't `sk_refcnt` *be* 1, not something that requires a check for being *greater* than 2?
Wait a minute... is it the `struct socket` itself holding a reference to the `struct sock`? No, no, they're inextricably linked; one doesn't "hold" the other in that way. They are like conjoined twins, if you will.
So, if `sk_refcnt` is 1, and `rxe_sock_put` is checking against 2, the `else` branch of the conditional must be triggered. That `else` branch does this:
```c
if (refcount_read(&sk->sk_refcnt) > SK_REF_FOR_TUNNEL) {
__sock_put(sk);
} else {
rxe_release_udp_tunnel(sk->sk_socket);
sk = NULL;
set_sk(net, sk);
}
```
That means `rxe_release_udp_tunnel` gets called, specifically `udp_tunnel_sock_release(sk->sk_socket)`. This seems to suggest that there's a specific release path for UDP tunnels when the initial reference is the only one. Okay, the tunnel-specific cleanup is handled here when the refcount is, as expected, 1. Good.
|
| 43/2 |
2026/05/30 23:09 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/ipv4/udp_tunnel_core.c Name:udp_tunnel_sock_release]
Results: map[SourceCode: 198: void udp_tunnel_sock_release(struct socket *sock)
199: {
200: rcu_assign_sk_user_data(sock->sk, NULL);
201: synchronize_rcu();
202: kernel_sock_shutdown(sock, SHUT_RDWR);
203: sock_release(sock);
204: }
SourceFile:net/ipv4/udp_tunnel_core.c]
|
| 44/2 |
2026/05/30 23:09 |
llm |
expert |
0m
Model:
Tokens:
input: 21993
output: 30
thoughts: 1125
Thoughts: **Refcount Confusion in RXE Network Device Handling**
Okay, so here's the issue I'm seeing in this RXE network device code, laid bare. It starts with a simple socket release: `sock_release` calls `__sock_release`, which eventually boils down to a `sock_put(sk)`. This should, in most scenarios, decrement the refcount to zero and free the socket. Fine.
But then, we have this `rxe_net_ipv4_init` business. It gets called, and the socket's refcount is set to 1. `rxe_net_init` then proceeds to call `rxe_net_ipv6_init`. If IPv6 initialization *fails*, we hit `err_out`. Here, a call to `rxe_sock_put` is made on `sk`, where the refcount should still be 1. `rxe_sock_put` sees `1 <= 2`, and calls `rxe_release_udp_tunnel`, which seems to release the socket. But does it really? It looks like the pernet storage is cleared.
Now, here's where things get interesting. What if we create a device again? `rxe_newlink` calls `rxe_net_init`, making a new socket and all seems well.
However, if `rxe_newlink` *succeeds*, and we make *another* device in the same netns, a call to `rxe_net_init` will cause `rxe_net_ipv4_init` to run again. This function will return the *existing* socket from pernet storage. A `sock_hold` is called, increasing the refcount to 2. `rxe_net_ipv6_init` is also called and it also calls `sock_hold`, meaning the refcount of the IPv6 socket also goes to 2. So now we've got two devices sharing one socket, with refcount 2. Great!
But, when we delete one of those devices via `rxe_dellink`, `rxe_net_del` calls `rxe_sock_put(sk4)`. The refcount is still 2 at this point. And here's the crux: the code *should* call `__sock_put(sk)` in the `if` statement, but it doesn't. `refcount_read(&sk->sk_refcnt) > SK_REF_FOR_TUNNEL` (which is 2) evaluates to false because `2 > 2` is false. So we go into the `else` branch!
The `else` branch calls `rxe_release_udp_tunnel`, which then calls `sock_release`, which should drop the refcount by 1, right? And release the socket? *No!*. `sock_release` calls `inet_release`, which calls `sk_common_release`, which, in turn calls `sock_put(sk)`, which drops the refcount to 1! The socket *isn't* freed at this point!
I get a little stuck here... `sock_release` does call `sock->ops->release(sock)`, which then calls `iput`. This frees the `struct socket` and the inode. Fine. But, the `struct sock` is still alive with the refcount at 1. But wait... `rxe_release_udp_tunnel` sets `sk->sk_socket` to NULL, and the `sock_release` function also sets `sock->sk = NULL`.
The tricky bit is the fact that the second device *still* thinks the socket is alive. It's not *technically* freed, just detached from the first device. And `rxe_sock_put` does `set_sk(net, sk)` where `sk` is now NULL, so the pernet storage is cleared.
So the bigger problem is this: with the second device, refcount 2 means the `else` branch is triggered, and `rxe_release_udp_tunnel` is called even though the socket is still being used by the *second* device!
Why is `SK_REF_FOR_TUNNEL` set to 2? I need to check the history of that define, because something here seems wrong.
|
| 45/2 |
2026/05/30 23:09 |
tool |
git-log |
10m
Error:
Args: map[CodeRegexp:SK_REF_FOR_TUNNEL Count:10]
Results:
|
| 46/2 |
2026/05/30 23:19 |
llm |
expert |
0m
Model:
Tokens:
input: 24115
output: 54
thoughts: 24
Thoughts: Here's a summary of my thought process, as requested:
**Diving into the RXE Net Code**
Okay, so I need to understand this part of the InfiniBand driver better. I'm going to start with the source code for the RXE network interface, specifically `drivers/infiniband/sw/rxe/rxe_net.c`. This file likely handles the network-facing aspects of the Reliable eXpress Transfer (RXE) protocol within the driver. I expect to find the code responsible for things like packet processing, socket management, network interface configuration, and interactions with the kernel's networking stack. I'm hoping to get a handle on how RXE translates its InfiniBand semantics to a regular network environment. I need to understand how this integration works, and this seems like the logical starting point. Let's see what's in there.
|
| 47/2 |
2026/05/30 23:19 |
tool |
git-log |
0m
Args: map[CodeRegexp:SK_REF_FOR_TUNNEL Count:10 PathPrefix:drivers/infiniband/sw/rxe/rxe_net.c]
Results: map[Output:f1327abd6abe RDMA/rxe: Support RDMA link creation and destruction per net namespace
]
|
| 48/2 |
2026/05/30 23:20 |
llm |
expert |
0m
Model:
Tokens:
input: 24419
output: 23
thoughts: 15
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Analyzing Commit f1327abd6abe**
Alright, let's dive right in and dissect commit `f1327abd6abe`. My initial focus is always on the scope and impact indicated by the commit message. I'm already formulating hypotheses: is this a hotfix, a feature addition, a refactor, or something more involved? I'll probably start by checking the diff to immediately gauge the magnitude of changes β were we talking single-file tweaks, significant structural alterations across the codebase, or something in between? Given the commit hash alone, the context is missing, so I'll need to reconstruct the why.
From there, I'll move quickly to understand the specific code changes. I'll be looking for potential regressions, performance bottlenecks introduced, or subtle security vulnerabilities. My pattern recognition for common coding errors will kick in right away. Are there any coding style violations that might indicate rushed work? I'll be particularly keen to assess how this commit interacts with the existing codebase β are there any integration points I need to flag? Dependency changes are also a major concern, as they can sometimes lead to unexpected conflicts or runtime issues. I'll also be considering whether testing was adequate and appropriate and whether there will be any cascading impacts. I am expecting thorough code reviews of this commit, and will be looking to determine if those are adequate. My objective is to quickly ascertain whether this commit merits a deeper dive or a quick approval with caveats.
|
| 49/2 |
2026/05/30 23:20 |
tool |
git-show |
0m
Args:
Results: map[Output:commit f1327abd6abed031ae4146825c6b28bdd1456474
Author: Zhu Yanjun <yanjun.zhu@linux.dev>
Date: Thu Mar 12 19:30:57 2026 -0700
RDMA/rxe: Support RDMA link creation and destruction per net namespace
After introducing dellink handling and per-net namespace management
for IPv4 and IPv6 sockets, extend rxe to create and destroy RDMA links
within each network namespace.
With this change, RDMA links can be instantiated both in init_net and
in other network namespaces. The lifecycle of the RDMA link is now tied
to the corresponding namespace and is properly cleaned up when the
namespace or link is removed.
This ensures rxe behaves correctly in multi-namespace environments and
keeps socket and RDMA link resources consistent across namespace
creation and teardown.
Reviewed-by: David Ahern <dsahern@kernel.org>
Signed-off-by: Zhu Yanjun <yanjun.zhu@linux.dev>
Link: https://patch.msgid.link/20260313023058.13020-4-yanjun.zhu@linux.dev
Signed-off-by: Leon Romanovsky <leon@kernel.org>
diff --git a/drivers/infiniband/sw/rxe/rxe.c b/drivers/infiniband/sw/rxe/rxe.c
index e891199cbdef..b0714f9abe3d 100644
--- a/drivers/infiniband/sw/rxe/rxe.c
+++ b/drivers/infiniband/sw/rxe/rxe.c
@@ -8,6 +8,8 @@
#include <net/addrconf.h>
#include "rxe.h"
#include "rxe_loc.h"
+#include "rxe_net.h"
+#include "rxe_ns.h"
MODULE_AUTHOR("Bob Pearson, Frank Zago, John Groves, Kamal Heib");
MODULE_DESCRIPTION("Soft RDMA transport");
@@ -200,6 +202,8 @@ void rxe_set_mtu(struct rxe_dev *rxe, unsigned int ndev_mtu)
port->mtu_cap = ib_mtu_enum_to_int(mtu);
}
+static struct rdma_link_ops rxe_link_ops;
+
/* called by ifc layer to create new rxe device.
* The caller should allocate memory for rxe by calling ib_alloc_device.
*/
@@ -208,6 +212,7 @@ int rxe_add(struct rxe_dev *rxe, unsigned int mtu, const char *ibdev_name,
{
rxe_init(rxe, ndev);
rxe_set_mtu(rxe, mtu);
+ rxe->ib_dev.link_ops = &rxe_link_ops;
return rxe_register_device(rxe, ibdev_name, ndev);
}
@@ -231,6 +236,10 @@ static int rxe_newlink(const char *ibdev_name, struct net_device *ndev)
goto err;
}
+ err = rxe_net_init(ndev);
+ if (err)
+ return err;
+
err = rxe_net_add(ibdev_name, ndev);
if (err) {
rxe_err("failed to add %s\n", ndev->name);
@@ -240,9 +249,17 @@ static int rxe_newlink(const char *ibdev_name, struct net_device *ndev)
return err;
}
+static int rxe_dellink(struct ib_device *dev)
+{
+ rxe_net_del(dev);
+
+ return 0;
+}
+
static struct rdma_link_ops rxe_link_ops = {
.type = "rxe",
.newlink = rxe_newlink,
+ .dellink = rxe_dellink,
};
static int __init rxe_module_init(void)
@@ -253,15 +270,24 @@ static int __init rxe_module_init(void)
if (err)
return err;
- err = rxe_net_init();
- if (err) {
- rxe_destroy_wq();
- return err;
- }
+ err = rxe_namespace_init();
+ if (err)
+ goto err_destroy_wq;
+
+ err = rxe_register_notifier();
+ if (err)
+ goto err_namespace_exit;
rdma_link_register(&rxe_link_ops);
+
pr_info("loaded\n");
return 0;
+
+err_namespace_exit:
+ rxe_namespace_exit();
+err_destroy_wq:
+ rxe_destroy_wq();
+ return err;
}
static void __exit rxe_module_exit(void)
@@ -271,6 +297,8 @@ static void __exit rxe_module_exit(void)
rxe_net_exit();
rxe_destroy_wq();
+ rxe_namespace_exit();
+
pr_info("unloaded\n");
}
diff --git a/drivers/infiniband/sw/rxe/rxe_net.c b/drivers/infiniband/sw/rxe/rxe_net.c
index 0bd0902b11f7..211bd3000acc 100644
--- a/drivers/infiniband/sw/rxe/rxe_net.c
+++ b/drivers/infiniband/sw/rxe/rxe_net.c
@@ -17,8 +17,11 @@
#include "rxe.h"
#include "rxe_net.h"
#include "rxe_loc.h"
+#include "rxe_ns.h"
-static struct rxe_recv_sockets recv_sockets;
+#ifndef SK_REF_FOR_TUNNEL
+#define SK_REF_FOR_TUNNEL 2
+#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
/*
@@ -101,20 +104,20 @@ static inline void rxe_reclassify_recv_socket(struct socket *sock)
}
static struct dst_entry *rxe_find_route4(struct rxe_qp *qp,
+ struct net *net,
struct net_device *ndev,
struct in_addr *saddr,
struct in_addr *daddr)
{
struct rtable *rt;
- struct flowi4 fl = { { 0 } };
+ struct flowi4 fl = {};
- memset(&fl, 0, sizeof(fl));
fl.flowi4_oif = ndev->ifindex;
memcpy(&fl.saddr, saddr, sizeof(*saddr));
memcpy(&fl.daddr, daddr, sizeof(*daddr));
fl.flowi4_proto = IPPROTO_UDP;
- rt = ip_route_output_key(&init_net, &fl);
+ rt = ip_route_output_key(net, &fl);
if (IS_ERR(rt)) {
rxe_dbg_qp(qp, "no route to %pI4\n", &daddr->s_addr);
return NULL;
@@ -125,21 +128,21 @@ static struct dst_entry *rxe_find_route4(struct rxe_qp *qp,
#if IS_ENABLED(CONFIG_IPV6)
static struct dst_entry *rxe_find_route6(struct rxe_qp *qp,
+ struct net *net,
struct net_device *ndev,
struct in6_addr *saddr,
struct in6_addr *daddr)
{
struct dst_entry *ndst;
- struct flowi6 fl6 = { { 0 } };
+ struct flowi6 fl6 = {};
- memset(&fl6, 0, sizeof(fl6));
fl6.flowi6_oif = ndev->ifindex;
memcpy(&fl6.saddr, saddr, sizeof(*saddr));
memcpy(&fl6.daddr, daddr, sizeof(*daddr));
fl6.flowi6_proto = IPPROTO_UDP;
- ndst = ipv6_stub->ipv6_dst_lookup_flow(sock_net(recv_sockets.sk6->sk),
- recv_sockets.sk6->sk, &fl6,
+ ndst = ipv6_stub->ipv6_dst_lookup_flow(net,
+ rxe_ns_pernet_sk6(net), &fl6,
NULL);
if (IS_ERR(ndst)) {
rxe_dbg_qp(qp, "no route to %pI6\n", daddr);
@@ -160,6 +163,7 @@ static struct dst_entry *rxe_find_route6(struct rxe_qp *qp,
#else
static struct dst_entry *rxe_find_route6(struct rxe_qp *qp,
+ struct net *net,
struct net_device *ndev,
struct in6_addr *saddr,
struct in6_addr *daddr)
@@ -174,6 +178,7 @@ static struct dst_entry *rxe_find_route(struct net_device *ndev,
struct rxe_av *av)
{
struct dst_entry *dst = NULL;
+ struct net *net;
if (qp_type(qp) == IB_QPT_RC)
dst = sk_dst_get(qp->sk->sk);
@@ -182,20 +187,22 @@ static struct dst_entry *rxe_find_route(struct net_device *ndev,
if (dst)
dst_release(dst);
+ net = dev_net(ndev);
+
if (av->network_type == RXE_NETWORK_TYPE_IPV4) {
struct in_addr *saddr;
struct in_addr *daddr;
saddr = &av->sgid_addr._sockaddr_in.sin_addr;
daddr = &av->dgid_addr._sockaddr_in.sin_addr;
- dst = rxe_find_route4(qp, ndev, saddr, daddr);
+ dst = rxe_find_route4(qp, net, ndev, saddr, daddr);
} else if (av->network_type == RXE_NETWORK_TYPE_IPV6) {
struct in6_addr *saddr6;
struct in6_addr *daddr6;
saddr6 = &av->sgid_addr._sockaddr_in6.sin6_addr;
daddr6 = &av->dgid_addr._sockaddr_in6.sin6_addr;
- dst = rxe_find_route6(qp, ndev, saddr6, daddr6);
+ dst = rxe_find_route6(qp, net, ndev, saddr6, daddr6);
#if IS_ENABLED(CONFIG_IPV6)
if (dst)
qp->dst_cookie =
@@ -624,6 +631,43 @@ int rxe_net_add(const char *ibdev_name, struct net_device *ndev)
return 0;
}
+static void rxe_sock_put(struct sock *sk,
+ void (*set_sk)(struct net *, struct sock *),
+ struct net *net)
+{
+ if (refcount_read(&sk->sk_refcnt) > SK_REF_FOR_TUNNEL) {
+ __sock_put(sk);
+ } else {
+ rxe_release_udp_tunnel(sk->sk_socket);
+ sk = NULL;
+ set_sk(net, sk);
+ }
+}
+
+void rxe_net_del(struct ib_device *dev)
+{
+ struct rxe_dev *rxe = container_of(dev, struct rxe_dev, ib_dev);
+ struct net_device *ndev;
+ struct sock *sk;
+ struct net *net;
+
+ ndev = rxe_ib_device_get_netdev(&rxe->ib_dev);
+ if (!ndev)
+ return;
+
+ net = dev_net(ndev);
+
+ sk = rxe_ns_pernet_sk4(net);
+ if (sk)
+ rxe_sock_put(sk, rxe_ns_pernet_set_sk4, net);
+
+ sk = rxe_ns_pernet_sk6(net);
+ if (sk)
+ rxe_sock_put(sk, rxe_ns_pernet_set_sk6, net);
+
+ dev_put(ndev);
+}
+
static void rxe_port_event(struct rxe_dev *rxe,
enum ib_event_type event)
{
@@ -680,6 +724,7 @@ static int rxe_notify(struct notifier_block *not_blk,
switch (event) {
case NETDEV_UNREGISTER:
ib_unregister_device_queued(&rxe->ib_dev);
+ rxe_net_del(&rxe->ib_dev);
break;
case NETDEV_CHANGEMTU:
rxe_dbg_dev(rxe, "%s changed mtu to %d\n", ndev->name, ndev->mtu);
@@ -709,66 +754,97 @@ static struct notifier_block rxe_net_notifier = {
.notifier_call = rxe_notify,
};
-static int rxe_net_ipv4_init(void)
+static int rxe_net_ipv4_init(struct net *net)
{
- recv_sockets.sk4 = rxe_setup_udp_tunnel(&init_net,
- htons(ROCE_V2_UDP_DPORT), false);
- if (IS_ERR(recv_sockets.sk4)) {
- recv_sockets.sk4 = NULL;
+ struct sock *sk;
+ struct socket *sock;
+
+ sk = rxe_ns_pernet_sk4(net);
+ if (sk) {
+ sock_hold(sk);
+ return 0;
+ }
+
+ sock = rxe_setup_udp_tunnel(net, htons(ROCE_V2_UDP_DPORT), false);
+ if (IS_ERR(sock)) {
pr_err("Failed to create IPv4 UDP tunnel\n");
return -1;
}
+ rxe_ns_pernet_set_sk4(net, sock->sk);
return 0;
}
-static int rxe_net_ipv6_init(void)
+static int rxe_net_ipv6_init(struct net *net)
{
#if IS_ENABLED(CONFIG_IPV6)
+ struct sock *sk;
+ struct socket *sock;
- recv_sockets.sk6 = rxe_setup_udp_tunnel(&init_net,
- htons(ROCE_V2_UDP_DPORT), true);
- if (PTR_ERR(recv_sockets.sk6) == -EAFNOSUPPORT) {
- recv_sockets.sk6 = NULL;
+ sk = rxe_ns_pernet_sk6(net);
+ if (sk) {
+ sock_hold(sk);
+ return 0;
+ }
+
+ sock = rxe_setup_udp_tunnel(net, htons(ROCE_V2_UDP_DPORT), true);
+ if (PTR_ERR(sock) == -EAFNOSUPPORT) {
pr_warn("IPv6 is not supported, can not create a UDPv6 socket\n");
return 0;
}
- if (IS_ERR(recv_sockets.sk6)) {
- recv_sockets.sk6 = NULL;
+ if (IS_ERR(sock)) {
pr_err("Failed to create IPv6 UDP tunnel\n");
return -1;
}
+
+ rxe_ns_pernet_set_sk6(net, sock->sk);
+
#endif
return 0;
}
+int rxe_register_notifier(void)
+{
+ int err;
+
+ err = register_netdevice_notifier(&rxe_net_notifier);
+ if (err) {
+ pr_err("Failed to register netdev notifier\n");
+ return -1;
+ }
+
+ return 0;
+}
+
void rxe_net_exit(void)
{
- rxe_release_udp_tunnel(recv_sockets.sk6);
- rxe_release_udp_tunnel(recv_sockets.sk4);
unregister_netdevice_notifier(&rxe_net_notifier);
}
-int rxe_net_init(void)
+int rxe_net_init(struct net_device *ndev)
{
+ struct net *net;
+ struct sock *sk;
int err;
- recv_sockets.sk6 = NULL;
+ net = dev_net(ndev);
- err = rxe_net_ipv4_init();
+ err = rxe_net_ipv4_init(net);
if (err)
return err;
- err = rxe_net_ipv6_init();
+
+ err = rxe_net_ipv6_init(net);
if (err)
goto err_out;
- err = register_netdevice_notifier(&rxe_net_notifier);
- if (err) {
- pr_err("Failed to register netdev notifier\n");
- goto err_out;
- }
+
return 0;
+
err_out:
- rxe_net_exit();
+ /* If ipv6 error, release ipv4 resource */
+ sk = rxe_ns_pernet_sk4(net);
+ if (sk)
+ rxe_sock_put(sk, rxe_ns_pernet_set_sk4, net);
+
return err;
}
diff --git a/drivers/infiniband/sw/rxe/rxe_net.h b/drivers/infiniband/sw/rxe/rxe_net.h
index 45d80d00f86b..56249677d692 100644
--- a/drivers/infiniband/sw/rxe/rxe_net.h
+++ b/drivers/infiniband/sw/rxe/rxe_net.h
@@ -11,14 +11,11 @@
#include <net/if_inet6.h>
#include <linux/module.h>
-struct rxe_recv_sockets {
- struct socket *sk4;
- struct socket *sk6;
-};
-
int rxe_net_add(const char *ibdev_name, struct net_device *ndev);
+void rxe_net_del(struct ib_device *dev);
-int rxe_net_init(void);
+int rxe_register_notifier(void);
+int rxe_net_init(struct net_device *ndev);
void rxe_net_exit(void);
#endif /* RXE_NET_H */
]
|
| 50/2 |
2026/05/30 23:20 |
llm |
expert |
0m
Model:
Tokens:
input: 29866
output: 46
thoughts: 1615
Thoughts: **Analyzing the RXE UDP Tunnel Socket Lifecycle**
Okay, so I'm looking at this `rxe_sock_put` function and the surrounding code, trying to understand this potential use-after-free situation, as reported by the bug. The core of it seems to hinge on how the reference count (`sk_refcnt`) of the UDP tunnel sockets is managed in the RXE (RDMA over Converged Ethernet) driver.
First, the code: This `rxe_sock_put` function seems to be responsible for releasing the socket. It checks the reference count against `SK_REF_FOR_TUNNEL`, which is defined as 2. A socket initially created by `udp_sock_create` starts with a refcount of 1. It gets increased when the RXE net namespace stuff is associated, and potentially again when `rxe_newlink` is called, potentially to a value of 2 or 3. The important thing is how we get back to zero.
When we call `rxe_dellink`, `rxe_sock_put` is called to release the socket. If the reference count is 3, a straight `__sock_put` is used which just drops the count and it's fine. If the count is 2 (or less), it goes down the path of `rxe_release_udp_tunnel`, which eventually calls `sock_release` and then `sock_put`.
The initial thought I had was that there might be a leak. If the refcount is 2, `rxe_release_udp_tunnel` calls `sock_release`, which ends up calling `sock_put`. So it goes down to 1. But `rxe_sock_put` sets the pernet storage to NULL. So it might be a leak, as the socket still exists but is not reachable. But the bug report is not a leak. It says it's a "use-after-free."
So, let's look at the underflow. In the single-link creation and deletion case, the `sk_refcnt` starts at 1, goes into the `else` path of `rxe_sock_put`, where `rxe_release_udp_tunnel` is called and eventually calls `sock_put`. So that's normal. That would take the socket refcount from 1 to 0, which is perfectly valid, since we want to free the socket.
But the bug report describes an underflow. That means the `sock_put` is called on a socket with refcount 0. So how do we get there? I see from the traceback that the underflow occurs within `sock_put`. From the stack trace, the warning happens in `sock_put` called from `sk_common_release`. If we get an underflow here, it means `sk_refcnt` was zero *before* we entered `sock_put`. How do we get there?
I'm thinking about how the references are manipulated. The netdev code can call `rxe_dellink` from netlink, but also `rxe_net_del` from the unregister of a device! The crucial insight here is if `rxe_net_del` is called *twice* for the same device. `nldev_dellink` looks up by index so it seems okay. But `rxe_net_del` also gets called from the `NETDEV_UNREGISTER` event.
If `rxe_net_del` is called twice, the first time, refcount could be 1, or 2, and the socket gets correctly cleaned up. Then, the second time, we might already have the refcount to zero before we even get to the function! So, when `rxe_sock_put` is called, it checks `refcount_read(&sk->sk_refcnt)`. If this returns 0, then the logic proceeds to `rxe_release_udp_tunnel` and calls `sock_put` on a socket whose refcount is already zero, leading to the underflow warning and a use-after-free.
So the problem is that `rxe_net_del` is called more times than expected.
|
| 51/2 |
2026/05/30 23:20 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/infiniband/sw/rxe/rxe_net.c Name:rxe_notify]
Results: map[SourceCode: 712: static int rxe_notify(struct notifier_block *not_blk,
713: unsigned long event,
714: void *arg)
715: {
716: struct net_device *ndev = netdev_notifier_info_to_dev(arg);
717: struct rxe_dev *rxe = rxe_get_dev_from_net(ndev);
718:
719: if (!rxe)
720: return NOTIFY_OK;
721:
722: switch (event) {
723: case NETDEV_UNREGISTER:
724: ib_unregister_device_queued(&rxe->ib_dev);
725: rxe_net_del(&rxe->ib_dev);
726: break;
727: case NETDEV_CHANGEMTU:
728: rxe_dbg_dev(rxe, "%s changed mtu to %d\n", ndev->name, ndev->mtu);
729: rxe_set_mtu(rxe, ndev->mtu);
730: break;
731: case NETDEV_DOWN:
732: case NETDEV_CHANGE:
733: if (ib_get_curr_port_state(ndev) == IB_PORT_DOWN)
734: rxe_counter_inc(rxe, RXE_CNT_LINK_DOWNED);
735: break;
736: case NETDEV_REBOOT:
737: case NETDEV_GOING_DOWN:
738: case NETDEV_CHANGEADDR:
739: case NETDEV_CHANGENAME:
740: case NETDEV_FEAT_CHANGE:
741: default:
742: rxe_dbg_dev(rxe, "ignoring netdev event = %ld for %s\n",
743: event, ndev->name);
744: break;
745: }
746:
747: ib_device_put(&rxe->ib_dev);
748: return NOTIFY_OK;
749: }
SourceFile:drivers/infiniband/sw/rxe/rxe_net.c]
|
| 52/2 |
2026/05/30 23:20 |
llm |
expert |
0m
Model:
Tokens:
input: 33075
output: 44
thoughts: 297
Thoughts: **My Analysis of RXE Device Removal**
Okay, let me think this through. So, `rxe_notify` is called during `NETDEV_UNREGISTER`, and that in turn calls `rxe_net_del` with the IB device. Right. And `rxe_dellink` also calls `rxe_net_del(dev)`. That's important. Now, `rxe_dellink` is triggered when a user explicitly deletes the RDMA link via netlink commands. Then there is a scenario if a user deletes the underlying netdev directly (like using `ip link del`). In this case, `NETDEV_UNREGISTER` will be triggered. Which would subsequently trigger `rxe_notify` and thus call `rxe_net_del`.
But wait, does `rxe_dellink` also unregister the netdev itself? No, `rxe` is a software RDMA device that sits *on top* of a netdev. If the user deletes the RDMA device using something like `rdma link del`, then `rxe_dellink` gets called. On the other hand, if the underlying netdev is deleted, then `rxe_notify` gets called. The question becomes, could both `rxe_dellink` and `rxe_notify` be called in some sequence?
Hang on, if `rxe_dellink` is called, it *also* calls `rxe_net_del`. So what happens to the IB device registration? Does `rxe_net_del` actually unregister the IB device? Let's trace it back a bit. `nldev_dellink` calls `ib_unregister_device_and_put`. Okay, and `nldev_dellink` calls `ops->dellink(dev)`. So, the link removal happens through the network link device's operations via `nldev_dellink`. I need to verify what happens in `nldev_dellink`. Let me check that.
|
| 53/2 |
2026/05/30 23:20 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/infiniband/core/nldev.c Name:nldev_dellink]
Results: map[SourceCode:1819: static int nldev_dellink(struct sk_buff *skb, struct nlmsghdr *nlh,
1820: struct netlink_ext_ack *extack)
1821: {
1822: struct nlattr *tb[RDMA_NLDEV_ATTR_MAX];
1823: struct ib_device *device;
1824: u32 index;
1825: int err;
1826:
1827: err = nlmsg_parse(nlh, 0, tb, RDMA_NLDEV_ATTR_MAX - 1,
1828: nldev_policy, extack);
1829: if (err || !tb[RDMA_NLDEV_ATTR_DEV_INDEX])
1830: return -EINVAL;
1831:
1832: index = nla_get_u32(tb[RDMA_NLDEV_ATTR_DEV_INDEX]);
1833: device = ib_device_get_by_index(sock_net(skb->sk), index);
1834: if (!device)
1835: return -EINVAL;
1836:
1837: if (!(device->attrs.kernel_cap_flags & IBK_ALLOW_USER_UNREG)) {
1838: ib_device_put(device);
1839: return -EINVAL;
1840: }
1841:
1842: /*
1843: * This path is triggered by the 'rdma link delete' administrative command.
1844: * For Soft-RoCE (RXE), we ensure that transport sockets are closed here.
1845: * Note: iWARP driver does not implement .dellink, so this logic is
1846: * implicitly scoped to the driver supporting dynamic link deletion like RXE.
1847: */
1848: if (device->link_ops && device->link_ops->dellink) {
1849: err = device->link_ops->dellink(device);
1850: if (err)
1851: return err;
1852: }
1853:
1854: ib_unregister_device_and_put(device);
1855: return 0;
1856: }
SourceFile:drivers/infiniband/core/nldev.c]
|
| 54/2 |
2026/05/30 23:20 |
llm |
expert |
0m
Model:
Tokens:
input: 34502
output: 48
thoughts: 2070
Thoughts: **Analysis of Potential Double-Free in RXE Device Deletion**
Okay, let's break this down. My initial thought process is following the calls from `nldev_dellink` down to `rxe_dellink` and eventually to `rxe_net_del`, which leads to socket operations. The core of the problem seems to stem from a double call to `rxe_net_del`, specifically in the interaction with the `ib_unregister_device_queued` function and concurrent calls to `rxe_notify`.
My primary concern is the potential for `rxe_net_del` to be called more than once. When itβs called the first time, `rxe_sock_put` is called to release the UDP tunnel socket. If the `sk_refcnt` is 1, `rxe_release_udp_tunnel` is called, which will free the socket. If `sk_refcnt` is 2 then there are two devices sharing the socket, and instead of freeing it, the reference count is decremented.
If `sk_refcnt` is 2, the first call to `rxe_net_del` calls `rxe_release_udp_tunnel`, which decrements `sk_refcnt` to 1, and the second device is still alive and uses the socket. However, the first call to `rxe_sock_put` also sets the pernet storage to NULL for the socket. So, the second time it's called, it won't do anything because the pointer in the pernet storage is NULL. This seems fine, but I need to investigate the implications of what happens to the socket.
My main concern is the scenario where `sk_refcnt` starts at 2 (two devices in the same netns) and the first device's deletion leads to a partial socket release. I've confirmed that the socket is not freed, but its reference count drops to 1, and the pernet storage gets set to NULL. My worry is that the second device is left with a potentially dangling pointer. It is clear that the code assumes only one device can be using the socket. The check `if (refcount_read(&sk->sk_refcnt) > SK_REF_FOR_TUNNEL)` is intended to protect the socket by keeping it alive for other devices if the refcount is greater than 2. However, the value of `SK_REF_FOR_TUNNEL` (2) leads to a double-free, because if the refcount is equal to 2 (two devices) then the code frees the socket, and decrements the refcount. If the refcount is 2, then the if-condition `2 > 2` is false, and the code incorrectly assumes refcount = 1 (socket is freed).
Now, the bug report indicates a use-after-free, and it occurs during `rxe_dellink` -> `rxe_net_del` -> `rxe_sock_put` -> `rxe_release_udp_tunnel` -> `sock_release` -> `sock_put`, which suggests that the refcount is already zero *before* the decrement. This is problematic, and my mind quickly goes to the idea of concurrent calls. Specifically, are we dealing with concurrent execution paths?
The fact that `nldev_dellink` does not hold RTNL is critical. `rxe_notify` *does* hold RTNL. Therefore, if `rxe_dellink` and `rxe_notify` are running concurrently, `rxe_net_del` can be called multiple times. Now I need to figure out a condition where the refcount could be zero. It's likely because the socket is freed before it should be. The vulnerability must be related to race conditions with the calls of `rxe_dellink` and `rxe_notify`.
|
| 55/2 |
2026/05/30 23:20 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/infiniband/sw/rxe/rxe_net.c Name:rxe_net_del]
Results: map[SourceCode: 645: void rxe_net_del(struct ib_device *dev)
646: {
647: struct rxe_dev *rxe = container_of(dev, struct rxe_dev, ib_dev);
648: struct net_device *ndev;
649: struct sock *sk;
650: struct net *net;
651:
652: ndev = rxe_ib_device_get_netdev(&rxe->ib_dev);
653: if (!ndev)
654: return;
655:
656: net = dev_net(ndev);
657:
658: sk = rxe_ns_pernet_sk4(net);
659: if (sk)
660: rxe_sock_put(sk, rxe_ns_pernet_set_sk4, net);
661:
662: sk = rxe_ns_pernet_sk6(net);
663: if (sk)
664: rxe_sock_put(sk, rxe_ns_pernet_set_sk6, net);
665:
666: dev_put(ndev);
667: }
SourceFile:drivers/infiniband/sw/rxe/rxe_net.c]
|
| 56/2 |
2026/05/30 23:20 |
llm |
expert |
0m
Model:
Tokens:
input: 37844
output: 44
thoughts: 648
Thoughts: **My Analysis of a Potential RXE Device Double-Free Vulnerability**
Okay, so I've been digging into this `rxe_net_del` function, and something's really bugging me. There's no locking in this crucial function, and that's a red flag. Specifically, I'm concerned about concurrent calls to `rxe_net_del` for the same device (or even different devices in the same network namespace). Let's walk through the sequence.
Both threads get the same socket (`sk`) using `rxe_ns_pernet_sk4(net);`. Thread 1 grabs `sk`, and then Thread 2 does the same, leading to a race condition. Now, they both call `rxe_sock_put(sk)`. Inside `rxe_sock_put`, we have that `if` statement based on `sk->sk_refcnt`. If the refcount is, say, 1 (which it will be for the first device), neither thread will meet the `> SK_REF_FOR_TUNNEL` condition.
This is where it gets nasty. Thread 1 enters the `else` block and calls `rxe_release_udp_tunnel(sk->sk_socket)`. Crucially, this seems to be the culprit. I see that this will free the socket! Then, Thread 2, blissfully unaware, also calls `rxe_release_udp_tunnel(sk->sk_socket)`. Since the socket was already read *before* the first thread, or possibly read from the now freed `sk`, the pointer is still valid, and Thread 2 now tries to use the *freed* socket. The function then calls `sock_release` again, and that's when things really go south β it calls `sock_put`, which decrements `sk_refcnt` from 0 to -1, which is the underflow warning we're getting! It's a classic double-free/use-after-free scenario.
The `rxe_release_udp_tunnel` function is the key. If one thread frees `sk`, the other will call `rxe_release_udp_tunnel` with that freed pointer. It's a direct race, and it's ugly.
The next question is, can this be triggered? I think it can. An unprivileged user can create a net namespace and then create a veth pair within it, right? And then, the RXE device is created on top of that. Can an unprivileged user make the RXE device? That's the real test.
Let's see: `rdma link add` uses netlink commands. Those commands require `CAP_NET_ADMIN`. But hold on, in a user namespace, the user *does* have `CAP_NET_ADMIN` privileges over that namespace. That's a key point. Now, the real test. Does the RDMA subsystem allow unprivileged users within a user namespace to create and destroy these RXE devices? I need to check `nldev_newlink` to confirm. If that's allowed, then we've got a definite vulnerability on our hands.
|
| 57/2 |
2026/05/30 23:20 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/infiniband/core/nldev.c Name:nldev_newlink]
Results: map[SourceCode:1772: static int nldev_newlink(struct sk_buff *skb, struct nlmsghdr *nlh,
1773: struct netlink_ext_ack *extack)
1774: {
1775: struct nlattr *tb[RDMA_NLDEV_ATTR_MAX];
1776: char ibdev_name[IB_DEVICE_NAME_MAX];
1777: const struct rdma_link_ops *ops;
1778: char ndev_name[IFNAMSIZ];
1779: struct net_device *ndev;
1780: char type[IFNAMSIZ];
1781: int err;
1782:
1783: err = nlmsg_parse(nlh, 0, tb, RDMA_NLDEV_ATTR_MAX - 1,
1784: nldev_policy, extack);
1785: if (err || !tb[RDMA_NLDEV_ATTR_DEV_NAME] ||
1786: !tb[RDMA_NLDEV_ATTR_LINK_TYPE] || !tb[RDMA_NLDEV_ATTR_NDEV_NAME])
1787: return -EINVAL;
1788:
1789: nla_strscpy(ibdev_name, tb[RDMA_NLDEV_ATTR_DEV_NAME],
1790: sizeof(ibdev_name));
1791: if (strchr(ibdev_name, '%') || strlen(ibdev_name) == 0)
1792: return -EINVAL;
1793:
1794: nla_strscpy(type, tb[RDMA_NLDEV_ATTR_LINK_TYPE], sizeof(type));
1795: nla_strscpy(ndev_name, tb[RDMA_NLDEV_ATTR_NDEV_NAME],
1796: sizeof(ndev_name));
1797:
1798: ndev = dev_get_by_name(sock_net(skb->sk), ndev_name);
1799: if (!ndev)
1800: return -ENODEV;
1801:
1802: down_read(&link_ops_rwsem);
1803: ops = link_ops_get(type);
1804: #ifdef CONFIG_MODULES
1805: if (!ops) {
1806: up_read(&link_ops_rwsem);
1807: request_module("rdma-link-%s", type);
1808: down_read(&link_ops_rwsem);
1809: ops = link_ops_get(type);
1810: }
1811: #endif
1812: err = ops ? ops->newlink(ibdev_name, ndev) : -EINVAL;
1813: up_read(&link_ops_rwsem);
1814: dev_put(ndev);
1815:
1816: return err;
1817: }
SourceFile:drivers/infiniband/core/nldev.c]
|
| 58/2 |
2026/05/30 23:20 |
llm |
expert |
0m
Model:
Tokens:
input: 40064
output: 24
thoughts: 23
Thoughts: Here's my thought process, summarized as if I were considering the situation:
**Delving into RDMA Netlink Registration**
Okay, so I'm looking at this code, and the first thing that jumps out is that `nldev_newlink` function. I've seen that before, and I immediately recall where it's hooked in: it's registered within the `rdma_nl_cb_table`. That's the crucial data structure, isn't it? That table is *the* place where the RDMA subsystem defines its Netlink callback handlers. The fact that `nldev_newlink` is in *that* table tells me it's handling a specific kind of event, likely related to device creation or configuration through Netlink. This is the starting point for understanding how new RDMA devices or links are set up within the system via the network interface. I need to now remember what the `rdma_nl_cb_table` entry does.
|
| 59/2 |
2026/05/30 23:21 |
tool |
codesearch-find-references |
0m
Args:
Results: map[References:<nil> TruncatedOutput:false]
|
| 60/2 |
2026/05/30 23:21 |
llm |
expert |
0m
Model:
Tokens:
input: 40338
output: 20
thoughts: 33
Thoughts: Here's a first-person summary of the provided text, assuming I'm an expert:
**Investigating RDMA NL Callback Structures**
Okay, time to get to the heart of this. My gut feeling tells me the problem lies somewhere in the Netlink interaction for RDMA devices. I need to dive into the kernel source and start with the core networking side of things. Let's see...I'm going to check out the `rdma_nl_cb_table` data structure, or something analogous, most likely found within `drivers/infiniband/core/nldev.c`. That's the likely spot where the Netlink callback handlers are defined and registered for RDMA devices. This table should tell me which functions are called in response to specific Netlink messages related to RDMA device management. That's where I'll begin my investigation.
|
| 61/2 |
2026/05/30 23:21 |
tool |
grepper |
0m
Args: map[Expression:nldev_newlink]
Results: map[Output:drivers/infiniband/core/nldev.c=1770=EXPORT_SYMBOL(rdma_link_unregister);
drivers/infiniband/core/nldev.c-1771-
drivers/infiniband/core/nldev.c:1772:static int nldev_newlink(struct sk_buff *skb, struct nlmsghdr *nlh,
drivers/infiniband/core/nldev.c-1773- struct netlink_ext_ack *extack)
--
drivers/infiniband/core/nldev.c=2931=static const struct rdma_nl_cbs nldev_cb_table[RDMA_NLDEV_NUM_OPS] = {
--
drivers/infiniband/core/nldev.c-2943- [RDMA_NLDEV_CMD_NEWLINK] = {
drivers/infiniband/core/nldev.c:2944: .doit = nldev_newlink,
drivers/infiniband/core/nldev.c-2945- .flags = RDMA_NL_ADMIN_PERM,
]
|
| 62/2 |
2026/05/30 23:21 |
llm |
expert |
0m
Model:
Tokens:
input: 40881
output: 46
thoughts: 52
Thoughts: **Examining RDMA Network Administration Permissions**
Okay, so I'm digging into this `RDMA_NL_ADMIN_PERM` requirement. It's pretty straightforward β to wield the administrative powers implied, you need the `CAP_NET_ADMIN` capability. My immediate question, though, is how precisely this check is being implemented. I need to understand the control flow, so I'll be heading over to `rdma_nl_rcv_msg`. That seems like the logical entry point for network layer message reception, and therefore, the perfect spot to examine the `RDMA_NL_ADMIN_PERM` verification logic. I'm expecting to find the standard capability check routine, probably something like `capable(CAP_NET_ADMIN)`, but I'm eager to see the specifics within that context. I'm looking for any nuances or subtleties in the implementation.
|
| 63/2 |
2026/05/30 23:21 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/infiniband/core/netlink.c Name:rdma_nl_rcv_msg]
Results: map[SourceCode: 152: static int rdma_nl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh,
153: struct netlink_ext_ack *extack)
154: {
155: int type = nlh->nlmsg_type;
156: unsigned int index = RDMA_NL_GET_CLIENT(type);
157: unsigned int op = RDMA_NL_GET_OP(type);
158: const struct rdma_nl_cbs *cb_table;
159: int err = -EINVAL;
160:
161: if (!is_nl_msg_valid(index, op))
162: return -EINVAL;
163:
164: down_read(&rdma_nl_types[index].sem);
165: cb_table = get_cb_table(skb, index, op);
166: if (!cb_table)
167: goto done;
168:
169: if ((cb_table[op].flags & RDMA_NL_ADMIN_PERM) &&
170: !netlink_capable(skb, CAP_NET_ADMIN)) {
171: err = -EPERM;
172: goto done;
173: }
174:
175: /*
176: * LS responses overload the 0x100 (NLM_F_ROOT) flag. Don't
177: * mistakenly call the .dump() function.
178: */
179: if (index == RDMA_NL_LS) {
180: if (cb_table[op].doit)
181: err = cb_table[op].doit(skb, nlh, extack);
182: goto done;
183: }
184: /* FIXME: Convert IWCM to properly handle doit callbacks */
185: if ((nlh->nlmsg_flags & NLM_F_DUMP) || index == RDMA_NL_IWCM) {
186: struct netlink_dump_control c = {
187: .dump = cb_table[op].dump,
188: };
189: if (c.dump)
190: err = netlink_dump_start(skb->sk, skb, nlh, &c);
191: goto done;
192: }
193:
194: if (cb_table[op].doit)
195: err = cb_table[op].doit(skb, nlh, extack);
196: done:
197: up_read(&rdma_nl_types[index].sem);
198: return err;
199: }
SourceFile:drivers/infiniband/core/netlink.c]
|
| 64/2 |
2026/05/30 23:21 |
llm |
expert |
0m
Model:
Tokens:
input: 41982
output: 44
thoughts: 334
Thoughts: **Race Condition in `nldev_dellink` - An Unprivileged User's Playground**
Okay, so `netlink_capable(skb, CAP_NET_ADMIN)` is the gatekeeper, checking for `CAP_NET_ADMIN` in the correct user namespace. That's the key. So, an unprivileged user can totally exploit this by creating new namespaces! My mind is already racing.
Let's see. `rxe_net_del` is called from `rxe_dellink`, which in turn is called from both `rxe_dellink` *and* `rxe_notify`. Can an unprivileged user somehow trigger a race here? Aha! They could run `rdma link del` and `ip link del` concurrently. That's a classic concurrency bug setup. Or perhaps, just run `rdma link del` from two threads concurrently? Okay, the pieces are falling into place.
Now, `nldev_dellink` is looking up the device by index, which is always a red flag for races. Does it employ any kind of locking mechanism? Let's trace it. It calls `ib_device_get_by_index`, which increments the IB device's reference count. Then, we check `IBK_ALLOW_USER_UNREG` β a sensible check. Then, we call `device->link_ops->dellink(device)`. Finally, `ib_unregister_device_and_put(device)` is called.
The vulnerability is clear! If two threads call `nldev_dellink` concurrently for the *same* device index... both get the device, both call `rxe_dellink(device)`, and both call `rxe_net_del(device)`. Boom! Thatβs the race!
Okay, time to check: is there any lock preventing two `nldev_dellink` calls? I need to look closer at `nldev_dellink` again.
|
| 65/2 |
2026/05/30 23:21 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/infiniband/core/nldev.c Name:nldev_dellink]
Results: map[SourceCode:1819: static int nldev_dellink(struct sk_buff *skb, struct nlmsghdr *nlh,
1820: struct netlink_ext_ack *extack)
1821: {
1822: struct nlattr *tb[RDMA_NLDEV_ATTR_MAX];
1823: struct ib_device *device;
1824: u32 index;
1825: int err;
1826:
1827: err = nlmsg_parse(nlh, 0, tb, RDMA_NLDEV_ATTR_MAX - 1,
1828: nldev_policy, extack);
1829: if (err || !tb[RDMA_NLDEV_ATTR_DEV_INDEX])
1830: return -EINVAL;
1831:
1832: index = nla_get_u32(tb[RDMA_NLDEV_ATTR_DEV_INDEX]);
1833: device = ib_device_get_by_index(sock_net(skb->sk), index);
1834: if (!device)
1835: return -EINVAL;
1836:
1837: if (!(device->attrs.kernel_cap_flags & IBK_ALLOW_USER_UNREG)) {
1838: ib_device_put(device);
1839: return -EINVAL;
1840: }
1841:
1842: /*
1843: * This path is triggered by the 'rdma link delete' administrative command.
1844: * For Soft-RoCE (RXE), we ensure that transport sockets are closed here.
1845: * Note: iWARP driver does not implement .dellink, so this logic is
1846: * implicitly scoped to the driver supporting dynamic link deletion like RXE.
1847: */
1848: if (device->link_ops && device->link_ops->dellink) {
1849: err = device->link_ops->dellink(device);
1850: if (err)
1851: return err;
1852: }
1853:
1854: ib_unregister_device_and_put(device);
1855: return 0;
1856: }
SourceFile:drivers/infiniband/core/nldev.c]
|
| 66/2 |
2026/05/30 23:21 |
llm |
expert |
0m
Model:
Tokens:
input: 43438
output: 52
thoughts: 146
Thoughts: **Race Condition Analysis - RDMA Netlink and RXE Cleanup**
Okay, so I'm looking at this code, and the first thing that jumps out at me is... wait a second, there's no actual lock on this crucial section! I see that `rdma_nl_rcv_msg` is using a read lock on `rdma_nl_types[index].sem`. Aha! That `down_read()` call means it's a *READ* lock. That's significant.
So, multiple netlink messages can be handled concurrently! Right. And that means `nldev_dellink` can absolutely be running in parallel for the *same* RDMA device. This is where it gets interesting... or rather, worrisome.
And then I look at `rxe_net_del`, and I don't see any sort of locking in there at all. None. Zip. Nada.
Okay, so the race condition is not just theoretical; it's practically screaming to be triggered, especially by an unprivileged user within a user namespace. Easy peasy.
Just to be absolutely sure, let me take a quick look to see if `rxe_ns_pernet_sk4` is holding any of the locks that would be required.
|
| 67/2 |
2026/05/30 23:21 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/infiniband/sw/rxe/rxe_ns.c Name:rxe_ns_pernet_sk4]
Results: map[SourceCode: 74: struct sock *rxe_ns_pernet_sk4(struct net *net)
75: {
76: struct rxe_ns_sock *ns_sk = net_generic(net, rxe_pernet_id);
77: struct sock *sk;
78:
79: rcu_read_lock();
80: sk = rcu_dereference(ns_sk->rxe_sk4);
81: rcu_read_unlock();
82:
83: return sk;
84: }
SourceFile:drivers/infiniband/sw/rxe/rxe_ns.c]
|
| 68/2 |
2026/05/30 23:21 |
llm |
expert |
0m
Model:
Tokens:
input: 44101
output: 51
thoughts: 1526
Thoughts: **Analyzing the RCU-protected Socket Handling**
Okay, so I'm looking at this RCU code and immediately see a potential issue with concurrent access to the `rxe_ns_pernet_sk4` function. The fact that both threads get the *same* `sk` pointer is a red flag. The double call to `rxe_sock_put(sk)` confirms my initial thought. Now, the `rxe_sock_put` function's logic is where it gets interesting, especially this `SK_REF_FOR_TUNNEL` check.
If `sk_refcnt` starts at 1 (representing a single device), both threads entering `rxe_sock_put` will see it as 1. Both threads enter the `else` block, leading to the dreaded `rxe_release_udp_tunnel` calls. `rxe_release_udp_tunnel` calls `udp_tunnel_sock_release`, then `sock_release`, and finally `sock_put`. The first call to `sock_put` decrements from 1 to 0, freeing the socket. The *second* call, however, decrements from 0 to -1, causing a double-free on `struct sock` and potentially `struct socket`. That's textbook use-after-free and a classic double-free vulnerability, ripe for exploitation, especially with the complexity of `struct sock` which includes those juicy function pointers like `sk_destruct`.
Now I'm trying to think, what other scenarios can trigger this? What if we have two devices? If `sk_refcnt` is 2, then `2 > 2` is false in both threads, so both again enter the problematic path, but now, the socket is still refcounted at 2 when we call `rxe_release_udp_tunnel`!
Thinking more, if `sk_refcnt` is 2 and we delete a device, the `sock_put` inside `rxe_release_udp_tunnel` decrements the refcount from 2 to 1 and frees the associated `struct socket`. But the `struct sock` itself is *not* freed. Wait a minute, `sock_release` calls `iput` which frees the inode, which contains the `struct socket`! However, at the point when we call `sock_put` the refcount to the `struct sock` is at 1! But `rxe_sock_put` clears the pernet storage, effectively cutting the second device off from the socket!
So, the second device is now cut off from the socket; it will not receive packets. When it goes to be deleted, it returns `NULL`. That means a leak for `struct sock`! So, if we create two devices and delete them sequentially, we leak a `struct sock`. But a double free on concurrent deletion!
Then I'm wondering, why the author chose to use `> SK_REF_FOR_TUNNEL`? If `SK_REF_FOR_TUNNEL` is 2, then with three devices, `sk_refcnt` becomes 3, only the first device deleted frees the socket, and the other two result in a leak. This logic is completely broken. It looks like the author made some incorrect assumptions about how `sk_refcnt` is managed... I really should revisit the `rxe_net_ipv4_init` function to clarify the reference counting in this architecture.
|
| 69/2 |
2026/05/30 23:21 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/infiniband/sw/rxe/rxe_net.c Name:rxe_net_ipv4_init]
Results: map[SourceCode: 755: static int rxe_net_ipv4_init(struct net *net)
756: {
757: struct sock *sk;
758: struct socket *sock;
759:
760: sk = rxe_ns_pernet_sk4(net);
761: if (sk) {
762: sock_hold(sk);
763: return 0;
764: }
765:
766: sock = rxe_setup_udp_tunnel(net, htons(ROCE_V2_UDP_DPORT), false);
767: if (IS_ERR(sock)) {
768: pr_err("Failed to create IPv4 UDP tunnel\n");
769: return -1;
770: }
771: rxe_ns_pernet_set_sk4(net, sock->sk);
772:
773: return 0;
774: }
SourceFile:drivers/infiniband/sw/rxe/rxe_net.c]
|
| 70/2 |
2026/05/30 23:21 |
llm |
expert |
1m
Model:
Tokens:
input: 46742
output: 43
thoughts: 682
Thoughts: **UDP Tunnel Socket Reference Counting Issue**
Okay, so here's the situation as I see it. When `rxe_setup_udp_tunnel` is called, a UDP socket is created via `udp_sock_create`. `sock_create_kern` initializes the socket's reference count, `sk_refcnt`, to 1. Then, `setup_udp_tunnel_sock` gets called. Now, I need to know if `setup_udp_tunnel_sock` is supposed to increment that `sk_refcnt`. It doesn't look like it does.
So, the refcount is still 1. This means, when the socket gets "put" by `rxe_sock_put`, the check `sk_refcnt > 2` will be false. For a *single* device, this is fine because `rxe_release_udp_tunnel` gets called, which is correct and cleans up the socket.
However, things fall apart with multiple devices. If `rxe_net_ipv4_init` is called a second time, it retrieves the existing socket. Crucially, `sock_hold(sk)` then increments `sk_refcnt` to 2.
Now, with two devices, `sk_refcnt` is 2. When deleting the *first* device, `rxe_sock_put` sees `2 > 2` is still *false*! So `rxe_release_udp_tunnel` is called *again*, which then frees the socket and clears the pernet storage associated with it.
But the *second* device is still using that now-freed socket! That's a massive problem. The UDP tunnel is destroyed for that second device. And when the second device is eventually deleted, that `struct sock` is leaked. The logic is broken.
It *seems* the intent was for `SK_REF_FOR_TUNNEL` to be set to 1. With that value: one device: the refcount would be 1, so `1 > 1` would be false, leading to the correct `rxe_release_udp_tunnel`. For two devices, the refcount would be 2, so the check `2 > 1` would be true, calling `__sock_put` which decrements the refcount. When deleting the 2nd device, it would then decrement to 1, and so the test `1 > 1` becomes false, resulting in `rxe_release_udp_tunnel` being called, which is again the correct behavior. This is exactly what we want.
Now, why was `SK_REF_FOR_TUNNEL` set to 2 in the first place? It's time to dig into the commit history. The definition simply reads:
```c
#ifndef SK_REF_FOR_TUNNEL
#define SK_REF_FOR_TUNNEL 2
#endif
```
My suspicion is that the original author observed the socket reference count being 2 in a particular kernel version or configuration, and that drove the `2` value. The next step is to examine the current kernel and confirm my assumption. Let's see if `setup_udp_tunnel_sock` behaves differently. Additionally, let's confirm the lack of influence from `rxe_net_ipv6_init` or `rxe_add` on `sk4`. I need to ensure that the scope of this is limited to the socket created at the start, and not influenced elsewhere.
|
| 71/2 |
2026/05/30 23:22 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/ipv4/udp_tunnel_core.c Name:setup_udp_tunnel_sock]
Results: map[SourceCode: 71: void setup_udp_tunnel_sock(struct net *net, struct socket *sock,
72: struct udp_tunnel_sock_cfg *cfg)
73: {
74: struct sock *sk = sock->sk;
75:
76: /* Disable multicast loopback */
77: inet_clear_bit(MC_LOOP, sk);
78:
79: /* Enable CHECKSUM_UNNECESSARY to CHECKSUM_COMPLETE conversion */
80: inet_inc_convert_csum(sk);
81:
82: rcu_assign_sk_user_data(sk, cfg->sk_user_data);
83:
84: udp_sk(sk)->encap_type = cfg->encap_type;
85: udp_sk(sk)->encap_rcv = cfg->encap_rcv;
86: udp_sk(sk)->encap_err_rcv = cfg->encap_err_rcv;
87: udp_sk(sk)->encap_err_lookup = cfg->encap_err_lookup;
88: udp_sk(sk)->encap_destroy = cfg->encap_destroy;
89: udp_sk(sk)->gro_receive = cfg->gro_receive;
90: udp_sk(sk)->gro_complete = cfg->gro_complete;
91:
92: udp_tunnel_encap_enable(sk);
93:
94: udp_tunnel_update_gro_rcv(sk, true);
95:
96: if (!sk->sk_dport && !sk->sk_bound_dev_if && sk_saddr_any(sk) &&
97: sk->sk_kern_sock)
98: udp_tunnel_update_gro_lookup(net, sk, true);
99: }
SourceFile:net/ipv4/udp_tunnel_core.c]
|
| 72/2 |
2026/05/30 23:22 |
llm |
expert |
0m
Model:
Tokens:
input: 48757
output: 51
thoughts: 22
Thoughts: Okay, here's my attempt at summarizing those thoughts as a first-person expert perspective:
**Initial Assessment: A Contradiction and a Targeted Question**
No, that's not quite right. My immediate reaction is a flat contradiction. Something is off. I need to dig deeper to understand why.
Okay, instead of the initial idea, I should consider other concepts. What about, specifically, the `rxe_reclassify_recv_socket` function? Does that play a role here? I need to focus my thought process. Let's explore that particular area.
|
| 73/2 |
2026/05/30 23:22 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/infiniband/sw/rxe/rxe_net.c Name:rxe_reclassify_recv_socket]
Results: map[SourceCode: 45: static inline void rxe_reclassify_recv_socket(struct socket *sock)
46: {
47: #ifdef CONFIG_DEBUG_LOCK_ALLOC
48: struct sock *sk = sock->sk;
49:
50: if (WARN_ON_ONCE(!sock_allow_reclassification(sk)))
51: return;
52:
53: switch (sk->sk_family) {
54: case AF_INET:
55: sock_lock_init_class_and_name(sk,
56: "slock-AF_INET-RDMA-RXE-RECV",
57: &rxe_recv_slock_key[0],
58: "sk_lock-AF_INET-RDMA-RXE-RECV",
59: &rxe_recv_sk_key[0]);
60: break;
61: case AF_INET6:
62: sock_lock_init_class_and_name(sk,
63: "slock-AF_INET6-RDMA-RXE-RECV",
64: &rxe_recv_slock_key[1],
65: "sk_lock-AF_INET6-RDMA-RXE-RECV",
66: &rxe_recv_sk_key[1]);
67: break;
68: default:
69: WARN_ON_ONCE(1);
70: return;
71: }
72: /*
73: * sock_lock_init_class_and_name() calls
74: * sk_owner_set(sk, THIS_MODULE); in order
75: * to make sure the referenced global
76: * variables rxe_recv_slock_key and
77: * rxe_recv_sk_key are not removed
78: * before the socket is closed.
79: *
80: * However this prevents rxe_net_exit()
81: * from being called and 'rmmod rdma_rxe'
82: * is refused because of the references.
83: *
84: * For the global sockets in recv_sockets,
85: * we are sure that rxe_net_exit() will call
86: * rxe_release_udp_tunnel -> udp_tunnel_sock_release.
87: *
88: * So we don't need the additional reference to
89: * our own (THIS_MODULE).
90: */
91: sk_owner_put(sk);
92: /*
93: * We also call sk_owner_clear() otherwise
94: * sk_owner_put(sk) in sk_prot_free will
95: * fail, which is called via
96: * sk_free -> __sk_free -> sk_destruct
97: * and sk_destruct calls __sk_destruct
98: * directly or via call_rcu()
99: * so sk_prot_free() might be called
100: * after rxe_net_exit().
101: */
102: sk_owner_clear(sk);
103: #endif /* CONFIG_DEBUG_LOCK_ALLOC */
104: }
SourceFile:drivers/infiniband/sw/rxe/rxe_net.c]
|
| 74/2 |
2026/05/30 23:22 |
llm |
expert |
0m
Model:
Tokens:
input: 49896
output: 34
thoughts: 5305
Thoughts: **Analysis of an RXE Double-Free Vulnerability**
Okay, so I've been digging into this RXE (RDMA over Ethernet) driver code and I think I've found a nasty double-free vulnerability. Essentially, it boils down to a race condition when unregistering or deleting RXE devices, leading to a crash or potential privilege escalation. Let me walk you through the logic and my findings.
The core issue lies within the `rxe_net_del` function, which is called when an RXE device is removed. It involves reference counting, specifically related to the UDP tunnel sockets used by RXE. The function `rxe_sock_put` is called to potentially release the UDP tunnel and decrement a reference count on the underlying `struct sock`. Hereβs the breakdown. The code checks `refcount_read(&sk->sk_refcnt) > SK_REF_FOR_TUNNEL`. `SK_REF_FOR_TUNNEL` is defined as 2. A single RXE device has `sk_refcnt` as 1, so the `> ` is evaluated as false and it calls `rxe_release_udp_tunnel(sk->sk_socket)`.
Now, if two threads *concurrently* trigger `rxe_net_del` for the *same* RXE device (for instance, via two concurrent `rdma link del` commands or a combination of `rdma link del` and netdev deletion), both threads can read the same, original `sk_refcnt` of 1. Because of the read, they will *both* call `rxe_release_udp_tunnel`! `rxe_release_udp_tunnel` leads to a call to `sock_release` which frees the `struct socket` and decrements `sk_refcnt`.
The second thread will then try to use the *already freed* `struct socket` and decrement `sk_refcnt` again, triggering a double-free and a classic refcount underflow that can lead to memory corruption. This is a big problem.
So, how can this race be triggered? Several ways:
1. **Concurrent `rdma link del`:** Two `rdma link del` commands running concurrently on the same RXE device. Netlink messages are processed concurrently, allowing this. The `ib_unregister_device_and_put` function *does* have a mutex to prevent concurrent unregistration, but `rxe_dellink` and consequently `rxe_net_del` is called *before* that mutex is acquired. This provides a race window for the double free to happen.
2. **Netdev Unregistration and `rdma link del`:** Removing the underlying network device (e.g., `ip link del veth0` when the RXE device is on `veth0`) *and* concurrently deleting the RXE link via `rdma link del`. Both operations call `rxe_net_del`.
3. **If multiple devices exist:** It seems to me, that if two RDMA devices exist, the first would have its socket released due to the check against `SK_REF_FOR_TUNNEL` (2), but the second would cause a leak on unregister.
Now the million-dollar question: is this exploitable, and who can trigger it? The good news is, an *unprivileged* user in a user namespace with `CAP_NET_ADMIN` *can* trigger this. A user can create a user namespace (if not disabled), create a network namespace within it, create a virtual network device like a veth pair or a dummy device, and then create an RXE device on top of that. They can then run the `rdma link del` commands concurrently from different threads within that namespace to trigger the race. The `rxe` module is even auto-loaded via module aliases, so no privileged module loading is required.
I need to confirm if I understand the capabilities requirements correctly:
1. **Unprivileged (without user namespaces):** This is *not* accessible without user namespaces as it requires `CAP_NET_ADMIN`.
2. **User Namespace:** Yes, it *is* accessible within a user namespace because the process can obtain `CAP_NET_ADMIN` in the new network namespace it creates.
There are no constraints to prevent a user from creating a veth or dummy device on top of which the rxe can be created. The only check that exists is if a device is a vlan device.
In summary, this is a dangerous double-free vulnerability, exploitable by unprivileged users in a user namespace. The race condition centers around the lack of proper synchronization when releasing resources for RXE devices, specifically the UDP tunnel sockets. The crash or the memory corruption that results
|
| 75/2 |
2026/05/30 23:23 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/socket.c Name:sock_create_kern]
Results: map[SourceCode:1738: int sock_create_kern(struct net *net, int family, int type, int protocol, struct socket **res)
1739: {
1740: return __sock_create(net, family, type, protocol, res, 1);
1741: }
SourceFile:net/socket.c]
|
| 76/2 |
2026/05/30 23:23 |
llm |
expert |
0m
Model:
Tokens:
input: 56363
output: 33
thoughts: 10
Thoughts: Okay, here's my attempt at summarizing that thought process, keeping in mind I'm speaking to a fellow expert:
**Diving into `__sock_create`**
Right, let's get right to it. My initial thought is to go straight to `__sock_create`. It's the usual suspect when I'm chasing down socket creation issues. I need to understand exactly what's going on at the very core of socket initialization, and `__sock_create` is the function that handles the nitty-gritty. I'm going to start there to see if I can get a handle on the root cause. This is where the real action is, and where I can gain the most insight quickly. Let's see what we've got.
|
| 77/2 |
2026/05/30 23:23 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/socket.c Name:__sock_create]
Results: map[SourceCode:1593: int __sock_create(struct net *net, int family, int type, int protocol,
1594: struct socket **res, int kern)
1595: {
1596: int err;
1597: struct socket *sock;
1598: const struct net_proto_family *pf;
1599:
1600: /*
1601: * Check protocol is in range
1602: */
1603: if (family < 0 || family >= NPROTO)
1604: return -EAFNOSUPPORT;
1605: if (type < 0 || type >= SOCK_MAX)
1606: return -EINVAL;
1607:
1608: /* Compatibility.
1609:
1610: This uglymoron is moved from INET layer to here to avoid
1611: deadlock in module load.
1612: */
1613: if (family == PF_INET && type == SOCK_PACKET) {
1614: pr_info_once("%s uses obsolete (PF_INET,SOCK_PACKET)\n",
1615: current->comm);
1616: family = PF_PACKET;
1617: }
1618:
1619: err = security_socket_create(family, type, protocol, kern);
1620: if (err)
1621: return err;
1622:
1623: /*
1624: * Allocate the socket and allow the family to set things up. if
1625: * the protocol is 0, the family is instructed to select an appropriate
1626: * default.
1627: */
1628: sock = sock_alloc();
1629: if (!sock) {
1630: net_warn_ratelimited("socket: no more sockets\n");
1631: return -ENFILE; /* Not exactly a match, but its the
1632: closest posix thing */
1633: }
1634:
1635: sock->type = type;
1636:
1637: #ifdef CONFIG_MODULES
1638: /* Attempt to load a protocol module if the find failed.
1639: *
1640: * 12/09/1996 Marcin: But! this makes REALLY only sense, if the user
1641: * requested real, full-featured networking support upon configuration.
1642: * Otherwise module support will break!
1643: */
1644: if (rcu_access_pointer(net_families[family]) == NULL)
1645: request_module("net-pf-%d", family);
1646: #endif
1647:
1648: rcu_read_lock();
1649: pf = rcu_dereference(net_families[family]);
1650: err = -EAFNOSUPPORT;
1651: if (!pf)
1652: goto out_release;
1653:
1654: /*
1655: * We will call the ->create function, that possibly is in a loadable
1656: * module, so we have to bump that loadable module refcnt first.
1657: */
1658: if (!try_module_get(pf->owner))
1659: goto out_release;
1660:
1661: /* Now protected by module ref count */
1662: rcu_read_unlock();
1663:
1664: err = pf->create(net, sock, protocol, kern);
1665: if (err < 0) {
1666: /* ->create should release the allocated sock->sk object on error
1667: * and make sure sock->sk is set to NULL to avoid use-after-free
1668: */
1669: DEBUG_NET_WARN_ONCE(sock->sk,
1670: "%ps must clear sock->sk on failure, family: %d, type: %d, protocol: %d\n",
1671: pf->create, family, type, protocol);
1672: goto out_module_put;
1673: }
1674:
1675: /*
1676: * Now to bump the refcnt of the [loadable] module that owns this
1677: * socket at sock_release time we decrement its refcnt.
1678: */
1679: if (!try_module_get(sock->ops->owner))
1680: goto out_module_busy;
1681:
1682: /*
1683: * Now that we're done with the ->create function, the [loadable]
1684: * module can have its refcnt decremented
1685: */
1686: module_put(pf->owner);
1687: err = security_socket_post_create(sock, family, type, protocol, kern);
1688: if (err)
1689: goto out_sock_release;
1690: *res = sock;
1691:
1692: return 0;
1693:
1694: out_module_busy:
1695: err = -EAFNOSUPPORT;
1696: out_module_put:
1697: sock->ops = NULL;
1698: module_put(pf->owner);
1699: out_sock_release:
1700: sock_release(sock);
1701: return err;
1702:
1703: out_release:
1704: rcu_read_unlock();
1705: goto out_sock_release;
1706: }
SourceFile:net/socket.c]
|
| 78/2 |
2026/05/30 23:23 |
llm |
expert |
0m
Model:
Tokens:
input: 58195
output: 66
thoughts: 603
Thoughts: **My Analysis of the RXE Double-Free Vulnerability**
Okay, so here's what I've figured out about this RXE vulnerability. The core issue boils down to how `sk_refcnt` (reference count) is managed and how that interacts with the `rxe_release_udp_tunnel` function. Let me lay it out:
`pf->create` is actually calling `inet_create`. Now, `inet_create` then calls `sk_alloc`, and this is where it sets `sk_refcnt` to 1. So we *know* `sk_refcnt` starts at 1. The code, however, assumes that `sk_refcnt` is 2 when it's checking `> SK_REF_FOR_TUNNEL` (where `SK_REF_FOR_TUNNEL` equals 2), which is just fundamentally wrong.
Because the assumption is wrong, it means that `1 > 2` is evaluated as false, and the code incorrectly calls `rxe_release_udp_tunnel`. This "happens" to work for a single device because the ref count eventually gets decremented correctly. However, with two devices, `sk_refcnt` *does* eventually reach 2. Now the comparison is `2 > 2`, which is still false! So, it ends up calling `rxe_release_udp_tunnel` for the *first* device that is deleted. And even worse, with concurrent deletions of a single device, both threads see `1 > 2` and thus both call `rxe_release_udp_tunnel`, resulting in a classic double free scenario!
Therefore, this vulnerability is a double-free of `struct sock` and `struct socket`, triggered by a race condition within `rxe_net_del`. It's exploitable and can cause a DoS (Denial of Service). The interesting part is that it is accessible from unprivileged processes, *but only through user namespaces*.
Now, the next thing I needed to consider was exactly how this can be exploited. Can we bypass user namespaces? No, because `rdma link add` needs `CAP_NET_ADMIN`. Is there another way to call `rxe_net_del`? Yes! Through `rxe_notify` on `NETDEV_UNREGISTER`. Can unprivileged users without namespaces trigger `NETDEV_UNREGISTER`? No, because they can't delete network devices without `CAP_NET_ADMIN`. Could this be triggered by unplugging a USB device? Yes! If someone plugs in a USB network adapter, an RXE device is created on top of it. However, it seems that RXE is not auto-configured.
So I worked out whether this is a network-triggered vulnerability or a peripheral trigger? It's not a network trigger, and it's not a VM guest trigger or host trigger. It's also not a malicious filesystem or network traffic trigger. It seems to be a peripheral trigger.
So, here's my summarized breakdown:
* **Exploitable:** Yes
* **DenialOfService:** Yes
* **Unprivileged:** False
* **UserNamespace:** True
* **VMGuestTrigger:** False
* **VMHostTrigger:** False
* **NetworkTrigger:** False
* **RemoteTrigger:** False
* **PeripheralTrigger:** False
* **FilesystemTrigger:** False
Now, I'm just going to double-check my findings with the `set-results` tool and make sure everything is consistent.
|
| 79/2 |
2026/05/30 23:23 |
tool |
set-results |
0m
Args: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
|
| 80/2 |
2026/05/30 23:23 |
llm |
expert |
0m
Model:
Tokens:
input: 59687
output: 727
thoughts: 389
Thoughts: **Vulnerability Deep Dive: Race Condition in RXE Device Deletion**
Okay, so the results are in. Time to articulate this vulnerability clearly. My analysis points to a nasty race condition within the `rxe_net_del` function, specifically related to the removal of an RXE device. It boils down to a double-free scenario involving both a `struct socket` and its associated `struct sock`.
Let me break it down: when an RXE device is deleted, `rxe_net_del` gets invoked. This function is responsible for cleaning things up. Crucially, it retrieves the shared UDP tunnel socket from per-network namespace storage β `rxe_ns_pernet_sk4`. Then, it calls `rxe_sock_put`.
Now, here's where things get interesting. `rxe_sock_put` checks the socket's reference count. In a typical scenario, with a single RXE device, this refcount starts at 1. The key is in the condition: it checks if the refcount is greater than `SK_REF_FOR_TUNNEL` (which is 2). With only one device, the answer is no, and the `else` branch kicks in. This leads to `rxe_release_udp_tunnel`, which is the function that ultimately frees the socket.
The problem? `rxe_net_del` lacks *any* locking. This is a huge red flag. Because of this missing protection, if `rxe_net_del` is called concurrently β say, from two simultaneous `rdma link del` netlink commands, or a concurrent `rdma link del` and a netdev unregister event handled by `rxe_notify`β both threads can end up retrieving the same socket pointer. Both threads will independently see the same refcount of 1. Consequently, both threads will then call `rxe_release_udp_tunnel`, resulting in a devastating double free of the socket and sock.
This double-free is a critical memory corruption vulnerability. It opens the door to potential privilege escalation or, at the very least, a denial-of-service condition.
The really worrying part? An unprivileged user can exploit this. Since RDMA netlink commands require only `CAP_NET_ADMIN` in the network namespace, a new user can create their own namespace, load the vulnerable `rdma_rxe` module, create a dummy netdev, add an RXE link, and then *concurrently* delete it. The race is on, and the kernel is in trouble.
|
| 81/1 |
2026/05/30 23:23 |
action |
format-explanation |
0m
Results: map[Explanation:Based on the analysis of the bug report and the kernel source code, here is the
assessment of the vulnerability:
### Bug Mechanism
The bug is a race condition in the Soft-RoCE (RXE) driver that leads to a double
free of a `struct socket` and its associated `struct sock`.
When an RXE device is deleted, `rxe_net_del()` is called to clean up the
underlying UDP tunnel sockets. It retrieves the shared socket from the
per-network namespace storage (`rxe_ns_pernet_sk4`) and calls `rxe_sock_put()`.
`rxe_sock_put()` checks if the socket's refcount is greater than
`SK_REF_FOR_TUNNEL` (which is hardcoded to 2). For a single RXE device, the
socket's refcount is 1, so the condition `1 > 2` is false. It then proceeds to
the `else` branch, calling `rxe_release_udp_tunnel()`, which ultimately calls
`sock_release()` and frees the socket.
The critical flaw is that `rxe_net_del()` lacks any locking or synchronization.
If it is called concurrently for the same deviceβfor example, via two concurrent
`rdma link del` netlink commands, or a concurrent `rdma link del` and a netdev
unregister event handled by `rxe_notify()`βboth threads can retrieve the same
socket pointer from `rxe_ns_pernet_sk4()`. Both threads will observe a refcount
of 1, and both will call `rxe_release_udp_tunnel()` on the same socket. The
first thread frees the socket, and the second thread uses the freed socket and
attempts to free it again, resulting in a double free and the observed
`refcount_t: underflow; use-after-free` warning.
### Security Impact
* **Exploitable:** **Yes.** A double free of a complex kernel object like
`struct sock` (which contains function pointers such as `sk_destruct`) is highly
exploitable. An attacker can use heap spraying techniques to overlap the freed
object with controlled data and hijack the control flow when the second free or
subsequent uses occur, leading to Local Privilege Escalation (LPE).
* **Denial Of Service:** **Yes.** If not successfully exploited for LPE, the
double free will corrupt kernel memory and cause a system crash (kernel panic).
* **Accessible From User Namespaces:** **Yes.** The RDMA netlink commands (`rdma
link add` / `rdma link del`) require `CAP_NET_ADMIN`, but this capability is
only checked against the user namespace that owns the network namespace of the
netlink socket. An unprivileged user can create a new user namespace and a new
network namespace to gain `CAP_NET_ADMIN`. They can then trigger the
auto-loading of the `rdma_rxe` module, create a dummy network device, attach an
RXE link to it, and concurrently delete it to trigger the double free.
* **Accessible From Unprivileged Processes:** **False.** Without the ability to
create a user namespace to gain `CAP_NET_ADMIN`, an unprivileged user cannot
send the required RDMA netlink commands or manipulate network devices.
### Conclusion
This is a highly critical, locally exploitable vulnerability (LPE/DoS) reachable
by unprivileged users on systems where user namespaces are enabled.]
|