[PATCH 2/2] scsi: 3w-xxxx: sanitize passthrough SGLs

From: Yousef Alhouseen

Date: Thu Jun 25 2026 - 05:01:37 EST


TW_CMD_PACKET_WITH_DATA accepts a command packet from userspace and
patches only the first SGL entry before posting it to the controller. The
command size and remaining SGL contents can still describe user-controlled
DMA descriptors to firmware.

Reject unknown SGL offsets, clear the relevant SGL array, and force the
command size to the single driver-owned DMA buffer. Zero the coherent
ioctl buffer before copying the request so short device writes do not leak
stale memory back to userspace.

Signed-off-by: Yousef Alhouseen <alhouseenyousef@xxxxxxxxx>
---
drivers/scsi/3w-xxxx.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)

diff --git a/drivers/scsi/3w-xxxx.c b/drivers/scsi/3w-xxxx.c
index 147a47e6b..033f79eaa 100644
--- a/drivers/scsi/3w-xxxx.c
+++ b/drivers/scsi/3w-xxxx.c
@@ -925,6 +925,7 @@ static long tw_chrdev_ioctl(struct file *file, unsigned int cmd, unsigned long a
}

tw_ioctl = (TW_New_Ioctl *)cpu_addr;
+ memset(tw_ioctl, 0, data_buffer_length_adjusted + sizeof(TW_New_Ioctl));

/* Now copy down the entire ioctl */
if (copy_from_user(tw_ioctl, argp, data_buffer_length + sizeof(TW_New_Ioctl)))
@@ -972,17 +973,31 @@ static long tw_chrdev_ioctl(struct file *file, unsigned int cmd, unsigned long a
/* Load the sg list */
switch (TW_SGL_OUT(tw_ioctl->firmware_command.opcode__sgloffset)) {
case 2:
+ memset(tw_ioctl->firmware_command.byte8.param.sgl, 0,
+ sizeof(tw_ioctl->firmware_command.byte8.param.sgl));
+ tw_ioctl->firmware_command.size = 4;
tw_ioctl->firmware_command.byte8.param.sgl[0].address = dma_handle + sizeof(TW_New_Ioctl);
tw_ioctl->firmware_command.byte8.param.sgl[0].length = data_buffer_length_adjusted;
break;
case 3:
+ memset(tw_ioctl->firmware_command.byte8.io.sgl, 0,
+ sizeof(tw_ioctl->firmware_command.byte8.io.sgl));
+ tw_ioctl->firmware_command.size = 5;
tw_ioctl->firmware_command.byte8.io.sgl[0].address = dma_handle + sizeof(TW_New_Ioctl);
tw_ioctl->firmware_command.byte8.io.sgl[0].length = data_buffer_length_adjusted;
break;
case 5:
+ memset(passthru->sg_list, 0, sizeof(passthru->sg_list));
+ passthru->size = 7;
passthru->sg_list[0].address = dma_handle + sizeof(TW_New_Ioctl);
passthru->sg_list[0].length = data_buffer_length_adjusted;
break;
+ default:
+ retval = -EINVAL;
+ tw_dev->chrdev_request_id = TW_IOCTL_CHRDEV_FREE;
+ tw_state_request_finish(tw_dev, request_id);
+ spin_unlock_irqrestore(tw_dev->host->host_lock, flags);
+ goto out2;
}

memcpy(tw_dev->command_packet_virtual_address[request_id], &(tw_ioctl->firmware_command), sizeof(TW_Command));
--
2.54.0