RE: [EXTERNAL] Re: [PATCH net v3 6/6] net: mana: fix stale HWC response after command timeout

Long Li <[email protected]>
Newsgroups org.kernel.vger.linux-rdma,org.kernel.vger.linux-hyperv,org.kernel.vger.linux-kernel,org.kernel.vger.netdev
Message-ID <SA1PR21MB66830BCF3291090E754ABCB3CED02@SA1PR21MB6683.namprd21.prod.outlook.com>
> 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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.