All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v9 0/9] scsi: mvumi: bound the values the firmware supplies
@ 2026-10-15  4:52 Yehyeong Lee
  2026-10-15  4:52 ` [PATCH v9 1/9] scsi: mvumi: bound the per-event parameter and sense dumps Yehyeong Lee
                   ` (8 more replies)
  0 siblings, 9 replies; 13+ messages in thread
From: Yehyeong Lee @ 2026-10-15  4:52 UTC (permalink / raw)
  To: Martin K . Petersen, James E . J . Bottomley
  Cc: linux-scsi, linux-kernel, stable, yhlee

This supersedes

  [PATCH v8 0/8] scsi: mvumi: bound the values the firmware supplies
  <20261015022728.3936714-1-yhlee@isslab.korea.ac.kr>

which can be dropped.

Patches 1-6 and 8 are unchanged from v8.  Patch 7 replaces v8's 7/8.
Patch 9 is new.

Everything the firmware writes reaches the driver either through the
handshake page, through the event buffer, or through a response frame in
the outbound list.  The patches follow that split:

 1  event buffer: param_count and sense_data_length index fixed arrays in
    mvumi_show_event().
 2  handshake page: frame_length drives the checksum walk, whose counter is
    an unsigned char, so 256 or more never terminates.
 3  event buffer: the hotplug bitmap size drives two walks with nothing
    holding it inside what the firmware was asked for.
 4  event buffer: those walks cast a four-byte-aligned bitmap to
    unsigned long *.
 5  handshake page: the entry sizes and max_io size three allocations, the
    list depth drives the ring index, the device count has to fit the byte
    mvumi_build_frame() gives it, a reset reports all of them again without
    reallocating anything, and every field is read once.
 6  response frame: the tag indexes tag_cmd[], and the value that is
    checked has to be the value that is used.
 7  DMA teardown: mvumi_delete_internal_cmd() rebuilt the buffer address
    out of the command frame with a shift that cannot produce the upper
    half of it.  Rather than widen the shift, keep the address and the
    length in struct mvumi_cmd and stop reading the frame back.
 8  outbound list: the 0xff the driver stamps over the tag of an entry it
    has consumed is a tag in range once the firmware asks for more than 255
    commands, so a stale entry is no longer turned away.  Stamp 0xffff,
    which is outside the u16 tag range whatever the firmware asks for.
 9  handshake: nothing bounds how many times mvumi_check_handshake() will
    answer the firmware, so a controller that keeps the handshake going
    keeps it going forever.  Count the steps and give up after 1024.

Patch 7 is what the review of v8 7/8 pointed at.  I had sent that as a
one-line fix to widen the shift; the review asked why the driver reads
those fields back out of the command frame at all, which is the better
question, so the read-back is gone instead and the shift with it.  The
truncation is not reachable as a bug: dma_set_mask() moves only the
streaming mask and nothing in this driver raises the coherent mask above
the 32 bits pci_device_add() sets, so dma_alloc_coherent() never returns
an address whose upper half could be lost.  No Cc: stable on that patch
for the same reason.

Patch 9 answers the review of v8 5/8, which said a rejected handshake page
would leave the driver retrying forever.  The quoted reason was a
"before = jiffies" reset inside the DRBL_HANDSHAKE_ISR branch, and there
is no such code in the tree -- mvumi_handshake_event() samples that once
on entry and times out after FW_MAX_DELAY -- but the outer loop in
mvumi_check_handshake() really is unbounded, so that is what patch 9
bounds.  It does not touch the doorbell writes or the HS_S_ABORT store, so
the FW_STATE_STARTING restart still works.

Two points from the v8 reviews I am not changing.

Endianness: the driver does no device-to-host endian conversion anywhere,
and these fields are plain u16 and u32 used natively throughout
(host->max_id = mhba->max_target_id, tag_pool.size = mhba->max_io).  The
new checks read the same values the driver already consumes, so they
follow the existing convention rather than change it.  Making the driver
big-endian-correct is a driver-wide change: fifteen multi-byte fields are
read from firmware-written memory without a conversion, across the
handshake header and page 1, the response frame, the event and hotplug
records, and the inbound frame fields the driver reads back.  I will send
that as its own series.  Patch 7 takes the SGL fields off that list, since
nothing reads them any more.  On a big-endian build the base driver
silently accepts a byte-swapped device count and scans phantom targets;
patch 5 turns that into an explicit probe failure, so the behaviour there
does change.

dma_rmb(): both reads of mhba->ob_list, and the memcpy of the payload, are
reached only through mvumi_receive_ob_list_entry(), which begins by
calling mvumi_check_ob_list_9143() or _9580().  Both of those read the
MMIO copy pointer with ioread32() before returning, and that read carries
the architecture's read barrier, which orders every load after it, so a
dma_rmb() further down would be covered by a barrier that has already run.
Making the tag the thing that is ordered against instead would need the
controller to write the payload before the tag, and the tag is at offset 2
of the response frame while the payload starts at offset 8.  Nothing in
the tree states a write order, so I have left it.

One more thing I left alone: the second hotplug bitmap starts at
bitmap[size >> 3], which is only self-consistent when size is a multiple
of eight, and nothing in the tree says whether the firmware packs the two
bitmaps bit by bit or aligns the second to a byte.  Either reading makes
the index wrong for a size that is not a multiple of eight, but correcting
it one way breaks the other, so patch 4 keeps the existing numbering
exactly.

I don't have mvumi hardware; these are reasoned from the code, not
reproduced on a controller.

Yehyeong Lee (9):
  scsi: mvumi: bound the per-event parameter and sense dumps
  scsi: mvumi: reject an out-of-range handshake frame length
  scsi: mvumi: bound the hotplug event bitmap
  scsi: mvumi: walk the hotplug event bitmap a byte at a time
  scsi: mvumi: reject handshake sizes that overflow the list allocations
  scsi: mvumi: validate the response frame tag before using it
  scsi: mvumi: free the internal command buffer by what was allocated
  scsi: mvumi: stamp a consumed outbound list entry out of range
  scsi: mvumi: bound the firmware handshake retry loop

 drivers/scsi/mvumi.c | 264 ++++++++++++++++++++++++++++++++-----------
 drivers/scsi/mvumi.h |  36 ++++--
 2 files changed, 226 insertions(+), 74 deletions(-)


base-commit: 24a19b953e2a3d0ea5a8c4951a0ab8f32720c907
-- 
2.43.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v9 1/9] scsi: mvumi: bound the per-event parameter and sense dumps
  2026-10-15  4:52 [PATCH v9 0/9] scsi: mvumi: bound the values the firmware supplies Yehyeong Lee
@ 2026-10-15  4:52 ` Yehyeong Lee
  2026-10-15  4:52 ` [PATCH v9 2/9] scsi: mvumi: reject an out-of-range handshake frame length Yehyeong Lee
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 13+ messages in thread
From: Yehyeong Lee @ 2026-10-15  4:52 UTC (permalink / raw)
  To: Martin K . Petersen, James E . J . Bottomley
  Cc: linux-scsi, linux-kernel, stable, yhlee

mvumi_show_event() prints param_count u32 words from params[] and
sense_data_length bytes from sense_data[], but both counts come from the
firmware event (struct mvumi_driver_event) and are never checked against
the arrays, which hold four words and 30 bytes.  A firmware that reports
the maximum (255 each) makes the loops read far past the event and past
the 512-byte buffer the event block is allocated in, printing the result
to the kernel log.

Stop each loop at the size of its array.

Fixes: f0c568a478f0 ("[SCSI] mvumi: Add Marvell UMI driver")
Cc: stable@vger.kernel.org
Signed-off-by: Yehyeong Lee <yhlee@isslab.korea.ac.kr>
---
 drivers/scsi/mvumi.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/drivers/scsi/mvumi.c b/drivers/scsi/mvumi.c
index e70d336b4ab39..38a92f8c25f5b 100644
--- a/drivers/scsi/mvumi.c
+++ b/drivers/scsi/mvumi.c
@@ -1361,7 +1361,8 @@ static void mvumi_show_event(struct mvumi_hba *mhba,
 	if (ptr->param_count) {
 		printk(KERN_WARNING "Event param(len 0x%x): ",
 						ptr->param_count);
-		for (i = 0; i < ptr->param_count; i++)
+		for (i = 0; i < ptr->param_count &&
+		     i < ARRAY_SIZE(ptr->params); i++)
 			printk(KERN_WARNING "0x%x ", ptr->params[i]);
 
 		printk(KERN_WARNING "\n");
@@ -1370,7 +1371,8 @@ static void mvumi_show_event(struct mvumi_hba *mhba,
 	if (ptr->sense_data_length) {
 		printk(KERN_WARNING "Event sense data(len 0x%x): ",
 						ptr->sense_data_length);
-		for (i = 0; i < ptr->sense_data_length; i++)
+		for (i = 0; i < ptr->sense_data_length &&
+		     i < sizeof(ptr->sense_data); i++)
 			printk(KERN_WARNING "0x%x ", ptr->sense_data[i]);
 		printk(KERN_WARNING "\n");
 	}
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v9 2/9] scsi: mvumi: reject an out-of-range handshake frame length
  2026-10-15  4:52 [PATCH v9 0/9] scsi: mvumi: bound the values the firmware supplies Yehyeong Lee
  2026-10-15  4:52 ` [PATCH v9 1/9] scsi: mvumi: bound the per-event parameter and sense dumps Yehyeong Lee
@ 2026-10-15  4:52 ` Yehyeong Lee
  2026-10-15  4:52 ` [PATCH v9 3/9] scsi: mvumi: bound the hotplug event bitmap Yehyeong Lee
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 13+ messages in thread
From: Yehyeong Lee @ 2026-10-15  4:52 UTC (permalink / raw)
  To: Martin K . Petersen, James E . J . Bottomley
  Cc: linux-scsi, linux-kernel, stable, yhlee

mvumi_hs_process_page() checksums and parses a handshake page using
frame_length from the page header, a u16 the firmware fills in, without
checking it against the HSP_MAX_SIZE buffer the page lives in.
mvumi_calculate_checksum() then walks frame_length bytes of frame_content
with an unsigned char loop counter, so a frame_length of 256 or more never
terminates and the probe path hangs; values between the buffer size and
255 read past the page.

Reject a frame_length that does not fit the handshake buffer before using
it.

Read frame_length once into a local and use it for both the bound check
and the checksum, so the device cannot change the value in the DMA page
between the two, and use READ_ONCE() so the single fetch is guaranteed
rather than left to the compiler.

Fixes: f0c568a478f0 ("[SCSI] mvumi: Add Marvell UMI driver")
Cc: stable@vger.kernel.org
Signed-off-by: Yehyeong Lee <yhlee@isslab.korea.ac.kr>
---
 drivers/scsi/mvumi.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/drivers/scsi/mvumi.c b/drivers/scsi/mvumi.c
index 38a92f8c25f5b..2378b676a7a78 100644
--- a/drivers/scsi/mvumi.c
+++ b/drivers/scsi/mvumi.c
@@ -1039,9 +1039,15 @@ static int mvumi_hs_process_page(struct mvumi_hba *mhba,
 {
 	struct mvumi_hs_page1 *hs_page1;
 	unsigned char page_checksum;
+	u16 frame_length = READ_ONCE(hs_header->frame_length);
 
-	page_checksum = mvumi_calculate_checksum(hs_header,
-						hs_header->frame_length);
+	if (frame_length > HSP_MAX_SIZE - sizeof(struct mvumi_hs_header)) {
+		dev_err(&mhba->pdev->dev, "invalid handshake frame length %d\n",
+			frame_length);
+		return -1;
+	}
+
+	page_checksum = mvumi_calculate_checksum(hs_header, frame_length);
 	if (page_checksum != hs_header->checksum) {
 		dev_err(&mhba->pdev->dev, "checksum error\n");
 		return -1;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v9 3/9] scsi: mvumi: bound the hotplug event bitmap
  2026-10-15  4:52 [PATCH v9 0/9] scsi: mvumi: bound the values the firmware supplies Yehyeong Lee
  2026-10-15  4:52 ` [PATCH v9 1/9] scsi: mvumi: bound the per-event parameter and sense dumps Yehyeong Lee
  2026-10-15  4:52 ` [PATCH v9 2/9] scsi: mvumi: reject an out-of-range handshake frame length Yehyeong Lee
@ 2026-10-15  4:52 ` Yehyeong Lee
  2026-10-15  5:08   ` sashiko-bot
  2026-10-15  4:52 ` [PATCH v9 4/9] scsi: mvumi: walk the hotplug event bitmap a byte at a time Yehyeong Lee
                   ` (5 subsequent siblings)
  8 siblings, 1 reply; 13+ messages in thread
From: Yehyeong Lee @ 2026-10-15  4:52 UTC (permalink / raw)
  To: Martin K . Petersen, James E . J . Bottomley
  Cc: linux-scsi, linux-kernel, stable, yhlee

mvumi_proc_msg() takes size from the hotplug event the firmware DMAs into
the event buffer and uses it twice: as the bit count for two
find_next_zero_bit() scans, and as the byte offset of the second bitmap.
Nothing checks it against the buffer, which mvumi_get_event() allocates at
a fixed 512 bytes, and size is a u16, so a firmware reporting 65535 makes
both scans run roughly 8 KiB past the allocation and places the second
bitmap past it to begin with.

Reject a size whose bitmaps do not fit.  The second bitmap starts size bits
in and find_next_zero_bit() reads a long at a time, so that span is the
furthest either scan reaches.  Compare it against sizeof(struct
mvumi_event_req), the only length the driver ever puts in
data_transfer_length and therefore the most the firmware may write into the
shared buffer, rather than against the buffer itself: dma_alloc_coherent()
hands back zeroed memory, a zero bit in these bitmaps means a device is
present, so scanning past what the firmware wrote would add devices that do
not exist.  Read size once, since it lives in memory the firmware can still
write, and give the buffer size a name while here.

Fixes: bd756ddea18e ("[SCSI] mvumi: Add support for Marvell SAS/SATA RAID-on-Chip(ROC) 88RC9580")
Cc: stable@vger.kernel.org
Signed-off-by: Yehyeong Lee <yhlee@isslab.korea.ac.kr>
---
 drivers/scsi/mvumi.c | 18 ++++++++++++++++--
 drivers/scsi/mvumi.h |  3 +++
 2 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/drivers/scsi/mvumi.c b/drivers/scsi/mvumi.c
index 2378b676a7a78..9a3dde53dea97 100644
--- a/drivers/scsi/mvumi.c
+++ b/drivers/scsi/mvumi.c
@@ -1652,11 +1652,25 @@ static int mvumi_rescan_bus(void *data)
 static void mvumi_proc_msg(struct mvumi_hba *mhba,
 					struct mvumi_hotplug_event *param)
 {
-	u16 size = param->size;
+	u16 size = READ_ONCE(param->size);
 	const unsigned long *ar_bitmap;
 	const unsigned long *re_bitmap;
 	int index;
 
+	/*
+	 * The second bitmap starts size bits into the event buffer and
+	 * find_next_zero_bit() reads it a long at a time, so that is the
+	 * furthest the two scans reach.  Bound it by the length the firmware
+	 * was told it may write, not by the buffer: the bytes past that are
+	 * still zero, and a zero bit here means a device is present.
+	 */
+	if (sizeof(*param) + (size >> 3) + BITS_TO_LONGS(size) * sizeof(long) >
+			sizeof(struct mvumi_event_req)) {
+		dev_err(&mhba->pdev->dev,
+			"invalid hotplug event bitmap size %u\n", size);
+		return;
+	}
+
 	if (mhba->fw_flag & MVUMI_FW_ATTACH) {
 		index = -1;
 		ar_bitmap = (const unsigned long *) param->bitmap;
@@ -1708,7 +1722,7 @@ static int mvumi_get_event(struct mvumi_hba *mhba, unsigned char msg)
 	struct mvumi_cmd *cmd;
 	struct mvumi_msg_frame *frame;
 
-	cmd = mvumi_create_internal_cmd(mhba, 512);
+	cmd = mvumi_create_internal_cmd(mhba, MVUMI_EVENT_BUF_SIZE);
 	if (!cmd)
 		return -1;
 	cmd->scmd = NULL;
diff --git a/drivers/scsi/mvumi.h b/drivers/scsi/mvumi.h
index 1306a4abf19ad..5d45f0d99e639 100644
--- a/drivers/scsi/mvumi.h
+++ b/drivers/scsi/mvumi.h
@@ -124,6 +124,9 @@ enum {
 #define APICDB1_HOST_GETEVENT		1
 #define MAX_EVENTS_RETURNED		6
 
+/* size of the buffer the driver hands the firmware for event replies */
+#define MVUMI_EVENT_BUF_SIZE		512
+
 #define DEVICE_OFFLINE	0
 #define DEVICE_ONLINE	1
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v9 4/9] scsi: mvumi: walk the hotplug event bitmap a byte at a time
  2026-10-15  4:52 [PATCH v9 0/9] scsi: mvumi: bound the values the firmware supplies Yehyeong Lee
                   ` (2 preceding siblings ...)
  2026-10-15  4:52 ` [PATCH v9 3/9] scsi: mvumi: bound the hotplug event bitmap Yehyeong Lee
@ 2026-10-15  4:52 ` Yehyeong Lee
  2026-10-15  4:52 ` [PATCH v9 5/9] scsi: mvumi: reject handshake sizes that overflow the list allocations Yehyeong Lee
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 13+ messages in thread
From: Yehyeong Lee @ 2026-10-15  4:52 UTC (permalink / raw)
  To: Martin K . Petersen, James E . J . Bottomley
  Cc: linux-scsi, linux-kernel, stable, yhlee

mvumi_proc_msg() casts param->bitmap to unsigned long * and hands it to
find_next_zero_bit().  bitmap sits four bytes into struct
mvumi_hotplug_event, so on a 64-bit host the pointer is four-byte aligned
where the bitmap API expects long alignment.  On an architecture that
traps unaligned loads that faults, and the second bitmap, which starts at
an arbitrary byte offset derived from size, can be misaligned on any host.

Walk the bitmap bytes instead.  The driver reads every other
firmware-supplied field natively, so bit n of the bitmap is already byte
n / 8 bit n % 8 in practice; writing that out keeps the device numbering
this driver has always used, needs no alignment from the buffer, and drops
the long-at-a-time reads the earlier bound had to allow for.  That bound
stays as it is, which is now more conservative than the walk needs.

Fixes: bd756ddea18e ("[SCSI] mvumi: Add support for Marvell SAS/SATA RAID-on-Chip(ROC) 88RC9580")
Cc: stable@vger.kernel.org
Signed-off-by: Yehyeong Lee <yhlee@isslab.korea.ac.kr>
---
 drivers/scsi/mvumi.c | 41 +++++++++++++++++------------------------
 1 file changed, 17 insertions(+), 24 deletions(-)

diff --git a/drivers/scsi/mvumi.c b/drivers/scsi/mvumi.c
index 9a3dde53dea97..18964e860ac8e 100644
--- a/drivers/scsi/mvumi.c
+++ b/drivers/scsi/mvumi.c
@@ -1653,16 +1653,15 @@ static void mvumi_proc_msg(struct mvumi_hba *mhba,
 					struct mvumi_hotplug_event *param)
 {
 	u16 size = READ_ONCE(param->size);
-	const unsigned long *ar_bitmap;
-	const unsigned long *re_bitmap;
+	const u8 *ar_bitmap;
+	const u8 *re_bitmap;
 	int index;
 
 	/*
-	 * The second bitmap starts size bits into the event buffer and
-	 * find_next_zero_bit() reads it a long at a time, so that is the
-	 * furthest the two scans reach.  Bound it by the length the firmware
-	 * was told it may write, not by the buffer: the bytes past that are
-	 * still zero, and a zero bit here means a device is present.
+	 * The second bitmap starts size bits into the event buffer, so that
+	 * is the furthest the two walks reach.  Bound it by the length the
+	 * firmware was told it may write, not by the buffer: the bytes past
+	 * that are still zero, and a zero bit here means a device is present.
 	 */
 	if (sizeof(*param) + (size >> 3) + BITS_TO_LONGS(size) * sizeof(long) >
 			sizeof(struct mvumi_event_req)) {
@@ -1672,25 +1671,19 @@ static void mvumi_proc_msg(struct mvumi_hba *mhba,
 	}
 
 	if (mhba->fw_flag & MVUMI_FW_ATTACH) {
-		index = -1;
-		ar_bitmap = (const unsigned long *) param->bitmap;
-		re_bitmap = (const unsigned long *) &param->bitmap[size >> 3];
+		ar_bitmap = param->bitmap;
+		re_bitmap = &param->bitmap[size >> 3];
 
 		mutex_lock(&mhba->sas_discovery_mutex);
-		do {
-			index = find_next_zero_bit(ar_bitmap, size, index + 1);
-			if (index >= size)
-				break;
-			mvumi_handle_hotplug(mhba, index, DEVICE_ONLINE);
-		} while (1);
-
-		index = -1;
-		do {
-			index = find_next_zero_bit(re_bitmap, size, index + 1);
-			if (index >= size)
-				break;
-			mvumi_handle_hotplug(mhba, index, DEVICE_OFFLINE);
-		} while (1);
+		for (index = 0; index < size; index++)
+			if (!(ar_bitmap[index >> 3] & (1 << (index & 7))))
+				mvumi_handle_hotplug(mhba, index,
+						     DEVICE_ONLINE);
+
+		for (index = 0; index < size; index++)
+			if (!(re_bitmap[index >> 3] & (1 << (index & 7))))
+				mvumi_handle_hotplug(mhba, index,
+						     DEVICE_OFFLINE);
 		mutex_unlock(&mhba->sas_discovery_mutex);
 	}
 }
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v9 5/9] scsi: mvumi: reject handshake sizes that overflow the list allocations
  2026-10-15  4:52 [PATCH v9 0/9] scsi: mvumi: bound the values the firmware supplies Yehyeong Lee
                   ` (3 preceding siblings ...)
  2026-10-15  4:52 ` [PATCH v9 4/9] scsi: mvumi: walk the hotplug event bitmap a byte at a time Yehyeong Lee
@ 2026-10-15  4:52 ` Yehyeong Lee
  2026-10-15  4:52 ` [PATCH v9 6/9] scsi: mvumi: validate the response frame tag before using it Yehyeong Lee
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 13+ messages in thread
From: Yehyeong Lee @ 2026-10-15  4:52 UTC (permalink / raw)
  To: Martin K . Petersen, James E . J . Bottomley
  Cc: linux-scsi, linux-kernel, stable, yhlee

mvumi_hs_process_page() takes max_io, the two command entry sizes and the
list depth from the firmware handshake page.  The entry sizes are u8 fields
used as shift counts, and 1 is an int, so a count of 29 or more makes the
<< 2 applied to the shift overflow an int; a count of 32 or more is
undefined outright.  max_io is a u16, and the sizes derived from the shifts
are multiplied by it in mvumi_init_data() to size three unsigned int
allocations, so a large entry size wraps the product and the loops that
follow then walk and write past the short allocation.  The list depth is
used as a shift count too, but only when the firmware sets
HS_CAPABILITY_NEW_PAGE_IO_DEPTH_DEF; a count of 32 or more is undefined
there as well.  Nothing bounds the entry sizes from below either, and an
entry has to hold what the driver puts in it: a command frame and at least
one scatter-gather element going in, a response frame and sense data coming
back.  An entry size of 32 bytes or less makes mvumi_internal_cmd_sgl()
write its element past an inbound entry, since it writes one whatever
host->sg_tablesize says, and makes mvumi_complete_cmd() read sense data
past an outbound one; it also makes the max_sg subtraction in
mvumi_io_attach() wrap, which hands mvumi_build_frame() and
mvumi_send_command() a table size no entry can hold.  The depth also sets
how many entries the inbound and outbound rings hold, and the per-command
slot index wraps on that count while the rings are allocated for max_io
entries, so a depth yielding more entries than max_io -- or none -- walks
the index past them.  max_devices_support sizes the target_map bitmap in
the same allocation, and it is a u16 while mvumi_build_frame() packs the
target id into the low byte of device_id and the LUN into the high byte, so
a target of 256 or more aliases another device's LUN; it is also what makes
the unsigned char loop counter in mvumi_flush_cache() wrap before it
reaches the bound, which never ends that loop.  A host reset runs the
handshake again, but mvumi_init_data() returns early once MVUMI_FW_ALLOC is
set, so the buffers keep the sizes they were allocated with while the newly
reported values drive the indexing: mvumi_flush_cache() then reads
target_map past its end and mvumi_sdev_configure() sets a bit past it.  The
handshake is retried as well, because mvumi_handshake_event() discards the
error, so a page that was already rejected can be presented again.

Reject an entry size above 29, which keeps 1U << n and the shift that
follows it inside an unsigned int, and reject one too small to hold those
frames.  Reject a device count the frame layout cannot address, which also
keeps mvumi_flush_cache() terminating.  Then compute the worst-case
allocation size in u64 at the top of mvumi_init_data() and reject it if it
does not fit an unsigned int.  That total bounds all three allocations: the
inbound list total, the outbound data pool and the tag arrays, whose sizes
are the same products plus smaller terms.  Reject a list depth of 32 or
more on the shifted path so the shift stays defined, and reject a resulting
entry count that is zero or above max_io so the slot index cannot walk past
the rings.  The shifts also use 1U now so that a legal count cannot
overflow an int.  Take every field the page carries into a local with
READ_ONCE(), and run every check and every assignment on those.  The page
is coherent DMA memory, so a second fetch of a field is not the value that
was checked: a second fetch of an entry size could turn a count of 29 into
one of 31, a second fetch of the list depth could turn 31 into 32, which is
the undefined shift the check exists to stop, and a second fetch of the
device count could put a value above 255 into mhba->max_target_id after the
bound above has passed.  Once MVUMI_FW_ALLOC is set the buffers already
exist, so only check that no reported size, max_devices_support included,
is larger than the one its buffer was allocated for, and leave the size
fields alone: they already hold the sizes those buffers have.  Only the
first handshake, whose values size the allocations, assigns them.

No realistic controller is rejected: one reporting 4096 commands with 64
KiB entries and a depth of 12 needs 512 MiB and passes unchanged.  The
device count is the one new bound that could turn a controller away, and a
controller reporting more than 255 devices is already one this driver
cannot address.

Fixes: f0c568a478f0 ("[SCSI] mvumi: Add Marvell UMI driver")
Cc: stable@vger.kernel.org
Signed-off-by: Yehyeong Lee <yhlee@isslab.korea.ac.kr>
---
 drivers/scsi/mvumi.c | 124 ++++++++++++++++++++++++++++++++++++++-----
 drivers/scsi/mvumi.h |   6 +++
 2 files changed, 117 insertions(+), 13 deletions(-)

diff --git a/drivers/scsi/mvumi.c b/drivers/scsi/mvumi.c
index 18964e860ac8e..8b8e5545747e8 100644
--- a/drivers/scsi/mvumi.c
+++ b/drivers/scsi/mvumi.c
@@ -912,10 +912,23 @@ static int mvumi_init_data(struct mvumi_hba *mhba)
 	unsigned int tmp_size, offset, i;
 	void *virmem, *v;
 	dma_addr_t p;
+	u64 total;
 
 	if (mhba->fw_flag & MVUMI_FW_ALLOC)
 		return 0;
 
+	/* These are firmware-supplied and size the allocations below. */
+	total = (u64)mhba->ib_max_size * mhba->max_io +
+		(u64)mhba->ob_max_size * mhba->max_io +
+		sizeof(struct mvumi_dyn_list_entry) * (u64)mhba->max_io +
+		160;
+	if (total > UINT_MAX) {
+		dev_err(&mhba->pdev->dev,
+			"handshake sizes too large: io %u, in %u, out %u\n",
+			mhba->max_io, mhba->ib_max_size, mhba->ob_max_size);
+		return -1;
+	}
+
 	tmp_size = mhba->ib_max_size * mhba->max_io;
 	if (mhba->hba_capability & HS_CAPABILITY_SUPPORT_DYN_SRC)
 		tmp_size += sizeof(struct mvumi_dyn_list_entry) * mhba->max_io;
@@ -1054,30 +1067,115 @@ static int mvumi_hs_process_page(struct mvumi_hba *mhba,
 	}
 
 	switch (hs_header->page_code) {
-	case HS_PAGE_FIRM_CAP:
+	case HS_PAGE_FIRM_CAP: {
+		unsigned int new_max_io, new_ib_max_size, new_ob_max_size;
+		unsigned int new_list_num_io;
+		unsigned short new_max_target_id, new_capability;
+		unsigned int new_max_transfer_size;
+		u8 in_sz, out_sz, list_depth;
+
 		hs_page1 = (struct mvumi_hs_page1 *) hs_header;
 
-		mhba->max_io = hs_page1->max_io_support;
-		mhba->list_num_io = hs_page1->cl_inout_list_depth;
-		mhba->max_transfer_size = hs_page1->max_transfer_size;
-		mhba->max_target_id = hs_page1->max_devices_support;
-		mhba->hba_capability = hs_page1->capability;
-		mhba->ib_max_size_setting = hs_page1->cl_in_max_entry_size;
-		mhba->ib_max_size = (1 << hs_page1->cl_in_max_entry_size) << 2;
+		/*
+		 * The page is in coherent DMA memory, so take every field
+		 * out of it once: a second fetch of a value that has already
+		 * been checked would not be the value that was checked.
+		 */
+		in_sz = READ_ONCE(hs_page1->cl_in_max_entry_size);
+		out_sz = READ_ONCE(hs_page1->cl_out_max_entry_size);
+		list_depth = READ_ONCE(hs_page1->cl_inout_list_depth);
+		new_max_io = READ_ONCE(hs_page1->max_io_support);
+		new_max_target_id = READ_ONCE(hs_page1->max_devices_support);
+		new_capability = READ_ONCE(hs_page1->capability);
+		new_max_transfer_size = READ_ONCE(hs_page1->max_transfer_size);
+
+		/* Firmware-supplied shift counts: (1U << n) << 2 must fit. */
+		if (in_sz > 29 || out_sz > 29) {
+			dev_err(&mhba->pdev->dev,
+				"invalid handshake entry size\n");
+			return -1;
+		}
+
+		new_ib_max_size = (1U << in_sz) << 2;
+		new_ob_max_size = (1U << out_sz) << 2;
+
+		/*
+		 * A target above MVUMI_MAX_TARGET_ID cannot be addressed: its
+		 * id would land in the LUN byte of device_id and alias another
+		 * device.  It is also what makes the loop counter in
+		 * mvumi_flush_cache() wrap before it reaches the bound.
+		 */
+		if (new_max_target_id > MVUMI_MAX_TARGET_ID) {
+			dev_err(&mhba->pdev->dev,
+				"too many devices reported: %u\n",
+				new_max_target_id);
+			return -1;
+		}
 
-		mhba->ob_max_size_setting = hs_page1->cl_out_max_entry_size;
-		mhba->ob_max_size = (1 << hs_page1->cl_out_max_entry_size) << 2;
+		/* An entry has to hold the frame the driver puts in it. */
+		if (new_ib_max_size < sizeof(struct mvumi_msg_frame) +
+					sizeof(struct mvumi_sgl) ||
+		    new_ob_max_size < sizeof(struct mvumi_rsp_frame) +
+					sizeof(struct mvumi_sense_data)) {
+			dev_err(&mhba->pdev->dev,
+				"handshake entry size too small: in %u, out %u\n",
+				new_ib_max_size, new_ob_max_size);
+			return -1;
+		}
+
+		new_list_num_io = list_depth;
+		if (new_capability & HS_CAPABILITY_NEW_PAGE_IO_DEPTH_DEF) {
+			if (list_depth >= 32) {
+				dev_err(&mhba->pdev->dev,
+					"invalid handshake list depth\n");
+				return -1;
+			}
+			new_list_num_io = 1U << list_depth;
+		}
+		/* The rings hold max_io entries but the index wraps on list_num_io. */
+		if (new_list_num_io == 0 || new_list_num_io > new_max_io) {
+			dev_err(&mhba->pdev->dev,
+				"invalid handshake list depth: %u entries for %u commands\n",
+				new_list_num_io, new_max_io);
+			return -1;
+		}
 
 		dev_dbg(&mhba->pdev->dev, "FW version:%d\n",
-						hs_page1->fw_ver.ver_build);
+				READ_ONCE(hs_page1->fw_ver.ver_build));
+
+		/* A host reset does not reallocate, so the sizes have to stay
+		 * the ones the buffers were allocated with.  Only check that
+		 * the firmware is not asking for more than they hold.
+		 */
+		if (mhba->fw_flag & MVUMI_FW_ALLOC) {
+			if (new_max_io > mhba->max_io ||
+			    new_ib_max_size > mhba->ib_max_size ||
+			    new_ob_max_size > mhba->ob_max_size ||
+			    new_list_num_io > mhba->list_num_io ||
+			    new_max_target_id > mhba->max_target_id) {
+				dev_err(&mhba->pdev->dev,
+					"handshake sizes grew across reset\n");
+				return -1;
+			}
+			break;
+		}
+
+		mhba->max_io = new_max_io;
+		mhba->list_num_io = new_list_num_io;
+		mhba->max_transfer_size = new_max_transfer_size;
+		mhba->max_target_id = new_max_target_id;
+		mhba->hba_capability = new_capability;
+		mhba->ib_max_size_setting = in_sz;
+		mhba->ib_max_size = new_ib_max_size;
+		mhba->ob_max_size_setting = out_sz;
+		mhba->ob_max_size = new_ob_max_size;
 
 		if (mhba->hba_capability & HS_CAPABILITY_SUPPORT_COMPACT_SG)
 			mhba->eot_flag = 22;
 		else
 			mhba->eot_flag = 27;
-		if (mhba->hba_capability & HS_CAPABILITY_NEW_PAGE_IO_DEPTH_DEF)
-			mhba->list_num_io = 1 << hs_page1->cl_inout_list_depth;
 		break;
+	}
 	default:
 		dev_err(&mhba->pdev->dev, "handshake: page code error\n");
 		return -1;
diff --git a/drivers/scsi/mvumi.h b/drivers/scsi/mvumi.h
index 5d45f0d99e639..d20eea2b61a31 100644
--- a/drivers/scsi/mvumi.h
+++ b/drivers/scsi/mvumi.h
@@ -127,6 +127,12 @@ enum {
 /* size of the buffer the driver hands the firmware for event replies */
 #define MVUMI_EVENT_BUF_SIZE		512
 
+/*
+ * mvumi_build_frame() packs the target id into the low byte of device_id
+ * and the LUN into the high byte, so only these ids can be addressed.
+ */
+#define MVUMI_MAX_TARGET_ID		255
+
 #define DEVICE_OFFLINE	0
 #define DEVICE_ONLINE	1
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v9 6/9] scsi: mvumi: validate the response frame tag before using it
  2026-10-15  4:52 [PATCH v9 0/9] scsi: mvumi: bound the values the firmware supplies Yehyeong Lee
                   ` (4 preceding siblings ...)
  2026-10-15  4:52 ` [PATCH v9 5/9] scsi: mvumi: reject handshake sizes that overflow the list allocations Yehyeong Lee
@ 2026-10-15  4:52 ` Yehyeong Lee
  2026-10-15  4:52 ` [PATCH v9 7/9] scsi: mvumi: free the internal command buffer by what was allocated Yehyeong Lee
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 13+ messages in thread
From: Yehyeong Lee @ 2026-10-15  4:52 UTC (permalink / raw)
  To: Martin K . Petersen, James E . J . Bottomley
  Cc: linux-scsi, linux-kernel, stable, yhlee

mvumi_handle_clob() indexes mhba->tag_cmd[] with the tag of a response
frame the firmware DMAs into the outbound list.  The array holds
tag_pool.size entries, and the only check on that tag runs earlier, in
mvumi_receive_ob_list_entry() and mvumi_check_ob_frame(), against the frame
still sitting in the outbound list.  mvumi_receive_ob_list_entry() then
copies the frame into an ob_data buffer and mvumi_handle_clob() reads the
tag out of that copy, so the value that is checked and the value that is
used are two separate fetches of firmware-writable memory.  A firmware that
changes the tag after the check reaches tag_cmd[] with any u16, reads a
pointer past the array and dereferences it, writes NULL past the array and
pushes the tag onto the free stack for a later command to pick up.  The
check is also off by one: tag_cmd[] and tag_pool.stack[] both hold
tag_pool.size entries, so a tag equal to tag_pool.size passes a > test and
lands one entry past both.

Check the tag that mvumi_handle_clob() actually uses, against the size of
the array it indexes, and check the command it finds for NULL before
dereferencing it: with two fetches the earlier NULL check no longer says
anything about this one.  Re-check request_id against that command too,
where the controller provides one, for the same reason the earlier check
does: a tag changed between the check and the copy can land on a different
command that is also live, and then only request_id tells the two apart.
Make the two existing bounds checks >= so a tag equal to the entry count is
rejected there too, and read the tag into a local in
mvumi_receive_ob_list_entry() as well, so the check there and the accesses
beside it see one value instead of three fetches.  Use READ_ONCE() for the
tag and request_id both functions take out of the outbound list, so the
single fetch is guaranteed rather than left to the compiler.

Fixes: f0c568a478f0 ("[SCSI] mvumi: Add Marvell UMI driver")
Cc: stable@vger.kernel.org
Signed-off-by: Yehyeong Lee <yhlee@isslab.korea.ac.kr>
---
 drivers/scsi/mvumi.c | 45 ++++++++++++++++++++++++++++++++++----------
 1 file changed, 35 insertions(+), 10 deletions(-)

diff --git a/drivers/scsi/mvumi.c b/drivers/scsi/mvumi.c
index 8b8e5545747e8..2410d2aa147d9 100644
--- a/drivers/scsi/mvumi.c
+++ b/drivers/scsi/mvumi.c
@@ -466,9 +466,9 @@ static char mvumi_check_ob_frame(struct mvumi_hba *mhba,
 
 	udelay(1);
 	p_outb_frame = mhba->ob_list + cur_obf * mhba->ob_max_size;
-	request_id = p_outb_frame->request_id;
-	tag = p_outb_frame->tag;
-	if (tag > mhba->tag_pool.size) {
+	request_id = READ_ONCE(p_outb_frame->request_id);
+	tag = READ_ONCE(p_outb_frame->tag);
+	if (tag >= mhba->tag_pool.size) {
 		dev_err(&mhba->pdev->dev, "ob frame data error\n");
 		return -1;
 	}
@@ -530,6 +530,7 @@ static void mvumi_receive_ob_list_entry(struct mvumi_hba *mhba)
 	struct mvumi_ob_data *ob_data;
 	struct mvumi_rsp_frame *p_outb_frame;
 	struct mvumi_hw_regs *regs = mhba->regs;
+	unsigned short tag;
 
 	if (mhba->instancet->check_ob_list(mhba, &cur_obf, &assign_obf_end))
 		return;
@@ -546,10 +547,11 @@ static void mvumi_receive_ob_list_entry(struct mvumi_hba *mhba)
 		/* Copy pointer may point to entry in outbound list
 		*  before entry has valid data
 		*/
-		if (unlikely(p_outb_frame->tag > mhba->tag_pool.size ||
-			mhba->tag_cmd[p_outb_frame->tag] == NULL ||
-			p_outb_frame->request_id !=
-				mhba->tag_cmd[p_outb_frame->tag]->request_id))
+		tag = READ_ONCE(p_outb_frame->tag);
+		if (unlikely(tag >= mhba->tag_pool.size ||
+			     !mhba->tag_cmd[tag] ||
+			     mhba->tag_cmd[tag]->request_id !=
+			     READ_ONCE(p_outb_frame->request_id)))
 			if (mvumi_check_ob_frame(mhba, cur_obf, p_outb_frame))
 				continue;
 
@@ -1878,6 +1880,7 @@ static void mvumi_handle_clob(struct mvumi_hba *mhba)
 	struct mvumi_rsp_frame *ob_frame;
 	struct mvumi_cmd *cmd;
 	struct mvumi_ob_data *pool;
+	unsigned short tag;
 
 	while (!list_empty(&mhba->free_ob_list)) {
 		pool = list_first_entry(&mhba->free_ob_list,
@@ -1886,11 +1889,33 @@ static void mvumi_handle_clob(struct mvumi_hba *mhba)
 		list_add_tail(&pool->list, &mhba->ob_data_list);
 
 		ob_frame = (struct mvumi_rsp_frame *) &pool->data[0];
-		cmd = mhba->tag_cmd[ob_frame->tag];
+		/*
+		 * The tag was checked against the frame in the outbound list.
+		 * This is the copy, so check the value that is used.
+		 */
+		tag = ob_frame->tag;
+		if (tag >= mhba->tag_pool.size) {
+			dev_err(&mhba->pdev->dev,
+				"ob frame tag[0x%x] out of range\n", tag);
+			continue;
+		}
+		cmd = mhba->tag_cmd[tag];
+		if (!cmd) {
+			dev_err(&mhba->pdev->dev,
+				"ob frame tag[0x%x] with no command\n", tag);
+			continue;
+		}
+		if (mhba->request_id_enabled &&
+		    ob_frame->request_id != cmd->request_id) {
+			dev_err(&mhba->pdev->dev,
+				"ob frame tag[0x%x] request ID 0x%x != 0x%x\n",
+				tag, ob_frame->request_id, cmd->request_id);
+			continue;
+		}
 
 		atomic_dec(&mhba->fw_outstanding);
-		mhba->tag_cmd[ob_frame->tag] = NULL;
-		tag_release_one(mhba, &mhba->tag_pool, ob_frame->tag);
+		mhba->tag_cmd[tag] = NULL;
+		tag_release_one(mhba, &mhba->tag_pool, tag);
 		if (cmd->scmd)
 			mvumi_complete_cmd(mhba, cmd, ob_frame);
 		else
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v9 7/9] scsi: mvumi: free the internal command buffer by what was allocated
  2026-10-15  4:52 [PATCH v9 0/9] scsi: mvumi: bound the values the firmware supplies Yehyeong Lee
                   ` (5 preceding siblings ...)
  2026-10-15  4:52 ` [PATCH v9 6/9] scsi: mvumi: validate the response frame tag before using it Yehyeong Lee
@ 2026-10-15  4:52 ` Yehyeong Lee
  2026-10-15  5:03   ` sashiko-bot
  2026-10-15  4:52 ` [PATCH v9 8/9] scsi: mvumi: stamp a consumed outbound list entry out of range Yehyeong Lee
  2026-10-15  4:52 ` [PATCH v9 9/9] scsi: mvumi: bound the firmware handshake retry loop Yehyeong Lee
  8 siblings, 1 reply; 13+ messages in thread
From: Yehyeong Lee @ 2026-10-15  4:52 UTC (permalink / raw)
  To: Martin K . Petersen, James E . J . Bottomley
  Cc: linux-scsi, linux-kernel, stable, yhlee

mvumi_internal_cmd_sgl() allocates the internal command's data buffer
with dma_alloc_coherent() and then publishes the address and the length
to the controller by writing them into the command frame: lower_32_bits()
in baseaddr_l, upper_32_bits() in baseaddr_h and the length through
sgd_setsz().

mvumi_delete_internal_cmd() did not keep any of that.  It read the frame
back to work out what to free -- sg_counts to decide whether there was a
buffer at all, sgd_getsz() for the length, baseaddr_h and baseaddr_l for
the address -- and handed the three results to dma_free_coherent().

That rebuild does not say what it means to say:

	phy_addr = (dma_addr_t) m_sg->baseaddr_l |
		(dma_addr_t) ((m_sg->baseaddr_h << 16) << 16);

baseaddr_h is a u32, so both shifts happen in 32-bit arithmetic and the
term is always zero; the upper half is never put back.  That is not
visible today, because the driver never raises this device's coherent DMA
mask above the 32 bits pci_device_add() gives it -- dma_set_mask() moves
only the streaming mask -- so dma_alloc_coherent() cannot hand out an
address with an upper half to lose.  It is still not the address the
expression is written to produce.

Rather than widen the shift, stop reading the frame back.  The driver
already has every value it needs: keep the address and the length in
struct mvumi_cmd next to the virtual address it keeps there now, free by
those, and decide whether there is a buffer from data_buf instead of from
sg_counts.

sgd_getsz() and GET_COMPACT_SGD_SIZE() had no other caller, so they go
too.  sgd_setsz(), which writes the field, is unchanged.

Fixes: f0c568a478f0 ("[SCSI] mvumi: Add Marvell UMI driver")
Signed-off-by: Yehyeong Lee <yhlee@isslab.korea.ac.kr>
---
 drivers/scsi/mvumi.c | 19 +++++--------------
 drivers/scsi/mvumi.h | 12 ++----------
 2 files changed, 7 insertions(+), 24 deletions(-)

diff --git a/drivers/scsi/mvumi.c b/drivers/scsi/mvumi.c
index 2410d2aa147d9..133e53bc2cb37 100644
--- a/drivers/scsi/mvumi.c
+++ b/drivers/scsi/mvumi.c
@@ -238,6 +238,8 @@ static int mvumi_internal_cmd_sgl(struct mvumi_hba *mhba, struct mvumi_cmd *cmd,
 	m_sg = (struct mvumi_sgl *) &cmd->frame->payload[0];
 	cmd->frame->sg_counts = 1;
 	cmd->data_buf = virt_addr;
+	cmd->data_buf_phys = phy_addr;
+	cmd->data_buf_size = size;
 
 	m_sg->baseaddr_l = cpu_to_le32(lower_32_bits(phy_addr));
 	m_sg->baseaddr_h = cpu_to_le32(upper_32_bits(phy_addr));
@@ -286,21 +288,10 @@ static struct mvumi_cmd *mvumi_create_internal_cmd(struct mvumi_hba *mhba,
 static void mvumi_delete_internal_cmd(struct mvumi_hba *mhba,
 						struct mvumi_cmd *cmd)
 {
-	struct mvumi_sgl *m_sg;
-	unsigned int size;
-	dma_addr_t phy_addr;
-
 	if (cmd && cmd->frame) {
-		if (cmd->frame->sg_counts) {
-			m_sg = (struct mvumi_sgl *) &cmd->frame->payload[0];
-			sgd_getsz(mhba, m_sg, size);
-
-			phy_addr = (dma_addr_t) m_sg->baseaddr_l |
-				(dma_addr_t) ((m_sg->baseaddr_h << 16) << 16);
-
-			dma_free_coherent(&mhba->pdev->dev, size, cmd->data_buf,
-								phy_addr);
-		}
+		if (cmd->data_buf)
+			dma_free_coherent(&mhba->pdev->dev, cmd->data_buf_size,
+					  cmd->data_buf, cmd->data_buf_phys);
 		dma_free_coherent(&mhba->pdev->dev, mhba->ib_max_size,
 				cmd->frame, cmd->frame_phys);
 		kfree(cmd);
diff --git a/drivers/scsi/mvumi.h b/drivers/scsi/mvumi.h
index d20eea2b61a31..3fc76addbf304 100644
--- a/drivers/scsi/mvumi.h
+++ b/drivers/scsi/mvumi.h
@@ -189,20 +189,10 @@ struct mvumi_compact_sgl {
 	u32	flags;
 };
 
-#define GET_COMPACT_SGD_SIZE(sgd)	\
-	((((struct mvumi_compact_sgl *)(sgd))->flags) & 0x3FFFFFL)
-
 #define SET_COMPACT_SGD_SIZE(sgd, sz) do {			\
 	(((struct mvumi_compact_sgl *)(sgd))->flags) &= ~0x3FFFFFL;	\
 	(((struct mvumi_compact_sgl *)(sgd))->flags) |= (sz);		\
 } while (0)
-#define sgd_getsz(_mhba, sgd, sz) do {				\
-	if (_mhba->hba_capability & HS_CAPABILITY_SUPPORT_COMPACT_SG)	\
-		(sz) = GET_COMPACT_SGD_SIZE(sgd);	\
-	else \
-		(sz) = (sgd)->size;			\
-} while (0)
-
 #define sgd_setsz(_mhba, sgd, sz) do {				\
 	if (_mhba->hba_capability & HS_CAPABILITY_SUPPORT_COMPACT_SG)	\
 		SET_COMPACT_SGD_SIZE(sgd, sz);		\
@@ -259,6 +249,8 @@ struct mvumi_cmd {
 	struct scsi_cmnd *scmd;
 	atomic_t sync_cmd;
 	void *data_buf;
+	dma_addr_t data_buf_phys;
+	unsigned int data_buf_size;
 	unsigned short request_id;
 	unsigned char cmd_status;
 };
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v9 8/9] scsi: mvumi: stamp a consumed outbound list entry out of range
  2026-10-15  4:52 [PATCH v9 0/9] scsi: mvumi: bound the values the firmware supplies Yehyeong Lee
                   ` (6 preceding siblings ...)
  2026-10-15  4:52 ` [PATCH v9 7/9] scsi: mvumi: free the internal command buffer by what was allocated Yehyeong Lee
@ 2026-10-15  4:52 ` Yehyeong Lee
  2026-10-15  4:52 ` [PATCH v9 9/9] scsi: mvumi: bound the firmware handshake retry loop Yehyeong Lee
  8 siblings, 0 replies; 13+ messages in thread
From: Yehyeong Lee @ 2026-10-15  4:52 UTC (permalink / raw)
  To: Martin K . Petersen, James E . J . Bottomley
  Cc: linux-scsi, linux-kernel, stable, yhlee

mvumi_receive_ob_list_entry() copies a completion out of the outbound
list and then writes 0xff over the tag, so that the entry is rejected if
the firmware's copy pointer brings the driver back to it.  The comment
right above the checks says why that can happen: "Copy pointer may point
to entry in outbound list before entry has valid data".

Nothing ever compares a tag against 0xff.  The only test for a usable
entry is

	tag >= mhba->tag_pool.size

and tag_pool.size is mhba->max_io, which the firmware supplies in the u16
max_io_support.  As soon as the firmware asks for more than 255 commands,
0xff is a tag in range and tag_cmd[255] can hold a live command, so a
stale consumed entry is no longer turned away by its tag.

What happens next depends on the controller.  Where request_id_enabled is
set, mvumi_check_ob_frame() still compares the request_id left in the
entry and rejects the frame.  Where it is not set, and the MV9580 case in
mvumi_init_fw() never sets it, that comparison is skipped, so the entry
passes, is copied, and completes whichever command owns tag 255 with the
bytes of an older completion.

The tag field is a u16 and tag_pool.size is an unsigned short, so 0xffff
is >= every value the bound can take and is never a valid tag.  Stamp
that instead.  The store is the same width it was.

Fixes: f0c568a478f0 ("[SCSI] mvumi: Add Marvell UMI driver")
Cc: stable@vger.kernel.org
Signed-off-by: Yehyeong Lee <yhlee@isslab.korea.ac.kr>
---
 drivers/scsi/mvumi.c | 2 +-
 drivers/scsi/mvumi.h | 6 ++++++
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/drivers/scsi/mvumi.c b/drivers/scsi/mvumi.c
index 133e53bc2cb37..994c04902fa12 100644
--- a/drivers/scsi/mvumi.c
+++ b/drivers/scsi/mvumi.c
@@ -562,7 +562,7 @@ static void mvumi_receive_ob_list_entry(struct mvumi_hba *mhba)
 		}
 
 		memcpy(ob_data->data, p_outb_frame, mhba->ob_max_size);
-		p_outb_frame->tag = 0xff;
+		p_outb_frame->tag = MVUMI_TAG_CONSUMED;
 
 		list_add_tail(&ob_data->list, &mhba->free_ob_list);
 	}
diff --git a/drivers/scsi/mvumi.h b/drivers/scsi/mvumi.h
index 3fc76addbf304..d998c65c26472 100644
--- a/drivers/scsi/mvumi.h
+++ b/drivers/scsi/mvumi.h
@@ -133,6 +133,12 @@ enum {
  */
 #define MVUMI_MAX_TARGET_ID		255
 
+/*
+ * Stamped into a consumed outbound list entry.  A valid tag is below
+ * tag_pool.size, which is a u16, so the largest u16 is never one.
+ */
+#define MVUMI_TAG_CONSUMED		0xffff
+
 #define DEVICE_OFFLINE	0
 #define DEVICE_ONLINE	1
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v9 9/9] scsi: mvumi: bound the firmware handshake retry loop
  2026-10-15  4:52 [PATCH v9 0/9] scsi: mvumi: bound the values the firmware supplies Yehyeong Lee
                   ` (7 preceding siblings ...)
  2026-10-15  4:52 ` [PATCH v9 8/9] scsi: mvumi: stamp a consumed outbound list entry out of range Yehyeong Lee
@ 2026-10-15  4:52 ` Yehyeong Lee
  8 siblings, 0 replies; 13+ messages in thread
From: Yehyeong Lee @ 2026-10-15  4:52 UTC (permalink / raw)
  To: Martin K . Petersen, James E . J . Bottomley
  Cc: linux-scsi, linux-kernel, stable, yhlee

mvumi_check_handshake() drives the handshake by calling
mvumi_handshake_event() until mhba->fw_state reaches FW_STATE_STARTED,
with nothing bounding how many times that can happen:

	do {
		if (mvumi_handshake_event(mhba)) {
			...
			return -1;
		}
	} while (mhba->fw_state != FW_STATE_STARTED);

Each call advances the state machine by one step and has its own
FW_MAX_DELAY timeout, but that timeout is sampled on entry to
mvumi_handshake_event(), so it only bounds one step.  A firmware that
keeps asserting DRBL_HANDSHAKE_ISR, or that reports the same handshake
state every round, is answered every round and the loop does not end.

The firmware does not have to be hostile to keep the loop going.  The
page walk advances hs_header->page_code, which lives in the coherent DMA
handshake page, so a controller that writes that field back can keep the
walk at the same page.  The restart that mvumi_handshake() takes when the
firmware reports a status other than HS_STATUS_OK also returns to
HS_S_START without ending the loop.

Count the steps and give up after MVUMI_HS_MAX_STEPS.  A handshake that
completes needs four fixed steps plus one per page the firmware asks for
in the u8 total_pages, so 259 at most, and a restart adds two; 1024
leaves room for both and still terminates.

This does not change what the driver writes to the firmware.  In
particular, the HS_S_ABORT that mvumi_handshake() stores into its local
hs_fun before returning -1 is still never written to the doorbell, so the
FW_STATE_STARTING restart continues to work as before.

Fixes: f0c568a478f0 ("[SCSI] mvumi: Add Marvell UMI driver")
Cc: stable@vger.kernel.org
Signed-off-by: Yehyeong Lee <yhlee@isslab.korea.ac.kr>
---
 drivers/scsi/mvumi.c | 9 ++++++++-
 drivers/scsi/mvumi.h | 9 +++++++++
 2 files changed, 17 insertions(+), 1 deletion(-)

diff --git a/drivers/scsi/mvumi.c b/drivers/scsi/mvumi.c
index 994c04902fa12..a2759dbeedf75 100644
--- a/drivers/scsi/mvumi.c
+++ b/drivers/scsi/mvumi.c
@@ -1334,7 +1334,7 @@ static unsigned char mvumi_handshake_event(struct mvumi_hba *mhba)
 
 static unsigned char mvumi_check_handshake(struct mvumi_hba *mhba)
 {
-	unsigned int tmp;
+	unsigned int tmp, steps;
 	unsigned long before;
 
 	before = jiffies;
@@ -1355,6 +1355,7 @@ static unsigned char mvumi_check_handshake(struct mvumi_hba *mhba)
 
 	mhba->fw_state = FW_STATE_STARTING;
 	dev_dbg(&mhba->pdev->dev, "start firmware handshake...\n");
+	steps = 0;
 	do {
 		if (mvumi_handshake_event(mhba)) {
 			dev_err(&mhba->pdev->dev,
@@ -1362,6 +1363,12 @@ static unsigned char mvumi_check_handshake(struct mvumi_hba *mhba)
 						mhba->fw_state);
 			return -1;
 		}
+		if (++steps > MVUMI_HS_MAX_STEPS) {
+			dev_err(&mhba->pdev->dev,
+				"handshake did not finish in %u steps, state 0x%x.\n",
+				MVUMI_HS_MAX_STEPS, mhba->fw_state);
+			return -1;
+		}
 	} while (mhba->fw_state != FW_STATE_STARTED);
 
 	dev_dbg(&mhba->pdev->dev, "firmware handshake done\n");
diff --git a/drivers/scsi/mvumi.h b/drivers/scsi/mvumi.h
index d998c65c26472..9597e87a08167 100644
--- a/drivers/scsi/mvumi.h
+++ b/drivers/scsi/mvumi.h
@@ -399,6 +399,15 @@ struct mvumi_hs_header {
 #define HS_PAGE_CL_INFO		0x4
 #define HS_PAGE_TOTAL		0x5
 
+/*
+ * Each mvumi_handshake() call advances the state machine by one step: four
+ * fixed steps, plus one per page the firmware asks for in the u8 total_pages,
+ * plus a restart if it ever reports a status other than HS_STATUS_OK.  This
+ * caps how many steps the driver will take before giving up, so that a
+ * firmware that keeps the handshake going cannot keep it going forever.
+ */
+#define MVUMI_HS_MAX_STEPS	1024
+
 #define HSP_SIZE(i)	sizeof(struct mvumi_hs_page##i)
 
 #define HSP_MAX_SIZE ({					\
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH v9 7/9] scsi: mvumi: free the internal command buffer by what was allocated
  2026-10-15  4:52 ` [PATCH v9 7/9] scsi: mvumi: free the internal command buffer by what was allocated Yehyeong Lee
@ 2026-10-15  5:03   ` sashiko-bot
  2026-10-15  5:57     ` Yehyeong Lee
  0 siblings, 1 reply; 13+ messages in thread
From: sashiko-bot @ 2026-10-15  5:03 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: mvumi: DMA Use-After-Free on internal command timeout
--

commit a045f4b7ba7499dba25350d8e9d333f7c9956722
Author: Yehyeong Lee <yhlee@isslab.korea.ac.kr>

scsi: mvumi: free the internal command buffer by what was allocated
    
This patch stores the DMA buffer address and length directly in the
mvumi_cmd struct instead of reconstructing it from the command frame.
This avoids an issue where a 32-bit shift in mvumi_delete_internal_cmd()
caused the upper 32 bits of the DMA address to be truncated.

> diff --git a/drivers/scsi/mvumi.c b/drivers/scsi/mvumi.c
> index 2410d2aa147d9..133e53bc2cb37 100644
> --- a/drivers/scsi/mvumi.c
> +++ b/drivers/scsi/mvumi.c
[ ... ]
> @@ -286,21 +288,10 @@ static struct mvumi_cmd *mvumi_create_internal_cmd(struct mvumi_hba *mhba,
>  static void mvumi_delete_internal_cmd(struct mvumi_hba *mhba,
>  						struct mvumi_cmd *cmd)
>  {
> -	struct mvumi_sgl *m_sg;
> -	unsigned int size;
> -	dma_addr_t phy_addr;
> -
>  	if (cmd && cmd->frame) {
> -		if (cmd->frame->sg_counts) {
> -			m_sg = (struct mvumi_sgl *) &cmd->frame->payload[0];
> -			sgd_getsz(mhba, m_sg, size);
> -
> -			phy_addr = (dma_addr_t) m_sg->baseaddr_l |
> -				(dma_addr_t) ((m_sg->baseaddr_h << 16) << 16);
> -
> -			dma_free_coherent(&mhba->pdev->dev, size, cmd->data_buf,
> -								phy_addr);
> -		}
> +		if (cmd->data_buf)
> +			dma_free_coherent(&mhba->pdev->dev, cmd->data_buf_size,
> +					  cmd->data_buf, cmd->data_buf_phys);

[Severity: High]
If an internal command times out in mvumi_issue_blocked_cmd(), the function
returns without explicitly aborting the hardware operation or resetting the
controller.

The callers of mvumi_issue_blocked_cmd() (such as mvumi_inquiry(),
mvumi_flush_cache(), or mvumi_scan_events()) then immediately invoke
mvumi_delete_internal_cmd(), which unconditionally frees the DMA-mapped
command frame and data buffer back to the system.

Since the hardware is unaware that the software has abandoned the command,
can a delayed completion cause the hardware to write to the freed physical
memory, resulting in a DMA use-after-free and memory corruption?

>  		dma_free_coherent(&mhba->pdev->dev, mhba->ib_max_size,
>  				cmd->frame, cmd->frame_phys);
>  		kfree(cmd);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261015045213.3975845-1-yhlee@isslab.korea.ac.kr?part=7

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v9 3/9] scsi: mvumi: bound the hotplug event bitmap
  2026-10-15  4:52 ` [PATCH v9 3/9] scsi: mvumi: bound the hotplug event bitmap Yehyeong Lee
@ 2026-10-15  5:08   ` sashiko-bot
  0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-10-15  5:08 UTC (permalink / raw)
  To: Yehyeong Lee; +Cc: linux-scsi

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] scsi: mvumi: missing endian conversion for param->size
--

commit 67e2f811bb5307d634893d7bcc1a4ae7f4bb47eb
Author: Yehyeong Lee <yhlee@isslab.korea.ac.kr>

scsi: mvumi: bound the hotplug event bitmap

This commit adds a bounds check to mvumi_proc_msg() to prevent scanning
past the allocated event buffer. It reads the size parameter from the
hotplug event and rejects events where the size exceeds expected limits.

> diff --git a/drivers/scsi/mvumi.c b/drivers/scsi/mvumi.c
> --- a/drivers/scsi/mvumi.c
> +++ b/drivers/scsi/mvumi.c
> @@ -1652,11 +1652,25 @@ static int mvumi_rescan_bus(void *data)
>  static void mvumi_proc_msg(struct mvumi_hba *mhba,
>  					struct mvumi_hotplug_event *param)
>  {
> -	u16 size = param->size;
> +	u16 size = READ_ONCE(param->size);

[Severity: Medium]
Does param->size require an endianness conversion like le16_to_cpu() here?

If the Marvell hardware writes the event buffer in little-endian format
during normal hotplug events, reading param->size natively on a big-endian
host would yield a heavily swapped value.

>  	const unsigned long *ar_bitmap;
>  	const unsigned long *re_bitmap;
>  	int index;
>  
> +	/*
> +	 * The second bitmap starts size bits into the event buffer and
> +	 * find_next_zero_bit() reads it a long at a time, so that is the
> +	 * furthest the two scans reach.  Bound it by the length the firmware
> +	 * was told it may write, not by the buffer: the bytes past that are
> +	 * still zero, and a zero bit here means a device is present.
> +	 */
> +	if (sizeof(*param) + (size >> 3) + BITS_TO_LONGS(size) * sizeof(long) >
> +			sizeof(struct mvumi_event_req)) {

[Severity: Medium]
Since the newly added bounds check in mvumi_proc_msg() relies on this
size value, would an incorrectly swapped size on big-endian hosts cause
this check to spuriously fail and reject valid hotplug events?

> +		dev_err(&mhba->pdev->dev,
> +			"invalid hotplug event bitmap size %u\n", size);
> +		return;
> +	}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261015045213.3975845-1-yhlee@isslab.korea.ac.kr?part=3

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v9 7/9] scsi: mvumi: free the internal command buffer by what was allocated
  2026-10-15  5:03   ` sashiko-bot
@ 2026-10-15  5:57     ` Yehyeong Lee
  0 siblings, 0 replies; 13+ messages in thread
From: Yehyeong Lee @ 2026-10-15  5:57 UTC (permalink / raw)
  To: linux-scsi
  Cc: Martin K . Petersen, James E . J . Bottomley, linux-kernel,
	stable, yhlee

Thanks. Two points across the v9 reviews.

7/9, the DMA use-after-free on an internal command timeout: this is real,
but it is pre-existing and this series neither introduces nor widens it.
Patch 7 only changes where mvumi_delete_internal_cmd() gets the address and
length it frees -- from the command frame, to fields kept in struct
mvumi_cmd; mvumi_issue_blocked_cmd() and the point at which the buffers are
freed are byte-for-byte what they were before the series. The window is
real: an internal command (scmd is NULL, so SCSI EH is not involved) that
the controller completes after the 45s timeout would have its frame and
data buffer already freed. Closing it needs either an abort of the
outstanding command, or freeing the timed-out buffer only once the
controller is no longer bus-mastering, which today happens only at
pci_disable_device() -- after the buffer is already freed. That is a
teardown-ordering change beyond this series, and I will follow up on it
separately.

3/9, the endianness of param->size: as in the cover letter, the driver does
no device-to-host endian conversion anywhere and uses these fields natively
throughout, so the check follows the existing convention; making the driver
big-endian-correct is the driver-wide __le series noted there.

^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2026-10-15  5:57 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-15  4:52 [PATCH v9 0/9] scsi: mvumi: bound the values the firmware supplies Yehyeong Lee
2026-10-15  4:52 ` [PATCH v9 1/9] scsi: mvumi: bound the per-event parameter and sense dumps Yehyeong Lee
2026-10-15  4:52 ` [PATCH v9 2/9] scsi: mvumi: reject an out-of-range handshake frame length Yehyeong Lee
2026-10-15  4:52 ` [PATCH v9 3/9] scsi: mvumi: bound the hotplug event bitmap Yehyeong Lee
2026-10-15  5:08   ` sashiko-bot
2026-10-15  4:52 ` [PATCH v9 4/9] scsi: mvumi: walk the hotplug event bitmap a byte at a time Yehyeong Lee
2026-10-15  4:52 ` [PATCH v9 5/9] scsi: mvumi: reject handshake sizes that overflow the list allocations Yehyeong Lee
2026-10-15  4:52 ` [PATCH v9 6/9] scsi: mvumi: validate the response frame tag before using it Yehyeong Lee
2026-10-15  4:52 ` [PATCH v9 7/9] scsi: mvumi: free the internal command buffer by what was allocated Yehyeong Lee
2026-10-15  5:03   ` sashiko-bot
2026-10-15  5:57     ` Yehyeong Lee
2026-10-15  4:52 ` [PATCH v9 8/9] scsi: mvumi: stamp a consumed outbound list entry out of range Yehyeong Lee
2026-10-15  4:52 ` [PATCH v9 9/9] scsi: mvumi: bound the firmware handshake retry loop Yehyeong Lee

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.