[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <s5hd2zs9db6.wl%tiwai@suse.de>
Date: Mon, 05 Nov 2012 18:20:29 +0100
From: Takashi Iwai <tiwai@...e.de>
To: Matthew Garrett <mjg59@...f.ucam.org>
Cc: Alan Cox <alan@...rguk.ukuu.org.uk>, joeyli <jlee@...e.com>,
Jiri Kosina <jkosina@...e.cz>,
David Howells <dhowells@...hat.com>,
Rusty Russell <rusty@...tcorp.com.au>,
linux-kernel@...r.kernel.org,
linux-security-module@...r.kernel.org, linux-efi@...r.kernel.org
Subject: [PATCH RFC 3/4] firmware: Add a signature check
Add a feature to check the firmware signature, specified via Kconfig
CONFIG_FIRMWARE_SIG. The signature check is performed only for the
direct fw loading without udev. Also no check for built-in firmware
blobs is implemented yet.
Signed-off-by: Takashi Iwai <tiwai@...e.de>
---
drivers/base/Kconfig | 6 +++++
drivers/base/firmware_class.c | 56 +++++++++++++++++++++++++++++++++++---
include/linux/firmware.h | 7 +++++
kernel/module_signing.c | 63 +++++++++++++++++++++++++++++++++++++++++++
4 files changed, 128 insertions(+), 4 deletions(-)
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig
index b34b5cd..3696fd7 100644
--- a/drivers/base/Kconfig
+++ b/drivers/base/Kconfig
@@ -145,6 +145,12 @@ config EXTRA_FIRMWARE_DIR
this option you can point it elsewhere, such as /lib/firmware/ or
some other directory containing the firmware files.
+config FIRMWARE_SIG
+ bool "Firmware signature verification"
+ depends on FW_LOADER && MODULE_SIG
+ help
+ Enable firmware signature check.
+
config DEBUG_DRIVER
bool "Driver Core verbose debug messages"
depends on DEBUG_KERNEL
diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c
index 8945f4e..575bc4c 100644
--- a/drivers/base/firmware_class.c
+++ b/drivers/base/firmware_class.c
@@ -36,6 +36,11 @@ MODULE_AUTHOR("Manuel Estrada Sainz");
MODULE_DESCRIPTION("Multi purpose firmware loading support");
MODULE_LICENSE("GPL");
+#ifdef CONFIG_FIRMWARE_SIG
+static bool sig_enforce;
+module_param(sig_enforce, bool, 0644);
+#endif
+
/* Builtin firmware support */
#ifdef CONFIG_FW_LOADER
@@ -287,7 +292,7 @@ static noinline long fw_file_size(struct file *file)
return st.size;
}
-static bool fw_read_file_contents(struct file *file, struct firmware_buf *fw_buf)
+static bool fw_read_file_contents(struct file *file, void **bufp, size_t *sizep)
{
long size;
char *buf;
@@ -302,11 +307,39 @@ static bool fw_read_file_contents(struct file *file, struct firmware_buf *fw_buf
vfree(buf);
return false;
}
- fw_buf->data = buf;
- fw_buf->size = size;
+ *bufp = buf;
+ *sizep = size;
return true;
}
+#ifdef CONFIG_FIRMWARE_SIG
+static bool verify_signature(struct firmware_buf *buf, const char *path)
+{
+ const unsigned long markerlen = sizeof(FIRMWARE_SIG_STRING) - 1;
+ struct file *file;
+ void *sig_data;
+ size_t sig_size;
+ bool success;
+
+ file = filp_open(path, O_RDONLY, 0);
+ if (IS_ERR(file))
+ return false;
+
+ success = fw_read_file_contents(file, &sig_data, &sig_size);
+ fput(file);
+ if (success) {
+ if (sig_size > markerlen &&
+ !memcmp(sig_data, FIRMWARE_SIG_STRING, markerlen))
+ success = !fw_verify_sig(buf->data, buf->size,
+ sig_data + markerlen,
+ sig_size - markerlen);
+ pr_debug("verified signature %s: %d\n", path, success);
+ vfree(sig_data);
+ }
+ return success;
+}
+#endif /* CONFIG_FIRMWARE_SIG */
+
static bool fw_get_filesystem_firmware(struct firmware_buf *buf)
{
int i;
@@ -320,8 +353,23 @@ static bool fw_get_filesystem_firmware(struct firmware_buf *buf)
file = filp_open(path, O_RDONLY, 0);
if (IS_ERR(file))
continue;
- success = fw_read_file_contents(file, buf);
+ success = fw_read_file_contents(file, &buf->data, &buf->size);
fput(file);
+#ifdef CONFIG_FIRMWARE_SIG
+ if (success) {
+ snprintf(path, PATH_MAX, "%s/%s.sig", fw_path[i],
+ buf->fw_id);
+ if (!verify_signature(buf, path)) {
+ pr_err("Invalid signature file %s\n", path);
+ if (sig_enforce) {
+ vfree(buf->data);
+ buf->data = NULL;
+ buf->size = 0;
+ success = false;
+ }
+ }
+ }
+#endif /* CONFIG_FIRMWARE_SIG */
if (success)
break;
}
diff --git a/include/linux/firmware.h b/include/linux/firmware.h
index e4279fe..2e9e457 100644
--- a/include/linux/firmware.h
+++ b/include/linux/firmware.h
@@ -79,4 +79,11 @@ static inline int uncache_firmware(const char *name)
}
#endif
+#ifdef CONFIG_FIRMWARE_SIG
+#define FIRMWARE_SIG_STRING "~Linux firmware signature~\n"
+/* defined in kernel/module_signing.c */
+int fw_verify_sig(const void *fw_data, size_t fw_size,
+ const void *sig_data, size_t sig_size);
+#endif
+
#endif
diff --git a/kernel/module_signing.c b/kernel/module_signing.c
index ea1b1df..7994452 100644
--- a/kernel/module_signing.c
+++ b/kernel/module_signing.c
@@ -11,6 +11,7 @@
#include <linux/kernel.h>
#include <linux/err.h>
+#include <linux/export.h>
#include <crypto/public_key.h>
#include <crypto/hash.h>
#include <keys/asymmetric-type.h>
@@ -247,3 +248,65 @@ error_put_key:
pr_devel("<==%s() = %d\n", __func__, ret);
return ret;
}
+
+#ifdef CONFIG_FIRMWARE_SIG
+/*
+ * Verify the firmware signature, similar like module signature check
+ * but it's stored in a separate file
+ */
+int fw_verify_sig(const void *fw_data, size_t fw_size,
+ const void *sig_data, size_t sig_size)
+{
+ struct public_key_signature *pks;
+ struct module_signature ms;
+ struct key *key;
+ size_t sig_len;
+ int ret;
+
+ if (sig_size <= sizeof(ms))
+ return -EBADMSG;
+
+ memcpy(&ms, sig_data, sizeof(ms));
+ sig_data += sizeof(ms);
+ sig_size -= sizeof(ms);
+
+ sig_len = be32_to_cpu(ms.sig_len);
+ if (sig_size < sig_len + (size_t)ms.signer_len + ms.key_id_len)
+ return -EBADMSG;
+
+ /* For the moment, only support RSA and X.509 identifiers */
+ if (ms.algo != PKEY_ALGO_RSA ||
+ ms.id_type != PKEY_ID_X509)
+ return -ENOPKG;
+
+ if (ms.hash >= PKEY_HASH__LAST ||
+ !pkey_hash_algo[ms.hash])
+ return -ENOPKG;
+
+ key = request_asymmetric_key(sig_data, ms.signer_len,
+ sig_data + ms.signer_len, ms.key_id_len);
+ if (IS_ERR(key))
+ return PTR_ERR(key);
+
+ pks = mod_make_digest(ms.hash, fw_data, fw_size);
+ if (IS_ERR(pks)) {
+ ret = PTR_ERR(pks);
+ goto error_put_key;
+ }
+
+ sig_data += ms.signer_len + ms.key_id_len;
+ ret = mod_extract_mpi_array(pks, sig_data, sig_len);
+ if (ret < 0)
+ goto error_free_pks;
+
+ ret = verify_signature(key, pks);
+
+error_free_pks:
+ mpi_free(pks->rsa.s);
+ kfree(pks);
+error_put_key:
+ key_put(key);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(fw_verify_sig);
+#endif /* CONFIG_FIRMWARE_SIG */
--
1.8.0
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Powered by blists - more mailing lists