* [PATCH v10 0/2] RDMA/rxe: copy send WQE to kernel buffer (TOCTOU hardening)
@ 2026-10-30 19:32 Tristan Madani
2026-10-30 19:32 ` [PATCH v10 1/2] RDMA/rxe: copy send WQE to kernel buffer before processing Tristan Madani
2026-10-30 19:32 ` [PATCH v10 2/2] RDMA/rxe: copy send WQE to kernel buffer in completer path Tristan Madani
0 siblings, 2 replies; 7+ messages in thread
From: Tristan Madani @ 2026-10-30 19:32 UTC (permalink / raw)
To: Zhu Yanjun, Jason Gunthorpe, Leon Romanovsky
Cc: linux-rdma, linux-kernel, stable, Tristan Madani
The SoftRoCE (rxe) send queue is mmap-ed into userspace, allowing
the requester and completer to process work queue entries (WQEs)
whose fields can change between kernel reads.
This series copies each WQE to a kernel-private buffer before
processing: patch 1 for the requester, patch 2 for the completer.
Both paths validate opcode bounds and SGE indices after copying,
preserve DMA progress across multi-packet operations, and write
back updated fields to the shared queue via WRITE_ONCE / smp_store_release.
Changes since v9:
- Patch 1: check dma.resid != sizeof(u64) instead of just > to
also catch undersized resid that leaks uninitialized pad bytes.
- Bisection fix: move qp->comp.comp_wqe_valid invalidation from
patch 1 to patch 2, where the field is first defined.
Tristan Madani (2):
RDMA/rxe: copy send WQE to kernel buffer before processing
RDMA/rxe: copy send WQE to kernel buffer in completer path
drivers/infiniband/sw/rxe/rxe_comp.c | 76 ++++++++++++++++++++++++++-
drivers/infiniband/sw/rxe/rxe_qp.c | 2 +
drivers/infiniband/sw/rxe/rxe_req.c | 72 +++++++++++++++++++++++--
drivers/infiniband/sw/rxe/rxe_verbs.h | 12 +++++
4 files changed, 155 insertions(+), 7 deletions(-)
--
2.47.3
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH v10 1/2] RDMA/rxe: copy send WQE to kernel buffer before processing 2026-10-30 19:32 [PATCH v10 0/2] RDMA/rxe: copy send WQE to kernel buffer (TOCTOU hardening) Tristan Madani @ 2026-10-30 19:32 ` Tristan Madani 2026-10-30 19:45 ` sashiko-bot 2026-10-30 22:20 ` Zhu Yanjun 2026-10-30 19:32 ` [PATCH v10 2/2] RDMA/rxe: copy send WQE to kernel buffer in completer path Tristan Madani 1 sibling, 2 replies; 7+ messages in thread From: Tristan Madani @ 2026-10-30 19:32 UTC (permalink / raw) To: Zhu Yanjun, Jason Gunthorpe, Leon Romanovsky Cc: linux-rdma, linux-kernel, stable, Tristan Madani The rxe send queue is mapped into userspace via mmap. The requester processes Work Queue Entries (WQEs) directly from this shared buffer without first copying them to kernel memory. Userspace can modify WQE fields (num_sge, sge_offset, SGE entries) between kernel reads, leading to inconsistent state in copy_data(). This is the send-path counterpart to the receive-path fixes: - commit 22b8fbded65b8 ("RDMA/rxe: Fix TOCTOU heap overflow in get_srq_wqe") - commit d6ab440240a04 ("RDMA/rxe: Copy WQE to local buffer in non-SRQ receive path") Fix by copying the send WQE to a kernel-private buffer in req_next_wqe() before processing. The copy uses the full queue element data size (max of max_sge SGEs and max_inline bytes) so that both SGE entries and inline data are always captured. After copying, reject out-of-range opcodes by setting mask to 0 so that next_opcode() transitions the QP to error state. Clamp atomic write residual to exactly sizeof(u64) to prevent finish_packet() from reading past the WQE buffer or leaking uninitialized pad bytes. The WR-level mask (WR_ATOMIC_WRITE_MASK) is checked since wr_opcode_mask() populates wqe->mask from enum rxe_wr_mask. The local copy is reused across multi-packet sends to preserve DMA progress state (cur_sge, sge_offset, resid). Field updates including DMA progress are written back to the shared queue using WRITE_ONCE() for individual fields and smp_store_release() for state transitions, so the completer and userspace observe consistent values without the tearing risk of bulk memcpy(). DMA progress writeback ensures retries can resume from the correct offset. The copy is invalidated when the WQE index advances (last packet sent, error, local ops, UD oversized), when a retry resets WQE state, on QP reset, or when the QP enters error state for flush. Fixes: 8700e3e7c485 ("Soft RoCE driver") Cc: stable@vger.kernel.org Signed-off-by: Tristan Madani <tristan@talencesecurity.com> --- Changes since v9: - Atomic write resid: check != sizeof(u64) instead of just > to also catch undersized resid that would leak uninitialized skb pad bytes (sashiko finding). - Bisection fix: move qp->comp.comp_wqe_valid invalidation from this patch to patch 2/2, since comp_wqe_valid is defined there (sashiko finding). Changes since v8: - Fix atomic write resid check: use WR_ATOMIC_WRITE_MASK (WR-level mask) instead of RXE_ATOMIC_WRITE_MASK (packet-level mask) so the condition actually fires for IB_WR_ATOMIC_WRITE WQEs (sashiko finding). - Fix atomic write rejection: clamp dma.resid to sizeof(u64) instead of setting mask to 0, since next_opcode() ignores wqe->mask and uses wqe->wr.opcode directly (sashiko finding). Changes since v7: - Reject atomic write WQEs with oversized dma.resid to prevent OOB read in finish_packet() (sashiko finding). Changes since v6: - Fixed all sashiko review findings (2 Critical + 9 High). - FORTIFY_SOURCE: memcpy into enclosing struct, not inner member. - Copy size: max(max_sge * sizeof(ib_sge), max_inline). - Opcode bound: IB_WR_REG_MR (was IB_WR_ATOMIC_WRITE). - Removed silent num_sge clamp, use mask=0 for invalid opcodes. - DMA progress writeback (resid, cur_sge, sge_offset). - Addressed Zhu Yanjun review feedback. Changes since v5: - Added opcode bounds check and num_sge clamp after copy. Changes since v4: - Resent from the correct sender address. Changes since v3: - Copy full queue element (max_sge SGEs) instead of per-WQE size. - Invalidate on QP reset, ERR flush, RESET state check. Changes since v2: - WRITE_ONCE/smp_store_release for field writeback. Changes since v1: - Split into per-path patches. Cache across multi-packet ops. drivers/infiniband/sw/rxe/rxe_qp.c | 1 + drivers/infiniband/sw/rxe/rxe_req.c | 71 +++++++++++++++++++++++++-- drivers/infiniband/sw/rxe/rxe_verbs.h | 6 +++ 3 files changed, 73 insertions(+), 5 deletions(-) diff --git a/drivers/infiniband/sw/rxe/rxe_qp.c b/drivers/infiniband/sw/rxe/rxe_qp.c index 311f285d78a6b..77606c4a039b1 100644 --- a/drivers/infiniband/sw/rxe/rxe_qp.c +++ b/drivers/infiniband/sw/rxe/rxe_qp.c @@ -581,6 +581,7 @@ static void rxe_qp_reset(struct rxe_qp *qp) qp->req.need_retry = 0; qp->req.wait_for_rnr_timer = 0; qp->req.noack_pkts = 0; + qp->req.send_wqe_valid = false; qp->resp.msn = 0; qp->resp.opcode = -1; qp->resp.drop_msg = 0; diff --git a/drivers/infiniband/sw/rxe/rxe_req.c b/drivers/infiniband/sw/rxe/rxe_req.c index 24f5c044363f7..dd8ad19ed1d86 100644 --- a/drivers/infiniband/sw/rxe/rxe_req.c +++ b/drivers/infiniband/sw/rxe/rxe_req.c @@ -161,6 +161,26 @@ static void req_check_sq_drain_done(struct rxe_qp *qp) spin_unlock_irqrestore(&qp->state_lock, flags); } +static void rxe_req_writeback_wqe(struct rxe_qp *qp) +{ + struct rxe_send_wqe *shared = qp->req.shared_wqe; + struct rxe_send_wqe *local = &qp->req.send_wqe.wqe; + + if (!qp->req.send_wqe_valid || !shared) + return; + + WRITE_ONCE(shared->status, local->status); + WRITE_ONCE(shared->first_psn, local->first_psn); + WRITE_ONCE(shared->last_psn, local->last_psn); + WRITE_ONCE(shared->mask, local->mask); + WRITE_ONCE(shared->has_rd_atomic, local->has_rd_atomic); + WRITE_ONCE(shared->dma.resid, local->dma.resid); + WRITE_ONCE(shared->dma.cur_sge, local->dma.cur_sge); + WRITE_ONCE(shared->dma.sge_offset, local->dma.sge_offset); + /* State must be last so the completer sees prior updates */ + smp_store_release(&shared->state, local->state); +} + static struct rxe_send_wqe *__req_next_wqe(struct rxe_qp *qp) { struct rxe_queue *q = qp->sq.queue; @@ -193,7 +213,35 @@ static struct rxe_send_wqe *req_next_wqe(struct rxe_qp *qp) } spin_unlock_irqrestore(&qp->state_lock, flags); - wqe->mask = wr_opcode_mask(wqe->wr.opcode, qp); + /* Reuse the existing kernel-private copy if still valid */ + if (qp->req.send_wqe_valid && qp->req.shared_wqe == wqe) + return &qp->req.send_wqe.wqe; + + /* Copy WQE from userspace-mapped shared queue to kernel-private + * buffer. Copy size matches the queue element data area. + */ + memcpy(&qp->req.send_wqe, wqe, sizeof(*wqe) + + max_t(int, qp->sq.max_sge * sizeof(struct ib_sge), + qp->sq.max_inline)); + qp->req.shared_wqe = wqe; + qp->req.send_wqe_valid = true; + + wqe = &qp->req.send_wqe.wqe; + + /* Prevent OOB in wr_opcode_mask() for corrupted opcodes. + * Mask 0 causes next_opcode() to fail, moving QP to error state. + */ + if (unlikely(wqe->wr.opcode > IB_WR_REG_MR)) + wqe->mask = 0; + else + wqe->mask = wr_opcode_mask(wqe->wr.opcode, qp); + + /* Atomic writes carry exactly sizeof(u64) payload. + * Clamp oversized resid to prevent OOB read in finish_packet(). + */ + if (unlikely((wqe->mask & WR_ATOMIC_WRITE_MASK) && + wqe->dma.resid != sizeof(u64))) + wqe->dma.resid = sizeof(u64); return wqe; } @@ -582,9 +630,13 @@ static void update_state(struct rxe_qp *qp, struct rxe_pkt_info *pkt) { qp->req.opcode = pkt->opcode; - if (pkt->mask & RXE_END_MASK) + rxe_req_writeback_wqe(qp); + + if (pkt->mask & RXE_END_MASK) { qp->req.wqe_index = queue_next_index(qp->sq.queue, qp->req.wqe_index); + qp->req.send_wqe_valid = false; + } qp->need_req_skb = 0; @@ -634,7 +686,9 @@ static int rxe_do_local_ops(struct rxe_qp *qp, struct rxe_send_wqe *wqe) wqe->state = wqe_state_done; wqe->status = IB_WC_SUCCESS; + rxe_req_writeback_wqe(qp); qp->req.wqe_index = queue_next_index(qp->sq.queue, qp->req.wqe_index); + qp->req.send_wqe_valid = false; return 0; } @@ -666,6 +720,7 @@ int rxe_requester(struct rxe_qp *qp) wqe = __req_next_wqe(qp); spin_unlock_irqrestore(&qp->state_lock, flags); if (wqe) { + qp->req.send_wqe_valid = false; wqe->status = IB_WC_WR_FLUSH_ERR; goto err; } else { @@ -681,6 +736,7 @@ int rxe_requester(struct rxe_qp *qp) qp->req.wait_psn = 0; qp->req.need_retry = 0; qp->req.wait_for_rnr_timer = 0; + qp->req.send_wqe_valid = false; spin_unlock_irqrestore(&qp->state_lock, flags); goto exit; } @@ -695,6 +751,7 @@ int rxe_requester(struct rxe_qp *qp) if (unlikely(qp->req.need_retry && !qp->req.wait_for_rnr_timer)) { req_retry(qp); qp->req.need_retry = 0; + qp->req.send_wqe_valid = false; } wqe = req_next_wqe(qp); @@ -772,10 +829,12 @@ int rxe_requester(struct rxe_qp *qp) wqe->last_psn = qp->req.psn; qp->req.psn = (qp->req.psn + 1) & BTH_PSN_MASK; qp->req.opcode = IB_OPCODE_UD_SEND_ONLY; - qp->req.wqe_index = queue_next_index(qp->sq.queue, - qp->req.wqe_index); wqe->state = wqe_state_done; wqe->status = IB_WC_SUCCESS; + rxe_req_writeback_wqe(qp); + qp->req.wqe_index = queue_next_index(qp->sq.queue, + qp->req.wqe_index); + qp->req.send_wqe_valid = false; goto done; } payload = mtu; @@ -839,8 +898,10 @@ int rxe_requester(struct rxe_qp *qp) goto out; err: /* update wqe_index for each wqe completion */ - qp->req.wqe_index = queue_next_index(qp->sq.queue, qp->req.wqe_index); wqe->state = wqe_state_error; + rxe_req_writeback_wqe(qp); + qp->req.wqe_index = queue_next_index(qp->sq.queue, qp->req.wqe_index); + qp->req.send_wqe_valid = false; rxe_qp_error(qp); exit: ret = -EAGAIN; diff --git a/drivers/infiniband/sw/rxe/rxe_verbs.h b/drivers/infiniband/sw/rxe/rxe_verbs.h index 0f5ffd94643f9..a22dfc6e5ae3c 100644 --- a/drivers/infiniband/sw/rxe/rxe_verbs.h +++ b/drivers/infiniband/sw/rxe/rxe_verbs.h @@ -114,6 +114,12 @@ struct rxe_req_info { int wait_for_rnr_timer; int noack_pkts; int again; + struct rxe_send_wqe *shared_wqe; + bool send_wqe_valid; + struct { + struct rxe_send_wqe wqe; + struct ib_sge sge[RXE_MAX_SGE]; + } send_wqe; }; struct rxe_comp_info { -- 2.47.3 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v10 1/2] RDMA/rxe: copy send WQE to kernel buffer before processing 2026-10-30 19:32 ` [PATCH v10 1/2] RDMA/rxe: copy send WQE to kernel buffer before processing Tristan Madani @ 2026-10-30 19:45 ` sashiko-bot 2026-10-30 22:20 ` Zhu Yanjun 1 sibling, 0 replies; 7+ messages in thread From: sashiko-bot @ 2026-10-30 19:45 UTC (permalink / raw) To: Tristan Madani; +Cc: linux-rdma > The rxe send queue is mapped into userspace via mmap. The requester > processes Work Queue Entries (WQEs) directly from this shared buffer > without first copying them to kernel memory. Userspace can modify WQE > fields (num_sge, sge_offset, SGE entries) between kernel reads, > leading to inconsistent state in copy_data(). > > This is the send-path counterpart to the receive-path fixes: > - commit 22b8fbded65b8 ("RDMA/rxe: Fix TOCTOU heap overflow in > get_srq_wqe") > - commit d6ab440240a04 ("RDMA/rxe: Copy WQE to local buffer in > non-SRQ receive path") > > Fix by copying the send WQE to a kernel-private buffer in req_next_wqe() > before processing. The copy uses the full queue element data size > (max of max_sge SGEs and max_inline bytes) so that both SGE entries > [ ... ] > reset, or when the QP enters error state for flush. > > Fixes: 8700e3e7c485 ("Soft RoCE driver") > Cc: stable@vger.kernel.org > Signed-off-by: Tristan Madani <tristan@talencesecurity.com> Sashiko has reviewed this patch and found no issues. -- Sashiko AI review · https://sashiko.dev/#/patchset/20261030193235.3470653-1-tristan@talencesecurity.com?part=1 ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v10 1/2] RDMA/rxe: copy send WQE to kernel buffer before processing 2026-10-30 19:32 ` [PATCH v10 1/2] RDMA/rxe: copy send WQE to kernel buffer before processing Tristan Madani 2026-10-30 19:45 ` sashiko-bot @ 2026-10-30 22:20 ` Zhu Yanjun 1 sibling, 0 replies; 7+ messages in thread From: Zhu Yanjun @ 2026-10-30 22:20 UTC (permalink / raw) To: Tristan Madani, Zhu Yanjun, Jason Gunthorpe, Leon Romanovsky, yanjun.zhu@linux.dev Cc: linux-rdma, linux-kernel, stable, Tristan Madani 在 2026/10/30 12:32, Tristan Madani 写道: > The rxe send queue is mapped into userspace via mmap. The requester > processes Work Queue Entries (WQEs) directly from this shared buffer > without first copying them to kernel memory. Userspace can modify WQE > fields (num_sge, sge_offset, SGE entries) between kernel reads, > leading to inconsistent state in copy_data(). > > This is the send-path counterpart to the receive-path fixes: > - commit 22b8fbded65b8 ("RDMA/rxe: Fix TOCTOU heap overflow in > get_srq_wqe") > - commit d6ab440240a04 ("RDMA/rxe: Copy WQE to local buffer in > non-SRQ receive path") > > Fix by copying the send WQE to a kernel-private buffer in req_next_wqe() > before processing. The copy uses the full queue element data size > (max of max_sge SGEs and max_inline bytes) so that both SGE entries > and inline data are always captured. > > After copying, reject out-of-range opcodes by setting mask to 0 so that > next_opcode() transitions the QP to error state. Clamp atomic write > residual to exactly sizeof(u64) to prevent finish_packet() from reading > past the WQE buffer or leaking uninitialized pad bytes. The WR-level > mask (WR_ATOMIC_WRITE_MASK) is checked since wr_opcode_mask() populates > wqe->mask from enum rxe_wr_mask. > > The local copy is reused across multi-packet sends to preserve DMA > progress state (cur_sge, sge_offset, resid). Field updates including > DMA progress are written back to the shared queue using WRITE_ONCE() > for individual fields and smp_store_release() for state transitions, > so the completer and userspace observe consistent values without the > tearing risk of bulk memcpy(). DMA progress writeback ensures retries > can resume from the correct offset. > > The copy is invalidated when the WQE index advances (last packet sent, > error, local ops, UD oversized), when a retry resets WQE state, on QP > reset, or when the QP enters error state for flush. > > Fixes: 8700e3e7c485 ("Soft RoCE driver") > Cc: stable@vger.kernel.org > Signed-off-by: Tristan Madani <tristan@talencesecurity.com> Thanks a lot for your efforts. I am fine with this. Reviewed-by: Zhu Yanjun <yanjun.zhu@linux.dev> Zhu Yanjun > --- > > Changes since v9: > - Atomic write resid: check != sizeof(u64) instead of just > to also > catch undersized resid that would leak uninitialized skb pad bytes > (sashiko finding). > - Bisection fix: move qp->comp.comp_wqe_valid invalidation from > this patch to patch 2/2, since comp_wqe_valid is defined there > (sashiko finding). > > Changes since v8: > - Fix atomic write resid check: use WR_ATOMIC_WRITE_MASK (WR-level > mask) instead of RXE_ATOMIC_WRITE_MASK (packet-level mask) so the > condition actually fires for IB_WR_ATOMIC_WRITE WQEs (sashiko > finding). > - Fix atomic write rejection: clamp dma.resid to sizeof(u64) instead > of setting mask to 0, since next_opcode() ignores wqe->mask and > uses wqe->wr.opcode directly (sashiko finding). > > Changes since v7: > - Reject atomic write WQEs with oversized dma.resid to prevent > OOB read in finish_packet() (sashiko finding). > > Changes since v6: > - Fixed all sashiko review findings (2 Critical + 9 High). > - FORTIFY_SOURCE: memcpy into enclosing struct, not inner member. > - Copy size: max(max_sge * sizeof(ib_sge), max_inline). > - Opcode bound: IB_WR_REG_MR (was IB_WR_ATOMIC_WRITE). > - Removed silent num_sge clamp, use mask=0 for invalid opcodes. > - DMA progress writeback (resid, cur_sge, sge_offset). > - Addressed Zhu Yanjun review feedback. > > Changes since v5: > - Added opcode bounds check and num_sge clamp after copy. > > Changes since v4: > - Resent from the correct sender address. > > Changes since v3: > - Copy full queue element (max_sge SGEs) instead of per-WQE size. > - Invalidate on QP reset, ERR flush, RESET state check. > > Changes since v2: > - WRITE_ONCE/smp_store_release for field writeback. > > Changes since v1: > - Split into per-path patches. Cache across multi-packet ops. > > drivers/infiniband/sw/rxe/rxe_qp.c | 1 + > drivers/infiniband/sw/rxe/rxe_req.c | 71 +++++++++++++++++++++++++-- > drivers/infiniband/sw/rxe/rxe_verbs.h | 6 +++ > 3 files changed, 73 insertions(+), 5 deletions(-) > > diff --git a/drivers/infiniband/sw/rxe/rxe_qp.c b/drivers/infiniband/sw/rxe/rxe_qp.c > index 311f285d78a6b..77606c4a039b1 100644 > --- a/drivers/infiniband/sw/rxe/rxe_qp.c > +++ b/drivers/infiniband/sw/rxe/rxe_qp.c > @@ -581,6 +581,7 @@ static void rxe_qp_reset(struct rxe_qp *qp) > qp->req.need_retry = 0; > qp->req.wait_for_rnr_timer = 0; > qp->req.noack_pkts = 0; > + qp->req.send_wqe_valid = false; > qp->resp.msn = 0; > qp->resp.opcode = -1; > qp->resp.drop_msg = 0; > diff --git a/drivers/infiniband/sw/rxe/rxe_req.c b/drivers/infiniband/sw/rxe/rxe_req.c > index 24f5c044363f7..dd8ad19ed1d86 100644 > --- a/drivers/infiniband/sw/rxe/rxe_req.c > +++ b/drivers/infiniband/sw/rxe/rxe_req.c > @@ -161,6 +161,26 @@ static void req_check_sq_drain_done(struct rxe_qp *qp) > spin_unlock_irqrestore(&qp->state_lock, flags); > } > > +static void rxe_req_writeback_wqe(struct rxe_qp *qp) > +{ > + struct rxe_send_wqe *shared = qp->req.shared_wqe; > + struct rxe_send_wqe *local = &qp->req.send_wqe.wqe; > + > + if (!qp->req.send_wqe_valid || !shared) > + return; > + > + WRITE_ONCE(shared->status, local->status); > + WRITE_ONCE(shared->first_psn, local->first_psn); > + WRITE_ONCE(shared->last_psn, local->last_psn); > + WRITE_ONCE(shared->mask, local->mask); > + WRITE_ONCE(shared->has_rd_atomic, local->has_rd_atomic); > + WRITE_ONCE(shared->dma.resid, local->dma.resid); > + WRITE_ONCE(shared->dma.cur_sge, local->dma.cur_sge); > + WRITE_ONCE(shared->dma.sge_offset, local->dma.sge_offset); > + /* State must be last so the completer sees prior updates */ > + smp_store_release(&shared->state, local->state); > +} > + > static struct rxe_send_wqe *__req_next_wqe(struct rxe_qp *qp) > { > struct rxe_queue *q = qp->sq.queue; > @@ -193,7 +213,35 @@ static struct rxe_send_wqe *req_next_wqe(struct rxe_qp *qp) > } > spin_unlock_irqrestore(&qp->state_lock, flags); > > - wqe->mask = wr_opcode_mask(wqe->wr.opcode, qp); > + /* Reuse the existing kernel-private copy if still valid */ > + if (qp->req.send_wqe_valid && qp->req.shared_wqe == wqe) > + return &qp->req.send_wqe.wqe; > + > + /* Copy WQE from userspace-mapped shared queue to kernel-private > + * buffer. Copy size matches the queue element data area. > + */ > + memcpy(&qp->req.send_wqe, wqe, sizeof(*wqe) + > + max_t(int, qp->sq.max_sge * sizeof(struct ib_sge), > + qp->sq.max_inline)); > + qp->req.shared_wqe = wqe; > + qp->req.send_wqe_valid = true; > + > + wqe = &qp->req.send_wqe.wqe; > + > + /* Prevent OOB in wr_opcode_mask() for corrupted opcodes. > + * Mask 0 causes next_opcode() to fail, moving QP to error state. > + */ > + if (unlikely(wqe->wr.opcode > IB_WR_REG_MR)) > + wqe->mask = 0; > + else > + wqe->mask = wr_opcode_mask(wqe->wr.opcode, qp); > + > + /* Atomic writes carry exactly sizeof(u64) payload. > + * Clamp oversized resid to prevent OOB read in finish_packet(). > + */ > + if (unlikely((wqe->mask & WR_ATOMIC_WRITE_MASK) && > + wqe->dma.resid != sizeof(u64))) > + wqe->dma.resid = sizeof(u64); > return wqe; > } > > @@ -582,9 +630,13 @@ static void update_state(struct rxe_qp *qp, struct rxe_pkt_info *pkt) > { > qp->req.opcode = pkt->opcode; > > - if (pkt->mask & RXE_END_MASK) > + rxe_req_writeback_wqe(qp); > + > + if (pkt->mask & RXE_END_MASK) { > qp->req.wqe_index = queue_next_index(qp->sq.queue, > qp->req.wqe_index); > + qp->req.send_wqe_valid = false; > + } > > qp->need_req_skb = 0; > > @@ -634,7 +686,9 @@ static int rxe_do_local_ops(struct rxe_qp *qp, struct rxe_send_wqe *wqe) > > wqe->state = wqe_state_done; > wqe->status = IB_WC_SUCCESS; > + rxe_req_writeback_wqe(qp); > qp->req.wqe_index = queue_next_index(qp->sq.queue, qp->req.wqe_index); > + qp->req.send_wqe_valid = false; > > return 0; > } > @@ -666,6 +720,7 @@ int rxe_requester(struct rxe_qp *qp) > wqe = __req_next_wqe(qp); > spin_unlock_irqrestore(&qp->state_lock, flags); > if (wqe) { > + qp->req.send_wqe_valid = false; > wqe->status = IB_WC_WR_FLUSH_ERR; > goto err; > } else { > @@ -681,6 +736,7 @@ int rxe_requester(struct rxe_qp *qp) > qp->req.wait_psn = 0; > qp->req.need_retry = 0; > qp->req.wait_for_rnr_timer = 0; > + qp->req.send_wqe_valid = false; > spin_unlock_irqrestore(&qp->state_lock, flags); > goto exit; > } > @@ -695,6 +751,7 @@ int rxe_requester(struct rxe_qp *qp) > if (unlikely(qp->req.need_retry && !qp->req.wait_for_rnr_timer)) { > req_retry(qp); > qp->req.need_retry = 0; > + qp->req.send_wqe_valid = false; > } > > wqe = req_next_wqe(qp); > @@ -772,10 +829,12 @@ int rxe_requester(struct rxe_qp *qp) > wqe->last_psn = qp->req.psn; > qp->req.psn = (qp->req.psn + 1) & BTH_PSN_MASK; > qp->req.opcode = IB_OPCODE_UD_SEND_ONLY; > - qp->req.wqe_index = queue_next_index(qp->sq.queue, > - qp->req.wqe_index); > wqe->state = wqe_state_done; > wqe->status = IB_WC_SUCCESS; > + rxe_req_writeback_wqe(qp); > + qp->req.wqe_index = queue_next_index(qp->sq.queue, > + qp->req.wqe_index); > + qp->req.send_wqe_valid = false; > goto done; > } > payload = mtu; > @@ -839,8 +898,10 @@ int rxe_requester(struct rxe_qp *qp) > goto out; > err: > /* update wqe_index for each wqe completion */ > - qp->req.wqe_index = queue_next_index(qp->sq.queue, qp->req.wqe_index); > wqe->state = wqe_state_error; > + rxe_req_writeback_wqe(qp); > + qp->req.wqe_index = queue_next_index(qp->sq.queue, qp->req.wqe_index); > + qp->req.send_wqe_valid = false; > rxe_qp_error(qp); > exit: > ret = -EAGAIN; > diff --git a/drivers/infiniband/sw/rxe/rxe_verbs.h b/drivers/infiniband/sw/rxe/rxe_verbs.h > index 0f5ffd94643f9..a22dfc6e5ae3c 100644 > --- a/drivers/infiniband/sw/rxe/rxe_verbs.h > +++ b/drivers/infiniband/sw/rxe/rxe_verbs.h > @@ -114,6 +114,12 @@ struct rxe_req_info { > int wait_for_rnr_timer; > int noack_pkts; > int again; > + struct rxe_send_wqe *shared_wqe; > + bool send_wqe_valid; > + struct { > + struct rxe_send_wqe wqe; > + struct ib_sge sge[RXE_MAX_SGE]; > + } send_wqe; > }; > > struct rxe_comp_info { ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v10 2/2] RDMA/rxe: copy send WQE to kernel buffer in completer path 2026-10-30 19:32 [PATCH v10 0/2] RDMA/rxe: copy send WQE to kernel buffer (TOCTOU hardening) Tristan Madani 2026-10-30 19:32 ` [PATCH v10 1/2] RDMA/rxe: copy send WQE to kernel buffer before processing Tristan Madani @ 2026-10-30 19:32 ` Tristan Madani 2026-10-30 19:50 ` sashiko-bot 2026-10-30 22:22 ` Zhu Yanjun 1 sibling, 2 replies; 7+ messages in thread From: Tristan Madani @ 2026-10-30 19:32 UTC (permalink / raw) To: Zhu Yanjun, Jason Gunthorpe, Leon Romanovsky Cc: linux-rdma, linux-kernel, stable, Tristan Madani The completer reads send WQEs from the same userspace-mapped shared queue as the requester. Even after the requester copies the WQE to a kernel-private buffer (previous patch), the completer still reads directly from shared memory, leaving it exposed to the same TOCTOU races. Fix by copying the WQE in get_wqe() to a completer-private buffer using the full queue element data size. The local copy is reused when the same WQE is being processed with unchanged state, which preserves DMA progress across multi-packet RDMA READ responses. A fresh copy is taken when the state changes or a new WQE appears. After copying, reject invalid opcodes (above IB_WR_REG_MR), out-of- bounds num_sge, and invalid cur_sge by transitioning the QP to error state through COMPST_ERROR. This prevents invalid SGE walks in copy_data() from corrupted DMA indices. The state read from the shared queue is preserved via smp_load_acquire() and restored after the memcpy to prevent a concurrent userspace write from overwriting it with a stale value. DMA progress (resid, cur_sge, sge_offset) is written back alongside completion status and rd_atomic state on completion, after each intermediate response packet, and before retry invalidation. The per-packet writeback in COMPST_UPDATE_COMP ensures the shared queue always reflects the latest DMA offset, so a forced cache re-read (from a state change in the shared queue) picks up current progress rather than stale initial values. The cache is invalidated on QP reset, when a completion advances to the next WQE, on error retry, and on RNR retry to ensure fresh state after the requester retransmits. The requester retry path also clears the completer cache so the completer re-reads from the shared queue after retransmission with updated PSN bounds. Fixes: 8700e3e7c485 ("Soft RoCE driver") Cc: stable@vger.kernel.org Signed-off-by: Tristan Madani <tristan@talencesecurity.com> --- Changes since v9: - Bisection fix: move qp->comp.comp_wqe_valid invalidation from patch 1/2 into this patch where the field is defined, so each commit compiles independently (sashiko finding). Changes since v8: - Write back DMA progress in COMPST_UPDATE_COMP after each intermediate response packet, so a forced cache re-read picks up current progress instead of stale initial values (sashiko finding). Changes since v7: - Write back DMA progress before invalidating completer cache on error retry, so req_retry() resumes multi-packet reads from the correct offset (sashiko finding). - Invalidate completer cache in COMPST_RNR_RETRY to prevent stale PSN comparison after RNR timer fires (sashiko finding). Changes since v6: - Fixed all sashiko review findings (2 Critical + 9 High). - FORTIFY_SOURCE: memcpy into enclosing struct, not inner member. - Copy size: max(max_sge * sizeof(ib_sge), max_inline). - Opcode bound: IB_WR_REG_MR (was IB_WR_ATOMIC_WRITE). - Validate num_sge + cur_sge, preserve smp_load_acquire state. - DMA progress writeback (resid, cur_sge, sge_offset). - *wqe_p = NULL for posted state, invalidate on retry. - Addressed Zhu Yanjun review feedback. Changes since v5: - Added opcode bounds check and num_sge clamp after copy. Changes since v4: - Resent from the correct sender address. Changes since v3: - Copy full queue element (max_sge SGEs) instead of per-WQE size. - Invalidate on QP reset, ERR flush, RESET state check. Changes since v2: - WRITE_ONCE/smp_store_release for field writeback. Changes since v1: - Split into per-path patches. Cache across multi-packet ops. drivers/infiniband/sw/rxe/rxe_comp.c | 76 ++++++++++++++++++++++++++- drivers/infiniband/sw/rxe/rxe_qp.c | 1 + drivers/infiniband/sw/rxe/rxe_req.c | 1 + drivers/infiniband/sw/rxe/rxe_verbs.h | 6 +++ 4 files changed, 82 insertions(+), 2 deletions(-) diff --git a/drivers/infiniband/sw/rxe/rxe_comp.c b/drivers/infiniband/sw/rxe/rxe_comp.c index 1390e861bd1d7..2021e7301c8a9 100644 --- a/drivers/infiniband/sw/rxe/rxe_comp.c +++ b/drivers/infiniband/sw/rxe/rxe_comp.c @@ -137,22 +137,87 @@ void rxe_comp_queue_pkt(struct rxe_qp *qp, struct sk_buff *skb) rxe_sched_task(&qp->send_task); } +/* Write back completer WQE fields to shared memory */ +static void rxe_comp_writeback_wqe(struct rxe_qp *qp) +{ + struct rxe_send_wqe *shared = qp->comp.shared_wqe; + struct rxe_send_wqe *local = &qp->comp.comp_wqe.wqe; + + if (!qp->comp.comp_wqe_valid || !shared) + return; + + WRITE_ONCE(shared->status, local->status); + WRITE_ONCE(shared->has_rd_atomic, local->has_rd_atomic); + WRITE_ONCE(shared->dma.resid, local->dma.resid); + WRITE_ONCE(shared->dma.cur_sge, local->dma.cur_sge); + WRITE_ONCE(shared->dma.sge_offset, local->dma.sge_offset); +} + static inline enum comp_state get_wqe(struct rxe_qp *qp, struct rxe_pkt_info *pkt, struct rxe_send_wqe **wqe_p) { struct rxe_send_wqe *wqe; + u32 state; /* we come here whether or not we found a response packet to see if * there are any posted WQEs */ wqe = queue_head(qp->sq.queue, QUEUE_TYPE_FROM_CLIENT); - *wqe_p = wqe; /* no WQE or requester has not started it yet */ - if (!wqe || wqe->state == wqe_state_posted) + if (!wqe) { + *wqe_p = NULL; return pkt ? COMPST_DONE : COMPST_EXIT; + } + + /* Pairs with smp_store_release() in rxe_req_writeback_wqe() */ + state = smp_load_acquire(&wqe->state); + if (state == wqe_state_posted) { + *wqe_p = NULL; + return pkt ? COMPST_DONE : COMPST_EXIT; + } + + /* Reuse existing local copy if still processing same WQE + * with unchanged state (preserves DMA progress for multi-packet ops) + */ + if (qp->comp.comp_wqe_valid && qp->comp.shared_wqe == wqe && + qp->comp.comp_wqe.wqe.state == state) { + wqe = &qp->comp.comp_wqe.wqe; + *wqe_p = wqe; + goto check_state; + } + + /* Copy shared WQE to kernel-private buffer. + * Copy size matches the queue element data area. + */ + qp->comp.shared_wqe = wqe; + memcpy(&qp->comp.comp_wqe, wqe, + sizeof(*wqe) + max_t(int, + qp->sq.max_sge * sizeof(struct ib_sge), + qp->sq.max_inline)); + qp->comp.comp_wqe_valid = true; + + /* Preserve the state we observed via smp_load_acquire() + * since memcpy may have overwritten it with a stale read. + */ + qp->comp.comp_wqe.wqe.state = state; + + /* Reject corrupted opcode or SGE indices, error the QP */ + if (unlikely(qp->comp.comp_wqe.wqe.wr.opcode > IB_WR_REG_MR) || + qp->comp.comp_wqe.wqe.dma.num_sge > qp->sq.max_sge || + (qp->comp.comp_wqe.wqe.dma.resid && + qp->comp.comp_wqe.wqe.dma.cur_sge >= qp->sq.max_sge)) { + qp->comp.comp_wqe.wqe.status = IB_WC_LOC_QP_OP_ERR; + wqe = &qp->comp.comp_wqe.wqe; + *wqe_p = wqe; + return COMPST_ERROR; + } + + wqe = &qp->comp.comp_wqe.wqe; + *wqe_p = wqe; +check_state: /* WQE does not require an ack */ if (wqe->state == wqe_state_done) return COMPST_COMP_WQE; @@ -454,6 +519,8 @@ static void do_complete(struct rxe_qp *qp, struct rxe_send_wqe *wqe) if (post) make_send_cqe(qp, wqe, &cqe); + rxe_comp_writeback_wqe(qp); + qp->comp.comp_wqe_valid = false; queue_advance_consumer(qp->sq.queue, QUEUE_TYPE_FROM_CLIENT); if (post) @@ -736,6 +803,7 @@ int rxe_completer(struct rxe_qp *qp) qp->req.again = 1; } + rxe_comp_writeback_wqe(qp); state = COMPST_DONE; break; @@ -788,6 +856,8 @@ int rxe_completer(struct rxe_qp *qp) RXE_CNT_COMP_RETRY); qp->req.need_retry = 1; qp->comp.started_retry = 1; + rxe_comp_writeback_wqe(qp); + qp->comp.comp_wqe_valid = false; qp->req.again = 1; } goto done; @@ -809,6 +879,8 @@ int rxe_completer(struct rxe_qp *qp) * rnr timer has fired */ qp->req.wait_for_rnr_timer = 1; + rxe_comp_writeback_wqe(qp); + qp->comp.comp_wqe_valid = false; rxe_dbg_qp(qp, "set rnr nak timer\n"); // TODO who protects from destroy_qp?? mod_timer(&qp->rnr_nak_timer, diff --git a/drivers/infiniband/sw/rxe/rxe_qp.c b/drivers/infiniband/sw/rxe/rxe_qp.c index 77606c4a039b1..450254a131017 100644 --- a/drivers/infiniband/sw/rxe/rxe_qp.c +++ b/drivers/infiniband/sw/rxe/rxe_qp.c @@ -582,6 +582,7 @@ static void rxe_qp_reset(struct rxe_qp *qp) qp->req.wait_for_rnr_timer = 0; qp->req.noack_pkts = 0; qp->req.send_wqe_valid = false; + qp->comp.comp_wqe_valid = false; qp->resp.msn = 0; qp->resp.opcode = -1; qp->resp.drop_msg = 0; diff --git a/drivers/infiniband/sw/rxe/rxe_req.c b/drivers/infiniband/sw/rxe/rxe_req.c index dd8ad19ed1d86..b226b9cec3a16 100644 --- a/drivers/infiniband/sw/rxe/rxe_req.c +++ b/drivers/infiniband/sw/rxe/rxe_req.c @@ -752,6 +752,7 @@ int rxe_requester(struct rxe_qp *qp) req_retry(qp); qp->req.need_retry = 0; qp->req.send_wqe_valid = false; + qp->comp.comp_wqe_valid = false; } wqe = req_next_wqe(qp); diff --git a/drivers/infiniband/sw/rxe/rxe_verbs.h b/drivers/infiniband/sw/rxe/rxe_verbs.h index a22dfc6e5ae3c..6205dbc29dff6 100644 --- a/drivers/infiniband/sw/rxe/rxe_verbs.h +++ b/drivers/infiniband/sw/rxe/rxe_verbs.h @@ -130,6 +130,12 @@ struct rxe_comp_info { int started_retry; u32 retry_cnt; u32 rnr_retry; + struct rxe_send_wqe *shared_wqe; + bool comp_wqe_valid; + struct { + struct rxe_send_wqe wqe; + struct ib_sge sge[RXE_MAX_SGE]; + } comp_wqe; }; /* responder states */ -- 2.47.3 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v10 2/2] RDMA/rxe: copy send WQE to kernel buffer in completer path 2026-10-30 19:32 ` [PATCH v10 2/2] RDMA/rxe: copy send WQE to kernel buffer in completer path Tristan Madani @ 2026-10-30 19:50 ` sashiko-bot 2026-10-30 22:22 ` Zhu Yanjun 1 sibling, 0 replies; 7+ messages in thread From: sashiko-bot @ 2026-10-30 19:50 UTC (permalink / raw) To: Tristan Madani; +Cc: linux-rdma > The completer reads send WQEs from the same userspace-mapped shared > queue as the requester. Even after the requester copies the WQE to a > kernel-private buffer (previous patch), the completer still reads > directly from shared memory, leaving it exposed to the same TOCTOU > races. > > Fix by copying the WQE in get_wqe() to a completer-private buffer > using the full queue element data size. The local copy is reused > when the same WQE is being processed with unchanged state, which > preserves DMA progress across multi-packet RDMA READ responses. > A fresh copy is taken when the state changes or a new WQE appears. > > After copying, reject invalid opcodes (above IB_WR_REG_MR), out-of- > bounds num_sge, and invalid cur_sge by transitioning the QP to error > state through COMPST_ERROR. This prevents invalid SGE walks in > [ ... ] > after retransmission with updated PSN bounds. > > Fixes: 8700e3e7c485 ("Soft RoCE driver") > Cc: stable@vger.kernel.org > Signed-off-by: Tristan Madani <tristan@talencesecurity.com> Sashiko has reviewed this patch and found no issues. -- Sashiko AI review · https://sashiko.dev/#/patchset/20261030193235.3470653-1-tristan@talencesecurity.com?part=2 ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v10 2/2] RDMA/rxe: copy send WQE to kernel buffer in completer path 2026-10-30 19:32 ` [PATCH v10 2/2] RDMA/rxe: copy send WQE to kernel buffer in completer path Tristan Madani 2026-10-30 19:50 ` sashiko-bot @ 2026-10-30 22:22 ` Zhu Yanjun 1 sibling, 0 replies; 7+ messages in thread From: Zhu Yanjun @ 2026-10-30 22:22 UTC (permalink / raw) To: Tristan Madani, Zhu Yanjun, Jason Gunthorpe, Leon Romanovsky, yanjun.zhu@linux.dev Cc: linux-rdma, linux-kernel, stable, Tristan Madani 在 2026/10/30 12:32, Tristan Madani 写道: > The completer reads send WQEs from the same userspace-mapped shared > queue as the requester. Even after the requester copies the WQE to a > kernel-private buffer (previous patch), the completer still reads > directly from shared memory, leaving it exposed to the same TOCTOU > races. > > Fix by copying the WQE in get_wqe() to a completer-private buffer > using the full queue element data size. The local copy is reused > when the same WQE is being processed with unchanged state, which > preserves DMA progress across multi-packet RDMA READ responses. > A fresh copy is taken when the state changes or a new WQE appears. > > After copying, reject invalid opcodes (above IB_WR_REG_MR), out-of- > bounds num_sge, and invalid cur_sge by transitioning the QP to error > state through COMPST_ERROR. This prevents invalid SGE walks in > copy_data() from corrupted DMA indices. > > The state read from the shared queue is preserved via smp_load_acquire() > and restored after the memcpy to prevent a concurrent userspace write > from overwriting it with a stale value. > > DMA progress (resid, cur_sge, sge_offset) is written back alongside > completion status and rd_atomic state on completion, after each > intermediate response packet, and before retry invalidation. The > per-packet writeback in COMPST_UPDATE_COMP ensures the shared queue > always reflects the latest DMA offset, so a forced cache re-read (from > a state change in the shared queue) picks up current progress rather > than stale initial values. > > The cache is invalidated on QP reset, when a completion advances to > the next WQE, on error retry, and on RNR retry to ensure fresh state > after the requester retransmits. The requester retry path also clears > the completer cache so the completer re-reads from the shared queue > after retransmission with updated PSN bounds. > > Fixes: 8700e3e7c485 ("Soft RoCE driver") > Cc: stable@vger.kernel.org > Signed-off-by: Tristan Madani <tristan@talencesecurity.com> Thanks a lot. Reviewed-by: Zhu Yanjun <yanjun.zhu@linux.dev> Zhu Yanjun > --- > > Changes since v9: > - Bisection fix: move qp->comp.comp_wqe_valid invalidation from > patch 1/2 into this patch where the field is defined, so each > commit compiles independently (sashiko finding). > > Changes since v8: > - Write back DMA progress in COMPST_UPDATE_COMP after each > intermediate response packet, so a forced cache re-read picks up > current progress instead of stale initial values (sashiko finding). > > Changes since v7: > - Write back DMA progress before invalidating completer cache on > error retry, so req_retry() resumes multi-packet reads from the > correct offset (sashiko finding). > - Invalidate completer cache in COMPST_RNR_RETRY to prevent stale > PSN comparison after RNR timer fires (sashiko finding). > > Changes since v6: > - Fixed all sashiko review findings (2 Critical + 9 High). > - FORTIFY_SOURCE: memcpy into enclosing struct, not inner member. > - Copy size: max(max_sge * sizeof(ib_sge), max_inline). > - Opcode bound: IB_WR_REG_MR (was IB_WR_ATOMIC_WRITE). > - Validate num_sge + cur_sge, preserve smp_load_acquire state. > - DMA progress writeback (resid, cur_sge, sge_offset). > - *wqe_p = NULL for posted state, invalidate on retry. > - Addressed Zhu Yanjun review feedback. > > Changes since v5: > - Added opcode bounds check and num_sge clamp after copy. > > Changes since v4: > - Resent from the correct sender address. > > Changes since v3: > - Copy full queue element (max_sge SGEs) instead of per-WQE size. > - Invalidate on QP reset, ERR flush, RESET state check. > > Changes since v2: > - WRITE_ONCE/smp_store_release for field writeback. > > Changes since v1: > - Split into per-path patches. Cache across multi-packet ops. > > drivers/infiniband/sw/rxe/rxe_comp.c | 76 ++++++++++++++++++++++++++- > drivers/infiniband/sw/rxe/rxe_qp.c | 1 + > drivers/infiniband/sw/rxe/rxe_req.c | 1 + > drivers/infiniband/sw/rxe/rxe_verbs.h | 6 +++ > 4 files changed, 82 insertions(+), 2 deletions(-) > > diff --git a/drivers/infiniband/sw/rxe/rxe_comp.c b/drivers/infiniband/sw/rxe/rxe_comp.c > index 1390e861bd1d7..2021e7301c8a9 100644 > --- a/drivers/infiniband/sw/rxe/rxe_comp.c > +++ b/drivers/infiniband/sw/rxe/rxe_comp.c > @@ -137,22 +137,87 @@ void rxe_comp_queue_pkt(struct rxe_qp *qp, struct sk_buff *skb) > rxe_sched_task(&qp->send_task); > } > > +/* Write back completer WQE fields to shared memory */ > +static void rxe_comp_writeback_wqe(struct rxe_qp *qp) > +{ > + struct rxe_send_wqe *shared = qp->comp.shared_wqe; > + struct rxe_send_wqe *local = &qp->comp.comp_wqe.wqe; > + > + if (!qp->comp.comp_wqe_valid || !shared) > + return; > + > + WRITE_ONCE(shared->status, local->status); > + WRITE_ONCE(shared->has_rd_atomic, local->has_rd_atomic); > + WRITE_ONCE(shared->dma.resid, local->dma.resid); > + WRITE_ONCE(shared->dma.cur_sge, local->dma.cur_sge); > + WRITE_ONCE(shared->dma.sge_offset, local->dma.sge_offset); > +} > + > static inline enum comp_state get_wqe(struct rxe_qp *qp, > struct rxe_pkt_info *pkt, > struct rxe_send_wqe **wqe_p) > { > struct rxe_send_wqe *wqe; > + u32 state; > > /* we come here whether or not we found a response packet to see if > * there are any posted WQEs > */ > wqe = queue_head(qp->sq.queue, QUEUE_TYPE_FROM_CLIENT); > - *wqe_p = wqe; > > /* no WQE or requester has not started it yet */ > - if (!wqe || wqe->state == wqe_state_posted) > + if (!wqe) { > + *wqe_p = NULL; > return pkt ? COMPST_DONE : COMPST_EXIT; > + } > + > + /* Pairs with smp_store_release() in rxe_req_writeback_wqe() */ > + state = smp_load_acquire(&wqe->state); > + if (state == wqe_state_posted) { > + *wqe_p = NULL; > + return pkt ? COMPST_DONE : COMPST_EXIT; > + } > + > + /* Reuse existing local copy if still processing same WQE > + * with unchanged state (preserves DMA progress for multi-packet ops) > + */ > + if (qp->comp.comp_wqe_valid && qp->comp.shared_wqe == wqe && > + qp->comp.comp_wqe.wqe.state == state) { > + wqe = &qp->comp.comp_wqe.wqe; > + *wqe_p = wqe; > + goto check_state; > + } > + > + /* Copy shared WQE to kernel-private buffer. > + * Copy size matches the queue element data area. > + */ > + qp->comp.shared_wqe = wqe; > + memcpy(&qp->comp.comp_wqe, wqe, > + sizeof(*wqe) + max_t(int, > + qp->sq.max_sge * sizeof(struct ib_sge), > + qp->sq.max_inline)); > + qp->comp.comp_wqe_valid = true; > + > + /* Preserve the state we observed via smp_load_acquire() > + * since memcpy may have overwritten it with a stale read. > + */ > + qp->comp.comp_wqe.wqe.state = state; > + > + /* Reject corrupted opcode or SGE indices, error the QP */ > + if (unlikely(qp->comp.comp_wqe.wqe.wr.opcode > IB_WR_REG_MR) || > + qp->comp.comp_wqe.wqe.dma.num_sge > qp->sq.max_sge || > + (qp->comp.comp_wqe.wqe.dma.resid && > + qp->comp.comp_wqe.wqe.dma.cur_sge >= qp->sq.max_sge)) { > + qp->comp.comp_wqe.wqe.status = IB_WC_LOC_QP_OP_ERR; > + wqe = &qp->comp.comp_wqe.wqe; > + *wqe_p = wqe; > + return COMPST_ERROR; > + } > + > + wqe = &qp->comp.comp_wqe.wqe; > + *wqe_p = wqe; > > +check_state: > /* WQE does not require an ack */ > if (wqe->state == wqe_state_done) > return COMPST_COMP_WQE; > @@ -454,6 +519,8 @@ static void do_complete(struct rxe_qp *qp, struct rxe_send_wqe *wqe) > if (post) > make_send_cqe(qp, wqe, &cqe); > > + rxe_comp_writeback_wqe(qp); > + qp->comp.comp_wqe_valid = false; > queue_advance_consumer(qp->sq.queue, QUEUE_TYPE_FROM_CLIENT); > > if (post) > @@ -736,6 +803,7 @@ int rxe_completer(struct rxe_qp *qp) > qp->req.again = 1; > } > > + rxe_comp_writeback_wqe(qp); > state = COMPST_DONE; > break; > > @@ -788,6 +856,8 @@ int rxe_completer(struct rxe_qp *qp) > RXE_CNT_COMP_RETRY); > qp->req.need_retry = 1; > qp->comp.started_retry = 1; > + rxe_comp_writeback_wqe(qp); > + qp->comp.comp_wqe_valid = false; > qp->req.again = 1; > } > goto done; > @@ -809,6 +879,8 @@ int rxe_completer(struct rxe_qp *qp) > * rnr timer has fired > */ > qp->req.wait_for_rnr_timer = 1; > + rxe_comp_writeback_wqe(qp); > + qp->comp.comp_wqe_valid = false; > rxe_dbg_qp(qp, "set rnr nak timer\n"); > // TODO who protects from destroy_qp?? > mod_timer(&qp->rnr_nak_timer, > diff --git a/drivers/infiniband/sw/rxe/rxe_qp.c b/drivers/infiniband/sw/rxe/rxe_qp.c > index 77606c4a039b1..450254a131017 100644 > --- a/drivers/infiniband/sw/rxe/rxe_qp.c > +++ b/drivers/infiniband/sw/rxe/rxe_qp.c > @@ -582,6 +582,7 @@ static void rxe_qp_reset(struct rxe_qp *qp) > qp->req.wait_for_rnr_timer = 0; > qp->req.noack_pkts = 0; > qp->req.send_wqe_valid = false; > + qp->comp.comp_wqe_valid = false; > qp->resp.msn = 0; > qp->resp.opcode = -1; > qp->resp.drop_msg = 0; > diff --git a/drivers/infiniband/sw/rxe/rxe_req.c b/drivers/infiniband/sw/rxe/rxe_req.c > index dd8ad19ed1d86..b226b9cec3a16 100644 > --- a/drivers/infiniband/sw/rxe/rxe_req.c > +++ b/drivers/infiniband/sw/rxe/rxe_req.c > @@ -752,6 +752,7 @@ int rxe_requester(struct rxe_qp *qp) > req_retry(qp); > qp->req.need_retry = 0; > qp->req.send_wqe_valid = false; > + qp->comp.comp_wqe_valid = false; > } > > wqe = req_next_wqe(qp); > diff --git a/drivers/infiniband/sw/rxe/rxe_verbs.h b/drivers/infiniband/sw/rxe/rxe_verbs.h > index a22dfc6e5ae3c..6205dbc29dff6 100644 > --- a/drivers/infiniband/sw/rxe/rxe_verbs.h > +++ b/drivers/infiniband/sw/rxe/rxe_verbs.h > @@ -130,6 +130,12 @@ struct rxe_comp_info { > int started_retry; > u32 retry_cnt; > u32 rnr_retry; > + struct rxe_send_wqe *shared_wqe; > + bool comp_wqe_valid; > + struct { > + struct rxe_send_wqe wqe; > + struct ib_sge sge[RXE_MAX_SGE]; > + } comp_wqe; > }; > > /* responder states */ ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-10-30 22:22 UTC | newest] Thread overview: 7+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-10-30 19:32 [PATCH v10 0/2] RDMA/rxe: copy send WQE to kernel buffer (TOCTOU hardening) Tristan Madani 2026-10-30 19:32 ` [PATCH v10 1/2] RDMA/rxe: copy send WQE to kernel buffer before processing Tristan Madani 2026-10-30 19:45 ` sashiko-bot 2026-10-30 22:20 ` Zhu Yanjun 2026-10-30 19:32 ` [PATCH v10 2/2] RDMA/rxe: copy send WQE to kernel buffer in completer path Tristan Madani 2026-10-30 19:50 ` sashiko-bot 2026-10-30 22:22 ` Zhu Yanjun
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox