Re: [syzbot] [net?] general protection fault in kernel_sock_shutdown (4)

Arjan van de Ven
2026-04-24T16:46:13Z

This report was analysed with the help of an automated kernel crash
analysis assistant. The analysis below is tentative and should be
reviewed by a human before any action is taken.

Decoded Backtrace
-----------------

1. 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 (R12 = 0x0); KASAN traps
null-ptr-deref at offset 0x68 = offsetof(proto_ops, shutdown) */
3786 }

Register context at crash:
RBX = 0xffff888058587240 (struct socket *sock)
R12 = 0x0000000000000000 (sock->ops, loaded from RBX+0x20 -- NULL)
RDI = 0x0000000000000068 (= NULL + 0x68, address of shutdown fn ptr)
RBP = 0x0000000000000002 (how = SHUT_RDWR)

2. udp_tunnel_sock_release (net/ipv4/udp_tunnel_core.c:196-202)

196 void udp_tunnel_sock_release(struct socket *sock)
197 {
198 rcu_assign_sk_user_data(sock->sk, NULL);
199 synchronize_rcu();
200 kernel_sock_shutdown(sock, SHUT_RDWR); /* <- calls crash site */
201 sock_release(sock);
202 }

3. rxe_release_udp_tunnel inlined (drivers/infiniband/sw/rxe/rxe_net.c:290-293)

290 static void rxe_release_udp_tunnel(struct socket *sk)
291 {
292 if (sk)
293 udp_tunnel_sock_release(sk);
294 }

4. rxe_sock_put (drivers/infiniband/sw/rxe/rxe_net.c:632-643)

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); /* <- release BEFORE clear */
640 sk = NULL;
641 set_sk(net, sk); /* <- clear AFTER (too late) */
642 }
643 }

Caller: rxe_net_del (rxe_net.c:644-666), triggered via:
nldev_dellink -> rxe_dellink -> rxe_net_del -> rxe_sock_put

Tentative Analysis
------------------

sock->ops is set to NULL by sock_release() (net/socket.c:726) after
calling ops->release(sock). The crash in kernel_sock_shutdown() means
the socket was already passed to sock_release() before this call.

Two independent code paths can release the same UDP tunnel socket stored
in the per-network-namespace rxe_ns_sock structure:

Path 1 -- namespace teardown (rxe_ns.c, rxe_ns_exit()):
rcu_assign_pointer(ns_sk->rxe_sk4, NULL); /* clears pointer FIRST */
udp_tunnel_sock_release(sk->sk_socket); /* then releases */

Path 2 -- RDMA link delete (rxe_net.c, rxe_net_del() -> rxe_sock_put()):
sk = rxe_ns_pernet_sk4(net); /* reads pointer (no ownership) */
rxe_release_udp_tunnel(sk->sk_socket); /* releases FIRST */
set_sk(net, NULL); /* clears AFTER */

The following TOCTOU (time-of-check time-of-use) race is possible when
namespace teardown and RDMA link deletion occur concurrently:

Thread A (rxe_net_del):
rxe_ns_pernet_sk4() -> sk = X (non-NULL)

Thread B (rxe_ns_exit):
rcu_assign_pointer(sk4, NULL)
udp_tunnel_sock_release(X->sk_socket)
sock_release(X->sk_socket)
X->sk_socket->ops = NULL <- clears ops

Thread A (rxe_net_del) continues:
rxe_sock_put(sk=X, ...)
rxe_release_udp_tunnel(X->sk_socket)
kernel_sock_shutdown(X->sk_socket, SHUT_RDWR)
READ_ONCE(sock->ops)->shutdown(...)
<- CRASH: sock->ops == NULL

The bug was introduced by two commits in March 2026 that added
per-network-namespace support to the Soft RoCE (RXE) driver:

13f2a53c2a71e RDMA/rxe: Add net namespace support for IPv4/IPv6 sockets
f1327abd6abed RDMA/rxe: Support RDMA link creation and destruction per
net namespace

Neither commit provides synchronisation between the two teardown paths.

Potential Solution
------------------

Replace rxe_ns_pernet_sk4() calls in rxe_net_del() (and rxe_notify())
with an atomic exchange that simultaneously reads and clears the pernet
pointer, so only one of the two teardown paths can ever obtain a
non-NULL socket pointer:

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)));
}

Whichever path (rxe_ns_exit or rxe_net_del) wins the xchg gets the
socket and releases it; the loser gets NULL and skips the release.

More information
----------------

Oops-Analysis: https://lore.kernel.org/r/69ea344f.a00a0220.17a17.0040.GAE@google.com
Assisted-by: linux-kernel-oops-x86 skill (Claude Sonnet 4.6)