lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:   Wed, 10 Nov 2021 17:13:42 -0800
From:   Russ Weight <russell.h.weight@...el.com>
To:     sudeep.holla@....com, cristian.marussi@....com, ardb@...nel.org,
        bjorn.andersson@...aro.org, gregkh@...uxfoundation.org,
        linux-kernel@...r.kernel.org
Cc:     trix@...hat.com, lgoncalv@...hat.com, yilun.xu@...el.com,
        hao.wu@...el.com, matthew.gerlach@...el.com,
        Russ Weight <russell.h.weight@...el.com>
Subject: [RFC PATCH 2/5] firmware: upload: Enable firmware uploads

Extend the Firmware Upload framework to add a character device and provide
IOCTL support (FW_UPLOAD_WRITE) for uploading a firmware file to a device.
The IOCTL will return immediately, and the upload will begin in the context
of a kernel worker thread.

Signed-off-by: Russ Weight <russell.h.weight@...el.com>
---
 .../driver-api/firmware/firmware-upload.rst   |  21 ++
 MAINTAINERS                                   |   1 +
 drivers/firmware/firmware-upload.c            | 216 +++++++++++++++++-
 include/linux/firmware/firmware-upload.h      |  27 +++
 include/uapi/linux/firmware-upload.h          |  53 +++++
 5 files changed, 317 insertions(+), 1 deletion(-)
 create mode 100644 include/uapi/linux/firmware-upload.h

diff --git a/Documentation/driver-api/firmware/firmware-upload.rst b/Documentation/driver-api/firmware/firmware-upload.rst
index 9f38f18be97a..8a7c50087028 100644
--- a/Documentation/driver-api/firmware/firmware-upload.rst
+++ b/Documentation/driver-api/firmware/firmware-upload.rst
@@ -15,3 +15,24 @@ Firmware Upload class driver will interact with the target device to
 transfer and authenticate the firmware data. Uploads are performed in the
 context of a kernel worker thread in order to facilitate progress
 indicators during lengthy uploads.
+
+User API
+========
+
+open
+----
+
+An fw_upload device is opened exclusively to control a firmware upload.
+The device must remain open throughout the duration of the firmware upload.
+An attempt to close the device while an upload is in progress will block
+until the firmware upload is complete.
+
+ioctl
+-----
+
+FW_UPLOAD_WRITE:
+
+The FW_UPLOAD_WRITE IOCTL passes in the address of a data buffer and starts
+the firmware upload. This IOCTL returns immediately after assigning the work
+to a kernel worker thread. This is an exclusive operation; an attempt to
+start concurrent firmware uploads for the same device will fail with EBUSY.
diff --git a/MAINTAINERS b/MAINTAINERS
index 19b3d3ccc406..4d59e8e11813 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7422,6 +7422,7 @@ S:	Maintained
 F:	Documentation/driver-api/firmware/firmware-upload.rst
 F:	drivers/firmware/firmware-upload.c
 F:	include/linux/firmware/firmware-upload.h
+F:	include/uapi/linux/firmware-upload.h
 
 FLASH ADAPTER DRIVER (IBM Flash Adapter 900GB Full Height PCI Flash Card)
 M:	Joshua Morris <josh.h.morris@...ibm.com>
diff --git a/drivers/firmware/firmware-upload.c b/drivers/firmware/firmware-upload.c
index bc26df8b6b52..e930611262fb 100644
--- a/drivers/firmware/firmware-upload.c
+++ b/drivers/firmware/firmware-upload.c
@@ -5,18 +5,189 @@
  * Copyright (C) 2019-2021 Intel Corporation, Inc.
  */
 
+#include <linux/delay.h>
 #include <linux/firmware/firmware-upload.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/slab.h>
+#include <linux/uaccess.h>
 #include <linux/vmalloc.h>
 
 #define FW_UPLOAD_XA_LIMIT	XA_LIMIT(0, INT_MAX)
 static DEFINE_XARRAY_ALLOC(fw_upload_xa);
 
 static struct class *fw_upload_class;
+static dev_t fw_upload_devt;
 
 #define to_fw_upload(d) container_of(d, struct fw_upload, dev)
 
+static void fw_upload_prog_complete(struct fw_upload *fwl)
+{
+	mutex_lock(&fwl->lock);
+	fwl->progress = FW_UPLOAD_PROG_IDLE;
+	mutex_unlock(&fwl->lock);
+}
+
+static void fw_upload_do_load(struct work_struct *work)
+{
+	struct fw_upload *fwl;
+	s32 ret, offset = 0;
+
+	fwl = container_of(work, struct fw_upload, work);
+
+	if (fwl->driver_unload) {
+		fwl->err_code = FW_UPLOAD_ERR_CANCELED;
+		goto idle_exit;
+	}
+
+	get_device(&fwl->dev);
+	if (!try_module_get(fwl->dev.parent->driver->owner)) {
+		fwl->err_code = FW_UPLOAD_ERR_BUSY;
+		goto putdev_exit;
+	}
+
+	fwl->progress = FW_UPLOAD_PROG_PREPARING;
+	ret = fwl->ops->prepare(fwl, fwl->data, fwl->remaining_size);
+	if (ret) {
+		fwl->err_code = ret;
+		goto modput_exit;
+	}
+
+	fwl->progress = FW_UPLOAD_PROG_WRITING;
+	while (fwl->remaining_size) {
+		ret = fwl->ops->write(fwl, fwl->data, offset,
+					fwl->remaining_size);
+		if (ret <= 0) {
+			if (!ret) {
+				dev_warn(&fwl->dev,
+					 "write-op wrote zero data\n");
+				ret = -FW_UPLOAD_ERR_RW_ERROR;
+			}
+			fwl->err_code = -ret;
+			goto done;
+		}
+
+		fwl->remaining_size -= ret;
+		offset += ret;
+	}
+
+	fwl->progress = FW_UPLOAD_PROG_PROGRAMMING;
+	ret = fwl->ops->poll_complete(fwl);
+	if (ret)
+		fwl->err_code = ret;
+
+done:
+	if (fwl->ops->cleanup)
+		fwl->ops->cleanup(fwl);
+
+modput_exit:
+	module_put(fwl->dev.parent->driver->owner);
+
+putdev_exit:
+	put_device(&fwl->dev);
+
+idle_exit:
+	/*
+	 * Note: fwl->remaining_size is left unmodified here to provide
+	 * additional information on errors. It will be reinitialized when
+	 * the next firmeware upload begins.
+	 */
+	vfree(fwl->data);
+	fwl->data = NULL;
+	fw_upload_prog_complete(fwl);
+}
+
+static int fw_upload_ioctl_write(struct fw_upload *fwl, unsigned long arg)
+{
+	struct fw_upload_write wb;
+	unsigned long minsz;
+	u8 *buf;
+
+	if (fwl->driver_unload || fwl->progress != FW_UPLOAD_PROG_IDLE)
+		return -EBUSY;
+
+	minsz = offsetofend(struct fw_upload_write, buf);
+	if (copy_from_user(&wb, (void __user *)arg, minsz))
+		return -EFAULT;
+
+	if (wb.flags)
+		return -EINVAL;
+
+	buf = vzalloc(wb.size);
+	if (!buf)
+		return -ENOMEM;
+
+	if (copy_from_user(buf, u64_to_user_ptr(wb.buf), wb.size)) {
+		vfree(buf);
+		return -EFAULT;
+	}
+
+	fwl->data = buf;
+	fwl->remaining_size = wb.size;
+	fwl->err_code = 0;
+	fwl->progress = FW_UPLOAD_PROG_STARTING;
+	queue_work(system_long_wq, &fwl->work);
+
+	return 0;
+}
+
+static long fw_upload_ioctl(struct file *filp, unsigned int cmd,
+			    unsigned long arg)
+{
+	struct fw_upload *fwl = filp->private_data;
+	int ret = -ENOTTY;
+
+	switch (cmd) {
+	case FW_UPLOAD_WRITE:
+		mutex_lock(&fwl->lock);
+		ret = fw_upload_ioctl_write(fwl, arg);
+		mutex_unlock(&fwl->lock);
+		break;
+	}
+
+	return ret;
+}
+
+static int fw_upload_open(struct inode *inode, struct file *filp)
+{
+	struct fw_upload *fwl = container_of(inode->i_cdev,
+						     struct fw_upload, cdev);
+
+	if (atomic_cmpxchg(&fwl->opened, 0, 1))
+		return -EBUSY;
+
+	filp->private_data = fwl;
+
+	return 0;
+}
+
+static int fw_upload_release(struct inode *inode, struct file *filp)
+{
+	struct fw_upload *fwl = filp->private_data;
+
+	mutex_lock(&fwl->lock);
+	if (fwl->progress == FW_UPLOAD_PROG_IDLE) {
+		mutex_unlock(&fwl->lock);
+		goto close_exit;
+	}
+
+	mutex_unlock(&fwl->lock);
+	flush_work(&fwl->work);
+
+close_exit:
+	atomic_set(&fwl->opened, 0);
+
+	return 0;
+}
+
+static const struct file_operations fw_upload_fops = {
+	.owner = THIS_MODULE,
+	.open = fw_upload_open,
+	.release = fw_upload_release,
+	.unlocked_ioctl = fw_upload_ioctl,
+};
+
 /**
  * fw_upload_register - create and register a Firmware Upload Device
  *
@@ -35,6 +206,11 @@ fw_upload_register(struct device *parent, const struct fw_upload_ops *ops,
 	struct fw_upload *fwl;
 	int ret;
 
+	if (!ops || !ops->prepare || !ops->write || !ops->poll_complete) {
+		dev_err(parent, "Attempt to register without all required ops\n");
+		return ERR_PTR(-ENOMEM);
+	}
+
 	fwl = kzalloc(sizeof(*fwl), GFP_KERNEL);
 	if (!fwl)
 		return ERR_PTR(-ENOMEM);
@@ -48,9 +224,13 @@ fw_upload_register(struct device *parent, const struct fw_upload_ops *ops,
 
 	fwl->priv = priv;
 	fwl->ops = ops;
+	fwl->err_code = 0;
+	fwl->progress = FW_UPLOAD_PROG_IDLE;
+	INIT_WORK(&fwl->work, fw_upload_do_load);
 
 	fwl->dev.class = fw_upload_class;
 	fwl->dev.parent = parent;
+	fwl->dev.devt = MKDEV(MAJOR(fw_upload_devt), fwl->dev.id);
 
 	ret = dev_set_name(&fwl->dev, "fw_upload%d", fwl->dev.id);
 	if (ret) {
@@ -65,6 +245,16 @@ fw_upload_register(struct device *parent, const struct fw_upload_ops *ops,
 		return ERR_PTR(ret);
 	}
 
+	cdev_init(&fwl->cdev, &fw_upload_fops);
+	fwl->cdev.owner = parent->driver->owner;
+	cdev_set_parent(&fwl->cdev, &fwl->dev.kobj);
+
+	ret = cdev_add(&fwl->cdev, fwl->dev.devt, 1);
+	if (ret) {
+		put_device(&fwl->dev);
+		return ERR_PTR(ret);
+	}
+
 	return fwl;
 
 error_device:
@@ -83,10 +273,23 @@ EXPORT_SYMBOL_GPL(fw_upload_register);
  * @fwl: pointer to struct fw_upload
  *
  * This function is intended for use in the parent driver's remove()
- * function.
+ * function. The driver_unload flag prevents new updates from starting
+ * once the unregister process has begun.
  */
 void fw_upload_unregister(struct fw_upload *fwl)
 {
+	mutex_lock(&fwl->lock);
+	fwl->driver_unload = true;
+	if (fwl->progress == FW_UPLOAD_PROG_IDLE) {
+		mutex_unlock(&fwl->lock);
+		goto unregister;
+	}
+
+	mutex_unlock(&fwl->lock);
+	flush_work(&fwl->work);
+
+unregister:
+	cdev_del(&fwl->cdev);
 	device_unregister(&fwl->dev);
 }
 EXPORT_SYMBOL_GPL(fw_upload_unregister);
@@ -101,19 +304,30 @@ static void fw_upload_dev_release(struct device *dev)
 
 static int __init fw_upload_class_init(void)
 {
+	int ret;
 	pr_info("Firmware Upload Framework\n");
 
 	fw_upload_class = class_create(THIS_MODULE, "fw_upload");
 	if (IS_ERR(fw_upload_class))
 		return PTR_ERR(fw_upload_class);
 
+	ret = alloc_chrdev_region(&fw_upload_devt, 0, MINORMASK,
+				  "fw_upload");
+	if (ret)
+		goto exit_destroy_class;
+
 	fw_upload_class->dev_release = fw_upload_dev_release;
 
 	return 0;
+
+exit_destroy_class:
+	class_destroy(fw_upload_class);
+	return ret;
 }
 
 static void __exit fw_upload_class_exit(void)
 {
+	unregister_chrdev_region(fw_upload_devt, MINORMASK);
 	class_destroy(fw_upload_class);
 	WARN_ON(!xa_empty(&fw_upload_xa));
 }
diff --git a/include/linux/firmware/firmware-upload.h b/include/linux/firmware/firmware-upload.h
index 767e0bdded7b..f80d5ba0c3f1 100644
--- a/include/linux/firmware/firmware-upload.h
+++ b/include/linux/firmware/firmware-upload.h
@@ -7,22 +7,49 @@
 #ifndef _LINUX_FIRMWARE_UPLOAD_H
 #define _LINUX_FIRMWARE_UPLOAD_H
 
+#include <linux/cdev.h>
 #include <linux/device.h>
 #include <linux/mutex.h>
 #include <linux/types.h>
+#include <uapi/linux/firmware-upload.h>
 
 struct fw_upload;
 
 /**
  * struct fw_upload_ops - device specific operations
+ * @prepare:		    Required: Prepare secure update
+ * @write:		    Required: The write() op receives the remaining
+ *			    size to be written and must return the actual
+ *			    size written or a negative error code. The write()
+ *			    op will be called repeatedly until all data is
+ *			    written.
+ * @poll_complete:	    Required: Check for the completion of the
+ *			    HW authentication/programming process.
+ * @cleanup:		    Optional: Complements the prepare()
+ *			    function and is called at the completion
+ *			    of the update, whether success or failure,
+ *			    if the prepare function succeeded.
  */
 struct fw_upload_ops {
+	u32 (*prepare)(struct fw_upload *fwl, const u8 *data, u32 size);
+	s32 (*write)(struct fw_upload *fwl, const u8 *data,
+		     u32 offset, u32 size);
+	u32 (*poll_complete)(struct fw_upload *fwl);
+	void (*cleanup)(struct fw_upload *fwl);
 };
 
 struct fw_upload {
 	struct device dev;
+	struct cdev cdev;
 	const struct fw_upload_ops *ops;
 	struct mutex lock;		/* protect data structure contents */
+	atomic_t opened;
+	struct work_struct work;
+	const u8 *data;			/* pointer to update data */
+	u32 remaining_size;		/* size remaining to transfer */
+	u32 progress;
+	u32 err_code;			/* upload error code */
+	bool driver_unload;
 	void *priv;
 };
 
diff --git a/include/uapi/linux/firmware-upload.h b/include/uapi/linux/firmware-upload.h
new file mode 100644
index 000000000000..034550071911
--- /dev/null
+++ b/include/uapi/linux/firmware-upload.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Header File for Firmware Upload User API
+ *
+ * Copyright (C) 2019-2021 Intel Corporation, Inc.
+ *
+ */
+
+#ifndef _UAPI_LINUX_FW_UPLOAD__H
+#define _UAPI_LINUX_FW_UPLOAD__H
+
+#include <linux/types.h>
+#include <linux/ioctl.h>
+
+#define FW_UPLOAD_MAGIC 0xB9
+
+/* Firmware upload progress codes */
+#define FW_UPLOAD_PROG_IDLE		0
+#define FW_UPLOAD_PROG_STARTING	1
+#define FW_UPLOAD_PROG_PREPARING	2
+#define FW_UPLOAD_PROG_WRITING		3
+#define FW_UPLOAD_PROG_PROGRAMMING	4
+#define FW_UPLOAD_PROG_MAX		5
+
+/* Firmware upload error codes */
+#define FW_UPLOAD_ERR_HW_ERROR		1
+#define FW_UPLOAD_ERR_TIMEOUT		2
+#define FW_UPLOAD_ERR_CANCELED		3
+#define FW_UPLOAD_ERR_BUSY		4
+#define FW_UPLOAD_ERR_INVALID_SIZE	5
+#define FW_UPLOAD_ERR_RW_ERROR		6
+#define FW_UPLOAD_ERR_WEAROUT		7
+#define FW_UPLOAD_ERR_MAX		8
+
+/**
+ * FW_UPLOAD_WRITE - _IOW(FW_UPLOAD_MAGIC, 0,
+ *				struct fw_upload_write)
+ *
+ * Upload a data buffer to the target device. The user must provide the
+ * data buffer and size.
+ *
+ * Return: 0 on success, -errno on failure.
+ */
+struct fw_upload_write {
+	/* Input */
+	__u32 flags;		/* Zero for now */
+	__u32 size;		/* Data size (in bytes) to be written */
+	__u64 buf;		/* User space address of source data */
+};
+
+#define FW_UPLOAD_WRITE	_IOW(FW_UPLOAD_MAGIC, 0, struct fw_upload_write)
+
+#endif /* _UAPI_LINUX_FW_UPLOAD_H */
-- 
2.25.1

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ