[PATCH RFC 14/23] scsi: esas2r: let procfs copy the ioctl argument of the ATTONode file

From: Christian Brauner

Date: Mon Nov 02 2026 - 11:46:02 EST


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) <brauner@xxxxxxxxxx>
---
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 f99318f61c93..eb39ba8263b3 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;
@@ -1515,19 +1501,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