Move the work of esas2r_ioctl_handler() into esas2r_ioctl_buf() which works on a kernel copy of struct atto_express_ioctl. The SCSI ioctl and the character device keep copying in and out in esas2r_ioctl_handler(). The procfs file implements ->proc_ioctl_buf() with esas2r_ioctl_buf() and lists every command from EXPRESS_IOCTL_MIN to EXPRESS_IOCTL_MAX, so procfs copies the struct in before it pins the entry and back out after unpinning it. Commands outside of that range fail with -ENOTTY instead of -ENOTSUPP now, a NULL argument with -EFAULT. Signed-off-by: Christian Brauner (Amutable) --- drivers/scsi/esas2r/esas2r.h | 2 ++ drivers/scsi/esas2r/esas2r_ioctl.c | 58 ++++++++++++++++++++++---------------- drivers/scsi/esas2r/esas2r_main.c | 44 +++++++++++++++++++++++------ 3 files changed, 72 insertions(+), 32 deletions(-) diff --git a/drivers/scsi/esas2r/esas2r.h b/drivers/scsi/esas2r/esas2r.h index 10101f50b282..4d75b8adbb3b 100644 --- a/drivers/scsi/esas2r/esas2r.h +++ b/drivers/scsi/esas2r/esas2r.h @@ -964,6 +964,8 @@ struct esas2r_adapter { const char *esas2r_info(struct Scsi_Host *); int esas2r_write_params(struct esas2r_adapter *a, struct esas2r_request *rq, struct esas2r_sas_nvram *data); +int esas2r_ioctl_buf(void *hostdata, unsigned int cmd, + struct atto_express_ioctl *ioctl); 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, diff --git a/drivers/scsi/esas2r/esas2r_ioctl.c b/drivers/scsi/esas2r/esas2r_ioctl.c index 80ae5b9d45db..901c5d40cda8 100644 --- a/drivers/scsi/esas2r/esas2r_ioctl.c +++ b/drivers/scsi/esas2r/esas2r_ioctl.c @@ -1279,37 +1279,25 @@ int esas2r_write_params(struct esas2r_adapter *a, struct esas2r_request *rq, } -/* This function only cares about ATTO-specific ioctls (atto_express_ioctl) */ -int esas2r_ioctl_handler(void *hostdata, unsigned int cmd, void __user *arg) +/* + * This function only cares about ATTO-specific ioctls (atto_express_ioctl). + * @ioctl is a kernel copy of what the caller passed. It goes back to the + * caller unless this fails. + */ +int esas2r_ioctl_buf(void *hostdata, unsigned int cmd, + struct atto_express_ioctl *ioctl) { - struct atto_express_ioctl *ioctl = NULL; struct esas2r_adapter *a; struct esas2r_request *rq; u16 code; int err; - esas2r_log(ESAS2R_LOG_DEBG, "ioctl (%p, %x, %p)", hostdata, cmd, arg); - - if ((arg == NULL) - || (cmd < EXPRESS_IOCTL_MIN) - || (cmd > EXPRESS_IOCTL_MAX)) - return -ENOTSUPP; - - ioctl = memdup_user(arg, sizeof(struct atto_express_ioctl)); - if (IS_ERR(ioctl)) { - esas2r_log(ESAS2R_LOG_WARN, - "ioctl_handler access_ok failed for cmd %u, address %p", - cmd, arg); - return PTR_ERR(ioctl); - } - /* verify the signature */ if (memcmp(ioctl->header.signature, EXPRESS_IOCTL_SIGNATURE, EXPRESS_IOCTL_SIGNATURE_SIZE) != 0) { esas2r_log(ESAS2R_LOG_WARN, "invalid signature"); - kfree(ioctl); return -ENOTSUPP; } @@ -1350,7 +1338,6 @@ int esas2r_ioctl_handler(void *hostdata, unsigned int cmd, void __user *arg) esas2r_adapters[ioctl->header.channel] == NULL) { ioctl->header.return_code = IOCTL_BAD_CHANNEL; esas2r_log(ESAS2R_LOG_WARN, "bad channel value"); - kfree(ioctl); return -ENOTSUPP; } @@ -1403,7 +1390,6 @@ int esas2r_ioctl_handler(void *hostdata, unsigned int cmd, void __user *arg) rq = esas2r_alloc_request(a); if (rq == NULL) { - kfree(ioctl); esas2r_log(ESAS2R_LOG_WARN, "could not allocate an internal request"); return -ENOMEM; @@ -1516,19 +1502,43 @@ ioctl_done: } + return 0; +} + +int esas2r_ioctl_handler(void *hostdata, unsigned int cmd, void __user *arg) +{ + struct atto_express_ioctl *ioctl __free(kfree) = NULL; + int err; + + esas2r_log(ESAS2R_LOG_DEBG, "ioctl (%p, %x, %p)", hostdata, cmd, arg); + + if ((arg == NULL) + || (cmd < EXPRESS_IOCTL_MIN) + || (cmd > EXPRESS_IOCTL_MAX)) + return -ENOTSUPP; + + ioctl = memdup_user(arg, sizeof(struct atto_express_ioctl)); + if (IS_ERR(ioctl)) { + esas2r_log(ESAS2R_LOG_WARN, + "ioctl_handler access_ok failed for cmd %u, address %p", + cmd, arg); + return PTR_ERR(ioctl); + } + + err = esas2r_ioctl_buf(hostdata, cmd, ioctl); + if (err) + return err; + /* Always copy the buffer back, if only to pick up the status */ err = copy_to_user(arg, ioctl, sizeof(struct atto_express_ioctl)); if (err != 0) { esas2r_log(ESAS2R_LOG_WARN, "ioctl_handler copy_to_user didn't copy everything (err %d, cmd %u)", err, cmd); - kfree(ioctl); return -EFAULT; } - kfree(ioctl); - return 0; } diff --git a/drivers/scsi/esas2r/esas2r_main.c b/drivers/scsi/esas2r/esas2r_main.c index 75a70ae9441b..720be2a0907d 100644 --- a/drivers/scsi/esas2r/esas2r_main.c +++ b/drivers/scsi/esas2r/esas2r_main.c @@ -613,14 +613,6 @@ static const struct file_operations esas2r_proc_fops = { .unlocked_ioctl = esas2r_proc_ioctl, }; -static const struct proc_ops esas2r_proc_ops = { - .proc_lseek = default_llseek, - .proc_ioctl = esas2r_proc_ioctl, -#ifdef CONFIG_COMPAT - .proc_compat_ioctl = compat_ptr_ioctl, -#endif -}; - static struct Scsi_Host *esas2r_proc_host; static int esas2r_proc_major; @@ -630,6 +622,42 @@ long esas2r_proc_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) cmd, (void __user *)arg); } +static long esas2r_proc_ioctl_buf(struct file *fp, unsigned int cmd, void *arg) +{ + return esas2r_ioctl_buf(esas2r_proc_host->hostdata, cmd, arg); +} + +#define ESAS2R_PROC_IOCTL(_cmd) \ + PROC_IOCTL_ARG(_cmd, sizeof(struct atto_express_ioctl), \ + sizeof(struct atto_express_ioctl)) + +/* esas2r_ioctl_buf() takes every command from EXPRESS_IOCTL_MIN to _MAX. */ +static const struct proc_ioctl_arg esas2r_proc_ioctl_args[] = { + ESAS2R_PROC_IOCTL(EXPRESS_IOCTL_RW_FIRMWARE), + ESAS2R_PROC_IOCTL(EXPRESS_IOCTL_READ_PARAMS), + ESAS2R_PROC_IOCTL(EXPRESS_IOCTL_WRITE_PARAMS), + ESAS2R_PROC_IOCTL(EXPRESS_IOCTL_FC_API), + ESAS2R_PROC_IOCTL(EXPRESS_IOCTL_GET_CHANNELS), + ESAS2R_PROC_IOCTL(EXPRESS_IOCTL_CHAN_INFO), + ESAS2R_PROC_IOCTL(EXPRESS_IOCTL_DEFAULT_PARAMS), + ESAS2R_PROC_IOCTL(EXPRESS_ADDR_MEMORY), + ESAS2R_PROC_IOCTL(EXPRESS_RW_MEMORY), + ESAS2R_PROC_IOCTL(EXPRESS_TSDK_DUMP), + ESAS2R_PROC_IOCTL(EXPRESS_IOCTL_SMP), + ESAS2R_PROC_IOCTL(EXPRESS_CSMI), + ESAS2R_PROC_IOCTL(EXPRESS_IOCTL_HBA), + ESAS2R_PROC_IOCTL(EXPRESS_IOCTL_VDA), + ESAS2R_PROC_IOCTL(EXPRESS_IOCTL_GET_ID), + ESAS2R_PROC_IOCTL(EXPRESS_IOCTL_GET_MOD_INFO), +}; + +static const struct proc_ops esas2r_proc_ops = { + .proc_lseek = default_llseek, + .proc_ioctl_buf = esas2r_proc_ioctl_buf, + .proc_ioctl_args = esas2r_proc_ioctl_args, + .proc_nr_ioctl_args = ARRAY_SIZE(esas2r_proc_ioctl_args), +}; + static void __exit esas2r_exit(void) { esas2r_log(ESAS2R_LOG_INFO, "%s called", __func__); -- 2.53.0