All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v3] RDMA/rxe: Fix use-after-free of the per-net tunnel socket
@ 2026-10-19  4:32 Binbin Deng
  2026-10-19  4:42 ` sashiko-bot
  0 siblings, 1 reply; 2+ messages in thread
From: Binbin Deng @ 2026-10-19  4:32 UTC (permalink / raw)
  To: zyjzyj2000, jgg, leon; +Cc: linux-rdma, linux-kernel, Binbin Deng

KASAN reported a slab-use-after-free in ip6_route_output_flags()
reached from rxe_find_route(): a user with CAP_NET_ADMIN can remove
an RXE device with "rdma link del rxe0" while RoCE v2 over IPv6
traffic keeps using the per-net tunnel socket for route lookups.

BUG: KASAN: slab-use-after-free in ip6_route_output_flags+0x300/0x360
Read of size 4 at addr ffff888115ddd794 by task kworker/u32:6/309
Call Trace:
 <TASK>
 dump_stack_lvl+0x53/0x70
 print_report+0xd0/0x630
 ? __pfx__raw_spin_lock_irqsave+0x10/0x10
 ? ip6_route_output_flags+0x300/0x360
 kasan_report+0xce/0x100
 ? ip6_route_output_flags+0x300/0x360
 ip6_route_output_flags+0x300/0x360
 ip6_dst_lookup_tail.constprop.0+0x76c/0xcc0
 ? ct_nmi_exit+0xc3/0xf0
 ip6_dst_lookup_flow+0xf5/0x1e0
 ? __pfx_ip6_dst_lookup_flow+0x10/0x10
 rxe_find_route+0x426/0xa30
 ? __kasan_slab_alloc+0x6e/0x70
 ? __pfx_rxe_find_route+0x10/0x10
 ? kmem_cache_alloc_node_noprof+0x141/0x370
 ? kmalloc_reserve+0x103/0x2b0
 ? rxe_icrc_generate+0x229/0x330
 ? __pfx___alloc_skb+0x10/0x10
 rxe_prepare+0x9e8/0x18b0
 ? rxe_init_packet+0x3c7/0x4f0
 rxe_requester+0x1a0f/0x51f0
 ? rxe_completer+0x1de9/0x38c0
 ? __pfx_rxe_completer+0x10/0x10
 ? __queue_work+0x43e/0x11f0
 ? __pfx_rxe_requester+0x10/0x10
 ? irqentry_exit+0xd2/0x640
 ? _raw_spin_lock_irqsave+0x85/0xe0
 ? __pfx__raw_spin_lock_irqsave+0x10/0x10
 ? __pfx_rxe_sender+0x10/0x10
 rxe_sender+0xe/0x30
 do_work+0x144/0x470
 process_one_work+0x633/0x1030
 ? assign_work+0x11d/0x370
 worker_thread+0x45b/0xd10
 ? __pfx_worker_thread+0x10/0x10
 kthread+0x2c6/0x3b0
 ? recalc_sigpending+0x15c/0x1e0
 ? __pfx_kthread+0x10/0x10
 ret_from_fork+0x36e/0x5a0
 ? __pfx_ret_from_fork+0x10/0x10
 ? __switch_to+0x572/0xdd0
 ? __pfx_kthread+0x10/0x10
 ret_from_fork_asm+0x1a/0x30
 </TASK>

Allocated by task 146020:
 kasan_save_stack+0x33/0x60
 kasan_save_track+0x14/0x30
 __kasan_slab_alloc+0x6e/0x70
 kmem_cache_alloc_noprof+0x130/0x360
 sk_prot_alloc+0x56/0x210

rxe_net_del() releases the tunnel socket before the device is torn
down: nldev_dellink() calls .dellink before unregistering the
device, and the NETDEV_UNREGISTER notifier calls rxe_net_del() right
after queueing the unregistration, so the rxe_sender() workqueue can
still perform route lookups while the socket is released.

Two gaps in the tunnel socket lifetime allow the use-after-free:

1. rxe_sock_put() released the socket with rxe_release_udp_tunnel()
   before the per-net pointer was cleared, so route lookups could
   fetch a socket that was already freed.

2. rxe_ns_pernet_sk4/6() return the raw socket pointer after
   rcu_read_unlock(), without taking a reference.  Readers like
   rxe_find_route6() dereference the pointer outside the RCU
   read-side critical section, so the synchronize_rcu() in
   rxe_ns_pernet_set_sk4/6() cannot protect a reader that already
   fetched the pointer, and ip6_route_output_flags() or
   xfrm_lookup_with_ifid() can still be using the socket while it
   is freed.

Fix both gaps:

- rxe_sock_put() clears the per-net pointer before it releases the
  socket, so no new lookup can fetch a socket that is being
  released;

- rxe_ns_pernet_sk4/6() take a reference on the socket with
  refcount_inc_not_zero() under rcu_read_lock(), and the callers
  sock_put() it after use.  The reference that rxe_net_ipv4/6_init()
  keeps now comes from the getter instead of an open-coded
  sock_hold() on the raw pointer, and rxe_sock_put() drops the
  getter's reference after the tunnel is released.  Since the
  reference is taken inside the RCU read-side critical section, the
  synchronize_rcu() after the pointer is cleared guarantees that
  every reader has finished taking its reference before the last
  reference is dropped, so the count never reaches zero while a
  reader still holds the pointer.

The remaining races on the tunnel socket lifetime (serialization of
rxe_net_init() against rxe_net_del(), a second rxe_net_del() of one
device from the NETDEV_UNREGISTER notifier, and the per-device
reference accounting of shared sockets) already exist in the code
this fixes and are left for a follow-up series.

Fixes: f1327abd6abe ("RDMA/rxe: Support RDMA link creation and destruction per net namespace")
Signed-off-by: Binbin Deng <18983559317@163.com>
---
 drivers/infiniband/sw/rxe/rxe_net.c | 29 ++++++++++++++++++++---------
 drivers/infiniband/sw/rxe/rxe_ns.c  | 10 ++++++++++
 2 files changed, 30 insertions(+), 9 deletions(-)

diff --git a/drivers/infiniband/sw/rxe/rxe_net.c b/drivers/infiniband/sw/rxe/rxe_net.c
index 53daaf4c1eb2..ec98133a6005 100644
--- a/drivers/infiniband/sw/rxe/rxe_net.c
+++ b/drivers/infiniband/sw/rxe/rxe_net.c
@@ -135,13 +135,20 @@ static struct dst_entry *rxe_find_route6(struct rxe_qp *qp,
 {
 	struct dst_entry *ndst;
 	struct flowi6 fl6 = {};
+	struct sock *sk;
 
 	fl6.flowi6_oif = ndev->ifindex;
 	memcpy(&fl6.saddr, saddr, sizeof(*saddr));
 	memcpy(&fl6.daddr, daddr, sizeof(*daddr));
 	fl6.flowi6_proto = IPPROTO_UDP;
 
-	ndst = ip6_dst_lookup_flow(net, rxe_ns_pernet_sk6(net), &fl6, NULL);
+	/* keep the socket pinned while it is passed to the IPv6 routing
+	 * code which dereferences it outside of rcu_read_lock()
+	 */
+	sk = rxe_ns_pernet_sk6(net);
+	ndst = ip6_dst_lookup_flow(net, sk, &fl6, NULL);
+	if (sk)
+		sock_put(sk);
 	if (IS_ERR(ndst)) {
 		rxe_dbg_qp(qp, "no route to %pI6\n", daddr);
 		return NULL;
@@ -638,9 +645,15 @@ static void rxe_sock_put(struct sock *sk,
 	if (refcount_read(&sk->sk_refcnt) > SK_REF_FOR_TUNNEL) {
 		__sock_put(sk);
 	} else {
+		/*
+		 * Clear the per-net pointer before the socket is released:
+		 * rxe_ns_pernet_sk4/6() take a reference on the socket, but
+		 * new lookups must not find it once it is being released.
+		 */
+		set_sk(net, NULL);
 		rxe_release_udp_tunnel(sk);
-		sk = NULL;
-		set_sk(net, sk);
+		/* drop the reference from rxe_ns_pernet_sk4/6() */
+		sock_put(sk);
 	}
 }
 
@@ -758,11 +771,10 @@ static int rxe_net_ipv4_init(struct net *net)
 	struct sock *sk;
 	struct socket *sock;
 
+	/* returns with a reference held for this device */
 	sk = rxe_ns_pernet_sk4(net);
-	if (sk) {
-		sock_hold(sk);
+	if (sk)
 		return 0;
-	}
 
 	sock = rxe_setup_udp_tunnel(net, htons(ROCE_V2_UDP_DPORT), false);
 	if (IS_ERR(sock)) {
@@ -780,11 +792,10 @@ static int rxe_net_ipv6_init(struct net *net)
 	struct sock *sk;
 	struct socket *sock;
 
+	/* returns with a reference held for this device */
 	sk = rxe_ns_pernet_sk6(net);
-	if (sk) {
-		sock_hold(sk);
+	if (sk)
 		return 0;
-	}
 
 	sock = rxe_setup_udp_tunnel(net, htons(ROCE_V2_UDP_DPORT), true);
 	if (PTR_ERR(sock) == -EAFNOSUPPORT) {
diff --git a/drivers/infiniband/sw/rxe/rxe_ns.c b/drivers/infiniband/sw/rxe/rxe_ns.c
index 64621c89f8bf..0a7d409c0f98 100644
--- a/drivers/infiniband/sw/rxe/rxe_ns.c
+++ b/drivers/infiniband/sw/rxe/rxe_ns.c
@@ -71,6 +71,12 @@ static struct pernet_operations rxe_net_ops = {
 	.size = sizeof(struct rxe_ns_sock),
 };
 
+/*
+ * Return the per-net tunnel socket with a reference taken, or NULL if
+ * the namespace has no tunnel socket.  The reference is taken under
+ * rcu_read_lock() so the socket cannot be freed while the pointer is
+ * still published; the caller must sock_put() it.
+ */
 struct sock *rxe_ns_pernet_sk4(struct net *net)
 {
 	struct rxe_ns_sock *ns_sk = net_generic(net, rxe_pernet_id);
@@ -78,6 +84,8 @@ struct sock *rxe_ns_pernet_sk4(struct net *net)
 
 	rcu_read_lock();
 	sk = rcu_dereference(ns_sk->rxe_sk4);
+	if (sk && !refcount_inc_not_zero(&sk->sk_refcnt))
+		sk = NULL;
 	rcu_read_unlock();
 
 	return sk;
@@ -99,6 +107,8 @@ struct sock *rxe_ns_pernet_sk6(struct net *net)
 
 	rcu_read_lock();
 	sk = rcu_dereference(ns_sk->rxe_sk6);
+	if (sk && !refcount_inc_not_zero(&sk->sk_refcnt))
+		sk = NULL;
 	rcu_read_unlock();
 
 	return sk;


^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [PATCH v3] RDMA/rxe: Fix use-after-free of the per-net tunnel socket
  2026-10-19  4:32 [PATCH v3] RDMA/rxe: Fix use-after-free of the per-net tunnel socket Binbin Deng
@ 2026-10-19  4:42 ` sashiko-bot
  0 siblings, 0 replies; 2+ messages in thread
From: sashiko-bot @ 2026-10-19  4:42 UTC (permalink / raw)
  To: Binbin Deng; +Cc: linux-rdma

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] RDMA/rxe: broken sk_refcnt check and reference leak in rxe_sock_put()
--

commit 4fbcdc28fc0ebeebf87dd5a747900ac13874c9a3
Author: Binbin Deng <18983559317@163.com>

RDMA/rxe: Fix use-after-free of the per-net tunnel socket

This patch fixes a use-after-free of the per-net UDP tunnel socket by
clearing the per-net socket pointer before releasing the tunnel in
rxe_sock_put(), and by taking a reference on sk->sk_refcnt under
rcu_read_lock() in rxe_ns_pernet_sk4() and rxe_ns_pernet_sk6().

> diff --git a/drivers/infiniband/sw/rxe/rxe_net.c b/drivers/infiniband/sw/rxe/rxe_net.c
> index 53daaf4c1eb2e..ec98133a60055 100644
> --- a/drivers/infiniband/sw/rxe/rxe_net.c
> +++ b/drivers/infiniband/sw/rxe/rxe_net.c
> @@ -135,13 +135,20 @@ static struct dst_entry *rxe_find_route6(struct rxe_qp *qp,
>  {
>  	struct dst_entry *ndst;
>  	struct flowi6 fl6 = {};
> +	struct sock *sk;
>  
>  	fl6.flowi6_oif = ndev->ifindex;
>  	memcpy(&fl6.saddr, saddr, sizeof(*saddr));
>  	memcpy(&fl6.daddr, daddr, sizeof(*daddr));
>  	fl6.flowi6_proto = IPPROTO_UDP;
>  
> -	ndst = ip6_dst_lookup_flow(net, rxe_ns_pernet_sk6(net), &fl6, NULL);
> +	/* keep the socket pinned while it is passed to the IPv6 routing
> +	 * code which dereferences it outside of rcu_read_lock()
> +	 */
> +	sk = rxe_ns_pernet_sk6(net);
> +	ndst = ip6_dst_lookup_flow(net, sk, &fl6, NULL);
> +	if (sk)
> +		sock_put(sk);
>  	if (IS_ERR(ndst)) {
>  		rxe_dbg_qp(qp, "no route to %pI6\n", daddr);
>  		return NULL;
> @@ -638,9 +645,15 @@ static void rxe_sock_put(struct sock *sk,
>  	if (refcount_read(&sk->sk_refcnt) > SK_REF_FOR_TUNNEL) {
>  		__sock_put(sk);
>  	} else {
> +		/*
> +		 * Clear the per-net pointer before the socket is released:
> +		 * rxe_ns_pernet_sk4/6() take a reference on the socket, but
> +		 * new lookups must not find it once it is being released.
> +		 */
> +		set_sk(net, NULL);
>  		rxe_release_udp_tunnel(sk);
> -		sk = NULL;
> -		set_sk(net, sk);
> +		/* drop the reference from rxe_ns_pernet_sk4/6() */
> +		sock_put(sk);
>  	}
>  }

[Severity: High]
Does this change make the else branch in rxe_sock_put() unreachable and
leak sk->sk_refcnt as well as the UDP tunnel sockets?

When the first RXE device in a network namespace creates a UDP tunnel
socket via rxe_setup_udp_tunnel() in rxe_net_ipv4_init() or
rxe_net_ipv6_init(), sk->sk_refcnt starts at 2 (SK_REF_FOR_TUNNEL). Each
additional device in the namespace increments sk->sk_refcnt by 1 via
rxe_ns_pernet_sk4() or rxe_ns_pernet_sk6().

Both callers of rxe_sock_put() fetch sk via rxe_ns_pernet_sk4() or
rxe_ns_pernet_sk6() right before calling rxe_sock_put():

drivers/infiniband/sw/rxe/rxe_net.c:rxe_net_del() {
	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);
}

drivers/infiniband/sw/rxe/rxe_net.c:rxe_net_init() {
    ...
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);
    ...
}

Because rxe_ns_pernet_sk4() and rxe_ns_pernet_sk6() now increment
sk->sk_refcnt with refcount_inc_not_zero(), refcount_read(&sk->sk_refcnt)
upon entry to rxe_sock_put() is at least SK_REF_FOR_TUNNEL + 1 (3 when one
device exists, or N + 2 for N > 1 devices).

As a result, in rxe_sock_put():

1. refcount_read(&sk->sk_refcnt) > SK_REF_FOR_TUNNEL is always true, so the
   else branch (set_sk(net, NULL) and rxe_release_udp_tunnel(sk)) is never
   reached when the last RXE device is deleted or when rxe_net_ipv6_init()
   fails.

2. The if branch calls __sock_put(sk) only once, which only drops the
   temporary reference just acquired by the caller's rxe_ns_pernet_sk4() or
   rxe_ns_pernet_sk6() call without dropping the per-device reference taken
   in rxe_net_ipv4_init() or rxe_net_ipv6_init(). Each device addition and
   removal cycle therefore leaks a reference on struct sock.

3. In addition, because rxe_find_route6() also increments sk->sk_refcnt via
   rxe_ns_pernet_sk6(net) and drops it with sock_put(sk), any concurrent
   rxe_find_route6() in flight during rxe_net_del() of the last device will
   elevate sk->sk_refcnt. Even if the threshold in rxe_sock_put() were
   adjusted for the caller's getter reference, wouldn't a concurrent route
   lookup cause rxe_sock_put() to take the if branch and skip releasing the
   UDP tunnel, while rxe_find_route6()'s subsequent sock_put(sk) only
   decrements sk->sk_refcnt without releasing the tunnel?

[ ... ]

> diff --git a/drivers/infiniband/sw/rxe/rxe_ns.c b/drivers/infiniband/sw/rxe/rxe_ns.c
> index 64621c89f8bf1..0a7d409c0f981 100644
> --- a/drivers/infiniband/sw/rxe/rxe_ns.c
> +++ b/drivers/infiniband/sw/rxe/rxe_ns.c
> @@ -71,6 +71,12 @@ static struct pernet_operations rxe_net_ops = {
>  	.size = sizeof(struct rxe_ns_sock),
>  };
>  
> +/*
> + * Return the per-net tunnel socket with a reference taken, or NULL if
> + * the namespace has no tunnel socket.  The reference is taken under
> + * rcu_read_lock() so the socket cannot be freed while the pointer is
> + * still published; the caller must sock_put() it.
> + */
>  struct sock *rxe_ns_pernet_sk4(struct net *net)
>  {
>  	struct rxe_ns_sock *ns_sk = net_generic(net, rxe_pernet_id);
> @@ -78,6 +84,8 @@ struct sock *rxe_ns_pernet_sk4(struct net *net)
>  
>  	rcu_read_lock();
>  	sk = rcu_dereference(ns_sk->rxe_sk4);
> +	if (sk && !refcount_inc_not_zero(&sk->sk_refcnt))
> +		sk = NULL;
>  	rcu_read_unlock();
>  
>  	return sk;
> @@ -99,6 +107,8 @@ struct sock *rxe_ns_pernet_sk6(struct net *net)
>  
>  	rcu_read_lock();
>  	sk = rcu_dereference(ns_sk->rxe_sk6);
> +	if (sk && !refcount_inc_not_zero(&sk->sk_refcnt))
> +		sk = NULL;
>  	rcu_read_unlock();
>  
>  	return sk;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261019043205.466134-1-18983559317@163.com?part=1

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-10-19  4:42 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-19  4:32 [PATCH v3] RDMA/rxe: Fix use-after-free of the per-net tunnel socket Binbin Deng
2026-10-19  4:42 ` sashiko-bot

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.