From: "yanjun.zhu" <yanjun.zhu@linux.dev>
To: Ibrahim Hashimov <security@auditcode.ai>,
Zhu Yanjun <zyjzyj2000@gmail.com>, Jason Gunthorpe <jgg@ziepe.ca>,
Leon Romanovsky <leon@kernel.org>,
Zhu Yanjun <yanjun.zhu@linux.dev>
Cc: linux-rdma@vger.kernel.org, linux-kernel@vger.kernel.org,
stable@vger.kernel.org
Subject: Re: [PATCH] RDMA/rxe: validate num_sge/cur_sge before indexing wqe->dma.sge[]
Date: Wed, 8 Jul 2026 19:38:40 -0700 [thread overview]
Message-ID: <71562c7f-183c-40e4-bb90-84b078cf079d@linux.dev> (raw)
In-Reply-To: <20260708224534.1206-1-security@auditcode.ai>
On 7/8/26 3:45 PM, Ibrahim Hashimov wrote:
> 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 ||
From this function,
static int rxe_init_sq(struct rxe_qp *qp, struct ibv_qp_init_attr
*init_attr)
{
...
qp->sq.max_sge = init_attr->cap.max_send_sge;
...
}
qp->sq.max_sge is also from the user space application. It is possible
that qp->sq.max_sge is 0.
Then this makes rxe_requester will return error and exit.
Zhu Yanjun
> + 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;
next prev parent reply other threads:[~2026-07-09 2:38 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-08 22:45 [PATCH] RDMA/rxe: validate num_sge/cur_sge before indexing wqe->dma.sge[] Ibrahim Hashimov
2026-07-09 2:38 ` yanjun.zhu [this message]
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=71562c7f-183c-40e4-bb90-84b078cf079d@linux.dev \
--to=yanjun.zhu@linux.dev \
--cc=jgg@ziepe.ca \
--cc=leon@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-rdma@vger.kernel.org \
--cc=security@auditcode.ai \
--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.