* [PATCH 1/3] scsi: arcmsr: bound the firmware-supplied message length
2026-10-12 7:38 [PATCH 0/3] scsi: arcmsr: bound adapter-supplied completion indices and message lengths Yehyeong Lee
@ 2026-10-12 7:38 ` Yehyeong Lee
2026-10-12 7:38 ` [PATCH 2/3] scsi: arcmsr: check the completion index the adapter reports Yehyeong Lee
2026-10-12 7:38 ` [PATCH 3/3] scsi: arcmsr: wrap the type B completion index before using it Yehyeong Lee
2 siblings, 0 replies; 4+ messages in thread
From: Yehyeong Lee @ 2026-10-12 7:38 UTC (permalink / raw)
To: ching2048, mkp, James.Bottomley
Cc: linux-scsi, linux-kernel, stable, thenzl, Yehyeong Lee
The adapter hands the driver an inbound message through a fixed
128-byte window:
struct QBUFFER {
uint32_t data_len;
uint8_t data[124];
};
arcmsr_Read_iop_rqbuffer_in_DWORD() takes data_len from that window and
uses it as the copy length without comparing it against the window:
iop_len = readl(&prbuffer->data_len);
if (iop_len > 0) {
buf1 = kmalloc(128, GFP_ATOMIC);
...
data_len = iop_len;
while (data_len >= 4) {
*buf2++ = readl(iop_data);
iop_data++;
data_len -= 4;
}
A data_len above 128 therefore walks the 128-byte allocation, writing
firmware-supplied data past its end, and the loop that follows reads the
same overrun back out of buf1. arcmsr_Read_iop_rqbuffer_data() has the
same unchecked loop for adapter types A and B; there the destination is
wrapped to ARCMSR_MAX_QBUFFER so nothing is written out of bounds, but
the loop still spins up to 2^32 times with rqbuffer_lock held.
Two of the three callers place no bound on the value at all:
arcmsr_iop_message_xfer() for ARCMSR_MESSAGE_READ_RQBUFFER and
arcmsr_sysfs_iop_message_read() both call straight into the reader on
ACB_F_IOPDATA_OVERFLOW. The third, arcmsr_iop2drv_data_wrote_handle(),
compares data_len against the free space in acb->rqbuffer, which is up
to ARCMSR_MAX_QBUFFER - 1 and so still far larger than the 128-byte
staging buffer; it also reads data_len separately from the reader, so
the adapter can change it between the two reads.
Clamp the length to the size of the window in both readers. A
conforming adapter never reports more than sizeof(prbuffer->data), so
this does not drop any data it sends.
With a KASAN build and a crafted window, data_len = 4096 produces 992
out-of-bounds 4-byte writes (1024 dwords less the 32 that fit) in
arcmsr_Read_iop_rqbuffer_in_DWORD():
BUG: KASAN: slab-out-of-bounds in arcmsr_Read_iop_rqbuffer_in_DWORD+0x240/0x2a0 [arcmsr]
Write of size 4 at addr ffff88800b5baf80 by task init/1
Call Trace:
<TASK>
dump_stack_lvl+0x66/0xa0
print_report+0xd0/0x630
kasan_report+0xe4/0x120
arcmsr_Read_iop_rqbuffer_in_DWORD+0x240/0x2a0 [arcmsr]
</TASK>
Allocated by task 1:
__kasan_kmalloc+0x8f/0xa0
__kmalloc_cache_noprof+0x22c/0x570
arcmsr_Read_iop_rqbuffer_in_DWORD+0x7b/0x2a0 [arcmsr]
The buggy address belongs to the object at ffff88800b5baf00
which belongs to the cache kmalloc-128 of size 128
The buggy address is located 0 bytes to the right of
The first value that overruns is data_len = 129; 128 and below are
reported clean, and 124 (the window) and 120 copy byte-for-byte
identically with and without the clamp.
Fixes: bb263c4ecbb1 ("arcmsr: fix ioctl data read/write error for adapter type C")
Cc: stable@vger.kernel.org
Signed-off-by: Yehyeong Lee <yhlee@isslab.korea.ac.kr>
---
drivers/scsi/arcmsr/arcmsr_hba.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/scsi/arcmsr/arcmsr_hba.c b/drivers/scsi/arcmsr/arcmsr_hba.c
index 8aa948f06cacf..255f1d898079f 100644
--- a/drivers/scsi/arcmsr/arcmsr_hba.c
+++ b/drivers/scsi/arcmsr/arcmsr_hba.c
@@ -2277,6 +2277,8 @@ arcmsr_Read_iop_rqbuffer_in_DWORD(struct AdapterControlBlock *acb,
iop_data = (uint32_t __iomem *)prbuffer->data;
iop_len = readl(&prbuffer->data_len);
+ if (iop_len > sizeof(prbuffer->data))
+ iop_len = sizeof(prbuffer->data);
if (iop_len > 0) {
buf1 = kmalloc(128, GFP_ATOMIC);
buf2 = (uint32_t *)buf1;
@@ -2319,6 +2321,8 @@ arcmsr_Read_iop_rqbuffer_data(struct AdapterControlBlock *acb,
return arcmsr_Read_iop_rqbuffer_in_DWORD(acb, prbuffer);
iop_data = (uint8_t __iomem *)prbuffer->data;
iop_len = readl(&prbuffer->data_len);
+ if (iop_len > sizeof(prbuffer->data))
+ iop_len = sizeof(prbuffer->data);
while (iop_len > 0) {
pQbuffer = &acb->rqbuffer[acb->rqbuf_putIndex];
*pQbuffer = readb(iop_data);
--
2.43.0
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH 2/3] scsi: arcmsr: check the completion index the adapter reports
2026-10-12 7:38 [PATCH 0/3] scsi: arcmsr: bound adapter-supplied completion indices and message lengths Yehyeong Lee
2026-10-12 7:38 ` [PATCH 1/3] scsi: arcmsr: bound the firmware-supplied message length Yehyeong Lee
@ 2026-10-12 7:38 ` Yehyeong Lee
2026-10-12 7:38 ` [PATCH 3/3] scsi: arcmsr: wrap the type B completion index before using it Yehyeong Lee
2 siblings, 0 replies; 4+ messages in thread
From: Yehyeong Lee @ 2026-10-12 7:38 UTC (permalink / raw)
To: ching2048, mkp, James.Bottomley
Cc: linux-scsi, linux-kernel, stable, thenzl, Yehyeong Lee
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 of which only acb->maxFreeCCB are
ever filled in. 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.
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.
Fixes: 235090241c55 ("scsi: arcmsr: Add code for ACB_ADAPTER_TYPE_E")
Cc: stable@vger.kernel.org
Signed-off-by: Yehyeong Lee <yhlee@isslab.korea.ac.kr>
---
drivers/scsi/arcmsr/arcmsr_hba.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/drivers/scsi/arcmsr/arcmsr_hba.c b/drivers/scsi/arcmsr/arcmsr_hba.c
index 255f1d898079f..1381420d9fbd7 100644
--- a/drivers/scsi/arcmsr/arcmsr_hba.c
+++ b/drivers/scsi/arcmsr/arcmsr_hba.c
@@ -2677,6 +2677,11 @@ 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 >= acb->maxFreeCCB) {
+ pr_err_ratelimited("arcmsr%d: bad cmdSMID %u from iop\n",
+ acb->host->host_no, cmdSMID);
+ break;
+ }
ccb = acb->pccb_pool[cmdSMID];
error = (acb->pCompletionQ[doneq_index].cmdFlag
& ARCMSR_CCBREPLY_FLAG_ERROR_MODE1) ? true : false;
@@ -2706,6 +2711,11 @@ static void arcmsr_hbaF_postqueue_isr(struct AdapterControlBlock *acb)
cmdSMID = acb->pCompletionQ[doneq_index].cmdSMID;
if (cmdSMID == 0xffff)
break;
+ if (cmdSMID >= acb->maxFreeCCB) {
+ pr_err_ratelimited("arcmsr%d: bad cmdSMID %u from iop\n",
+ acb->host->host_no, cmdSMID);
+ break;
+ }
ccb = acb->pccb_pool[cmdSMID];
error = (acb->pCompletionQ[doneq_index].cmdFlag &
ARCMSR_CCBREPLY_FLAG_ERROR_MODE1) ? true : false;
@@ -3880,6 +3890,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 >= acb->maxFreeCCB) {
+ 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
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH 3/3] scsi: arcmsr: wrap the type B completion index before using it
2026-10-12 7:38 [PATCH 0/3] scsi: arcmsr: bound adapter-supplied completion indices and message lengths Yehyeong Lee
2026-10-12 7:38 ` [PATCH 1/3] scsi: arcmsr: bound the firmware-supplied message length Yehyeong Lee
2026-10-12 7:38 ` [PATCH 2/3] scsi: arcmsr: check the completion index the adapter reports Yehyeong Lee
@ 2026-10-12 7:38 ` Yehyeong Lee
2 siblings, 0 replies; 4+ messages in thread
From: Yehyeong Lee @ 2026-10-12 7:38 UTC (permalink / raw)
To: ching2048, mkp, James.Bottomley
Cc: linux-scsi, linux-kernel, stable, thenzl, Yehyeong Lee
struct MessageUnit_B lives in the dma_alloc_coherent() area shared with
the adapter, and holds the completion ring next to the index into it:
uint32_t done_qbuffer[ARCMSR_MAX_HBB_POSTQUEUE];
uint32_t postq_index;
uint32_t doneq_index;
arcmsr_hbaB_postqueue_isr() takes doneq_index from there and subscripts
done_qbuffer[] with it, and only afterwards reduces it:
index = reg->doneq_index;
while ((flag_ccb = reg->done_qbuffer[index]) != 0) {
...
reg->done_qbuffer[index] = 0;
index++;
index %= ARCMSR_MAX_HBB_POSTQUEUE;
reg->doneq_index = index;
}
So the first iteration uses the value as it was found, unmasked. An
index beyond ARCMSR_MAX_HBB_POSTQUEUE (264) reads past done_qbuffer[]
and, should the word it finds be non-zero, stores a zero there as well.
arcmsr_hbaB_polling_ccbdone() has the same ordering.
Reduce the index where it is read instead, which leaves the rest of the
loop unchanged.
Measured with a KASAN build. struct MessageUnit_B is 2176 bytes with
done_qbuffer at offset 1056, so a reported index of 600 reads 3456 bytes
in:
BUG: KASAN: slab-out-of-bounds in arcmsr_hbaB_postqueue_isr+0x1f5/0x270 [arcmsr]
Read of size 4 at addr ffff88800c08cd80 by task init/1
...
which belongs to the cache kmalloc-4k of size 4096
The buggy address is located 1280 bytes to the right of
Fixes: 1c57e86d75cf ("[SCSI] arcmsr: initial driver, version 1.20.00.13")
Cc: stable@vger.kernel.org
Signed-off-by: Yehyeong Lee <yhlee@isslab.korea.ac.kr>
---
drivers/scsi/arcmsr/arcmsr_hba.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/scsi/arcmsr/arcmsr_hba.c b/drivers/scsi/arcmsr/arcmsr_hba.c
index 1381420d9fbd7..b73aaa0c8068c 100644
--- a/drivers/scsi/arcmsr/arcmsr_hba.c
+++ b/drivers/scsi/arcmsr/arcmsr_hba.c
@@ -2568,7 +2568,7 @@ static void arcmsr_hbaB_postqueue_isr(struct AdapterControlBlock *acb)
bool error;
unsigned long cdb_phy_addr;
- index = reg->doneq_index;
+ index = reg->doneq_index % ARCMSR_MAX_HBB_POSTQUEUE;
while ((flag_ccb = reg->done_qbuffer[index]) != 0) {
cdb_phy_addr = (flag_ccb << 5) & 0xffffffff;
if (acb->cdb_phyadd_hipart)
@@ -3662,7 +3662,7 @@ static int arcmsr_hbaB_polling_ccbdone(struct AdapterControlBlock *acb,
/* clear doorbell interrupt */
writel(ARCMSR_DOORBELL_INT_CLEAR_PATTERN, reg->iop2drv_doorbell);
while(1){
- index = reg->doneq_index;
+ index = reg->doneq_index % ARCMSR_MAX_HBB_POSTQUEUE;
flag_ccb = reg->done_qbuffer[index];
if (flag_ccb == 0) {
if (poll_ccb_done){
--
2.43.0
^ permalink raw reply [flat|nested] 4+ messages in thread