[PATCH] RDMA/ucma: Serialize join and leave on copy_to_user failure
From: Quanye Yang via B4 Relay
Date: Mon Aug 31 2026 - 08:37:49 EST
From: Quanye Yang <quanyeyang@xxxxxxxxx>
rdma_join_multicast() queues RoCE work that later reads the ucma_multicast
through event->param.ud.private_data, then list_add()s the CMA multicast
at the head of id_priv->mc_list. rdma_leave_multicast() matches only by
sockaddr and destroys the first hit.
ucma_process_join() used to drop ctx->mutex after a successful join and
retake it only if copy_to_user() failed. Two concurrent JOIN_MCAST calls
with the same address can therefore insert a second CMA entry before the
first thread's leave. leave then cancels the newer work and the older
worker still dereferences the ucma_multicast that the first thread frees.
Keep ctx->mutex held from rdma_join_multicast() through copy_to_user() and,
on -EFAULT, through rdma_leave_multicast() so leave cannot miss this join.
Do not leave if join itself failed: that path never published this address
on mc_list, and a leave-by-addr would destroy an earlier successful join.
Reported-by: syzbot+a6ffe86390c8a6afc818@xxxxxxxxxxxxxxxxxxxxxxxxx
Closes: https://syzkaller.appspot.com/bug?extid=a6ffe86390c8a6afc818
Fixes: fe454dc31e84 ("RDMA/ucma: Fix use-after-free bug in ucma_create_uevent")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Quanye Yang <quanyeyang@xxxxxxxxx>
---
KASAN + rxe, virtme-ng. The syzbot C repro (two concurrent JOIN_MCAST
with response=0) no longer reports slab-use-after-free in
ucma_create_uevent.
Based on linus/master
---
drivers/infiniband/core/ucma.c | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/drivers/infiniband/core/ucma.c b/drivers/infiniband/core/ucma.c
index 4929636f7c53..c3611e2f3248 100644
--- a/drivers/infiniband/core/ucma.c
+++ b/drivers/infiniband/core/ucma.c
@@ -1556,27 +1556,27 @@ static ssize_t ucma_process_join(struct ucma_file *file,
mutex_lock(&ctx->mutex);
ret = rdma_join_multicast(ctx->cm_id, (struct sockaddr *)&mc->addr,
join_state, mc);
- mutex_unlock(&ctx->mutex);
- if (ret)
+ if (ret) {
+ mutex_unlock(&ctx->mutex);
goto err_xa_erase;
+ }
resp.id = mc->id;
if (copy_to_user(u64_to_user_ptr(cmd->response),
&resp, sizeof(resp))) {
ret = -EFAULT;
- goto err_leave_multicast;
+ rdma_leave_multicast(ctx->cm_id, (struct sockaddr *)&mc->addr);
+ mutex_unlock(&ctx->mutex);
+ ucma_cleanup_mc_events(mc);
+ goto err_xa_erase;
}
+ mutex_unlock(&ctx->mutex);
xa_store(&multicast_table, mc->id, mc, 0);
ucma_put_ctx(ctx);
return 0;
-err_leave_multicast:
- mutex_lock(&ctx->mutex);
- rdma_leave_multicast(ctx->cm_id, (struct sockaddr *) &mc->addr);
- mutex_unlock(&ctx->mutex);
- ucma_cleanup_mc_events(mc);
err_xa_erase:
xa_lock(&multicast_table);
list_del(&mc->list);
---
base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
change-id: 20260831-rdma-ucma-mc-uaf-9b7a089b90a3
Best regards,
--
Quanye Yang <quanyeyang@xxxxxxxxx>