From: Ibrahim Hashimov <security@auditcode.ai>
To: yanjun.zhu@linux.dev, zyjzyj2000@gmail.com, jgg@ziepe.ca,
leon@kernel.org
Cc: linux-rdma@vger.kernel.org, linux-kernel@vger.kernel.org,
stable@vger.kernel.org
Subject: [PATCH v2] RDMA/rxe: fix responder UAF on IB_QP_MAX_DEST_RD_ATOMIC modify_qp
Date: Thu, 9 Jul 2026 09:26:51 +0200 [thread overview]
Message-ID: <20260709072651.9040-1-security@auditcode.ai> (raw)
In-Reply-To: <20260708224550.1281-1-security@auditcode.ai>
rxe_qp_from_attr()'s IB_QP_MAX_DEST_RD_ATOMIC branch frees and
reallocates qp->resp.resources[] (the rd_atomic resource array used by
the responder to track in-flight RDMA READ/ATOMIC/FLUSH requests)
completely outside of the IB_QP_STATE handling above it. Unlike every
other place that tears this array down -- rxe_qp_reset(), reached only
under IB_QP_STATE, always calls rxe_disable_task(&qp->recv_task) /
rxe_disable_task(&qp->send_task) to drain the responder and requester
tasks before touching per-QP state, then re-enables them -- this branch
runs with the responder task (rxe_receiver(), scheduled as recv_task on
the rxe_wq workqueue) fully live and unlocked. A userspace modify_qp()
that sets only IB_QP_MAX_DEST_RD_ATOMIC (no state change, so
__qp_chk_state()/ib_modify_qp_is_ok() never runs and qp->state_lock is
never taken here) can therefore race the responder in two ways:
1. free_rd_atomic_resources() calls kfree(qp->resp.resources) and
alloc_rd_atomic_resources() kzalloc_objs()'s a new array while
rxe_prepare_res()/find_resource() in rxe_resp.c are concurrently
walking &qp->resp.resources[i] with no lock held -- a straight
free-vs-read race on the array itself.
2. free_rd_atomic_resources() only NULLs qp->resp.resources; it never
clears qp->resp.res, the raw pointer *into* that array that
rxe_resp.c caches across a multi-packet RDMA READ/ATOMIC/FLUSH
reply (set at rxe_resp.c read/atomic/flush-reply sites, cleared
only on the normal completion paths). If a modify_qp() races a
resource still referenced by qp->resp.res, the array is freed out
from under the cached pointer and the next reply packet dereferences
it -- independent of the kfree/kzalloc_objs() window in (1).
Reproduced with KASAN: a single process driving one RC QP pair in rxe
loopback, one thread pumping large multi-packet IBV_WR_RDMA_READs
against qpB while a second thread hammers
ibv_modify_qp(qpB, IB_QP_MAX_DEST_RD_ATOMIC), reliably (~11s) produces
BUG: KASAN: slab-use-after-free in rxe_receiver+0x4f78/0x89e0 [rdma_rxe]
Workqueue: rxe_wq do_work [rdma_rxe]
with the freed kmalloc-1k object being the rd_atomic resource array
freed by the modify_qp() thread while the recv_task kworker reads it.
An identical run modifying only IB_QP_MIN_RNR_TIMER (no resource free)
is clean.
Fix both races the same way rxe_qp_reset() already handles tearing down
this exact array: quiesce the responder task around the free/realloc by
calling rxe_disable_task(&qp->recv_task) before free_rd_atomic_resources()
and rxe_enable_task(&qp->recv_task) only after alloc_rd_atomic_resources()
has succeeded, so rxe_receiver() cannot observe the array mid-free/
mid-realloc. On the alloc-failure path the responder is deliberately
left quiesced: qp->resp.resources is NULL at that point and
rxe_prepare_res()/find_resource() would dereference it, so recv_task
must not be re-enabled until a fresh array has been installed. And
close the still-open window for (2) at the source: have
free_rd_atomic_resources() clear qp->resp.res along with
qp->resp.resources, exactly like the existing completion paths in
rxe_resp.c (check_rkey()/duplicate_request()/RESPST_CLEANUP) already do
when a resource's lifetime ends, so a drained-and-resumed responder
restarts at RESPST_CHK_PSN against the fresh array instead of replaying
a stale reference into the old one.
Only qp->recv_task is drained: qp->resp.resources / qp->resp.res are
touched exclusively by the responder (rxe_resp.c); the requester
(send_task / rxe_sender()) never reads them, so there is no need to
widen this beyond what rxe_qp_reset() would drain for the equivalent
state.
Verified on the same v6.19 KASAN stand: with this fix applied, the
identical differential reproducer drives sustained MAX_DEST_RD_ATOMIC
storms against qpB well past the ~11s pre-fix time-to-first-splat with
zero KASAN reports, versus reliably tripping the slab-use-after-free in
rxe_receiver() described above before the fix.
Fixes: 8700e3e7c485 ("Soft RoCE driver")
Cc: stable@vger.kernel.org
Signed-off-by: Ibrahim Hashimov <security@auditcode.ai>
Assisted-by: AuditCode-AI:2026.07
---
v2: address Zhu Yanjun's review of v1
(https://lore.kernel.org/linux-rdma/20260708224550.1281-1-security@auditcode.ai/):
only re-enable recv_task after alloc_rd_atomic_resources() succeeds, so
the responder is not resumed against a NULL qp->resp.resources on the
ENOMEM path (rxe_prepare_res()/find_resource() would dereference it).
No change to the successful path; fix description updated accordingly.
drivers/infiniband/sw/rxe/rxe_qp.c | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/drivers/infiniband/sw/rxe/rxe_qp.c b/drivers/infiniband/sw/rxe/rxe_qp.c
index f3dff1aea96a..e39fb144cbbb 100644
--- a/drivers/infiniband/sw/rxe/rxe_qp.c
+++ b/drivers/infiniband/sw/rxe/rxe_qp.c
@@ -172,6 +172,7 @@ static void free_rd_atomic_resources(struct rxe_qp *qp)
}
kfree(qp->resp.resources);
qp->resp.resources = NULL;
+ qp->resp.res = NULL;
}
}
@@ -709,11 +710,24 @@ int rxe_qp_from_attr(struct rxe_qp *qp, struct ib_qp_attr *attr, int mask,
qp->attr.max_dest_rd_atomic = max_dest_rd_atomic;
+ /*
+ * This branch is not gated by IB_QP_STATE, so the responder
+ * task is live here. Quiesce it the way rxe_qp_reset() does
+ * before swapping the rd_atomic resource array, so
+ * rxe_receiver() cannot race the free/realloc.
+ */
+ rxe_disable_task(&qp->recv_task);
free_rd_atomic_resources(qp);
-
err = alloc_rd_atomic_resources(qp, max_dest_rd_atomic);
+ /*
+ * On failure the responder stays quiesced: qp->resp.resources
+ * is NULL now, and rxe_prepare_res()/find_resource() would
+ * dereference it, so do not re-enable recv_task until a fresh
+ * array has been installed.
+ */
if (err)
return err;
+ rxe_enable_task(&qp->recv_task);
}
if (mask & IB_QP_EN_SQD_ASYNC_NOTIFY)
--
2.50.1 (Apple Git-155)
next prev parent reply other threads:[~2026-07-09 7:27 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-08 22:45 [PATCH] RDMA/rxe: fix responder UAF on IB_QP_MAX_DEST_RD_ATOMIC modify_qp Ibrahim Hashimov
2026-07-09 1:22 ` yanjun.zhu
2026-07-09 7:22 ` Ibrahim Hashimov
2026-07-09 7:26 ` Ibrahim Hashimov [this message]
2026-07-09 18:24 ` [PATCH v2] " yanjun.zhu
2026-07-12 8:58 ` Leon Romanovsky
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=20260709072651.9040-1-security@auditcode.ai \
--to=security@auditcode.ai \
--cc=jgg@ziepe.ca \
--cc=leon@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-rdma@vger.kernel.org \
--cc=stable@vger.kernel.org \
--cc=yanjun.zhu@linux.dev \
--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.