* [PATCH v3] RDMA/rxe: Sanitize receive WQE in local buffer
@ 2026-09-10 8:37 Nicolas Morey
2026-09-10 9:02 ` sashiko-bot
0 siblings, 1 reply; 2+ messages in thread
From: Nicolas Morey @ 2026-09-10 8:37 UTC (permalink / raw)
To: Zhu Yanjun, Jason Gunthorpe, Leon Romanovsky, Moni Shoua,
Amir Vadai, Haggai Eran, Kamal Heib, Doug Ledford,
open list:SOFT-ROCE DRIVER (rxe), open list
Cc: Nicolas Morey
For both SRQ and non-SRQ receive paths, the WQE is copied from shared
user memory into a local buffer to provide a kernel-owned copy. However,
several issues remain:
1. Double-fetch TOCTOU race: Reading wqe->dma.num_sge directly from
shared memory allows the compiler to re-fetch it between the bounds
check and memcpy(). Furthermore, memcpy() copies num_sge from
shared memory, leaving an unvalidated value in the local buffer causing:
BUG: KASAN: slab-out-of-bounds in rxe_receiver+0x8109/0x9ec0 [rdma_rxe]
Read of size 4 at addr ffff88812c4867f8 by task kworker/u9:6/361
Workqueue: rxe_wq do_work [rdma_rxe]
Call Trace:
rxe_receiver+0x8109/0x9ec0 [rdma_rxe]
do_work+0x149/0x610 [rdma_rxe]
process_one_work+0x726/0x10a0
The buggy address belongs to the object at ffff88812c486000
which belongs to the cache kmalloc-part-13-2k of size 2048
The buggy address is located 0 bytes to the right of
allocated 2040-byte region [ffff88812c486000, ffff88812c4867f8)
2. Uninitialized DMA state fields: cur_sge, sge_offset, length,
and resid are copied directly from userspace without validation or
initialization. A malicious or malformed WQE can supply an arbitrary
cur_sge or sge_offset, leading to out-of-bounds array indexing in
copy_data().
Consolidate WQE validation into a helper recv_wqe_sanitize() that
- Uses READ_ONCE() on user_wqe->dma.num_sge and sizes the copy with
struct_size().
- Overwrites kernel_wqe->dma.num_sge with the validated value.
- Resets cur_sge and sge_offset to 0.
- Calculates and verifies length and resid from the SGE table using
check_add_overflow() and bounds-checks against RXE_PORT_MAX_MSG_SZ.
Fixes: 8700e3e7c485 ("Soft RoCE driver")
Signed-off-by: Nicolas Morey <nmorey@suse.com>
---
v2 -> v3:
- Extended fix to sanitize cur_sge, sge_offset, length, and resid (Sashiko).
- Added READ_ONCE, struct_size, and check_add_overflow helpers.
- Consolidated RQ and SRQ sanitization into recv_wqe_sanitize().
- Dropped Reviewed-by tag due to substantial changes.
drivers/infiniband/sw/rxe/rxe_resp.c | 70 ++++++++++++++++++++--------
1 file changed, 51 insertions(+), 19 deletions(-)
diff --git a/drivers/infiniband/sw/rxe/rxe_resp.c b/drivers/infiniband/sw/rxe/rxe_resp.c
index 02b16e2b49b8..d686bad3c03c 100644
--- a/drivers/infiniband/sw/rxe/rxe_resp.c
+++ b/drivers/infiniband/sw/rxe/rxe_resp.c
@@ -257,6 +257,47 @@ static enum resp_states check_op_valid(struct rxe_qp *qp,
return RESPST_CHK_RESOURCE;
}
+static enum resp_states recv_wqe_sanitize(struct rxe_qp *qp,
+ struct rxe_recv_wqe *kernel_wqe,
+ struct rxe_recv_wqe *user_wqe,
+ int max_sge)
+{
+ unsigned int num_sge;
+ unsigned long length = 0;
+ size_t size;
+ int i;
+
+ /* don't trust user space data */
+ num_sge = READ_ONCE(user_wqe->dma.num_sge);
+ if (unlikely(num_sge > max_sge)) {
+ rxe_dbg_qp(qp, "bad num_sge > max_sge\n");
+ return RESPST_ERR_MALFORMED_WQE;
+ }
+
+ size = struct_size(user_wqe, dma.sge, num_sge);
+ memcpy(kernel_wqe, user_wqe, size);
+
+ for (i = 0; i < num_sge; i++) {
+ if (check_add_overflow(length, kernel_wqe->dma.sge[i].length, &length)) {
+ rxe_dbg_qp(qp, "message length overflow\n");
+ return RESPST_ERR_MALFORMED_WQE;
+ }
+ }
+
+ if (unlikely(length > RXE_PORT_MAX_MSG_SZ)) {
+ rxe_dbg_qp(qp, "message length too long\n");
+ return RESPST_ERR_MALFORMED_WQE;
+ }
+
+ kernel_wqe->dma.length = length;
+ kernel_wqe->dma.resid = length;
+ kernel_wqe->dma.num_sge = num_sge;
+ kernel_wqe->dma.cur_sge = 0;
+ kernel_wqe->dma.sge_offset = 0;
+
+ return RESPST_NONE;
+}
+
static enum resp_states get_srq_wqe(struct rxe_qp *qp)
{
struct rxe_srq *srq = qp->srq;
@@ -264,9 +305,8 @@ static enum resp_states get_srq_wqe(struct rxe_qp *qp)
struct rxe_recv_wqe *wqe;
struct ib_event ev;
unsigned int count;
- unsigned int num_sge;
- size_t size;
unsigned long flags;
+ int err;
if (srq->error)
return RESPST_ERR_RNR;
@@ -279,17 +319,13 @@ static enum resp_states get_srq_wqe(struct rxe_qp *qp)
return RESPST_ERR_RNR;
}
- /* don't trust user space data */
- num_sge = wqe->dma.num_sge;
- if (unlikely(num_sge > srq->rq.max_sge)) {
+ err = recv_wqe_sanitize(qp, &qp->resp.srq_wqe.wqe, wqe, srq->rq.max_sge);
+ if (err) {
spin_unlock_irqrestore(&srq->rq.consumer_lock, flags);
- rxe_dbg_qp(qp, "invalid num_sge in SRQ entry\n");
- return RESPST_ERR_MALFORMED_WQE;
+ return err;
}
- size = sizeof(*wqe) + num_sge * sizeof(struct rxe_sge);
- memcpy(&qp->resp.srq_wqe, wqe, size);
-
qp->resp.wqe = &qp->resp.srq_wqe.wqe;
+
queue_advance_consumer(q, QUEUE_TYPE_FROM_CLIENT);
count = queue_count(q, QUEUE_TYPE_FROM_CLIENT);
@@ -314,22 +350,18 @@ static enum resp_states rxe_get_recv_wqe(struct rxe_qp *qp)
{
struct rxe_queue *q = qp->rq.queue;
struct rxe_recv_wqe *wqe;
- unsigned int num_sge;
- size_t size;
+ int err;
wqe = queue_head(q, QUEUE_TYPE_FROM_CLIENT);
if (!wqe)
return RESPST_ERR_RNR;
- num_sge = wqe->dma.num_sge;
- if (unlikely(num_sge > qp->rq.max_sge)) {
- rxe_dbg_qp(qp, "invalid num_sge in recv WQE\n");
- return RESPST_ERR_MALFORMED_WQE;
- }
- size = sizeof(*wqe) + num_sge * sizeof(struct rxe_sge);
- memcpy(&qp->resp.srq_wqe, wqe, size);
+ err = recv_wqe_sanitize(qp, &qp->resp.srq_wqe.wqe, wqe, qp->rq.max_sge);
+ if (err)
+ return err;
qp->resp.wqe = &qp->resp.srq_wqe.wqe;
+
return RESPST_CHK_LENGTH;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [PATCH v3] RDMA/rxe: Sanitize receive WQE in local buffer
2026-09-10 8:37 [PATCH v3] RDMA/rxe: Sanitize receive WQE in local buffer Nicolas Morey
@ 2026-09-10 9:02 ` sashiko-bot
0 siblings, 0 replies; 2+ messages in thread
From: sashiko-bot @ 2026-09-10 9:02 UTC (permalink / raw)
To: Nicolas Morey; +Cc: linux-rdma
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [High] Copying into the inner `wqe` field triggers a FORTIFY_SOURCE field-spanning write WARN_ONCE or panic.
Pre-existing issues:
- [Critical] Double-fetch TOCTOU vulnerability in the send path (`rxe_req.c`) leading to out-of-bounds memory access.
--
commit 5c427e8b4bbe51e8f334533fce0d0db77db27960
Author: Nicolas Morey <nmorey@suse.com>
RDMA/rxe: Sanitize receive WQE in local buffer
This patch sanitizes receive work queue elements by copying them to a local
buffer and validating their fields. This addresses TOCTOU races and
out-of-bounds accesses during the receive path.
> diff --git a/drivers/infiniband/sw/rxe/rxe_resp.c b/drivers/infiniband/sw/rxe/rxe_resp.c
> --- a/drivers/infiniband/sw/rxe/rxe_resp.c
> +++ b/drivers/infiniband/sw/rxe/rxe_resp.c
[ ... ]
> @@ -257,6 +257,47 @@ static enum resp_states check_op_valid(struct rxe_qp *qp,
> return RESPST_CHK_RESOURCE;
> }
>
> +static enum resp_states recv_wqe_sanitize(struct rxe_qp *qp,
> + struct rxe_recv_wqe *kernel_wqe,
> + struct rxe_recv_wqe *user_wqe,
> + int max_sge)
> +{
[ ... ]
> + size = struct_size(user_wqe, dma.sge, num_sge);
> + memcpy(kernel_wqe, user_wqe, size);
[Severity: High]
Will this memcpy trigger a FORTIFY_SOURCE warning or kernel panic due to a
field-spanning write?
When recv_wqe_sanitize is called from get_srq_wqe, it is passed a pointer to
the inner wqe sub-object:
> + err = recv_wqe_sanitize(qp, &qp->resp.srq_wqe.wqe, wqe, srq->rq.max_sge);
Because the destination pointer points specifically to the nested wqe struct
within the larger srq_wqe struct, the compiler evaluates the size of this
object to exactly 40 bytes. When memcpy attempts to write up to
40 + 16 * max_sge bytes, it violates this strict bounds check, resulting in a
denial of service on the receive path.
[ ... ]
[Severity: Critical]
This is a pre-existing issue, but I noticed the symmetric send path appears
to suffer from a very similar TOCTOU vulnerability that might lead to an
out-of-bounds memory access.
In rxe_req.c, rxe_requester performs a bounds check on cur_sge while the
data is still in shared memory:
if (unlikely(wqe->dma.num_sge > qp->sq.max_sge ||
(wqe->dma.resid &&
wqe->dma.cur_sge >= qp->sq.max_sge))) {
However, a malicious user could potentially overwrite cur_sge immediately
after this check. Later, copy_data in rxe_mr.c re-fetches cur_sge directly
from the shared memory and uses it as an array index:
struct rxe_sge *sge = &dma->sge[dma->cur_sge];
Could this bypass the validation and read memory outside the wqe if the
value is mutated between the check and the use?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260910083712.1595862-1-nmorey@suse.com?part=1
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-09-10 9:02 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-10 8:37 [PATCH v3] RDMA/rxe: Sanitize receive WQE in local buffer Nicolas Morey
2026-09-10 9:02 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox