From: "Yanjun.Zhu" <yanjun.zhu@linux.dev>
To: Serhat Kumral <serhatkumral1@gmail.com>,
Zhu Yanjun <zyjzyj2000@gmail.com>, Jason Gunthorpe <jgg@ziepe.ca>,
Leon Romanovsky <leon@kernel.org>,
Zhu Yanjun <yanjun.zhu@linux.dev>
Cc: David Ahern <dsahern@kernel.org>,
linux-rdma@vger.kernel.org, linux-kernel@vger.kernel.org,
syzbot+8c9eede336e3a843750e@syzkaller.appspotmail.com
Subject: Re: [PATCH v2] RDMA/rxe: rework per-net tunnel socket lifetime to fix refcount underflow
Date: Mon, 6 Jul 2026 18:15:55 -0700 [thread overview]
Message-ID: <91a47c3a-d6c5-4998-99e2-fb615253853b@linux.dev> (raw)
In-Reply-To: <20260706183328.7022-1-serhatkumral1@gmail.com>
On 7/6/26 11:33 AM, Serhat Kumral wrote:
> syzkaller reported a refcount_t underflow / use-after-free in
> sk_common_release() when tearing down an rxe device via
> RDMA_NLDEV_CMD_DELLINK. The shared per-namespace UDP tunnel sockets
> were released based on comparing sk->sk_refcnt against a magic
> constant (SK_REF_FOR_TUNNEL), but the network stack takes transient
> references on sk_refcnt of its own, so this check could pass on more
> than one path concurrently (dellink, NETDEV_UNREGISTER notifier,
> pernet exit), releasing the socket twice.
>
> Stop overloading sk_refcnt for driver-level user counting. Track the
> number of rxe devices using each tunnel socket with an explicit
> per-socket counter in the pernet struct, serialised by a mutex:
>
> - creation happens under the lock via a factory callback, closing
> the create/create race,
> - release happens in exactly one place, when the counter drops to
> zero: clear the RCU pointer, wait a grace period, then call
> udp_tunnel_sock_release(),
> - rxe_net_del() is idempotent per device: RDMA_NLDEV_CMD_DELLINK
> and the NETDEV_UNREGISTER notifier can run concurrently for the
> same device, and only the first invocation drops the references,
> - a failed rxe_newlink() now drops the references it took, so the
> counter cannot be left inflated,
> - rxe_ns_exit() performs the same coordinated teardown for
> namespace removal.
>
> IPv4 and IPv6 sockets get separate counters since the IPv6 socket may
> legitimately not exist (-EAFNOSUPPORT).
>
> Fixes: f1327abd6abe ("RDMA/rxe: Support RDMA link creation and destruction per net namespace")
> Reported-by: syzbot+8c9eede336e3a843750e@syzkaller.appspotmail.com
> Closes: https://syzkaller.appspot.com/bug?extid=8c9eede336e3a843750e
> Assisted-by: Claude:claude-fable-5
> Signed-off-by: Serhat Kumral <serhatkumral1@gmail.com>
> ---
> v2:
> - Make rxe_net_del() idempotent per device with test_and_set_bit()
> in struct rxe_dev, so a concurrent RDMA_NLDEV_CMD_DELLINK and
> NETDEV_UNREGISTER notifier cannot drop the pernet socket
> references twice for the same device. Suggested as an alternative
> to rtnl_lock()/a global mutex. [Zhu Yanjun]
> - Add Assisted-by tag per Documentation/process/coding-assistants.rst.
>
> drivers/infiniband/sw/rxe/rxe.c | 1 +
> drivers/infiniband/sw/rxe/rxe_net.c | 109 ++++++++++-----------
> drivers/infiniband/sw/rxe/rxe_net.h | 1 +
> drivers/infiniband/sw/rxe/rxe_ns.c | 132 +++++++++++++++++++-------
> drivers/infiniband/sw/rxe/rxe_ns.h | 24 ++++-
> drivers/infiniband/sw/rxe/rxe_verbs.h | 6 ++
> 6 files changed, 172 insertions(+), 101 deletions(-)
>
> diff --git a/drivers/infiniband/sw/rxe/rxe.c b/drivers/infiniband/sw/rxe/rxe.c
> index af39209d0fcf..bcc72b96ee00 100644
> --- a/drivers/infiniband/sw/rxe/rxe.c
> +++ b/drivers/infiniband/sw/rxe/rxe.c
> @@ -243,6 +243,7 @@ static int rxe_newlink(const char *ibdev_name, struct net_device *ndev)
> err = rxe_net_add(ibdev_name, ndev);
> if (err) {
> rxe_err("failed to add %s\n", ndev->name);
> + rxe_net_uninit(ndev);
> goto err;
> }
> err:
> diff --git a/drivers/infiniband/sw/rxe/rxe_net.c b/drivers/infiniband/sw/rxe/rxe_net.c
> index 3741b2c4b0bb..eba10a86f353 100644
> --- a/drivers/infiniband/sw/rxe/rxe_net.c
> +++ b/drivers/infiniband/sw/rxe/rxe_net.c
> @@ -19,10 +19,6 @@
> #include "rxe_loc.h"
> #include "rxe_ns.h"
>
> -#ifndef SK_REF_FOR_TUNNEL
> -#define SK_REF_FOR_TUNNEL 2
> -#endif
> -
> #ifdef CONFIG_DEBUG_LOCK_ALLOC
> /*
> * lockdep can detect false positive circular dependencies
> @@ -81,9 +77,9 @@ static inline void rxe_reclassify_recv_socket(struct socket *sock)
> * from being called and 'rmmod rdma_rxe'
> * is refused because of the references.
> *
> - * For the global sockets in recv_sockets,
> - * we are sure that rxe_net_exit() will call
> - * rxe_release_udp_tunnel -> udp_tunnel_sock_release.
> + * For the shared per-namespace sockets, we are sure
> + * that the pernet layer (rxe_ns_pernet_put_skX or
> + * rxe_ns_exit) will call udp_tunnel_sock_release.
> *
> * So we don't need the additional reference to
> * our own (THIS_MODULE).
> @@ -288,12 +284,6 @@ static struct socket *rxe_setup_udp_tunnel(struct net *net, __be16 port,
> return sock;
> }
>
> -static void rxe_release_udp_tunnel(struct sock *sk)
> -{
> - if (sk)
> - udp_tunnel_sock_release(sk);
> -}
> -
> static void prepare_udp_hdr(struct sk_buff *skb, __be16 src_port,
> __be16 dst_port)
> {
> @@ -631,38 +621,32 @@ 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)
> +void rxe_net_uninit(struct net_device *ndev)
> {
> - if (refcount_read(&sk->sk_refcnt) > SK_REF_FOR_TUNNEL) {
> - __sock_put(sk);
> - } else {
> - rxe_release_udp_tunnel(sk);
> - sk = NULL;
> - set_sk(net, sk);
> - }
> + struct net *net = dev_net(ndev);
> +
> + rxe_ns_pernet_put_sk4(net);
> + rxe_ns_pernet_put_sk6(net);
> }
>
> 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;
> +
> + /*
> + * Both RDMA_NLDEV_CMD_DELLINK and the NETDEV_UNREGISTER notifier
> + * can get here concurrently for the same device. Only the first
> + * one may drop the pernet socket references.
> + */
> + if (test_and_set_bit(RXE_NET_SK_PUT, &rxe->net_flags))
> + return;
>
> ndev = ib_device_get_netdev(dev, 1);
> if (!ndev)
> return;
The current ordering introduces a subtle race that
can leave an RXE link in an unrecoverable state.
If the DELLINK path sets RXE_NET_SK_PUT first and
then finds ndev == NULL, it returns without calling rxe_net_uninit().
Finally, the per-netns UDP socket reference (nr_sk4/nr_sk6) is never
decremented.
Zhu Yanjun
>
> - 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);
> + rxe_net_uninit(ndev);
>
> dev_put(ndev);
> }
> @@ -753,52 +737,58 @@ static struct notifier_block rxe_net_notifier = {
> .notifier_call = rxe_notify,
> };
>
> -static int rxe_net_ipv4_init(struct net *net)
> +static struct sock *rxe_create_sk4(struct net *net)
> {
> - 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;
> + return ERR_CAST(sock);
> }
> - rxe_ns_pernet_set_sk4(net, sock->sk);
>
> - return 0;
> + return sock->sk;
> }
>
> -static int rxe_net_ipv6_init(struct net *net)
> +static int rxe_net_ipv4_init(struct net *net)
> {
> -#if IS_ENABLED(CONFIG_IPV6)
> struct sock *sk;
> - struct socket *sock;
>
> - sk = rxe_ns_pernet_sk6(net);
> - if (sk) {
> - sock_hold(sk);
> - return 0;
> - }
> + sk = rxe_ns_pernet_hold_sk4(net, rxe_create_sk4);
> + if (IS_ERR(sk))
> + return PTR_ERR(sk);
> +
> + return 0;
> +}
> +
> +#if IS_ENABLED(CONFIG_IPV6)
> +static struct sock *rxe_create_sk6(struct net *net)
> +{
> + struct socket *sock;
>
> 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;
> + return NULL;
> }
>
> if (IS_ERR(sock)) {
> pr_err("Failed to create IPv6 UDP tunnel\n");
> - return -1;
> + return ERR_CAST(sock);
> }
>
> - rxe_ns_pernet_set_sk6(net, sock->sk);
> + return sock->sk;
> +}
> +#endif
>
> +static int rxe_net_ipv6_init(struct net *net)
> +{
> +#if IS_ENABLED(CONFIG_IPV6)
> + struct sock *sk;
> +
> + sk = rxe_ns_pernet_hold_sk6(net, rxe_create_sk6);
> + if (IS_ERR(sk))
> + return PTR_ERR(sk);
> #endif
> return 0;
> }
> @@ -824,7 +814,6 @@ void rxe_net_exit(void)
> int rxe_net_init(struct net_device *ndev)
> {
> struct net *net;
> - struct sock *sk;
> int err;
>
> net = dev_net(ndev);
> @@ -840,10 +829,8 @@ int rxe_net_init(struct net_device *ndev)
> return 0;
>
> err_out:
> - /* If ipv6 error, release ipv4 resource */
> - sk = rxe_ns_pernet_sk4(net);
> - if (sk)
> - rxe_sock_put(sk, rxe_ns_pernet_set_sk4, net);
> + /* If ipv6 error, drop the ipv4 reference taken above. */
> + rxe_ns_pernet_put_sk4(net);
>
> return err;
> }
> diff --git a/drivers/infiniband/sw/rxe/rxe_net.h b/drivers/infiniband/sw/rxe/rxe_net.h
> index 56249677d692..06e08a4d5897 100644
> --- a/drivers/infiniband/sw/rxe/rxe_net.h
> +++ b/drivers/infiniband/sw/rxe/rxe_net.h
> @@ -13,6 +13,7 @@
>
> int rxe_net_add(const char *ibdev_name, struct net_device *ndev);
> void rxe_net_del(struct ib_device *dev);
> +void rxe_net_uninit(struct net_device *ndev);
>
> int rxe_register_notifier(void);
> int rxe_net_init(struct net_device *ndev);
> diff --git a/drivers/infiniband/sw/rxe/rxe_ns.c b/drivers/infiniband/sw/rxe/rxe_ns.c
> index 64621c89f8bf..d12099a73aa2 100644
> --- a/drivers/infiniband/sw/rxe/rxe_ns.c
> +++ b/drivers/infiniband/sw/rxe/rxe_ns.c
> @@ -4,6 +4,8 @@
> #include <net/netns/generic.h>
> #include <net/net_namespace.h>
> #include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/err.h>
> #include <linux/skbuff.h>
> #include <linux/pid_namespace.h>
> #include <net/udp_tunnel.h>
> @@ -11,11 +13,21 @@
> #include "rxe_ns.h"
>
> /*
> - * Per network namespace data
> + * Per network namespace data.
> + *
> + * The IPv4/IPv6 UDP tunnel sockets are shared by every rxe device created in
> + * a given namespace. Their lifetime is owned here and tracked by an explicit
> + * user count (nr_skX) rather than by overloading sk->sk_refcnt: the network
> + * stack takes transient references on sk_refcnt of its own, so it can never be
> + * used to decide when the last rxe device is gone. All fields are serialised
> + * by @lock.
> */
> struct rxe_ns_sock {
> + struct mutex lock; /* protects rxe_sk4/6 and nr_sk4/6 */
> struct sock __rcu *rxe_sk4;
> struct sock __rcu *rxe_sk6;
> + int nr_sk4;
> + int nr_sk6;
> };
>
> /*
> @@ -28,37 +40,39 @@ static unsigned int rxe_pernet_id;
> */
> static int rxe_ns_init(struct net *net)
> {
> - /* defer socket create in the namespace to the first
> - * device create.
> - */
> + struct rxe_ns_sock *ns_sk = net_generic(net, rxe_pernet_id);
> +
> + /* Socket creation is deferred to the first device create. */
> + mutex_init(&ns_sk->lock);
>
> return 0;
> }
>
> static void rxe_ns_exit(struct net *net)
> {
> - /* called when the network namespace is removed
> - */
> + /* called when the network namespace is removed */
> 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);
> - rcu_read_unlock();
> - if (sk) {
> - rcu_assign_pointer(ns_sk->rxe_sk4, NULL);
> - udp_tunnel_sock_release(sk);
> - }
> -
> -#if IS_ENABLED(CONFIG_IPV6)
> - rcu_read_lock();
> - sk = rcu_dereference(ns_sk->rxe_sk6);
> - rcu_read_unlock();
> - if (sk) {
> - rcu_assign_pointer(ns_sk->rxe_sk6, NULL);
> - udp_tunnel_sock_release(sk);
> - }
> -#endif
> + struct sock *sk4, *sk6;
> +
> + mutex_lock(&ns_sk->lock);
> + sk4 = rcu_dereference_protected(ns_sk->rxe_sk4,
> + lockdep_is_held(&ns_sk->lock));
> + sk6 = rcu_dereference_protected(ns_sk->rxe_sk6,
> + lockdep_is_held(&ns_sk->lock));
> + rcu_assign_pointer(ns_sk->rxe_sk4, NULL);
> + rcu_assign_pointer(ns_sk->rxe_sk6, NULL);
> + ns_sk->nr_sk4 = 0;
> + ns_sk->nr_sk6 = 0;
> + mutex_unlock(&ns_sk->lock);
> +
> + if (sk4 || sk6)
> + synchronize_rcu();
> + if (sk4)
> + udp_tunnel_sock_release(sk4);
> + if (sk6)
> + udp_tunnel_sock_release(sk6);
> +
> + mutex_destroy(&ns_sk->lock);
> }
>
> /*
> @@ -71,24 +85,64 @@ static struct pernet_operations rxe_net_ops = {
> .size = sizeof(struct rxe_ns_sock),
> };
>
> -struct sock *rxe_ns_pernet_sk4(struct net *net)
> +static struct sock *rxe_ns_hold(struct rxe_ns_sock *ns_sk,
> + struct sock __rcu **skp, int *nrp,
> + struct net *net, rxe_sk_create_t create)
> {
> - 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);
> - rcu_read_unlock();
> + mutex_lock(&ns_sk->lock);
> + sk = rcu_dereference_protected(*skp, lockdep_is_held(&ns_sk->lock));
> + if (sk) {
> + (*nrp)++;
> + mutex_unlock(&ns_sk->lock);
> + return sk;
> + }
> +
> + sk = create(net);
> + if (IS_ERR_OR_NULL(sk)) {
> + mutex_unlock(&ns_sk->lock);
> + return sk;
> + }
> +
> + rcu_assign_pointer(*skp, sk);
> + *nrp = 1;
> + mutex_unlock(&ns_sk->lock);
>
> return sk;
> }
>
> -void rxe_ns_pernet_set_sk4(struct net *net, struct sock *sk)
> +static void rxe_ns_put(struct rxe_ns_sock *ns_sk,
> + struct sock __rcu **skp, int *nrp)
> +{
> + struct sock *sk = NULL;
> +
> + mutex_lock(&ns_sk->lock);
> + if (*nrp > 0 && --(*nrp) == 0) {
> + sk = rcu_dereference_protected(*skp,
> + lockdep_is_held(&ns_sk->lock));
> + rcu_assign_pointer(*skp, NULL);
> + }
> + mutex_unlock(&ns_sk->lock);
> +
> + if (sk) {
> + synchronize_rcu();
> + udp_tunnel_sock_release(sk);
> + }
> +}
> +
> +struct sock *rxe_ns_pernet_hold_sk4(struct net *net, rxe_sk_create_t create)
> +{
> + struct rxe_ns_sock *ns_sk = net_generic(net, rxe_pernet_id);
> +
> + return rxe_ns_hold(ns_sk, &ns_sk->rxe_sk4, &ns_sk->nr_sk4, net, create);
> +}
> +
> +void rxe_ns_pernet_put_sk4(struct net *net)
> {
> struct rxe_ns_sock *ns_sk = net_generic(net, rxe_pernet_id);
>
> - rcu_assign_pointer(ns_sk->rxe_sk4, sk);
> - synchronize_rcu();
> + rxe_ns_put(ns_sk, &ns_sk->rxe_sk4, &ns_sk->nr_sk4);
> }
>
> #if IS_ENABLED(CONFIG_IPV6)
> @@ -104,12 +158,18 @@ struct sock *rxe_ns_pernet_sk6(struct net *net)
> return sk;
> }
>
> -void rxe_ns_pernet_set_sk6(struct net *net, struct sock *sk)
> +struct sock *rxe_ns_pernet_hold_sk6(struct net *net, rxe_sk_create_t create)
> +{
> + struct rxe_ns_sock *ns_sk = net_generic(net, rxe_pernet_id);
> +
> + return rxe_ns_hold(ns_sk, &ns_sk->rxe_sk6, &ns_sk->nr_sk6, net, create);
> +}
> +
> +void rxe_ns_pernet_put_sk6(struct net *net)
> {
> struct rxe_ns_sock *ns_sk = net_generic(net, rxe_pernet_id);
>
> - rcu_assign_pointer(ns_sk->rxe_sk6, sk);
> - synchronize_rcu();
> + rxe_ns_put(ns_sk, &ns_sk->rxe_sk6, &ns_sk->nr_sk6);
> }
> #endif /* IPV6 */
>
> diff --git a/drivers/infiniband/sw/rxe/rxe_ns.h b/drivers/infiniband/sw/rxe/rxe_ns.h
> index 4da2709e6b71..fe96b8abb8dc 100644
> --- a/drivers/infiniband/sw/rxe/rxe_ns.h
> +++ b/drivers/infiniband/sw/rxe/rxe_ns.h
> @@ -3,19 +3,35 @@
> #ifndef RXE_NS_H
> #define RXE_NS_H
>
> -struct sock *rxe_ns_pernet_sk4(struct net *net);
> -void rxe_ns_pernet_set_sk4(struct net *net, struct sock *sk);
> +/*
> + * Factory used to create a shared per-namespace tunnel socket while the
> + * pernet lock is held. It must return:
> + * - a valid sk on success,
> + * - NULL if the address family is unsupported (not treated as an error),
> + * - an ERR_PTR() on failure.
> + */
> +typedef struct sock *(*rxe_sk_create_t)(struct net *net);
> +
> +struct sock *rxe_ns_pernet_hold_sk4(struct net *net, rxe_sk_create_t create);
> +void rxe_ns_pernet_put_sk4(struct net *net);
>
> #if IS_ENABLED(CONFIG_IPV6)
> -void rxe_ns_pernet_set_sk6(struct net *net, struct sock *sk);
> struct sock *rxe_ns_pernet_sk6(struct net *net);
> +struct sock *rxe_ns_pernet_hold_sk6(struct net *net, rxe_sk_create_t create);
> +void rxe_ns_pernet_put_sk6(struct net *net);
> #else /* IPv6 */
> static inline struct sock *rxe_ns_pernet_sk6(struct net *net)
> {
> return NULL;
> }
>
> -static inline void rxe_ns_pernet_set_sk6(struct net *net, struct sock *sk)
> +static inline struct sock *rxe_ns_pernet_hold_sk6(struct net *net,
> + rxe_sk_create_t create)
> +{
> + return NULL;
> +}
> +
> +static inline void rxe_ns_pernet_put_sk6(struct net *net)
> {
> }
> #endif /* IPv6 */
> diff --git a/drivers/infiniband/sw/rxe/rxe_verbs.h b/drivers/infiniband/sw/rxe/rxe_verbs.h
> index 0f5ffd94643f..5f4f1cd0402f 100644
> --- a/drivers/infiniband/sw/rxe/rxe_verbs.h
> +++ b/drivers/infiniband/sw/rxe/rxe_verbs.h
> @@ -415,12 +415,18 @@ struct rxe_port {
> u32 qp_gsi_index;
> };
>
> +enum rxe_net_flags {
> + /* rxe_net_del() must drop the pernet socket refs exactly once */
> + RXE_NET_SK_PUT,
> +};
> +
> struct rxe_dev {
> struct ib_device ib_dev;
> struct ib_device_attr attr;
> int max_ucontext;
> int max_inline_data;
> struct mutex usdev_lock;
> + unsigned long net_flags;
>
> char raw_gid[ETH_ALEN];
>
prev parent reply other threads:[~2026-07-07 1:16 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-06 18:33 [PATCH v2] RDMA/rxe: rework per-net tunnel socket lifetime to fix refcount underflow Serhat Kumral
2026-07-07 1:15 ` Yanjun.Zhu [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=91a47c3a-d6c5-4998-99e2-fb615253853b@linux.dev \
--to=yanjun.zhu@linux.dev \
--cc=dsahern@kernel.org \
--cc=jgg@ziepe.ca \
--cc=leon@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-rdma@vger.kernel.org \
--cc=serhatkumral1@gmail.com \
--cc=syzbot+8c9eede336e3a843750e@syzkaller.appspotmail.com \
--cc=zyjzyj2000@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.