> 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: fix stale HWC response after command timeout
> 
> This reworks the MANA HWC command slot lifetime so a late response cannot
> memcpy into a caller buffer that is already gone, and cannot complete a
> different command through a recycled msg_id.  Each caller_ctx gains a 
> spinlock,
> a refcount and an -EINPROGRESS sentinel, the semaphore is replaced by a
> waitqueue plus the inflight bitmap, and a first timeout latches a new
> hwc_timed_out flag.
> 
> A few questions about the new slot lifetime and the timeout latch below.
> 
> > diff --git a/drivers/net/ethernet/microsoft/mana/hw_channel.c
> > b/drivers/net/ethernet/microsoft/mana/hw_channel.c
> > index 1603968d7989a..d92032b466af2 100644
> > --- a/drivers/net/ethernet/microsoft/mana/hw_channel.c
> > +++ b/drivers/net/ethernet/microsoft/mana/hw_channel.c
> > @@ -7,25 +7,49 @@
> >  #include <linux/pci.h>
> >  #include <linux/vmalloc.h>
> >
> > +/* Acquire a free message slot from the inflight bitmap.  Returns
> > + * -ETIMEDOUT if a prior HWC command has timed out (preserving the
> > + * error code callers expect).
> > + */
> >  static int mana_hwc_get_msg_index(struct hw_channel_context *hwc, u16
> > *msg_id)  {
> >     struct gdma_resource *r = &hwc->inflight_msg_res;
> >     unsigned long flags;
> >     u32 index;
> >
> > -   down(&hwc->sema);
> > +   for (;;) {
> > +           spin_lock_irqsave(&r->lock, flags);
> >
> > -   spin_lock_irqsave(&r->lock, flags);
> > +           if (hwc->hwc_timed_out) {
> > +                   spin_unlock_irqrestore(&r->lock, flags);
> > +                   return -ETIMEDOUT;
> > +           }
> >
> > -   index = find_first_zero_bit(hwc->inflight_msg_res.map,
> > -                               hwc->inflight_msg_res.size);
> > +           index = find_first_zero_bit(r->map, r->size);
> > +           if (index < r->size) {
> > +                   struct hwc_caller_ctx *ctx;
> > +
> > +                   bitmap_set(r->map, index, 1);
> > +                   ctx = &hwc->caller_ctx[index];
> > +                   reinit_completion(&ctx->comp_event);
> > +                   refcount_set(&ctx->refcnt, 1);
> > +                   ctx->responded = false;
> > +                   ctx->msg_id = index;
> > +                   ctx->error = -EINPROGRESS;
> > +                   spin_unlock_irqrestore(&r->lock, flags);
> > +                   break;
> > +           }
> 
> [Severity: High]
> bitmap_set() publishes the slot to the response path here, while the response-
> side reference is only taken much later in
> mana_hwc_send_request():
> 
>       /* Take handle_resp's ref before posting ... */
>       refcount_inc(&ctx->refcnt);
> 
> In between, mana_hwc_handle_resp() already accepts the slot.  Its only gates
> are the range check and a lockless test_bit():
> 
>       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);
>               return;
>       }
> 
> and it ends with an unconditional hwc_ctx_put().  msg_id comes from host-
> writable DMA memory (READ_ONCE()d out of the RX buffer), and responded is
> false for a fresh allocation, so the new duplicate guard does not catch a
> response naming a slot that is still in this pre-post window.
> 
> Can such a response drop refcnt 1->0, clear the bitmap bit and wake msg_waitq
> while the sender still owns the slot?  If so, does that give:
> 
>   - the sender's refcount_inc() running on 0, i.e. a "refcount_t:
>     addition on 0; use-after-free" splat plus permanent saturation,
>     leaking that slot for good;
> 
>   - another sender being handed the same msg_id, sharing both
>     txq->msg_buf->reqs[msg_id] and the same hwc_caller_ctx, so one
>     command completes with the other command's payload, which is the
>     cross-completion this patch is meant to remove;
> 
>   - a spurious complete() letting wait_for_completion_timeout() succeed
>     with ctx->error still -EINPROGRESS, so -115 is handed back to
>     callers?
> 
> The same window also covers the req_len > tx_wr->buf_len path that jumps to
> out:, whose new comment states "no WQE was submitted so handle_resp()
> cannot race here" and "refcount is 1".  Can the slot already have been 
> released
> by then, making that hwc_ctx_put() a second release?
> 
> [Severity: Medium]
> comp_event, refcnt, responded, msg_id and error are (re)initialised here under
> inflight_msg_res.lock only, while mana_hwc_handle_resp() reads and writes
> responded and error under ctx->lock only and never takes 
> inflight_msg_res.lock.
> The intersection of the two locksets is empty, even though the header comment
> names ctx->lock as the protecting lock.
> 
> bitmap_set() runs before these field writes in the same critical section and
> handle_resp()'s visibility gate is a lockless test_bit(), so can a CQ 
> interrupt on
> another CPU observe the bit, take ctx->lock and interleave with this
> initialisation?  It could then see the stale responded == true from the 
> previous
> generation and silently drop a real response, or set responded/error and
> complete() while they are being reset, after which its hwc_ctx_put() consumes
> the new generation's initial reference.
> 
> reinit_completion() also writes x->done = 0 without the completion's 
> wait.lock,
> while handle_resp() calls complete() under ctx->lock.  Can a completion be 
> lost
> or spuriously observed that way?
> 
> > +           spin_unlock_irqrestore(&r->lock, flags);
> >
> > -   bitmap_set(hwc->inflight_msg_res.map, index, 1);
> > +           wait_event(hwc->msg_waitq,
> > +                      hwc->hwc_timed_out ||
> > +                      !bitmap_full(r->map, r->size));
> >
> > -   spin_unlock_irqrestore(&r->lock, flags);
> > +           if (hwc->hwc_timed_out)
> > +                   return -ETIMEDOUT;
> > +   }
> 
> [Severity: Low]
> hwc_timed_out is written under inflight_msg_res.lock in
> mana_hwc_send_request(), but read as a plain load both in this
> wait_event() condition and in the check right after it.  bitmap_full() also 
> reads
> r->map outside r->lock while other CPUs modify the same words with non-
> atomic bitmap_set()/bitmap_clear().  Would READ_ONCE()/
> WRITE_ONCE() (or evaluating the bitmap under r->lock) be preferable here to
> keep KCSAN quiet?
> 
> Related: the read-modify-write in the timeout path
> 
>       if (hwc->hwc_timeout > 1)
>               hwc->hwc_timeout = 1;
> 
> is unlocked while mana_hwc_init_event_handler() also writes hwc_timeout
> from EQ interrupt context.  That access is not introduced by this patch, it 
> is in
> the baseline, but the patch makes the value load-bearing since a zero or
> degraded value now latches the whole channel.
> 
> >
> >     *msg_id = index;
> > -
> >     return 0;
> >  }
> >
> > @@ -35,10 +59,17 @@ static void mana_hwc_put_msg_index(struct
> hw_channel_context *hwc, u16 msg_id)
> >     unsigned long flags;
> >
> >     spin_lock_irqsave(&r->lock, flags);
> > -   bitmap_clear(hwc->inflight_msg_res.map, msg_id, 1);
> > +   bitmap_clear(r->map, msg_id, 1);
> >     spin_unlock_irqrestore(&r->lock, flags);
> >
> > -   up(&hwc->sema);
> > +   wake_up(&hwc->msg_waitq);
> > +}
> > +
> > +static void hwc_ctx_put(struct hw_channel_context *hwc,
> > +                   struct hwc_caller_ctx *ctx)
> > +{
> > +   if (refcount_dec_and_test(&ctx->refcnt))
> > +           mana_hwc_put_msg_index(hwc, ctx->msg_id);
> >  }
> >
> >  static int mana_hwc_verify_resp_msg(const struct hwc_caller_ctx
> > *caller_ctx, @@ -114,22 +145,44 @@ static void
> mana_hwc_handle_resp(struct hw_channel_context *hwc, u32 resp_len,
> >             resp_len = 0;
> >     }
> >
> > -   err = mana_hwc_verify_resp_msg(ctx, resp_msg, resp_len);
> > -   if (err)
> > -           goto out;
> > +   spin_lock(&ctx->lock);
> >
> > -   ctx->status_code = resp_msg->status;
> > +   if (ctx->responded) {
> > +           /* A response for this slot was already delivered; this is a
> > +            * duplicate or replayed one.  Drop it so the hwc_ctx_put()
> > +            * a first response performs is not done twice, which would
> > +            * over-release the slot while the sender still owns it.
> > +            */
> > +           spin_unlock(&ctx->lock);
> > +           mana_hwc_post_rx_wqe(hwc->rxq, rx_req);
> > +           return;
> > +   }
> > +   ctx->responded = true;
> >
> > -   memcpy(ctx->output_buf, resp_msg, resp_len);
> > -out:
> > -   ctx->error = err;
> > +   err = mana_hwc_verify_resp_msg(ctx, resp_msg, resp_len);
> >
> > -   /* Must post rx wqe before complete(), otherwise the next rx may
> > -    * hit no_wqe error.
> > +   if (!err && ctx->output_buf) {
> > +           ctx->status_code = resp_msg->status;
> > +           memcpy(ctx->output_buf, resp_msg, resp_len);
> > +           ctx->error = 0;
> > +   } else if (ctx->output_buf) {
> > +           /* Only overwrite error if the sender hasn't timed out
> > +            * or been force-completed by destroy.  When output_buf
> > +            * is NULL, a terminal error (-ENODEV or timeout) has
> > +            * already been set — preserve it so the sender doesn't
> > +            * see a spurious success.
> > +            */
> > +           ctx->error = err;
> > +   }
> 
> [Severity: Low]
> Is this comment accurate?  It says that when output_buf is NULL "a terminal
> error (-ENODEV or timeout) has already been set".
> 
> On the timeout path mana_hwc_send_request() only NULLs ctx->output_buf
> and assigns its local err; ctx->error keeps the -EINPROGRESS sentinel written 
> by
> mana_hwc_get_msg_index().  Grepping drivers/net/ethernet/microsoft, the
> only ENODEV in hw_channel.c is inside this comment.
> 
> The comment also mentions senders "force-completed by destroy", but
> mana_hwc_destroy_channel() tears down the CQ and WQs and kfree()s
> caller_ctx without ever completing ctx->comp_event or writing ctx->error.
> 
> Could the comment be reworded to describe what the code actually
> guarantees?
> 
> > +
> > +   /* Post RX WQE before completing — the next response may arrive
> > +    * immediately and needs a posted buffer.
> >      */
> >     mana_hwc_post_rx_wqe(hwc->rxq, rx_req);
> > -
> >     complete(&ctx->comp_event);
> > +   spin_unlock(&ctx->lock);
> > +
> > +   hwc_ctx_put(hwc, ctx);
> >  }
> >
> >  static void mana_hwc_init_event_handler(void *ctx, struct gdma_queue
> > *q_self, @@ -216,7 +269,12 @@ static void
> > mana_hwc_init_event_handler(void *ctx, struct gdma_queue *q_self,
> >
> >             switch (type) {
> >             case HWC_DATA_CFG_HWC_TIMEOUT:
> > -                   hwc->hwc_timeout = val;
> > +                   /* A zero timeout would make every command time
> out
> > +                    * immediately and latch hwc_timed_out, disabling the
> > +                    * channel.  Ignore it and keep the positive default.
> > +                    */
> > +                   if (val)
> > +                           hwc->hwc_timeout = val;
> >                     break;
> 
> [Severity: Medium]
> This guard covers the reconfig EQE, but is the other device-supplied source of
> the same field covered?  mana_gd_query_hwc_timeout() still
> does:
> 
>       *timeout_val = resp.timeout_ms;
> 
> and mana_gd_verify_vf_version() calls it as
> mana_gd_query_hwc_timeout(pdev, &hwc->hwc_timeout), so a device-
> reported timeout_ms of 0 reaches hwc->hwc_timeout unfiltered.
> 
> With the new code, does the next command then wait msecs_to_jiffies(0), time
> out immediately and latch hwc_timed_out, after which every command fails
> with -ETIMEDOUT out of mana_hwc_get_msg_index() without being posted, for
> the remaining life of the channel?
> 
> >
> >             case HWC_DATA_HW_LINK_CONNECT:
> > @@ -708,7 +766,7 @@ static int mana_hwc_init_inflight_msg(struct
> > hw_channel_context *hwc,  {
> >     int err;
> >
> > -   sema_init(&hwc->sema, num_msg);
> > +   init_waitqueue_head(&hwc->msg_waitq);
> 
> [Severity: Low]
> The commit message says:
> 
>   Replace the depth-1 semaphore with a waitqueue + bitmap so a slot held
>   past a timeout does not deadlock admission and timed-out waiters can be
>   released.
> 
> Was the semaphore depth-1?  The removed line is sema_init(&hwc->sema,
> num_msg), and mana_hwc_init_queues() calls
> mana_hwc_init_inflight_msg(hwc, q_depth), so it admitted up to q_depth
> concurrent senders, matching the bitmap size.
> 
> The reason a counting semaphore no longer fits looks like the fact that a 
> bitmap
> bit can now outlive its sender (held by handle_resp's reference), which
> desynchronises the semaphore count from bitmap occupancy.  Could the
> message be corrected, given this is a Fixes:-tagged patch headed for stable?
> 
> >
> >     err = mana_gd_alloc_res_map(num_msg, &hwc->inflight_msg_res);
> >     if (err)
> 
> [ ... ]
> 
> > @@ -999,13 +1062,17 @@ int mana_hwc_send_request(struct
> hw_channel_context *hwc, u32 req_len,
> >     struct hwc_wq *txq = hwc->txq;
> >     struct gdma_req_hdr *req_msg;
> >     struct hwc_caller_ctx *ctx;
> > +   unsigned long flags;
> >     u32 dest_vrcq = 0;
> >     u32 dest_vrq = 0;
> >     u32 command;
> > +   u32 status;
> >     u16 msg_id;
> >     int err;
> >
> > -   mana_hwc_get_msg_index(hwc, &msg_id);
> > +   err = mana_hwc_get_msg_index(hwc, &msg_id);
> > +   if (err)
> > +           return err;
> >
> >     tx_wr = &txq->msg_buf->reqs[msg_id];
> >
> 
> [ ... ]
> 
> > @@ -1034,8 +1104,14 @@ int mana_hwc_send_request(struct
> hw_channel_context *hwc, u32 req_len,
> >             dest_vrcq = hwc->pf_dest_vrcq_id;
> >     }
> >
> > +   /* Take handle_resp's ref before posting — hardware can respond
> > +    * immediately after the doorbell ring.
> > +    */
> > +   refcount_inc(&ctx->refcnt);
> > +
> >     err = mana_hwc_post_tx_wqe(txq, tx_wr, dest_vrq, dest_vrcq, false);
> >     if (err) {
> > +           refcount_dec(&ctx->refcnt);
> >             dev_err(hwc->dev, "HWC: Failed to post send WQE: %d\n",
> err);
> >             goto out;
> >     }
> > @@ -1046,31 +1122,86 @@ int mana_hwc_send_request(struct
> hw_channel_context *hwc, u32 req_len,
> >                     dev_err(hwc->dev, "Command 0x%x timed out: %u
> ms\n",
> >                             command, hwc->hwc_timeout);
> >
> > -           /* Reduce further waiting if HWC no response */
> > +           /* NULL out output_buf so a late handle_resp() won't write
> > +            * into the caller's buffer after the sender returns, then
> > +            * check whether handle_resp() already delivered a valid
> > +            * response between the timeout firing and this lock
> > +            * acquisition — ctx->error != -EINPROGRESS means it ran.
> > +            */
> > +           spin_lock_irqsave(&ctx->lock, flags);
> > +           ctx->output_buf = NULL;
> > +           err = ctx->error;
> > +           status = ctx->status_code;
> > +           spin_unlock_irqrestore(&ctx->lock, flags);
> > +
> > +           if (err != -EINPROGRESS) {
> > +                   /* handle_resp() delivered a valid response just after
> > +                    * the timeout fired.  The hardware is alive, so use
> > +                    * the response and leave the channel usable; do not
> > +                    * latch hwc_timed_out or degrade hwc_timeout for
> what
> > +                    * turned out to be a transient race.
> > +                    */
> > +                   hwc_ctx_put(hwc, ctx);
> > +                   goto check_status;
> > +           }
> > +
> > +           /* Genuine timeout: no response arrived.  Reduce further
> > +            * waiting, and mark the channel timed out under the bitmap
> > +            * lock so get_msg_index() cannot acquire new slots after this.
> > +            */
> >             if (hwc->hwc_timeout > 1)
> >                     hwc->hwc_timeout = 1;
> >
> > +           spin_lock_irqsave(&hwc->inflight_msg_res.lock, flags);
> > +           hwc->hwc_timed_out = true;
> > +           spin_unlock_irqrestore(&hwc->inflight_msg_res.lock, flags);
> > +           wake_up_all(&hwc->msg_waitq);
> 
> [Severity: High]
> Once hwc_timed_out is set here, is anything expected to clear it?
> Grepping drivers/net/ethernet/microsoft and include/net/mana, this is the only
> write besides the kzalloc zeroing, so the flag stays set for the life of the
> hw_channel_context.
> 
> From then on mana_hwc_get_msg_index() bails out before a slot is even
> allocated:
> 
>       if (hwc->hwc_timed_out) {
>               spin_unlock_irqrestore(&r->lock, flags);
>               return -ETIMEDOUT;
>       }
> 
> so mana_hwc_post_tx_wqe() is never reached and no doorbell is rung for any
> later command.  Every GDMA control command funnels through
> mana_gd_send_request()->mana_hwc_send_request(), including the teardown
> commands mana_gd_disable_queue(), mana_gd_destroy_dma_region() and
> mana_gd_deregister_device(), all of which treat errors as non-fatal and then
> dma_free_coherent() the backing pages anyway.
> 
> Before this patch those commands were still built, posted and the doorbell 
> rung
> (only the wait was shortened to 1 ms), so the device did act on destroy and
> disable requests.  Can this leave the device holding registered DMA regions 
> that
> point at freed pages, which is the situation the comment in
> mana_hwc_destroy_channel() describes as risking memory corruption on
> systems without an IOMMU?
> 
> The commit message describes the latch only as:
> 
>   On a genuine timeout the channel is marked hwc_timed_out and further
>   mana_hwc_get_msg_index() callers fail with -ETIMEDOUT instead of
>   reusing a slot whose response may still arrive.
> 
> Could it state that the latch is channel-wide, unconditional and never 
> cleared,
> given the per-slot refcount already prevents reuse of a msg_id whose response
> is outstanding?
> 
> [Severity: High]
> Is the driver's own zero-timeout mode affected here too?  mana_serv_reset() in
> gdma_main.c does:
> 
>       /* HWC is not responding in this case, so don't wait */
>       hwc->hwc_timeout = 0;
> 
>       dev_info(&pdev->dev, "MANA reset cycle start\n");
> 
>       mana_gd_suspend(pdev, PMSG_SUSPEND);
> 
> and mana_gd_suspend() runs mana_rdma_remove() (which sends
> mana_gd_deregister_device()), mana_remove(&gc->mana, true) and
> mana_gd_cleanup_device(), all of which issue HWC commands.
> 
> For the first of those, wait_for_completion_timeout() with
> msecs_to_jiffies(0) returns 0, ctx->error is still -EINPROGRESS, so this 
> branch
> runs: hwc->hwc_timeout > 1 is false and hwc_timed_out is set unconditionally.
> Do the remaining teardown commands then abort in
> mana_hwc_get_msg_index() without ever reaching the SQ, turning the
> intended fire-and-forget teardown into no teardown at all while the buffers 
> are
> still freed?
> 
> The patch already guards the device-reported value with if (val) for this 
> exact
> reason.  Should the in-driver zero assignment be handled as well, for instance
> by not latching when hwc_timeout is 0, since mana_need_log() already treats
> hwc_timeout == 0 as a legitimate "don't wait, don't log"
> state?
> 
> >
> >             err = -ETIMEDOUT;
> > -           goto out;
> > +           hwc_ctx_put(hwc, ctx);
> > +           goto done;
> >     }
> >
> > -   if (ctx->error) {
> > -           err = ctx->error;
> > -           goto out;
> > -   }
> > +   /* NULL output_buf so a late handle_resp() won't memcpy into
> > +    * the caller's buffer after the sender exits.  Read error and
> > +    * status_code under the same lock — after hwc_ctx_put the slot
> > +    * may be reused and these fields overwritten.
> > +    */
> > +   spin_lock_irqsave(&ctx->lock, flags);
> > +   ctx->output_buf = NULL;
> > +   err = ctx->error;
> > +   status = ctx->status_code;
> > +   spin_unlock_irqrestore(&ctx->lock, flags);
> > +   hwc_ctx_put(hwc, ctx);
> > +
> > +check_status:
> > +   if (err)
> > +           goto done;
> 
> [ ... ]
> 
> > +
> > +   err = 0;
> > +   goto done;
> >  out:
> > -   mana_hwc_put_msg_index(hwc, msg_id);
> > +   /* Pre-post error paths: no WQE was submitted so handle_resp()
> > +    * cannot race here.  refcount is 1 (no second ref taken).
> > +    */
> > +   ctx = hwc->caller_ctx + msg_id;
> > +   spin_lock_irqsave(&ctx->lock, flags);
> > +   ctx->output_buf = NULL;
> > +   spin_unlock_irqrestore(&ctx->lock, flags);
> > +   hwc_ctx_put(hwc, ctx);
> > +done:
> >     return err;
> >  }
> > diff --git a/include/net/mana/hw_channel.h
> > b/include/net/mana/hw_channel.h index 3d8543acb5cc0..5a55cedf0607a
> > 100644
> > --- a/include/net/mana/hw_channel.h
> > +++ b/include/net/mana/hw_channel.h
> > @@ -173,6 +173,23 @@ struct hwc_caller_ctx {
> >
> >     u32 error; /* Linux error code */
> >     u32 status_code;
> 
> [Severity: Low]
> error stays declared as u32 while mana_hwc_get_msg_index() now stores
> ctx->error = -EINPROGRESS into it, and mana_hwc_send_request() reads it
> back into an int and compares it as signed with if (err != -EINPROGRESS).
> The decision whether to latch the whole channel therefore depends on an out-
> of-range u32 to int conversion rather than on the declared type.
> 
> Should the field become int error, matching its own "Linux error code"
> comment, or the sentinel be made unsigned-safe?
> 
> > +
> > +   /* Protects output_buf against concurrent access from
> > +    * handle_resp() (CQ interrupt) and the sender timeout path.
> > +    */
> > +   spinlock_t lock;
> > +
> > +   /* Tracks sender + handle_resp ownership.  The last put
> > +    * (refcount reaches 0) releases the bitmap slot.
> > +    */
> > +   refcount_t refcnt;
> > +   u16 msg_id;
> > +
> > +   /* Set under lock by the first handle_resp() for this slot so a
> > +    * duplicate or replayed response is dropped instead of consuming
> > +    * the response-side reference a second time.
> > +    */
> > +   bool responded;
> >  };
> >
> >  struct hw_channel_context {
> > @@ -193,13 +210,19 @@ struct hw_channel_context {
> >     u32 hwc_timeout;
> >
> > +   /* Set on first HWC timeout.  Causes get_msg_index() to return
> > +    * -ETIMEDOUT instead of waiting, draining all queued senders.
> > +    */
> > +   bool hwc_timed_out;
> > +
> 
> Could this comment also note that the state is never cleared, so it is 
> terminal
> for the lifetime of the channel?

I will send v4 to address the comments. v4 also adds a 7th patch, "keep
max_num_cqs immutable once cq_table is allocated", referenced below.

There are the summary of v4 addressing / not addressing the comments:

Legend: [FIX] fixed in v4, [DESIGN] intentional/won't change,
[PRE] pre-existing (not introduced by this series).


== Patch 1: RCU-protect gc->cq_table ==

[FIX] "Per-CQ blocking grace periods added to hot teardown paths ...
mana_gd_destroy_cq() now ends with an unconditional synchronize_rcu()":
the netdev teardown paths will be reworked into a two-pass quiesce/free
that takes a single grace period instead of one per CQ.

[FIX] "The bound that guards every cq_table index lives outside the
RCU-published object" / "A malicious host can trigger out-of-bounds ...
by dynamically inflating gc->max_num_cqs": v4 snapshots cq->id and
max_num_cqs with READ_ONCE() so the same value sizes, bounds and indexes
the table, and a new patch keeps max_num_cqs immutable once cq_table is
allocated.

[FIX] "two *separate* loads of the same interrupt-mutable, device-
controlled values": collapsed to single READ_ONCE() snapshots.

[FIX] "read gc->cq_table with rcu_dereference_protected(..., true)" /
gdma.h lifetime comment: the gdma.h comment will be corrected to state
the actual lifetime rule (base pointer is written only at establish and
teardown, so the "true" predicate is sound).

[FIX] "Pre-existing Use-After-Free in HWC channel teardown due to
inverted destruction order": addressed by the teardown-ordering rework
(see P3/P5 below).


== Patch 2: fix HWC RQ/SQ buffer size swap ==

[FIX] "Commit message overstates the impact ... describes a reachable
buffer overflow": changelog reworded as a latent-correctness fix (both
sizes are 0x1000, so no overflow is observable).

[FIX] "the queue dimensions are hoisted above mana_hwc_create_cq()":
the hoist and its comment are dropped; the assignments stay at the end
of mana_hwc_init_queues().

(gemini's "OOB read from inline_oob_size_div4", "UAF during teardown",
and "race/UAF in send_request timeout" against this patch are addressed
in P4/P5/P6 respectively; see below.)


== Patch 3: free HWC comp_buf after destroying the EQ ==

[FIX] "hwc->txq and hwc->rxq ... are freed by mana_hwc_destroy_wq()
BEFORE mana_hwc_destroy_cq() unpublishes" / "Destroying the TX and RX
queues before the CQ and EQ ... causes a use-after-free": v4 destroys
the CQ first (which tears down the EQ + IRQ and unpublishes the
cq_table slot with a grace period) before freeing the RQ/TX WQs.

[FIX] "the comment ... misdescribe[s] the fencing mechanism": the
comment is corrected to describe the EQ-destroy -> IRQ-deregister ->
synchronize_rcu fence accurately.


== Patch 4: validate hardware-supplied values in the HWC RX path ==

[FIX] "short-response gate ... drops the completion without waking the
pending sender, turning a prompt -EPROTO ... into a full-timeout hang"
/ "a timeout ... can lead to stack corruption and use-after-free": the
short-response early return is removed; a malformed response reaches
verify_resp_msg() -> -EPROTO and completes the sender.

[FIX] "Unbounded, unrecoverable RX WQE (RQ credit) leak": the failure
paths now account leaked WQEs and trip hwc_timeout on RQ exhaustion.

[FIX] "a plain (non-READ_ONCE) load of device/host-writable DMA memory"
/ "SGE ... validates a much weaker invariant ... corrupted
inline_oob_size_div4": v4 snapshots inline_oob_size_div4 (read once
through its u32 flags word, since it is a bit-field) and sge->address
with READ_ONCE(), and rejects any value other than the exact one the
driver programs.

[FIX] "Commit message claims an out-of-bounds indexing fix ... that
does not exist": the msg_id check is reframed in the changelog as
defense in depth.


== Patch 5: HWC teardown safety (setup_active + destroy ordering) ==

[FIX] "makes setup_active the definitive signal while still arming it
too late": setup_active is armed immediately after mana_smc_setup_hwc()
succeeds.

[FIX] "Redundant HWC teardown in mana_hwc_establish_channel() causes a
double hardware timeout ... and masks the original error code": the
redundant teardown is removed; teardown happens once via the caller.

[FIX] "the CQ is destroyed and its memory freed before the EQ is
deregistered ... Use-After-Free and WARN splats": the EQ is destroyed
(IRQ deregistered, in-flight handlers drained) before the CQ.

[FIX] "removes the only reset of gc->max_num_cqs and codifies ... that
it is 'Set once'": complemented by the new immutability patch; the
field is never reset and never re-inflated.

[FIX] "An early return on HWC teardown failure leaves a stale pointer
in gc->cq_table, which a malicious host can exploit to cause an out-of-
bounds read": on teardown failure the HWC and its CQ are leaked (not
freed), so the cq_table slot is not dangling, and max_num_cqs
immutability prevents any OOB index.  No UAF/OOB results.

[DESIGN] "mana_hwc_destroy_channel() ... returns early when
mana_smc_teardown_hwc() fails [and] skips the entire tail" / "the leak
path abandons that instance": intentional.  If teardown fails the
device may still DMA into those buffers; freeing them without an IOMMU
to fault the stale DMA risks memory corruption, so the resources are
leaked on purpose (the code says so and keeps setup_active set).


== Patch 6: fix stale HWC response after command timeout ==

[FIX] "Refcount underflow / premature slot release: the response-side
reference is taken late" / "a maliciously early hardware response can
prematurely free the message slot before the sender takes its
reference": v4 takes both references up front in
mana_hwc_get_msg_index() (refcount initialised to 2), under the same
lock that publishes the slot, so an early/stale/forged response drops
only one reference (2->1) and cannot free the slot under the sender.
The pre-post error path latches ->responded so it cannot double-drop.

[FIX] "zero-value guard for hwc->hwc_timeout only in the ... reconfig
handler": v4 rejects a device-supplied zero in the query path too.

[FIX] "hwc_timed_out is written under inflight_msg_res.lock ... but
read as a plain load": all accesses use READ_ONCE()/WRITE_ONCE().

[FIX] "u32 error; /* Linux error code */ ... stores the negative
sentinel": caller_ctx::error is changed from u32 to int so it holds the
negative -EINPROGRESS sentinel and errno values directly.

[FIX] "the output_buf == NULL comment ... is inaccurate" and "'Replace
the depth-1 semaphore ...'": the comment and the changelog wording are
corrected.

[FIX + PRE] "Lockset mismatch: mana_hwc_get_msg_index() initialises
per-slot state under inflight_msg_res.lock, while handle_resp() uses
ctx->lock; the gate is a lockless test_bit()": the dangerous
consequence you flagged (a raced handle_resp releasing a slot the new
sender still owns) is eliminated by the refcount-initialised-to-2 change
above.  The remaining consequences (dropping a stale response, lost
completion) require a duplicate/forged/unsolicited response, i.e. the
pre-existing msg_id-reuse (ABA) window; the honest in-order path never
races the initialiser.  Fully closing the ABA window needs a per-request
generation token, which is a larger, separable change I plan to send on
top rather than fold in here.

[DESIGN] "Terminal, channel-wide hwc_timed_out latch": intentional.
Once the HWC stops responding the channel is unusable and further
commands should fail fast.  v4 only narrows the false positive: a valid
response racing the timeout (ctx->error != -EINPROGRESS) no longer
latches the channel.

[DESIGN] "mana_serv_reset() deliberately sets hwc->hwc_timeout = 0 ...
and then runs the whole suspend/teardown sequence": also intentional --
there the HWC is known unresponsive and the driver must not wait.  This
is distinct from a device-supplied zero, which is now rejected.

All changes were built per-commit (mana + mana_ib, W=0 E=0), checked
with sparse C=1 and checkpatch, and boot/traffic tested on Azure MANA
hardware.

Thanks,
Long

Reply via email to