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

From: Arjan van de Ven <[email-masked]>
Date: Fri, 24 Apr 2026 09:47:17 -0700

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/[email-masked]
Assisted-by: linux-kernel-oops-x86 skill (Claude Sonnet 4.6)