* [PATCH v2 1/5] scsi: aic94xx: bail out of escb_tasklet_complete() on a bad EDB index
2026-10-15 8:19 [PATCH v2 0/5] scsi: aic94xx: validate the indices the sequencer supplies Yehyeong Lee
@ 2026-10-15 8:19 ` Yehyeong Lee
2026-10-15 8:19 ` [PATCH v2 2/5] scsi: aic94xx: validate the EDB index in asd_get_response_tasklet() Yehyeong Lee
` (3 subsequent siblings)
4 siblings, 0 replies; 7+ messages in thread
From: Yehyeong Lee @ 2026-10-15 8:19 UTC (permalink / raw)
To: Martin K . Petersen, James E . J . Bottomley
Cc: linux-scsi, linux-kernel, stable, yhlee
escb_tasklet_complete() derives the empty data buffer index from the done
list opcode the sequencer wrote:
int edb = (dl->opcode & DL_PHY_MASK) - 1; /* [0xc1,0xc7] -> [0,6] */
DL_PHY_MASK is 7, so the subtraction yields -1 through 6, and the
function already says that anything outside [0,6] is wrong:
if (edb > 6 || edb < 0) {
ASD_DPRINTK("edb is 0x%x! dl->opcode is 0x%x\n", ...);
...
}
The block reports the problem and then falls through to let it happen.
edb is used twice afterwards: asd_bytes_dmaed_tasklet() adds
ascb->edb_index to it to index seq->edb_arr[], and the out: label at the
end of the function hands the same value to asd_invalidate_edb(), which
indexes both escb->eb[] and seq->edb_arr[]. asd_assign_edbs2escbs() gives
the first ESCB edb_index 0, so for that one edb == -1 reads
seq->edb_arr[-1], one pointer before the array, and dereferences whatever
it finds.
Finish the check the way it reads: return instead of falling through.
Whether the sequencer can really report an opcode that makes this zero is
not something the tree answers. It is firmware loaded with
request_firmware(), the done list opcode is described only by the comment
above, and there is no Documentation/ file and no MAINTAINERS entry for
this driver. This is not a claim of a reachable out-of-bounds access,
only that the driver goes on to index an array with a value it has just
decided is invalid. The intent to bound what the sequencer supplies is
already in the same file, in asd_bytes_dmaed_tasklet():
size = min(size, (u16) sizeof(phy->frame_rcvd));
which clamps a device-supplied length a few lines after indexing
seq->edb_arr[] with a device-supplied index and no bound.
One consequence worth naming: an ESCB dropped this way is not invalidated
and so is not reposted to the sequencer, because the repost happens
inside asd_invalidate_edb(). That takes one empty buffer out of
circulation per malformed opcode. It is the lesser of the two, and
recovering the ESCB instead would mean choosing some other buffer to
invalidate, which this index cannot tell us.
Fixes: 2908d778ab3e ("[SCSI] aic94xx: new driver")
Cc: stable@vger.kernel.org
Signed-off-by: Yehyeong Lee <yhlee@isslab.korea.ac.kr>
---
drivers/scsi/aic94xx/aic94xx_scb.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/scsi/aic94xx/aic94xx_scb.c b/drivers/scsi/aic94xx/aic94xx_scb.c
index 08c8dad9ad621..d050e5325b943 100644
--- a/drivers/scsi/aic94xx/aic94xx_scb.c
+++ b/drivers/scsi/aic94xx/aic94xx_scb.c
@@ -421,6 +421,7 @@ static void escb_tasklet_complete(struct asd_ascb *ascb,
le64_to_cpu(ascb->scb->header.next_scb),
le16_to_cpu(ascb->scb->header.index),
ascb->scb->header.opcode);
+ return;
}
/* Catch these before we mask off the sb_opcode bits */
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v2 2/5] scsi: aic94xx: validate the EDB index in asd_get_response_tasklet()
2026-10-15 8:19 [PATCH v2 0/5] scsi: aic94xx: validate the indices the sequencer supplies Yehyeong Lee
2026-10-15 8:19 ` [PATCH v2 1/5] scsi: aic94xx: bail out of escb_tasklet_complete() on a bad EDB index Yehyeong Lee
@ 2026-10-15 8:19 ` Yehyeong Lee
2026-10-15 8:19 ` [PATCH v2 3/5] scsi: aic94xx: validate the EDB index in asd_get_tmf_resp_tasklet() Yehyeong Lee
` (2 subsequent siblings)
4 siblings, 0 replies; 7+ messages in thread
From: Yehyeong Lee @ 2026-10-15 8:19 UTC (permalink / raw)
To: Martin K . Petersen, James E . J . Bottomley
Cc: linux-scsi, linux-kernel, stable, yhlee
asd_get_response_tasklet() takes the empty data buffer index out of the
done list status block the sequencer wrote, with the same arithmetic
escb_tasklet_complete() uses on its own copy:
int edb_id = ((resp_sb->flags & 0x70) >> 4)-1;
The extracted field is three bits, so the subtraction yields -1 through
6, and the function indexes seq->edb_arr[] with it without looking:
edb = asd_ha->seq.edb_arr[edb_id + escb->edb_index];
r = edb->vaddr;
asd_assign_edbs2escbs() gives the first ESCB edb_index 0, so for that one
edb_id == -1 reads seq->edb_arr[-1], one pointer before the kmalloc'd
array, and the next line dereferences it. asd_invalidate_edb() at the end
of the function would index escb->eb[] and seq->edb_arr[] with the same
value.
Reject an index outside [0, ASD_EDBS_PER_SCB) and return, and set
ts->stat on the way out, because returning without touching it reports
the command as a success.
asd_task_tasklet_complete() sets ts->resp and ts->stat before it calls
this function:
case TC_SSP_RESP:
case TC_ATA_RESP:
ts->resp = SAS_TASK_COMPLETE;
ts->stat = SAS_PROTO_RESPONSE;
asd_get_response_tasklet(ascb, dl);
SAS_PROTO_RESPONSE is a promise that this function will go on to put the
real status there, from the response IU for SSP or the ending FIS for
STP. Returning early breaks that promise and leaves the promise in place.
For SSP, sas_end_task() starts from DID_OK and SAS_SAM_STAT_GOOD, and its
SAS_PROTO_RESPONSE case only prints a notice, so the command completes as
a success. For STP, sas_ata_task_done() takes SAS_PROTO_RESPONSE as "the
ending FIS has the status" and copies ATA_RESP_FIS_SIZE out of ts->buf,
which this function never filled, then derives err_mask from it.
So the bail reports SAS_INTERRUPTED, which is what this driver already
uses for a response that arrived but could not be used -- TI_BREAK,
TI_PROTO_ERR, TI_NAK, TI_ACK_NAK_TO, TF_SMP_XMIT_RCV_ERR and
TC_ATA_R_ERR_RECV all map to it. sas_end_task() turns it into
DID_NO_CONNECT, and sas_to_ata_err() into AC_ERR_ATA_BUS, so neither path
mistakes it for a completed command.
The lookup failure above it had the same problem and gets the same line.
Nothing changes on the path where the index is in range.
Whether the sequencer can really report zero in that field is not
something the tree answers. It is firmware loaded with
request_firmware(), status_block[] is an opaque four bytes in
aic94xx_sas.h whose layout is described only by a struct declared locally
in this function and again in aic94xx_tmf.c, and there is no
Documentation/ file or MAINTAINERS entry for this driver. This is not a
claim of a reachable out-of-bounds access. It makes the driver consistent
with itself: asd_bytes_dmaed_tasklet() in aic94xx_scb.c clamps a
device-supplied length a few lines after indexing seq->edb_arr[] with a
device-supplied index and no bound of its own.
As with the preceding patch, a response dropped here leaves its ESCB
uninvalidated and so not reposted, since the repost happens inside
asd_invalidate_edb(). The alternative is to index the array with a value
the driver cannot place.
Fixes: 2908d778ab3e ("[SCSI] aic94xx: new driver")
Signed-off-by: Yehyeong Lee <yhlee@isslab.korea.ac.kr>
---
drivers/scsi/aic94xx/aic94xx_task.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/drivers/scsi/aic94xx/aic94xx_task.c b/drivers/scsi/aic94xx/aic94xx_task.c
index b26a468ddc981..bb5094ca5de15 100644
--- a/drivers/scsi/aic94xx/aic94xx_task.c
+++ b/drivers/scsi/aic94xx/aic94xx_task.c
@@ -166,6 +166,14 @@ static void asd_get_response_tasklet(struct asd_ascb *ascb,
if (!escb) {
ASD_DPRINTK("Uh-oh! No escb for this dl?!\n");
+ ts->stat = SAS_INTERRUPTED;
+ return;
+ }
+
+ if (edb_id < 0 || edb_id >= ASD_EDBS_PER_SCB) {
+ ASD_DPRINTK("edb_id is %d! resp_sb->flags is 0x%x\n",
+ edb_id, resp_sb->flags);
+ ts->stat = SAS_INTERRUPTED;
return;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v2 3/5] scsi: aic94xx: validate the EDB index in asd_get_tmf_resp_tasklet()
2026-10-15 8:19 [PATCH v2 0/5] scsi: aic94xx: validate the indices the sequencer supplies Yehyeong Lee
2026-10-15 8:19 ` [PATCH v2 1/5] scsi: aic94xx: bail out of escb_tasklet_complete() on a bad EDB index Yehyeong Lee
2026-10-15 8:19 ` [PATCH v2 2/5] scsi: aic94xx: validate the EDB index in asd_get_response_tasklet() Yehyeong Lee
@ 2026-10-15 8:19 ` Yehyeong Lee
2026-10-15 8:19 ` [PATCH v2 4/5] scsi: aic94xx: bound the transaction context index from the sequencer Yehyeong Lee
2026-10-15 8:19 ` [PATCH v2 5/5] scsi: aic94xx: check that an ESCB lookup returned an ESCB Yehyeong Lee
4 siblings, 0 replies; 7+ messages in thread
From: Yehyeong Lee @ 2026-10-15 8:19 UTC (permalink / raw)
To: Martin K . Petersen, James E . J . Bottomley
Cc: linux-scsi, linux-kernel, stable, yhlee
asd_get_tmf_resp_tasklet() extracts the empty data buffer index from the
done list status block exactly as asd_get_response_tasklet() does:
int edb_id = ((resp_sb->flags & 0x70) >> 4)-1;
and indexes seq->edb_arr[] with it unchecked:
edb = asd_ha->seq.edb_arr[edb_id + escb->edb_index];
ascb->tag = *(__be16 *)(edb->vaddr+4);
The field is three bits, so the value runs from -1 to 6, and the first
ESCB has edb_index 0, which makes seq->edb_arr[-1] reachable arithmetic.
asd_invalidate_edb() at the end of the function uses the same value
again.
Apply the same check as the preceding patch and return res, which is
TMF_RESP_FUNC_FAILED at that point, the way the lookup failure above it
already does.
The caveats are the ones on that patch: the sequencer is firmware loaded
with request_firmware() and nothing in the tree describes what it may put
in this field, so this is not a claim of a reachable out-of-bounds
access, and a response dropped here leaves its ESCB uninvalidated and
unreposted.
Fixes: 2908d778ab3e ("[SCSI] aic94xx: new driver")
Signed-off-by: Yehyeong Lee <yhlee@isslab.korea.ac.kr>
---
drivers/scsi/aic94xx/aic94xx_tmf.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/drivers/scsi/aic94xx/aic94xx_tmf.c b/drivers/scsi/aic94xx/aic94xx_tmf.c
index 28ac92b041fe3..1ee6009d83051 100644
--- a/drivers/scsi/aic94xx/aic94xx_tmf.c
+++ b/drivers/scsi/aic94xx/aic94xx_tmf.c
@@ -282,6 +282,12 @@ static int asd_get_tmf_resp_tasklet(struct asd_ascb *ascb,
return res;
}
+ if (edb_id < 0 || edb_id >= ASD_EDBS_PER_SCB) {
+ ASD_DPRINTK("edb_id is %d! resp_sb->flags is 0x%x\n",
+ edb_id, resp_sb->flags);
+ return res;
+ }
+
edb = asd_ha->seq.edb_arr[edb_id + escb->edb_index];
ascb->tag = *(__be16 *)(edb->vaddr+4);
fh = edb->vaddr + 16;
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v2 4/5] scsi: aic94xx: bound the transaction context index from the sequencer
2026-10-15 8:19 [PATCH v2 0/5] scsi: aic94xx: validate the indices the sequencer supplies Yehyeong Lee
` (2 preceding siblings ...)
2026-10-15 8:19 ` [PATCH v2 3/5] scsi: aic94xx: validate the EDB index in asd_get_tmf_resp_tasklet() Yehyeong Lee
@ 2026-10-15 8:19 ` Yehyeong Lee
2026-10-15 8:19 ` [PATCH v2 5/5] scsi: aic94xx: check that an ESCB lookup returned an ESCB Yehyeong Lee
4 siblings, 0 replies; 7+ messages in thread
From: Yehyeong Lee @ 2026-10-15 8:19 UTC (permalink / raw)
To: Martin K . Petersen, James E . J . Bottomley
Cc: linux-scsi, linux-kernel, stable, yhlee
Every aSCB the driver allocates takes a slot in one shared index array,
and the driver tells the sequencer which slot it used:
/* asd_ascb_alloc() */
ascb->tc_index = asd_tc_index_get(seq, ascb);
...
ascb->scb->header.index = cpu_to_le16((u16)ascb->tc_index);
The sequencer hands that number back in the done list, and the driver
looks the aSCB up again with it in three places: asd_dl_tasklet_handler()
uses dl->index, and asd_get_response_tasklet() and
asd_get_tmf_resp_tasklet() use the index_escb field of the status block.
All three go through
static inline void *asd_tc_index_find(struct asd_seq_data *seq, int index)
{
return seq->tc_index_array[index];
}
which does not look at index at all.
The array is kcalloc()ed with tc_index_bitmap_bits entries, which is
hw_prof.max_scbs, which is asd_get_cmdctx_size() divided by sizeof(struct
scb). The two values asd_get_cmdctx_size() can return are 32768 and
65536, and struct scb starts with an 11-byte scb_header, so max_scbs
cannot exceed 5957. Both numbers the sequencer supplies are __le16, so
either one can name a slot past the end of the array, and the pointer
read from there is then dereferenced.
Check the index where it is used. The three callers already handle a NULL
return -- "Uh-oh! No escb for this dl?!" in the two status block paths
and "BUG:sequencer:dl:no ascb?!" in the done list handler -- so none of
them needs to change.
This is not a claim of a reachable out-of-bounds access: the index
originates with the driver and comes back echoed, and whether the
sequencer can echo something else is not something the tree answers,
since it is firmware loaded with request_firmware() and there is no
Documentation/ file or MAINTAINERS entry for this driver. It is a bound
on a number that arrives from outside, in the one place all three users
share.
Fixes: 2908d778ab3e ("[SCSI] aic94xx: new driver")
Cc: stable@vger.kernel.org
Signed-off-by: Yehyeong Lee <yhlee@isslab.korea.ac.kr>
---
drivers/scsi/aic94xx/aic94xx_hwi.h | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/scsi/aic94xx/aic94xx_hwi.h b/drivers/scsi/aic94xx/aic94xx_hwi.h
index 930e192b1cd41..841769a8130a9 100644
--- a/drivers/scsi/aic94xx/aic94xx_hwi.h
+++ b/drivers/scsi/aic94xx/aic94xx_hwi.h
@@ -306,6 +306,8 @@ static inline int asd_tc_index_get(struct asd_seq_data *seq, void *ptr)
*/
static inline void *asd_tc_index_find(struct asd_seq_data *seq, int index)
{
+ if (index < 0 || index >= seq->tc_index_bitmap_bits)
+ return NULL;
return seq->tc_index_array[index];
}
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v2 5/5] scsi: aic94xx: check that an ESCB lookup returned an ESCB
2026-10-15 8:19 [PATCH v2 0/5] scsi: aic94xx: validate the indices the sequencer supplies Yehyeong Lee
` (3 preceding siblings ...)
2026-10-15 8:19 ` [PATCH v2 4/5] scsi: aic94xx: bound the transaction context index from the sequencer Yehyeong Lee
@ 2026-10-15 8:19 ` Yehyeong Lee
2026-10-15 8:55 ` sashiko-bot
4 siblings, 1 reply; 7+ messages in thread
From: Yehyeong Lee @ 2026-10-15 8:19 UTC (permalink / raw)
To: Martin K . Petersen, James E . J . Bottomley
Cc: linux-scsi, linux-kernel, stable, yhlee
asd_get_response_tasklet() and asd_get_tmf_resp_tasklet() look an Empty
SCB up by the index_escb field of the done list status block and then use
it as one:
escb = asd_tc_index_find(&asd_ha->seq,
(int)le16_to_cpu(resp_sb->index_escb));
...
edb = asd_ha->seq.edb_arr[edb_id + escb->edb_index];
...
asd_invalidate_edb(escb, edb_id);
Nothing checks that what came back is an Empty SCB. There is one index
space for every aSCB the driver allocates -- asd_ascb_alloc() is the only
caller of asd_tc_index_get() -- so an in-range index can just as well
name a live task, a control phy SCB or an internal command.
That matters because struct scb is a union, and asd_invalidate_edb()
reads its escb member without looking at the opcode:
struct empty_scb *escb = &ascb->scb->escb;
struct sg_el *eb = &escb->eb[edb_id];
...
eb->flags |= ELEMENT_NOT_VALID;
escb->num_valid--;
On an SCB that is not an Empty SCB those two stores land in whichever
union member it really is -- for an initiate_ssp_task that region holds
the SSP frame header and the scatter-gather elements of a command that is
still in flight.
Check the opcode, the way asd_dl_tasklet_handler() already does when it
decides whether a done list entry belongs to an Empty SCB:
} else if (ascb->scb->header.opcode == EMPTY_SCB) {
asd_get_response_tasklet() reports SAS_INTERRUPTED, as it does for the
other two ways it can fail; asd_get_tmf_resp_tasklet() returns
TMF_RESP_FUNC_FAILED, which is what res already holds.
This is not a claim of a reachable type confusion. The index originates
with the driver and the sequencer echoes it back, and whether it can echo
a different one is not something the tree answers. The check is cheap and
the idiom is already in the driver.
Fixes: 2908d778ab3e ("[SCSI] aic94xx: new driver")
Cc: stable@vger.kernel.org
Signed-off-by: Yehyeong Lee <yhlee@isslab.korea.ac.kr>
---
drivers/scsi/aic94xx/aic94xx_task.c | 8 ++++++++
drivers/scsi/aic94xx/aic94xx_tmf.c | 7 +++++++
2 files changed, 15 insertions(+)
diff --git a/drivers/scsi/aic94xx/aic94xx_task.c b/drivers/scsi/aic94xx/aic94xx_task.c
index bb5094ca5de15..a8131407b2d08 100644
--- a/drivers/scsi/aic94xx/aic94xx_task.c
+++ b/drivers/scsi/aic94xx/aic94xx_task.c
@@ -170,6 +170,14 @@ static void asd_get_response_tasklet(struct asd_ascb *ascb,
return;
}
+ if (escb->scb->header.opcode != EMPTY_SCB) {
+ ASD_DPRINTK("index_escb %d is not an ESCB, opcode is 0x%x!\n",
+ le16_to_cpu(resp_sb->index_escb),
+ escb->scb->header.opcode);
+ ts->stat = SAS_INTERRUPTED;
+ return;
+ }
+
if (edb_id < 0 || edb_id >= ASD_EDBS_PER_SCB) {
ASD_DPRINTK("edb_id is %d! resp_sb->flags is 0x%x\n",
edb_id, resp_sb->flags);
diff --git a/drivers/scsi/aic94xx/aic94xx_tmf.c b/drivers/scsi/aic94xx/aic94xx_tmf.c
index 1ee6009d83051..e8205e64b870f 100644
--- a/drivers/scsi/aic94xx/aic94xx_tmf.c
+++ b/drivers/scsi/aic94xx/aic94xx_tmf.c
@@ -282,6 +282,13 @@ static int asd_get_tmf_resp_tasklet(struct asd_ascb *ascb,
return res;
}
+ if (escb->scb->header.opcode != EMPTY_SCB) {
+ ASD_DPRINTK("index_escb %d is not an ESCB, opcode is 0x%x!\n",
+ le16_to_cpu(resp_sb->index_escb),
+ escb->scb->header.opcode);
+ return res;
+ }
+
if (edb_id < 0 || edb_id >= ASD_EDBS_PER_SCB) {
ASD_DPRINTK("edb_id is %d! resp_sb->flags is 0x%x\n",
edb_id, resp_sb->flags);
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* Re: [PATCH v2 5/5] scsi: aic94xx: check that an ESCB lookup returned an ESCB
2026-10-15 8:19 ` [PATCH v2 5/5] scsi: aic94xx: check that an ESCB lookup returned an ESCB Yehyeong Lee
@ 2026-10-15 8:55 ` sashiko-bot
0 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-10-15 8:55 UTC (permalink / raw)
To: Yehyeong Lee; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] scsi: aic94xx: UAF race condition due to lockless escb access
--
commit 61c788d589a8ac9597298b6297ce45bf72a70621
Author: Yehyeong Lee <yhlee@isslab.korea.ac.kr>
scsi: aic94xx: check that an ESCB lookup returned an ESCB
This commit adds checks to verify that an ESCB lookup actually returns an
Empty SCB by inspecting the opcode.
> diff --git a/drivers/scsi/aic94xx/aic94xx_task.c b/drivers/scsi/aic94xx/aic94xx_task.c
> --- a/drivers/scsi/aic94xx/aic94xx_task.c
> +++ b/drivers/scsi/aic94xx/aic94xx_task.c
> @@ -170,6 +170,14 @@ static void asd_get_response_tasklet(struct asd_ascb *ascb,
> return;
> }
>
> + if (escb->scb->header.opcode != EMPTY_SCB) {
[Severity: High]
Does checking the escb->scb->header.opcode outside of the tc_index_lock
introduce a race condition?
If the hardware returns a bogus index corresponding to a live task, could
that task concurrently time out and be freed while we are evaluating the
opcode? This appears to introduce a use-after-free read when dereferencing
escb->scb since the tc_index_lock was already dropped earlier in
asd_get_response_tasklet().
> + ASD_DPRINTK("index_escb %d is not an ESCB, opcode is 0x%x!\n",
> + le16_to_cpu(resp_sb->index_escb),
> + escb->scb->header.opcode);
> + ts->stat = SAS_INTERRUPTED;
> + return;
> + }
> +
[ ... ]
> diff --git a/drivers/scsi/aic94xx/aic94xx_tmf.c b/drivers/scsi/aic94xx/aic94xx_tmf.c
> --- a/drivers/scsi/aic94xx/aic94xx_tmf.c
> +++ b/drivers/scsi/aic94xx/aic94xx_tmf.c
> @@ -282,6 +282,13 @@ static int asd_get_tmf_resp_tasklet(struct asd_ascb *ascb,
> return res;
> }
>
> + if (escb->scb->header.opcode != EMPTY_SCB) {
[Severity: High]
Can this lockless access to escb->scb in asd_get_tmf_resp_tasklet() also
risk a use-after-free if the escb is concurrently freed?
> + ASD_DPRINTK("index_escb %d is not an ESCB, opcode is 0x%x!\n",
> + le16_to_cpu(resp_sb->index_escb),
> + escb->scb->header.opcode);
> + return res;
> + }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261015081935.4168771-1-yhlee@isslab.korea.ac.kr?part=5
^ permalink raw reply [flat|nested] 7+ messages in thread