Oops reported via lkml (MSGID: 69ea344f.a00a0220.17a17.0040.GAE@google.com). Crash type: General Protection Fault / KASAN null-ptr-deref
Root cause: Double-release race on the RXE UDP tunnel socket —
rxe_ns_exit()andrxe_net_del()can both calludp_tunnel_sock_release()on the same socket; the second call crashes inkernel_sock_shutdown()becausesock->opswas set to NULL by the firstsock_release(). Introduced by commits13f2a53c2a71eandf1327abd6abed(March 2026 RXE per-namespace support).
| Field | Value | Implication |
|---|---|---|
| MSGID | 69ea344f.a00a0220.17a17.0040.GAE@google.com |
|
| MSGID_URL | 69ea344f.a00a0220.17a17.0040.GAE@google.com | |
| UNAME | syzkaller #0 PREEMPT(full) |
Syzkaller automated test kernel |
| DISTRO | (none detected — syzkaller custom build) | |
| PROCESS | syz.7.1709 |
Syzkaller fuzzer process |
| HARDWARE | QEMU Standard PC (Q35 + ICH9, 2009) |
|
| BIOS | 1.16.3-debian-1.16.3-2 04/01/2014 |
|
| TAINT | L (softlockup) |
Indicates a system stall, thermal issues, or malicious software. |
| CRASH_TYPE | General Protection Fault + KASAN null-ptr-deref | |
| KASAN_RANGE | [0x0000000000000068–0x000000000000006f] |
Null-pointer dereference at offset 0x68 (proto_ops→shutdown) |
| RIP | kernel_sock_shutdown+0x47/0x70 net/socket.c:3785 |
Crash in kernel socket shutdown |
| HEAD_COMMIT | 6596a02b2078
“Merge tag ‘drm-next-2026-04-22’” |
|
| SOURCEDIR | oops-workdir/linux |
Checked out to commit 6596a02b |
| VMLINUX | oops-workdir/syzbot/vmlinux-6596a02b |
Downloaded from syzbot assets |
| Module | Flags | Backtrace | Location | Flag Implication |
|---|---|---|---|---|
| (no modules listed — built-in only) |
| Address | Function | Offset | Size | Context | Module | Source location |
|---|---|---|---|---|---|---|
0xffffffff895ced47 (0xffffffff895ced00 + 0x47) |
kernel_sock_shutdown |
0x47 |
0x70 |
Task | (built-in) | net/socket.c:3785 |
0xffffffff8a20ec48 (0xffffffff8a20ebe0 + 0x68) |
udp_tunnel_sock_release |
0x68 |
0x80 |
Task | (built-in) | net/ipv4/udp_tunnel_core.c:202 |
rxe_release_udp_tunnel (inlined) |
Task | (built-in) | drivers/infiniband/sw/rxe/rxe_net.c:294 | |||
0xffffffff88c2b9ee (0xffffffff88c2b940 + 0xae) |
rxe_sock_put |
0xae |
0x130 |
Task | (built-in) | drivers/infiniband/sw/rxe/rxe_net.c:639 |
0xffffffff88c2fcd3 (0xffffffff88c2fc50 + 0x83) |
rxe_net_del |
0x83 |
0x120 |
Task | (built-in) | drivers/infiniband/sw/rxe/rxe_net.c:660 |
0xffffffff88bef275 (0xffffffff88bef260 + 0x15) |
rxe_dellink |
0x15 |
0x20 |
Task | (built-in) | drivers/infiniband/sw/rxe/rxe.c:254 |
0xffffffff88a0a2a9 (0xffffffff88a0a020 + 0x289) |
nldev_dellink |
0x289 |
0x3c0 |
Task | (built-in) | drivers/infiniband/core/nldev.c:1849 |
0xffffffff889beda2 (0xffffffff889bea10 + 0x392) |
rdma_nl_rcv_msg |
0x392 |
0x6f0 |
Task | (built-in) | drivers/infiniband/core/netlink.c:195 |
0xffffffff889bf3db (0xffffffff889bf110 + 0x2cb) |
rdma_nl_rcv_skb.constprop.0.isra.0 |
0x2cb |
0x410 |
Task | (built-in) | drivers/infiniband/core/netlink.c:240 |
netlink_unicast_kernel (inlined) |
Task | (built-in) | net/netlink/af_netlink.c:1318 | |||
0xffffffff89aaa135 (0xffffffff89aa9bb0 + 0x585) |
netlink_unicast |
0x585 |
0x850 |
Task | (built-in) | net/netlink/af_netlink.c:1344 |
0xffffffff89aaacc0 (0xffffffff89aaa410 + 0x8b0) |
netlink_sendmsg |
0x8b0 |
0xda0 |
Task | (built-in) | net/netlink/af_netlink.c:1894 |
sock_sendmsg_nosec (inlined) |
Task | (built-in) | net/socket.c:787 | |||
__sock_sendmsg (inlined) |
Task | (built-in) | net/socket.c:802 | |||
0xffffffff895d4ce1 (0xffffffff895d4300 + 0x9e1) |
____sys_sendmsg |
0x9e1 |
0xb70 |
Task | (built-in) | net/socket.c:2698 |
0xffffffff895d8aa0 (0xffffffff895d8910 + 0x190) |
___sys_sendmsg |
0x190 |
0x1e0 |
Task | (built-in) | net/socket.c:2752 |
0xffffffff895dfdf0 (0xffffffff895dfc80 + 0x170) |
__sys_sendmsg |
0x170 |
0x220 |
Task | (built-in) | net/socket.c:2784 |
do_syscall_x64 (inlined) |
Task | (built-in) | arch/x86/entry/syscall_64.c:63 | |||
0xffffffff8b9a318b (0xffffffff8b9a3080 + 0x10b) |
do_syscall_64 |
0x10b |
0xf80 |
Task | (built-in) | arch/x86/entry/syscall_64.c:94 |
0xffffffff81000130 (0xffffffff810000b9 + 0x77) |
entry_SYSCALL_64_after_hwframe |
0x77 |
Task | (built-in) | arch/x86/entry/entry_64.S:121 |
| Register | Value | Notes |
|---|---|---|
| RIP | 0xffffffff895ced47 |
kernel_sock_shutdown+0x47 — crash site |
| RSP | 0xffffc9000566f180 |
Kernel stack |
| EFLAGS | 0x00010202 |
|
| RAX | 0xdffffc0000000000 |
KASAN shadow base address |
| RBX | 0xffff888058587240 |
struct socket *sock (the socket being shut down) |
| RCX | 0x0000000000000000 |
|
| RDX | 0x000000000000000d |
KASAN shadow offset = 0x68 >> 3 = 0xd |
| RSI | 0xffffffff895ced12 |
Return address / callee |
| RDI | 0x0000000000000068 |
Computed as sock->ops + 0x68 (shutdown fn ptr
address); sock->ops = NULL |
| RBP | 0x0000000000000002 |
how = SHUT_RDWR = 2 |
| R08 | 0x0000000000000001 |
|
| R09 | 0xffffed1006d98945 |
|
| R10 | 0xffff888036cc4a2b |
|
| R11 | 0x0000003683c25c00 |
|
| R12 | 0x0000000000000000 |
sock->ops loaded from RBX+0x20 =
NULL — the null pointer |
| R13 | 0xffff88805c998000 |
|
| R14 | 0x0000000000000002 |
|
| R15 | 0x0000000000000018 |
|
| CR0 | 0x0000000080050033 |
|
| CR2 | 0x00007f1306d97d58 |
User-space address (unrelated) |
| CR3 | 0x00000000404f1000 |
Page table base |
| CR4 | 0x0000000000352ef0 |
kernel_sock_shutdown — crash site
(net/socket.c:3785)3783 int kernel_sock_shutdown(struct socket *sock, enum sock_shutdown_cmd how)
3784 {
3785 return READ_ONCE(sock->ops)->shutdown(sock, how); // ← CRASH: sock->ops is NULL
3786 }At offset +0x47 (address
0xffffffff895ced47), the KASAN instrumentation checks the
shadow byte for address 0x68 (= NULL + 0x68,
the shutdown function pointer in
struct proto_ops) and traps because this is a null-pointer
dereference. R12 = sock->ops = 0x0 (loaded from
RBX+0x20 where RBX = struct socket *sock).
Disassembly window around crash (from vmlinux):
ffffffff895ced37:mov 0x20(%rbx),%r12 ; r12 = sock->ops (= NULL)
ffffffff895ced3b:lea 0x68(%r12),%rdi ; rdi = &proto_ops->shutdown (= 0x68)
ffffffff895ced40:mov %rdi,%rdx
ffffffff895ced43:shr $0x3,%rdx ; KASAN shadow offset
ffffffff895ced47:cmpb $0x0,(%rdx,%rax,1) ; <<< CRASH: KASAN null-ptr check
ffffffff895ced4b:jne ffffffff895ced67
ffffffff895ced4d:mov 0x68(%r12),%rax ; would load proto_ops->shutdown
ffffffff895ced52:mov %ebp,%esi
ffffffff895ced54:mov %rbx,%rdi
udp_tunnel_sock_release
(net/ipv4/udp_tunnel_core.c:204)net/ipv4/udp_tunnel_core.c
@ 6596a02b
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); // ← calls crash site; sock->ops is NULL here
203 sock_release(sock);
204 }Backtrace entry at offset +0x68/0x80, source line 204
(annotated by syzbot).
rxe_release_udp_tunnel (inlined)
(drivers/infiniband/sw/rxe/rxe_net.c:295)drivers/infiniband/sw/rxe/rxe_net.c
@ 6596a02b
291 static void rxe_release_udp_tunnel(struct socket *sk)
292 {
293 if (sk)
294 udp_tunnel_sock_release(sk);
295 }Inlined into rxe_sock_put. The call at line 294 passes
sk->sk_socket (see rxe_sock_put below). The
guard checks sk != NULL but does not check
sk->ops.
rxe_sock_put
(drivers/infiniband/sw/rxe/rxe_net.c:639)drivers/infiniband/sw/rxe/rxe_net.c
@ 6596a02b
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); // ← calls with sk->sk_socket
640 sk = NULL;
641 set_sk(net, sk);
642 }
643 }SK_REF_FOR_TUNNEL = 2 (defined at line 23 of
rxe_net.c). When sk->sk_refcnt <= 2, the
else branch is taken and
rxe_release_udp_tunnel(sk->sk_socket) is called. The
struct socket * passed is
sk->sk_socket.
Factual observation: rxe_sock_put calls
rxe_release_udp_tunnel(sk->sk_socket) without checking
whether sk->sk_socket->ops is non-NULL.
kernel_sock_shutdown() dereferences
sock->ops (a const struct proto_ops *)
which is NULL at the time of the call.
3783 int kernel_sock_shutdown(struct socket *sock, enum sock_shutdown_cmd how)
3784 {
3785 return READ_ONCE(sock->ops)->shutdown(sock, how); // ← CRASH: sock->ops is NULL
3786 }KASAN confirms a null-pointer dereference at offset 0x68
— which matches offsetof(struct proto_ops, shutdown)
(confirmed from the struct layout with CONFIG_COMPAT=y).
Register R12 = 0x0 (sock->ops), loaded from
RBX+0x20 (struct socket.ops at offset 0x20),
proves ops == NULL.
The crash is called from udp_tunnel_sock_release()
(net/ipv4/udp_tunnel_core.c) which is called from
rxe_release_udp_tunnel() (inlined) inside
rxe_sock_put(), as part of the rxe_net_del()
RDMA link-delete path triggered via nldev_dellink →
rxe_dellink → rxe_net_del.
Q1: How did sock->ops become NULL at the
point of the crash?
A1: sock->ops is set to NULL inside
sock_release() at net/socket.c:726:
718 ops->release(sock);
719 sock->sk = NULL;
...
726 sock->ops = NULL;The call to udp_tunnel_sock_release() that crashes
attempts:
kernel_sock_shutdown(sock, SHUT_RDWR); // ← crash — sock->ops already NULL
sock_release(sock);kernel_sock_shutdown() is called before
sock_release() within
udp_tunnel_sock_release(). Therefore
sock->ops cannot be NULL on the first call — it must
have been set to NULL by a prior
sock_release() on the same socket.
Q2: Who called sock_release() on this socket
before rxe_net_del() ran?
A2: A second, independent code path — rxe_ns_exit() in
drivers/infiniband/sw/rxe/rxe_ns.c — can release the
same socket when the network namespace is
destroyed:
static void rxe_ns_exit(struct net *net)
{
struct rxe_ns_sock *ns_sk = net_generic(net, rxe_pernet_id);
struct sock *sk;
rcu_read_lock();
sk = rcu_dereference(ns_sk->rxe_sk4); // read pointer
rcu_read_unlock();
if (sk) {
rcu_assign_pointer(ns_sk->rxe_sk4, NULL); // clear pointer
udp_tunnel_sock_release(sk->sk_socket); // release socket → sock->ops = NULL
}
...
}And rxe_net_del() in rxe_net.c:
void rxe_net_del(struct ib_device *dev)
{
...
sk = rxe_ns_pernet_sk4(net); // read pointer (no atomic ownership transfer)
if (sk)
rxe_sock_put(sk, rxe_ns_pernet_set_sk4, net);
...
}rxe_sock_put() releases without first clearing:
static void rxe_sock_put(struct sock *sk, ...)
{
if (refcount_read(&sk->sk_refcnt) > SK_REF_FOR_TUNNEL) {
__sock_put(sk);
} else {
rxe_release_udp_tunnel(sk->sk_socket); // release FIRST (bug)
sk = NULL;
set_sk(net, sk); // clear AFTER (too late)
}
}Q3: How does the race materialise?
A3: The following TOCTOU sequence is possible when both namespace teardown and RDMA link deletion happen concurrently (as syzkaller exercises):
Thread A (rxe_net_del): Thread B (rxe_ns_exit):
rxe_ns_pernet_sk4() → sk=X
rcu_assign_pointer(sk4, NULL)
udp_tunnel_sock_release(X->sk_socket)
sock_release(X->sk_socket)
X->sk_socket->ops = NULL ← clears ops
rxe_sock_put(sk=X, ...)
rxe_release_udp_tunnel(X->sk_socket)
udp_tunnel_sock_release(X->sk_socket)
kernel_sock_shutdown(X->sk_socket, SHUT_RDWR)
READ_ONCE(sock->ops)->shutdown(...) ← CRASH: ops == NULL
rxe_ns_exit() atomically clears the pernet pointer
before releasing, but rxe_net_del() reads
the pointer independently (without taking ownership atomically). By the
time rxe_net_del() acts on the pointer,
rxe_ns_exit() may have already released the socket.
Note: even without a race, rxe_ns_exit() is also
triggered by the NETDEV_UNREGISTER notifier at
rxe_net.c:725 which also calls rxe_net_del(),
creating a double-invocation on a single-namespace teardown if both
fire.
Bug introduction commits (git count: 2):
| Hash | Subject | Role |
|---|---|---|
13f2a53c2a71e |
RDMA/rxe: Add net namespace support for IPv4/IPv6 sockets | Added rxe_ns.c with rxe_ns_exit() |
f1327abd6abed |
RDMA/rxe: Support RDMA link creation and destruction per net namespace | Added rxe_net_del() with
rxe_sock_put() |
Both commits authored by Zhu Yanjun
<yanjun.zhu@linux.dev>, merged 2026-03-12. Neither
commit synchronises ownership of the socket between the two teardown
paths.
Recommended fix:
Replace rxe_ns_pernet_sk4() in
rxe_net_del() (and rxe_notify()) with an
atomic exchange that simultaneously reads and clears the pointer, so
only one of the two teardown paths can ever obtain a non-NULL
socket:
--- a/drivers/infiniband/sw/rxe/rxe_ns.h
+++ b/drivers/infiniband/sw/rxe/rxe_ns.h
struct sock *rxe_ns_pernet_sk4(struct net *net);
void rxe_ns_pernet_set_sk4(struct net *net, struct sock *sk);
+struct sock *rxe_ns_pernet_take_sk4(struct net *net);
struct sock *rxe_ns_pernet_sk6(struct net *net);
void rxe_ns_pernet_set_sk6(struct net *net, struct sock *sk);
+struct sock *rxe_ns_pernet_take_sk6(struct net *net);--- a/drivers/infiniband/sw/rxe/rxe_ns.c
+++ b/drivers/infiniband/sw/rxe/rxe_ns.c
+struct sock *rxe_ns_pernet_take_sk4(struct net *net)
+{
+struct rxe_ns_sock *ns_sk = net_generic(net, rxe_pernet_id);
+
+return unrcu_pointer(xchg(&ns_sk->rxe_sk4,
+ RCU_INITIALIZER(NULL)));
+}
+
+struct sock *rxe_ns_pernet_take_sk6(struct net *net)
+{
+struct rxe_ns_sock *ns_sk = net_generic(net, rxe_pernet_id);
+
+return unrcu_pointer(xchg(&ns_sk->rxe_sk6,
+ RCU_INITIALIZER(NULL)));
+}--- a/drivers/infiniband/sw/rxe/rxe_net.c
+++ b/drivers/infiniband/sw/rxe/rxe_net.c
void rxe_net_del(struct ib_device *dev)
{
...
-sk = rxe_ns_pernet_sk4(net);
+sk = rxe_ns_pernet_take_sk4(net); /* atomically claim ownership */
if (sk)
-rxe_sock_put(sk, rxe_ns_pernet_set_sk4, net);
+udp_tunnel_sock_release(sk->sk_socket); /* sk4 already cleared */
-sk = rxe_ns_pernet_sk6(net);
+sk = rxe_ns_pernet_take_sk6(net);
if (sk)
-rxe_sock_put(sk, rxe_ns_pernet_set_sk6, net);
+udp_tunnel_sock_release(sk->sk_socket);
...
}The xchg ensures that whichever of
rxe_ns_exit() or rxe_net_del() runs first
atomically obtains the socket pointer; the second caller gets NULL and
skips the release. This eliminates the TOCTOU window.
An alternative minimal fix (if the xchg approach is
considered too intrusive) is to check sock->ops != NULL
inside udp_tunnel_sock_release() before calling
kernel_sock_shutdown(), but this treats the symptom rather
than the race.
6596a02b2078 (exact HEAD
commit from the oops report; validation was exact)git apply --check passed
cleanly against 6596a02b2078report.patch — clean unified diff (3 files, 25
insertions, 4 deletions)patch-email.txt — LKML-ready patch emailgit-send-email.sh — send script
(--to linux-rdma@vger.kernel.org,
--in-reply-to set)Key refinements vs. rough patch: - Added proper
indentation in rxe_ns.c function bodies - Used
xchg((__force struct sock **)&ns_sk->rxe_sk4, NULL)
instead of
unrcu_pointer(xchg(&ns_sk->rxe_sk4, RCU_INITIALIZER(NULL)))
— avoids type ambiguity with a NULL argument to
RCU_INITIALIZER() - Added missing
static inline rxe_ns_pernet_take_sk6() stub in the
#else /* IPv6 */ branch of rxe_ns.h (required
for non-IPv6 builds) - Filled real context lines in
rxe_net.c hunk (replaced sketch ...)
All checked items verified. Minor line number offsets (±1 to ±2 lines) found in udp_tunnel_sock_release and rxe_release_udp_tunnel citations; these are cosmetic drift and do not affect correctness of the identified functions or analysis. See factcheck.md for details.