> This is an AI-generated review of your patch. The human sending this email has
> considered the AI review valid, or at least plausible.
> ---
> net: mana: validate hardware-supplied values in the HWC RX path
>
> This adds validation of device-supplied values in the HWC RX completion
> path: the inline OOB size and the SGE address are snapshotted and checked
> before use, the recovered RX slot index is bounds checked and the SGE address
> must match the one the driver posted for that slot, and an oversized response
> length is rejected. Untrustworthy completions no longer repost their RX WQE;
> those leaks are counted and, once they reach the RQ depth, the command
> timeout is shortened.
>
> No Link: tags are present in the commit header.
>
> One question about the changelog before the diff:
>
> [Severity: Medium]
>
> > A short response is no longer rejected in the handler: it reaches
> > mana_hwc_handle_resp(), whose mana_hwc_verify_resp_msg() fails it with
> > -EPROTO and completes the waiting sender, so a single malformed
> > response cannot convert into a channel-wide timeout.
>
> Is this paragraph describing a change against the kernel tree, or against an
> earlier revision of the patch? In the parent commit,
> mana_hwc_rx_event_handler() has no response-length check between
>
> resp = (struct gdma_resp_hdr *)rx_req->buf_va;
>
> and the msg_id read, and this diff does not remove such a check, so there is
> no
> short-response rejection in the handler being relaxed here.
>
> Does the stated guarantee hold in the resulting code? hwc_msg_id sits at
> bytes 10-11 of struct gdma_msg_hdr:
>
> include/net/mana/gdma.h:
> struct gdma_msg_hdr {
> u32 hdr_type;
> u32 msg_type;
> u16 msg_version;
> u16 hwc_msg_id;
> u32 msg_size;
> };
>
> A response shorter than 12 bytes therefore does not contain the field the
> handler reads. If the bytes read there are out of range, the handler returns
> early and mana_hwc_verify_resp_msg() is never reached, so no sender is
> completed. Could the paragraph be reworded to describe what the code
> actually guarantees?
You're right -- a <12-byte response has no hwc_msg_id, so the handler returns
early at the msg_id bound check and never reaches mana_hwc_verify_resp_msg().
That paragraph is dropped in v7; the changelog no longer claims a short
response is handled downstream.
>
> > diff --git a/drivers/net/ethernet/microsoft/mana/hw_channel.c
> > b/drivers/net/ethernet/microsoft/mana/hw_channel.c
> > index 19896bb5ce1a4..5db8cfe2d8443 100644
> > --- a/drivers/net/ethernet/microsoft/mana/hw_channel.c
> > +++ b/drivers/net/ethernet/microsoft/mana/hw_channel.c
> > @@ -83,6 +83,19 @@ static void mana_hwc_handle_resp(struct
> hw_channel_context *hwc, u32 resp_len,
> > struct hwc_caller_ctx *ctx;
> > int err;
> >
> > + /* Defence in depth: the sole caller, mana_hwc_rx_event_handler(),
> > + * already rejects msg_id >= hwc->num_inflight_msg with the value it
> > + * passes here by value, so this cannot be reached out of range. Keep
> > + * the guard at the indexing site so the bitmap and caller_ctx array
> > + * are never indexed without a bound in view.
> > + */
> > + if (msg_id >= hwc->num_inflight_msg) {
> > + dev_err(hwc->dev, "hwc_rx: msg_id %u >= max %u\n",
> > + msg_id, hwc->num_inflight_msg);
> > + mana_hwc_post_rx_wqe(hwc->rxq, rx_req);
> > + return;
> > + }
> > +
> > if (!test_bit(msg_id, hwc->inflight_msg_res.map)) {
> > dev_err(hwc->dev, "hwc_rx: invalid msg_id = %u\n", msg_id);
> > mana_hwc_post_rx_wqe(hwc->rxq, rx_req);
>
> [Severity: High]
>
> This isn't a bug introduced by this patch, but while the msg_id guard is being
> added here, is the bare test_bit() on hwc->inflight_msg_res.map enough to
> own the slot? At this commit the bit is tested without inflight_msg_res.lock,
> and mana_hwc_send_request() releases the slot on its timeout path:
>
> out:
> mana_hwc_put_msg_index(hwc, msg_id);
> return err;
>
> so a late or replayed response can find the bit set again for a reused slot
> and
> then write through the previous caller's output_buf:
>
> ctx = hwc->caller_ctx + msg_id;
> ...
> memcpy(ctx->output_buf, resp_msg, resp_len);
>
> The new mana_hwc_rx_leak_wqe() latch below makes the abandon-and-reuse
> path much easier to reach. For completeness: this race is closed later in the
> same series by "net: mana: fix stale HWC response after command timeout",
> which adds per-slot lock, refcount and responded state and NULLs output_buf
> under the lock on timeout, so no change is needed here if the ordering of the
> series is kept.
Agreed, and thanks for confirming the fix. The stale-response patch adds a
per-slot lock, a refcount and a "responded" flag, and NULLs output_buf under
the lock on timeout, so the reuse race is closed in the final tree. No change
here.
>
> [ ... ]
>
> > @@ -237,18 +262,39 @@ static void mana_hwc_init_event_handler(void
> *ctx, struct gdma_queue *q_self,
> > }
> > }
> >
> > +/* An RX WQE whose SGE the handler cannot trust is deliberately not
> > + * reposted: reposting a slot we may have mis-identified could
> > +double-post
> > + * a buffer the device still owns. Each such leak permanently lowers
> > +the
> > + * RQ's posted depth, so once the whole depth is gone the channel can
> > +no
> > + * longer receive responses. Make that terminal state explicit --
> > +log it
> > + * once and shorten the command timeout so callers fail fast --
> > +rather than
> > + * letting every later command drain its full timeout against a dead RQ.
> > + */
> > +static void mana_hwc_rx_leak_wqe(struct hw_channel_context *hwc) {
> > + if (++hwc->rx_leaked_wqe == hwc->rxq->queue_depth) {
> > + dev_err(hwc->dev,
> > + "HWC RX: RQ exhausted after %u leaked WQEs;
> channel unusable\n",
> > + hwc->rx_leaked_wqe);
> > + hwc->hwc_timeout = 1;
> > + }
> > +}
>
> [Severity: High]
>
> Is hwc->rxq->queue_depth ever greater than 1 here? The header defines:
>
> include/net/mana/hw_channel.h:
> #define HW_CHANNEL_VF_BOOTSTRAP_QUEUE_DEPTH 1
>
> and that is the only depth used:
>
> mana_hwc_create_channel()
> mana_hwc_init_queues(hwc,
> HW_CHANNEL_VF_BOOTSTRAP_QUEUE_DEPTH, ...)
> mana_hwc_create_wq(..., q_depth, ...)
> hwc_wq->queue_depth = q_depth;
>
> If so, ++hwc->rx_leaked_wqe == hwc->rxq->queue_depth is true on the first
> call and hwc->hwc_timeout = 1 is latched immediately.
>
> Does that mean one single unattributable completion, from any of the three
> new early returns, permanently reduces every later HWC command to
>
> wait_for_completion_timeout(&ctx->comp_event,
> msecs_to_jiffies(hwc->hwc_timeout))
>
> with hwc_timeout == 1, returning -ETIMEDOUT? The inputs that decide those
> returns (rx_oob->wqe_offset, the WQE flags word, sge->address) all come
> from the device-writable memory this patch treats as untrusted, and none of
> the three paths establishes that a posted WQE was actually consumed, so a
> single forged or stale completion would appear to disable vport configuration,
> queries, RDMA setup and teardown for the life of the device.
Disagree.
Depth is 1 today, so yes, one un-attributable completion latches it. That is
intended: the three paths leak the WQE rather than repost it, and at depth 1
the RQ then has no posted buffer, so it genuinely cannot receive another
response -- shortening the timeout just makes later commands fail fast against
a dead RQ instead of each draining the full timeout. The alternative, reposting
a WQE we could not attribute, is worse: it could double-post a buffer the
device still owns.
>
> [Severity: High]
>
> This isn't a bug introduced by this patch, but does the new interrupt-context
> path make the following teardown behaviour reachable from device input?
> Once hwc_timeout is 1, mana_gd_destroy_queue() still frees the memory
> whether or not the destroy-region command succeeded:
>
> drivers/net/ethernet/microsoft/mana/gdma_main.c:mana_gd_destroy_queue
> () {
> ...
> mana_gd_destroy_dma_region(gc, gmi->dma_region_handle);
> mana_gd_free_memory(gmi);
> kfree(queue);
> }
>
> mana_gd_destroy_dma_region() is itself an HWC command, so with the
> timeout latched every unbind frees DMA regions whose device-side mappings
> were never torn down. mana_hwc_send_request() already collapses
> hwc_timeout to
> 1 after a genuine timeout, and this error return has always been ignored, but
> previously that state required a real hardware stall and a failing command in
> the
> log. Should the leak accounting request a service reset instead of silently
> forcing the fail-fast timeout from an interrupt?
Disagree.
mana_gd_destroy_queue() freeing the region regardless of the command result is
pre-existing and independent of this patch, so I'd keep it out of this series.
Requesting a service reset from hardirq is a heavier, separate design choice;
the fail-fast timeout is the minimal response to a permanently-exhausted RQ.
There are other reset mechanism used by the driver.
>
> [Severity: Medium]
>
> Is hwc_timeout a safe place to record this terminal state? The field has
> other
> writers, and a host-supplied reconfig event overwrites it:
>
> drivers/net/ethernet/microsoft/mana/hw_channel.c:mana_hwc_init_event_h
> andler() {
> ...
> case HWC_DATA_CFG_HWC_TIMEOUT:
> hwc->hwc_timeout = val;
> ...
> }
>
> Because the leak helper tests with == rather than >=, the shortening can never
> be applied a second time, so after such an event every later command drains
> its
> full timeout against a dead RQ again, which is the behaviour the comment
> above the helper says it avoids.
>
> In the other direction, the store is unconditional, unlike the existing guard
> in
> mana_hwc_send_request():
>
> if (hwc->hwc_timeout > 1)
> hwc->hwc_timeout = 1;
>
> so it can raise hwc_timeout from the 0 sentinel that mana_serv_reset()
> sets:
>
> /* HWC is not responding in this case, so don't wait */
> hwc->hwc_timeout = 0;
>
> which mana_need_log() also consumes:
>
> if (hwc && hwc->hwc_timeout == 0)
> return false;
>
> Would a separate sticky flag (and hwc->rx_leaked_wqe being reset when the
> channel is re-established) express this state better? As it stands
> rx_leaked_wqe
> is never reset and no recovery is requested.
Disagree.
The harmful direction -- resurrecting the serv_reset() 0 sentinel -- is guarded
in v7: the store is "if (hwc->hwc_timeout > 1) hwc->hwc_timeout = 1;", matching
mana_hwc_send_request(). The == vs >= and the reconfig overwrite are benign for
what is a terminal state, so I'd rather not add a separate sticky field for it.
>
> [Severity: Medium]
>
> Should this store be annotated? mana_hwc_rx_leak_wqe() runs in HWC
> interrupt context:
>
> mana_gd_intr() -> EQ handler -> mana_hwc_comp_event()
> -> mana_hwc_rx_event_handler() -> mana_hwc_rx_leak_wqe()
>
> while the same field is read and read-modify-written from process context with
> plain accesses in mana_hwc_send_request(), written by
> mana_gd_query_hwc_timeout() through a request/response round trip, read
> unlocked by mana_need_log() and used as a 0 sentinel by mana_serv_reset().
> There is no common lock and no READ_ONCE()/WRITE_ONCE() here, so the
> process-context update can be lost and the 0 sentinel can be resurrected to 1
> mid-reset. A structurally identical field in the same series,
> hwc_init_max_num_cqs, is annotated with WRITE_ONCE()/READ_ONCE() and
> a comment; was the omission here intentional?
Fix in v7.
>
> > +
> > static void mana_hwc_rx_event_handler(void *ctx, u32 gdma_rxq_id,
> > const struct hwc_rx_oob *rx_oob) {
>
> [ ... ]
>
> > @@ -259,28 +305,76 @@ static void mana_hwc_rx_event_handler(void *ctx,
> u32 gdma_rxq_id,
> > wqe = mana_gd_get_wqe_ptr(rq, rx_oob->wqe_offset /
> GDMA_WQE_BU_SIZE);
> > dma_oob = (struct gdma_wqe *)wqe;
> >
> > - sge = (struct gdma_sge *)(wqe + 8 + dma_oob->inline_oob_size_div4 *
> 4);
> > -
> > - /* Select the RX work request for virtual address and for reposting. */
> > + /* inline_oob_size_div4 lives in device-accessible RQ memory (shared
> > + * and host-writable in a confidential VM), so snapshot it once and
> > + * validate and use only the snapshot. It is a bit-field, which
> > + * READ_ONCE() cannot take the size of, so read the u32 flags word it
> > + * shares through the union and extract the field from the local copy.
> > + * The driver programs INLINE_OOB_SMALL_SIZE for every HWC RQ
> WQE via
> > + * mana_gd_post_work_request(), so the only valid value is
> > + * INLINE_OOB_SMALL_SIZE / 4, which puts the SGE at wqe + 16 inside
> > + * this WQE's own BU. Reject anything else -- the slot cannot be
> > + * trusted, so leak this RX WQE rather than repost the wrong one.
> > + */
> > + oob_snapshot.flags = READ_ONCE(dma_oob->flags);
> > + oob_div4 = oob_snapshot.inline_oob_size_div4;
> > + if (oob_div4 != INLINE_OOB_SMALL_SIZE / 4) {
> > + dev_err(hwc->dev, "HWC RX: unexpected
> inline_oob_size_div4=%u\n",
> > + oob_div4);
> > + mana_hwc_rx_leak_wqe(hwc);
> > + return;
> > + }
> > + sge = (struct gdma_sge *)(wqe + 8 + oob_div4 * 4);
> > +
> > + /* Recover the originating RX slot from the SGE address. Snapshot it
> > + * once, for the same shared-memory reason: of the three terms only
> > + * sge_addr comes from device memory; rq_base_addr and
> > + * max_resp_msg_size are driver-private. An in-range but wrong SGE
> > + * would otherwise truncate onto a neighbouring slot, letting us read
> > + * a stale response that could complete the wrong, reused in-flight
> > + * request. Require the index in range AND the address to exactly
> > + * match the value the driver posted for that slot.
> > + */
> > + sge_addr = READ_ONCE(sge->address);
> > rq_base_addr = hwc_rxq->msg_buf->mem_info.dma_handle;
> > - rx_req_idx = (sge->address - rq_base_addr) / hwc-
> >max_resp_msg_size;
> > -
> > - if (rx_req_idx >= hwc_rxq->msg_buf->num_reqs) {
> > - dev_err(hwc->dev, "HWC RX: wrong rx_req_idx=%llu,
> num_reqs=%u\n",
> > - rx_req_idx, hwc_rxq->msg_buf->num_reqs);
> > + rx_req_idx = (sge_addr - rq_base_addr) / hwc->max_resp_msg_size;
> > +
> > + if (rx_req_idx >= hwc_rxq->queue_depth) {
> > + /* Cannot identify the slot, so we cannot safely repost this
> > + * WQE; leak it. An out-of-range index means a corrupted SGE
> > + * from hardware or host tampering.
> > + */
> > + dev_err(hwc->dev, "HWC RX: SGE idx %llu out of range\n",
> > + rx_req_idx);
> > + mana_hwc_rx_leak_wqe(hwc);
> > return;
> > }
>
> [Severity: Low]
>
> Why was the bound changed from the array's own counter to a field of a
> different structure? The index is used immediately afterwards to index
> hwc_rxq->msg_buf->reqs[], which is declared as:
>
> include/net/mana/hw_channel.h:
> u32 num_reqs;
> struct hwc_work_request reqs[] __counted_by(num_reqs);
>
> so num_reqs is the annotated bound for that array, while queue_depth is a
> u16 in struct hwc_wq. The two are equal only by construction:
> mana_hwc_create_wq() sets hwc_wq->queue_depth = q_depth and
> separately calls mana_hwc_alloc_dma_buf(hwc, q_depth, ...), which allocates
> reqs[q_depth] and sets dma_buf->num_reqs = q_depth. Nothing ties them
> together, num_reqs becomes write-only in this file, and the change isn't
> mentioned in the changelog even though every other added validation is.
> Would keeping the check against msg_buf->num_reqs (and keeping num_reqs
> in the error message) be preferable?
Yes -- fixed in v7. The bound is back to hwc_rxq->msg_buf->num_reqs (the
__counted_by bound for reqs[]), and num_reqs is in the error message.
>
> >
> > rx_req = &hwc_rxq->msg_buf->reqs[rx_req_idx];
> > + if (sge_addr != (u64)rx_req->buf_sge_addr) {
> > + /* In-range index but the address does not match what the
> > + * driver posted for that slot; the same unrecoverable case,
> > + * so leak this WQE rather than repost the wrong one.
> > + */
> > + dev_err(hwc->dev, "HWC RX: invalid SGE address %llx
> (idx=%llu)\n",
> > + sge_addr, rx_req_idx);
> > + mana_hwc_rx_leak_wqe(hwc);
> > + return;
> > + }
> > +
>
> [Severity: Medium]
>
> This isn't a problem introduced by this patch, but does the address match
> establish the invariant the helper's comment relies on ("reposting a slot we
> may have mis-identified could double-post a buffer the device still owns")?
> The check only shows that the WQE named by the device-supplied completion
> offset still contains the OOB size and SGE address the driver once wrote
> there,
> and that offset is masked into the ring rather than
> rejected:
>
> drivers/net/ethernet/microsoft/mana/gdma_main.c:mana_gd_get_wqe_ptr()
> {
> u32 offset = (wqe_offset * GDMA_WQE_BU_SIZE) & (wq->queue_size -
> 1);
>
> WARN_ON_ONCE((offset + GDMA_WQE_BU_SIZE) > wq->queue_size);
> ...
> }
>
> RQ WQE memory keeps those driver-written values after the device consumes
> the WQE, and mana_hwc_comp_event() dispatches on completions[i].is_sq
> alone, without looking at rx_oob->vendor_err or eom, so a spurious, error or
> replayed completion naming a still-owned slot passes both new checks and
> gets reposted. The HWC RQ is created with spec.monitor_avl_buf = false, so
> mana_gd_post_work_request() does no free-space check and
> wq->head simply advances past the number of buffers the driver owns. Is
> some per-slot posted/consumed state needed for the address match to mean
> "this WQE was just consumed for this buffer"?
>
> > resp = (struct gdma_resp_hdr *)rx_req->buf_va;
> >
> > - /* Read msg_id once from DMA buffer to prevent TOCTOU:
> > - * DMA memory is shared/unencrypted in CVMs - host can
> > - * modify it between reads.
> > + /* Read msg_id once from the DMA buffer to prevent TOCTOU: DMA
> memory
> > + * is shared/unencrypted in CVMs, so the host can modify it between
> > + * reads. A short response is not rejected here; it is handed to
> > + * mana_hwc_handle_resp() below, whose
> mana_hwc_verify_resp_msg()
> > + * fails it with -EPROTO and completes the waiting sender, so one
> > + * malformed response cannot stall the whole channel.
> > */
> > msg_id = READ_ONCE(resp->response.hwc_msg_id);
> > if (msg_id >= hwc->num_inflight_msg) {
> > dev_err(hwc->dev, "HWC RX: wrong msg_id=%u\n", msg_id);
> > + mana_hwc_post_rx_wqe(hwc_rxq, rx_req);
> > return;
> > }
>
> [Severity: Low]
>
> Should these messages be rate limited? All four rejection paths in this
> handler
> use plain dev_err() from hardirq context with device-supplied values
> ("unexpected inline_oob_size_div4=%u", "SGE idx %llu out of range", "invalid
> SGE address %llx (idx=%llu)", "wrong msg_id=%u"). This path previously
> returned without reposting, which bounded how often a host could drive it;
> with the added mana_hwc_post_rx_wqe() a host that keeps fabricating
> completions can repeat it indefinitely and flood the log, which is unpleasant
> with a serial console. dev_err_ratelimited(), or the counter scheme used for
> the leak accounting, would avoid that.
Good point -- fixed in v7. The reposting paths (wrong msg_id, and the
msg_id/resp_len rejections in mana_hwc_handle_resp()) now use
dev_err_ratelimited(), so a host can't flood the log. The three leak paths
don't repost and are bounded by the RQ depth, so they keep a single dev_err().
Thanks,
Long