esas2r_ioctl_handler() copies in a struct atto_express_ioctl, a bit over
512k, and hands the union member for the command to the handlers. For
EXPRESS_IOCTL_HBA and EXPRESS_IOCTL_SMP the length of the buffered ioctl
is the size of the member plus lengths that come straight from
userspace:
bi.length = sizeof(struct atto_ioctl) + ioctl_hba->data_length;
bi.length = sizeof(struct atto_ioctl_smp)
+ le32_to_cpu(si->req_length)
+ le32_to_cpu(si->rsp_length);
handle_buffered_ioctl() copies bi.length bytes from the member into a
DMA buffer and, once the adapter is done, copies the DMA buffer back
over the member. Nothing checks that the lengths fit into the struct
that was copied in. So a large data_length makes it read past the
struct and then write the DMA buffer back over the memory behind it.
The firmware commands pass sizeof(struct atto_express_ioctl) to
esas2r_{read,write}_{fw,fs}() as the length of data.fwrw.image, which
starts 28 bytes into the struct. So they can read and write 28 bytes
past it as well.
The handler is reachable through /proc/scsi/esas2r/ATTONode. It is
created with mode 0, which procfs turns into 0444, and esas2r doesn't
check for a capability anywhere. So any local user can do this on a
machine with such an adapter.
Bound all of these by the room from the member to the end of the
struct. read_hw() passes handle_hba_ioctl() a single struct atto_ioctl
and only ever returns that, so there the data length has to be zero.
Reported-by: Chris Mason <[email protected]>
Fixes: 26780d9e12ed ("[SCSI] esas2r: ATTO Technology ExpressSAS 6G SAS/SATA
RAID Adapter Driver")
Cc: [email protected]
Signed-off-by: Christian Brauner (Amutable) <[email protected]>
---
drivers/scsi/esas2r/esas2r.h | 2 +-
drivers/scsi/esas2r/esas2r_ioctl.c | 41 ++++++++++++++++++++++++--------------
drivers/scsi/esas2r/esas2r_main.c | 3 ++-
3 files changed, 29 insertions(+), 17 deletions(-)
diff --git a/drivers/scsi/esas2r/esas2r.h b/drivers/scsi/esas2r/esas2r.h
index a763edd05fe4..10101f50b282 100644
--- a/drivers/scsi/esas2r/esas2r.h
+++ b/drivers/scsi/esas2r/esas2r.h
@@ -967,7 +967,7 @@ int esas2r_write_params(struct esas2r_adapter *a, struct
esas2r_request *rq,
int esas2r_ioctl_handler(void *hostdata, unsigned int cmd, void __user *arg);
int esas2r_ioctl(struct scsi_device *dev, unsigned int cmd, void __user *arg);
u8 handle_hba_ioctl(struct esas2r_adapter *a,
- struct atto_ioctl *ioctl_hba);
+ struct atto_ioctl *ioctl_hba, size_t room);
enum scsi_qc_status esas2r_queuecommand(struct Scsi_Host *host,
struct scsi_cmnd *cmd);
int esas2r_show_info(struct seq_file *m, struct Scsi_Host *sh);
diff --git a/drivers/scsi/esas2r/esas2r_ioctl.c
b/drivers/scsi/esas2r/esas2r_ioctl.c
index 3f7c1d131ec3..f99318f61c93 100644
--- a/drivers/scsi/esas2r/esas2r_ioctl.c
+++ b/drivers/scsi/esas2r/esas2r_ioctl.c
@@ -311,17 +311,27 @@ static int smp_ioctl_callback(struct esas2r_adapter *a,
return true;
}
+/* The room from @member to the end of a struct atto_express_ioctl. */
+#define EXPRESS_IOCTL_ROOM(member) \
+ (sizeof(struct atto_express_ioctl) - \
+ offsetof(struct atto_express_ioctl, member))
+
static u8 handle_smp_ioctl(struct esas2r_adapter *a, struct atto_ioctl_smp *si)
{
struct esas2r_buffered_ioctl bi;
+ u64 length = (u64)sizeof(struct atto_ioctl_smp) +
+ le32_to_cpu(si->req_length) +
+ le32_to_cpu(si->rsp_length);
+
+ /* The request and the response follow @si in the same buffer. */
+ if (length > EXPRESS_IOCTL_ROOM(data.ioctl_smp))
+ return IOCTL_INVALID_PARAM;
memset(&bi, 0, sizeof(bi));
bi.a = a;
bi.ioctl = si;
- bi.length = sizeof(struct atto_ioctl_smp)
- + le32_to_cpu(si->req_length)
- + le32_to_cpu(si->rsp_length);
+ bi.length = length;
bi.offset = 0;
bi.callback = smp_ioctl_callback;
return handle_buffered_ioctl(&bi);
@@ -1221,11 +1231,16 @@ static void hba_ioctl_done_callback(struct
esas2r_adapter *a,
}
}
+/* @room is the size of the buffer that starts with @ioctl_hba. */
u8 handle_hba_ioctl(struct esas2r_adapter *a,
- struct atto_ioctl *ioctl_hba)
+ struct atto_ioctl *ioctl_hba, size_t room)
{
struct esas2r_buffered_ioctl bi;
+ /* The data follows @ioctl_hba in the same buffer. */
+ if (ioctl_hba->data_length > room - sizeof(struct atto_ioctl))
+ return IOCTL_INVALID_PARAM;
+
memset(&bi, 0, sizeof(bi));
bi.a = a;
@@ -1349,31 +1364,27 @@ int esas2r_ioctl_handler(void *hostdata, unsigned int
cmd, void __user *arg)
err = esas2r_write_fw(a,
(char *)ioctl->data.fwrw.image,
0,
- sizeof(struct
- atto_express_ioctl));
+
EXPRESS_IOCTL_ROOM(data.fwrw.image));
if (err >= 0) {
err = esas2r_read_fw(a,
(char *)ioctl->data.fwrw.
image,
0,
- sizeof(struct
-
atto_express_ioctl));
+
EXPRESS_IOCTL_ROOM(data.fwrw.image));
}
} else if (ioctl->data.fwrw.img_type == FW_IMG_FS_API) {
err = esas2r_write_fs(a,
(char *)ioctl->data.fwrw.image,
0,
- sizeof(struct
- atto_express_ioctl));
+
EXPRESS_IOCTL_ROOM(data.fwrw.image));
if (err >= 0) {
err = esas2r_read_fs(a,
(char *)ioctl->data.fwrw.
image,
0,
- sizeof(struct
-
atto_express_ioctl));
+
EXPRESS_IOCTL_ROOM(data.fwrw.image));
}
} else {
ioctl->header.return_code = IOCTL_BAD_FLASH_IMGTYPE;
@@ -1441,9 +1452,9 @@ int esas2r_ioctl_handler(void *hostdata, unsigned int
cmd, void __user *arg)
break;
case EXPRESS_IOCTL_HBA:
- ioctl->header.return_code = handle_hba_ioctl(a,
- &ioctl->data.
- ioctl_hba);
+ ioctl->header.return_code =
+ handle_hba_ioctl(a, &ioctl->data.ioctl_hba,
+ EXPRESS_IOCTL_ROOM(data.ioctl_hba));
break;
case EXPRESS_IOCTL_VDA:
diff --git a/drivers/scsi/esas2r/esas2r_main.c
b/drivers/scsi/esas2r/esas2r_main.c
index ada278c24c51..75a70ae9441b 100644
--- a/drivers/scsi/esas2r/esas2r_main.c
+++ b/drivers/scsi/esas2r/esas2r_main.c
@@ -178,7 +178,8 @@ static ssize_t read_hw(struct file *file, struct kobject
*kobj,
if (!a->local_atto_ioctl)
return -ENOMEM;
- if (handle_hba_ioctl(a, a->local_atto_ioctl) != IOCTL_SUCCESS)
+ if (handle_hba_ioctl(a, a->local_atto_ioctl,
+ sizeof(struct atto_ioctl)) != IOCTL_SUCCESS)
return -ENOMEM;
memcpy(buf, a->local_atto_ioctl, length);
--
2.53.0