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 <ti...@suse.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 majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to