@@ -79,6 +79,31 @@ config FW_LOADER
require userspace firmware loading support, but a module built
out-of-tree does.
+config FIRMWARE_SIG
+ bool "Firmware signature verification"
+ depends on FW_LOADER
+ select SYSTEM_DATA_VERIFICATION
+ help
+ Check firmware files for valid signatures upon load: if the firmware
+ was called foo.bin, a respective foo.bin.p7s is expected to be
+ present as the signature in the same directory.
+
+ This configuration only affects drivers with driver_data APIs,
+ disabling DRIVER_DATA_REQ_NO_SIG_CHECK.
+
+ For more information see Documentation/driver-api/firmware/signing.rst
+
+config FIRMWARE_SIG_FORCE
+ bool "Require all firmware to be validly signed"
+ depends on FIRMWARE_SIG
+ help
+ Reject unsigned files or signed files for which we don't have
+ a trusted key. Without this, you'll only get a record on kernel
+ log and yet the firmware will be loaded.
+
+ This configuration only affects drivers with driver_data APIs,
+ disabling DRIVER_DATA_REQ_NO_SIG_CHECK.
+
config FIRMWARE_IN_KERNEL
bool "Include in-kernel firmware blobs in kernel binary"
depends on FW_LOADER
@@ -43,6 +43,7 @@
#include <linux/reboot.h>
#include <linux/security.h>
#include <linux/swait.h>
+#include <linux/verification.h>
#include <generated/utsrelease.h>
@@ -146,6 +147,9 @@ struct driver_data_params {
* o request_firmware_nowait(): __DATA_REQ_FIRMWARE_NOWAIT()
*/
#define __DATA_REQ_FIRMWARE() \
+ .req_params = { \
+ .reqs = DRIVER_DATA_REQ_NO_SIG_CHECK, \
+ }, \
.priv_params = { \
.priv_reqs = DRIVER_DATA_PRIV_REQ_FALLBACK | \
DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT, \
@@ -153,7 +157,8 @@ struct driver_data_params {
#define __DATA_REQ_FIRMWARE_DIRECT() \
.req_params = { \
- .reqs = DRIVER_DATA_REQ_OPTIONAL, \
+ .reqs = DRIVER_DATA_REQ_OPTIONAL | \
+ DRIVER_DATA_REQ_NO_SIG_CHECK, \
}, \
.priv_params = { \
.priv_reqs = DRIVER_DATA_PRIV_REQ_FALLBACK | \
@@ -161,6 +166,9 @@ struct driver_data_params {
}
#define __DATA_REQ_FIRMWARE_BUF(buf, size) \
+ .req_params = { \
+ .reqs = DRIVER_DATA_REQ_NO_SIG_CHECK, \
+ }, \
.priv_params = { \
.priv_reqs = DRIVER_DATA_PRIV_REQ_FALLBACK | \
DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT | \
@@ -177,6 +185,7 @@ struct driver_data_params {
.found_cb = NULL, \
.found_ctx = async_ctx, \
}, \
+ .reqs = DRIVER_DATA_REQ_NO_SIG_CHECK, \
}, \
.priv_params = { \
.mode = DRIVER_DATA_ASYNC, \
@@ -204,6 +213,8 @@ struct driver_data_params {
(!!((params)->reqs & DRIVER_DATA_REQ_KEEP))
#define driver_data_param_uses_api(params) \
(!!((params)->reqs & DRIVER_DATA_REQ_USE_API_VERSIONING))
+#define driver_data_param_no_sig_check(params) \
+ (!!((params)->reqs & DRIVER_DATA_REQ_NO_SIG_CHECK))
#define driver_data_sync_cb(param) ((params)->cbs.sync.found_cb)
#define driver_data_sync_ctx(params) ((params)->cbs.sync.found_ctx)
@@ -263,6 +274,11 @@ int driver_data_async_call_api_cb(const struct firmware *driver_data,
error);
}
+static bool fw_sig_enforce = IS_ENABLED(CONFIG_FIRMWARE_SIG_FORCE);
+#ifndef CONFIG_FIRMWARE_SIG_FORCE
+module_param(fw_sig_enforce, bool_enable_only, 0644);
+#endif /* !CONFIG_FIRMWARE_SIG_FORCE */
+
/* Builtin firmware support */
#ifdef CONFIG_FW_LOADER
@@ -445,6 +461,11 @@ struct firmware_buf {
void *data;
size_t size;
size_t allocated_size;
+ bool sig_ok;
+#ifdef CONFIG_FIRMWARE_SIG
+ void *sig_data;
+ size_t sig_size;
+#endif
#ifdef CONFIG_FW_LOADER_USER_HELPER
bool is_paged_buf;
bool need_uevent;
@@ -611,6 +632,9 @@ static void __fw_free_buf(struct kref *ref)
#endif
if (!buf->allocated_size)
vfree(buf->data);
+#ifdef CONFIG_FIRMWARE_SIG
+ vfree(buf->sig_data);
+#endif
kfree_const(buf->fw_id);
kfree(buf);
}
@@ -648,13 +672,16 @@ fw_get_filesystem_firmware(struct device *device, struct firmware_buf *buf)
int i, len;
int rc = -ENOENT;
char *path;
- enum kernel_read_file_id id = READING_FIRMWARE;
- size_t msize = INT_MAX;
+ enum kernel_read_file_id id;
+ loff_t msize;
/* Already populated data member means we're loading into a buffer */
if (buf->data) {
id = READING_FIRMWARE_PREALLOC_BUFFER;
msize = buf->allocated_size;
+ } else {
+ id = READING_FIRMWARE;
+ msize = LONG_MAX;
}
path = __getname();
@@ -673,7 +700,7 @@ fw_get_filesystem_firmware(struct device *device, struct firmware_buf *buf)
break;
}
- buf->size = 0;
+ /* Load firmware */
rc = kernel_read_file_from_path(path, &buf->data, &size, msize,
id);
if (rc) {
@@ -685,11 +712,38 @@ fw_get_filesystem_firmware(struct device *device, struct firmware_buf *buf)
path, rc);
continue;
}
- dev_dbg(device, "direct-loading %s\n", buf->fw_id);
buf->size = size;
+
+#ifdef CONFIG_FIRMWARE_SIG
+ len = snprintf(path, PATH_MAX, "%s/%s.p7s",
+ fw_path[i], buf->fw_id);
+ if (len >= PATH_MAX) {
+ rc = -ENAMETOOLONG;
+ break;
+ }
+
+ /* Load signature */
+ rc = kernel_read_file_from_path(path, &buf->sig_data, &size,
+ LONG_MAX, READING_FIRMWARE);
+ if (!rc)
+ buf->sig_size = size;
+ else if (rc == -ENOENT)
+ dev_info(device,
+ "signature of %s doesn't exist\n", buf->fw_id);
+ else
+ dev_warn(device,
+ "loading signature of %s failed (%d)\n",
+ buf->fw_id, rc);
+
+ /* caller should check existence of signature with sig_size */
+ rc = 0;
+#endif /* CONFIG_FIRMWARE_SIG */
+
+ dev_dbg(device, "direct-loading %s\n", buf->fw_id);
fw_state_done(&buf->fw_st);
break;
}
+
__putname(path);
return rc;
@@ -716,9 +770,9 @@ static void fw_set_page_data(struct firmware_buf *buf, struct firmware *fw)
fw->size = buf->size;
fw->data = buf->data;
- pr_debug("%s: fw-%s buf=%p data=%p size=%u\n",
+ pr_debug("%s: fw-%s buf=%p data=%p size=%u sig_ok=%s\n",
__func__, buf->fw_id, buf, buf->data,
- (unsigned int)buf->size);
+ (unsigned int)buf->size, buf->sig_ok ? "(yes)" : "(no)");
}
#ifdef CONFIG_PM_SLEEP
@@ -822,6 +876,25 @@ static int assign_firmware_buf(struct firmware *fw, struct device *device,
return 0;
}
+#ifdef CONFIG_FIRMWARE_SIG
+static int firmware_sig_check(struct firmware *fw)
+{
+ struct firmware_buf *buf = fw->priv;
+ int err;
+
+ if (!buf->sig_size)
+ return -ENOENT;
+
+ err = verify_pkcs7_signature(buf->data, buf->size,
+ buf->sig_data, buf->sig_size,
+ NULL, VERIFYING_FIRMWARE_SIGNATURE, NULL, NULL);
+ if (!err)
+ buf->sig_ok = true;
+
+ return err;
+}
+#endif /* CONFIG_FIRMWARE_SIG */
+
/*
* user-mode helper code
*/
@@ -1224,6 +1297,83 @@ static ssize_t firmware_data_write(struct file *filp, struct kobject *kobj,
return retval;
}
+static ssize_t firmware_sig_data_read(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buffer, loff_t offset, size_t count)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct firmware_priv *fw_priv = to_firmware_priv(dev);
+ struct firmware_buf *buf;
+ ssize_t ret_count;
+
+ mutex_lock(&fw_lock);
+ buf = fw_priv->buf;
+ if (!buf || fw_state_is_done(&buf->fw_st)) {
+ ret_count = -ENODEV;
+ goto out;
+ }
+ if (offset > buf->size) {
+ ret_count = 0;
+ goto out;
+ }
+ if (count > buf->size - offset)
+ ret_count = buf->size - offset;
+ else
+ ret_count = count;
+
+ memcpy(buffer, buf->data + offset, ret_count);
+
+out:
+ mutex_unlock(&fw_lock);
+ return ret_count;
+}
+
+static ssize_t firmware_sig_data_write(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buffer, loff_t offset,
+ size_t count)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct firmware_priv *fw_priv = to_firmware_priv(dev);
+ struct firmware_buf *buf;
+ void *buf_tmp;
+ size_t new_size;
+ ssize_t ret_count;
+
+ if (!capable(CAP_SYS_RAWIO))
+ return -EPERM;
+
+ mutex_lock(&fw_lock);
+ buf = fw_priv->buf;
+ if (!buf || fw_state_is_done(&buf->fw_st)) {
+ ret_count = -ENODEV;
+ goto out;
+ }
+
+ if (buf->sig_data && (offset + count > buf->sig_size)) {
+ /* grow a buffer */
+ new_size = min(size_t, PAGE_SIZE, offset + count);
+ buf_tmp = NULL;
+ if (new_size <= PAGE_SIZE)
+ buf_tmp = vmalloc(new_size);
+ if (!buf_tmp)
+ ret_count = -ENOMEM;
+ goto out;
+ }
+ memcpy(buf_tmp, buf->sig_data, buf->sig_size);
+ vfree(buf->sig_data);
+ buf->sig_data = buf_tmp;
+ buf->sig_size = new_size;
+ }
+
+ ret_count = min(size_t, buf->sig_size - offset, count);
+ memcpy(buf->data + offset, buffer, ret_count);
+
+out:
+ mutex_unlock(&fw_lock);
+ return ret_count;
+}
+
static struct bin_attribute firmware_attr_data = {
.attr = { .name = "data", .mode = 0644 },
.size = 0,
@@ -1231,6 +1381,13 @@ static struct bin_attribute firmware_attr_data = {
.write = firmware_data_write,
};
+static struct bin_attribute firmware_attr_sig_data = {
+ .attr = { .name = "sig_data", .mode = 0644 },
+ .size = 0,
+ .read = firmware_sig_data_read,
+ .write = firmware_sig_data_write,
+};
+
static struct attribute *fw_dev_attrs[] = {
&dev_attr_loading.attr,
NULL
@@ -1238,6 +1395,7 @@ static struct attribute *fw_dev_attrs[] = {
static struct bin_attribute *fw_dev_bin_attrs[] = {
&firmware_attr_data,
+ &firmware_attr_sig_data,
NULL
};
@@ -1364,9 +1522,6 @@ static int fw_load_from_user_helper(struct firmware *firmware,
fw_priv->buf = firmware->priv;
ret = _request_firmware_load(fw_priv, data_params, timeout);
- if (!ret)
- ret = assign_firmware_buf(firmware, device, data_params);
-
out_unlock:
usermodehelper_read_unlock();
@@ -1490,11 +1645,43 @@ _request_firmware(const struct firmware **firmware_p, const char *name,
"Direct firmware load for %s failed with error %d\n",
name, ret);
ret = driver_data_fallback(fw, name, device, data_params, ret);
- } else
- ret = assign_firmware_buf(fw, device, data_params);
+ }
+ if (ret)
+ goto out;
+
+#ifdef CONFIG_FIRMWARE_SIG
+ if (driver_data_param_no_sig_check(&data_params->req_params))
+ goto skip_sig_check;
+
+ /* Verify the data with its signature */
+ ret = firmware_sig_check(fw);
+ if (ret) {
+ if (fw_sig_enforce) {
+ /* ENFORCING */
+ dev_err(device,
+ "firmware [%s] verification failed (%d).\n",
+ name, ret);
+
+ goto out;
+ }
+
+ /* PERMISSIVE */
+ dev_warn(device,
+ "firmware [%s] verification failed (%d), but this is ok.\n",
+ name, ret);
+ } else {
+ dev_info(device, "firmware [%s] verification succeeded.\n",
+ name);
+ }
+
+ skip_sig_check:
+#endif /* CONFIG_FIRMWARE_SIG */
+ ret = assign_firmware_buf(fw, device, data_params);
out:
if (ret < 0) {
+ dev_warn(device, "Loading firmware failed (%d)\n", ret);
+
release_firmware(fw);
fw = NULL;
}
@@ -123,11 +123,16 @@ union driver_data_cbs {
* file to be present given the API range, it is only required for one
* file in the API range to be present. If the %DRIVER_DATA_REQ_OPTIONAL
* flag is also enabled then all files are treated as optional.
+ * @DRIVER_DATA_REQ_NO_SIG_CHECK: indicates that signature verification
+ * for this driver data is not required for any reason.
+ * So even if CONFIG_FIRMWARE_SIG_ENFORCE is enabled, the data will be
+ * successfully loaded if it does exist.
*/
enum driver_data_reqs {
DRIVER_DATA_REQ_OPTIONAL = 1 << 0,
DRIVER_DATA_REQ_KEEP = 1 << 1,
DRIVER_DATA_REQ_USE_API_VERSIONING = 1 << 2,
+ DRIVER_DATA_REQ_NO_SIG_CHECK = 1 << 3,
};
/**