[PATCH v2 2/3] scsi: arcmsr: check the completion index the adapter reports

From: Yehyeong Lee

Date: Mon Oct 12 2026 - 05:07:32 EST


On adapter types E and F the adapter reports a completed command by
writing its slot number into the completion queue, and the driver turns
that number straight into an array subscript:

cmdSMID = acb->pCompletionQ[doneq_index].cmdSMID;
ccb = acb->pccb_pool[cmdSMID];

cmdSMID is a uint16_t taken from DMA memory the adapter writes, so it
can be anything up to 65535, while acb->pccb_pool[] has
ARCMSR_MAX_FREECCB_NUM (1024) entries. Nothing compares the two.

arcmsr_drain_donequeue() cannot stand in for the missing check: it
starts by reading pCCB->acb and pCCB->startdone, so by the time it can
notice anything the bad pointer has already been loaded and
dereferenced. arcmsr_hbaF_postqueue_isr() rejects the single value
0xffff because that is its queue-empty sentinel, which leaves
1024..65534 accepted.

Reject an index the driver never handed out, in the two interrupt
handlers and in the polling path.

Bound against ARCMSR_MAX_FREECCB_NUM rather than acb->maxFreeCCB, which
is what actually guards the array: maxFreeCCB is derived from the
host_can_queue module parameter and gets 64 added to it when it is below
ARCMSR_MAX_FREECCB_NUM, so it can be larger than the array and would let
a slot number past the end through.

In the interrupt handlers, release the entry and step the queue index
before continuing, the same way the accepted path does. Stopping at the
bad entry instead would leave the consumer index behind the producer,
so the adapter would re-raise the interrupt on an entry the driver has
already refused and the completions queued behind it would never be
reaped.

Measured with a KASAN build and a crafted completion queue. struct
AdapterControlBlock is 17504 bytes with pccb_pool at offset 496, so a
reported slot of 60000 reads 480496 bytes in, past the end of the
controller block entirely:

BUG: KASAN: use-after-free in arcmsr_hbaE_postqueue_isr+0x242/0x2d0 [arcmsr]
Read of size 8 at addr ffff88800bf354f0 by task init/1

and the pointer it picked up then faults in arcmsr_drain_donequeue:

RIP: 0010:arcmsr_drain_donequeue+0x27/0x360 [arcmsr]
Kernel panic - not syncing: Attempted to kill init! exitcode=0x0000000b

A smaller out-of-range slot such as 2000 still lands inside the
controller block (offset 16496), so there is nothing for KASAN to
report; the driver simply reads an unrelated field of its own
AdapterControlBlock as a command pointer and faults on it in the same
place. With host_can_queue raised so that maxFreeCCB becomes 1087, a
reported slot of 1050 faults the same way while comparing below
maxFreeCCB.

A queue holding a rejected entry followed by a good one drains to the
producer index and the good completion is reaped; valid slot numbers are
accepted unchanged.

Fixes: 235090241c55 ("scsi: arcmsr: Add code for ACB_ADAPTER_TYPE_E")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Yehyeong Lee <yhlee@xxxxxxxxxxxxxxxxxx>
---
drivers/scsi/arcmsr/arcmsr_hba.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)

diff --git a/drivers/scsi/arcmsr/arcmsr_hba.c b/drivers/scsi/arcmsr/arcmsr_hba.c
index 255f1d898079f..9519715883b5a 100644
--- a/drivers/scsi/arcmsr/arcmsr_hba.c
+++ b/drivers/scsi/arcmsr/arcmsr_hba.c
@@ -2677,6 +2677,14 @@ static void arcmsr_hbaE_postqueue_isr(struct AdapterControlBlock *acb)
pmu = acb->pmuE;
while ((readl(&pmu->reply_post_producer_index) & 0xFFFF) != doneq_index) {
cmdSMID = acb->pCompletionQ[doneq_index].cmdSMID;
+ if (cmdSMID >= ARCMSR_MAX_FREECCB_NUM) {
+ pr_err_ratelimited("arcmsr%d: bad cmdSMID %u from iop\n",
+ acb->host->host_no, cmdSMID);
+ doneq_index++;
+ if (doneq_index >= acb->completionQ_entry)
+ doneq_index = 0;
+ continue;
+ }
ccb = acb->pccb_pool[cmdSMID];
error = (acb->pCompletionQ[doneq_index].cmdFlag
& ARCMSR_CCBREPLY_FLAG_ERROR_MODE1) ? true : false;
@@ -2706,6 +2714,15 @@ static void arcmsr_hbaF_postqueue_isr(struct AdapterControlBlock *acb)
cmdSMID = acb->pCompletionQ[doneq_index].cmdSMID;
if (cmdSMID == 0xffff)
break;
+ if (cmdSMID >= ARCMSR_MAX_FREECCB_NUM) {
+ pr_err_ratelimited("arcmsr%d: bad cmdSMID %u from iop\n",
+ acb->host->host_no, cmdSMID);
+ acb->pCompletionQ[doneq_index].cmdSMID = 0xffff;
+ doneq_index++;
+ if (doneq_index >= acb->completionQ_entry)
+ doneq_index = 0;
+ continue;
+ }
ccb = acb->pccb_pool[cmdSMID];
error = (acb->pCompletionQ[doneq_index].cmdFlag &
ARCMSR_CCBREPLY_FLAG_ERROR_MODE1) ? true : false;
@@ -3880,6 +3897,11 @@ static int arcmsr_hbaE_polling_ccbdone(struct AdapterControlBlock *acb,
doneq_index = 0;
acb->doneq_index = doneq_index;
spin_unlock_irqrestore(&acb->doneq_lock, flags);
+ if (cmdSMID >= ARCMSR_MAX_FREECCB_NUM) {
+ pr_err_ratelimited("arcmsr%d: bad cmdSMID %u from iop\n",
+ acb->host->host_no, cmdSMID);
+ continue;
+ }
pCCB = acb->pccb_pool[cmdSMID];
poll_ccb_done |= (pCCB == poll_ccb) ? 1 : 0;
/* check if command done with no error*/
--
2.43.0