All of lore.kernel.org
 help / color / mirror / Atom feed
From: Ibrahim Hashimov <security@auditcode.ai>
To: Zhu Yanjun <zyjzyj2000@gmail.com>, Jason Gunthorpe <jgg@ziepe.ca>,
	Leon Romanovsky <leon@kernel.org>
Cc: linux-rdma@vger.kernel.org, linux-kernel@vger.kernel.org,
	stable@vger.kernel.org
Subject: [PATCH] RDMA/rxe: validate num_sge/cur_sge before indexing wqe->dma.sge[]
Date: Thu,  9 Jul 2026 00:45:34 +0200	[thread overview]
Message-ID: <20260708224534.1206-1-security@auditcode.ai> (raw)

A user QP's send queue (qp->sq.queue) is a shared ring the userspace
application writes to directly via mmap (vmalloc_user(), see
rxe_queue.c). For such a QP, rxe_post_send() takes the qp->is_user
branch and only schedules the requester task -- it never validates or
copies the posted WQE:

	drivers/infiniband/sw/rxe/rxe_verbs.c:
		if (qp->is_user) {
			rxe_sched_task(&qp->send_task);
			...
		}

The requester then consumes the WQE in place straight out of that
mmap'd ring:

	rxe_req.c:	wqe = req_next_wqe(qp);
	rxe_req.c:	err = copy_data(qp->pd, 0, &wqe->dma,
					payload_addr(pkt), payload,
					RXE_FROM_MR_OBJ);

copy_data() immediately indexes the per-WQE sge array with the
attacker-controlled cur_sge field, before any bound is checked:

	rxe_mr.c:	struct rxe_sge *sge = &dma->sge[dma->cur_sge];
	rxe_mr.c:	...
	rxe_mr.c:	if (sge->length && (offset < sge->length)) {

dma->sge[] is a flex array whose real backing storage is exactly
qp->sq.max_sge entries per WQE slot (see rxe_qp.c, wqe_size computed
from max_sge at QP create time). Since a user QP's WQE bytes are
entirely attacker-supplied, both wqe->dma.num_sge and wqe->dma.cur_sge
can be set to arbitrary values independent of each other and of
max_sge. Only the *kernel*-QP post path bounds num_sge:

	rxe_verbs.c: validate_send_wr()
		if (num_sge > sq->max_sge) {
			rxe_err_qp(qp, "num_sge > max_sge\n");

but that function is only reachable from rxe_post_one_send() for
kernel-owned QPs; it is never consulted for a user QP's raw WQE.

The sibling receive path already has the equivalent guard, with the
literal comment documenting exactly why it is required:

	rxe_resp.c: get_srq_wqe()
		/* don't trust user space data */
		if (unlikely(wqe->dma.num_sge > srq->rq.max_sge)) {
			...
			rxe_dbg_qp(qp, "invalid num_sge in SRQ entry\n");
			return RESPST_ERR_MALFORMED_WQE;
		}

The send/requester path has no analogous check, so a local,
unprivileged user who can open /dev/infiniband/uverbs* and create a
user QP on a soft-RoCE (rxe) link can hand-craft a WQE in the shared
send queue with an out-of-range wqe->dma.cur_sge (or an oversized
wqe->dma.num_sge) and ring the send doorbell. rxe_requester() then
calls copy_data(), which dereferences &dma->sge[cur_sge] out of the
bounds of the per-WQE sge array -- a vmalloc out-of-bounds *read*
(confirmed via KASAN: "KASAN: vmalloc-out-of-bounds in copy_data"),
reliably panicking the kernel (local DoS). sge->addr itself is still
bounds-checked later by lookup_mr()/rxe_mr_copy(), so the primitive is
an OOB read of sge metadata, not an arbitrary read/write primitive.

Fix this the same way get_srq_wqe() already does for SRQ entries:
bound both fields pulled from the (possibly user-mapped) send queue
entry before they are ever used to index wqe->dma.sge[], right where
the requester fetches the next WQE off the ring in rxe_requester().
num_sge is capped at qp->sq.max_sge (matching the sibling SRQ check
and the kernel-QP validate_send_wr() check), and cur_sge is capped at
qp->sq.max_sge directly, since that is the true per-WQE array capacity
that copy_data() indexes into -- this also covers num_sge == 0 /
cur_sge == 0 local-op and zero-payload WQEs, which remain valid.

This is a long-standing bug in the rxe (soft-RoCE) driver: the
qp->is_user bypass in rxe_post_send() and the unbounded
&dma->sge[dma->cur_sge] indexing in copy_data() have been present
since the driver was introduced.

Runtime-verified on a v6.19 KASAN (CONFIG_KASAN_VMALLOC=y) stand: a
reproducer that posts a user QP send WQE with an out-of-range
cur_sge reliably tripped "KASAN: vmalloc-out-of-bounds in
copy_data" (an out-of-bounds read) before this patch, and no longer
triggers that report with the patch applied.

Fixes: 8700e3e7c485 ("Soft RoCE driver")
Cc: stable@vger.kernel.org
Signed-off-by: Ibrahim Hashimov <security@auditcode.ai>
Assisted-by: AuditCode-AI:2026.07
---
 drivers/infiniband/sw/rxe/rxe_req.c | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/drivers/infiniband/sw/rxe/rxe_req.c b/drivers/infiniband/sw/rxe/rxe_req.c
index 12d03f390b09..9fb2c49fb503 100644
--- a/drivers/infiniband/sw/rxe/rxe_req.c
+++ b/drivers/infiniband/sw/rxe/rxe_req.c
@@ -701,6 +701,22 @@ int rxe_requester(struct rxe_qp *qp)
 	if (unlikely(!wqe))
 		goto exit;
 
+	/*
+	 * Don't trust user space data: qp->sq.queue is a raw ring the
+	 * application writes directly for a user QP, so wqe->dma.num_sge
+	 * and wqe->dma.cur_sge must be bounds-checked the same way
+	 * get_srq_wqe() checks an SRQ entry's num_sge before it is used.
+	 * Otherwise copy_data() indexes wqe->dma.sge[wqe->dma.cur_sge]
+	 * with an unvalidated, attacker-controlled index/count and reads
+	 * out of bounds of the per-wqe sge array.
+	 */
+	if (unlikely(wqe->dma.num_sge > qp->sq.max_sge ||
+		     wqe->dma.cur_sge >= qp->sq.max_sge)) {
+		rxe_dbg_qp(qp, "invalid num_sge/cur_sge in send wqe\n");
+		wqe->status = IB_WC_LOC_QP_OP_ERR;
+		goto err;
+	}
+
 	if (rxe_wqe_is_fenced(qp, wqe)) {
 		qp->req.wait_fence = 1;
 		goto exit;
-- 
2.50.1 (Apple Git-155)


             reply	other threads:[~2026-07-08 22:45 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-08 22:45 Ibrahim Hashimov [this message]
2026-07-09  2:38 ` [PATCH] RDMA/rxe: validate num_sge/cur_sge before indexing wqe->dma.sge[] yanjun.zhu
2026-07-09  7:26   ` Ibrahim Hashimov
2026-07-09  7:26 ` [PATCH v2] " Ibrahim Hashimov
2026-07-09 18:37   ` yanjun.zhu
2026-07-12  9:00     ` 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=20260708224534.1206-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=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.