]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blobdiff - net/wireless/reg.c
cfg80211: implement regdb signature checking
[mirror_ubuntu-bionic-kernel.git] / net / wireless / reg.c
index ebf8267ffbc918be54731574bc9e4f05fd60cfdc..58319c82ecb34f14743bbffaa464fcd7b77fe2d5 100644 (file)
@@ -53,6 +53,7 @@
 #include <linux/ctype.h>
 #include <linux/nl80211.h>
 #include <linux/platform_device.h>
+#include <linux/verification.h>
 #include <linux/moduleparam.h>
 #include <linux/firmware.h>
 #include <net/cfg80211.h>
@@ -659,6 +660,115 @@ static bool valid_country(const u8 *data, unsigned int size,
        return true;
 }
 
+#ifdef CONFIG_CFG80211_REQUIRE_SIGNED_REGDB
+static struct key *builtin_regdb_keys;
+
+static void __init load_keys_from_buffer(const u8 *p, unsigned int buflen)
+{
+       const u8 *end = p + buflen;
+       size_t plen;
+       key_ref_t key;
+
+       while (p < end) {
+               /* Each cert begins with an ASN.1 SEQUENCE tag and must be more
+                * than 256 bytes in size.
+                */
+               if (end - p < 4)
+                       goto dodgy_cert;
+               if (p[0] != 0x30 &&
+                   p[1] != 0x82)
+                       goto dodgy_cert;
+               plen = (p[2] << 8) | p[3];
+               plen += 4;
+               if (plen > end - p)
+                       goto dodgy_cert;
+
+               key = key_create_or_update(make_key_ref(builtin_regdb_keys, 1),
+                                          "asymmetric", NULL, p, plen,
+                                          ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
+                                           KEY_USR_VIEW | KEY_USR_READ),
+                                          KEY_ALLOC_NOT_IN_QUOTA |
+                                          KEY_ALLOC_BUILT_IN |
+                                          KEY_ALLOC_BYPASS_RESTRICTION);
+               if (IS_ERR(key)) {
+                       pr_err("Problem loading in-kernel X.509 certificate (%ld)\n",
+                              PTR_ERR(key));
+               } else {
+                       pr_notice("Loaded X.509 cert '%s'\n",
+                                 key_ref_to_ptr(key)->description);
+                       key_ref_put(key);
+               }
+               p += plen;
+       }
+
+       return;
+
+dodgy_cert:
+       pr_err("Problem parsing in-kernel X.509 certificate list\n");
+}
+
+static int __init load_builtin_regdb_keys(void)
+{
+       builtin_regdb_keys =
+               keyring_alloc(".builtin_regdb_keys",
+                             KUIDT_INIT(0), KGIDT_INIT(0), current_cred(),
+                             ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
+                             KEY_USR_VIEW | KEY_USR_READ | KEY_USR_SEARCH),
+                             KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
+       if (IS_ERR(builtin_regdb_keys))
+               return PTR_ERR(builtin_regdb_keys);
+
+       pr_notice("Loading compiled-in X.509 certificates for regulatory database\n");
+
+#ifdef CONFIG_CFG80211_USE_KERNEL_REGDB_KEYS
+       load_keys_from_buffer(shipped_regdb_certs, shipped_regdb_certs_len);
+#endif
+#ifdef CFG80211_EXTRA_REGDB_KEYDIR
+       if (CONFIG_CFG80211_EXTRA_REGDB_KEYDIR[0] != '\0')
+               load_keys_from_buffer(extra_regdb_certs, extra_regdb_certs_len);
+#endif
+
+       return 0;
+}
+
+static bool regdb_has_valid_signature(const u8 *data, unsigned int size)
+{
+       const struct firmware *sig;
+       bool result;
+
+       if (request_firmware(&sig, "regulatory.db.p7s", &reg_pdev->dev))
+               return false;
+
+       result = verify_pkcs7_signature(data, size, sig->data, sig->size,
+                                       builtin_regdb_keys,
+                                       VERIFYING_UNSPECIFIED_SIGNATURE,
+                                       NULL, NULL) == 0;
+
+       release_firmware(sig);
+
+       return result;
+}
+
+static void free_regdb_keyring(void)
+{
+       key_put(builtin_regdb_keys);
+}
+#else
+static int load_builtin_regdb_keys(void)
+{
+       return 0;
+}
+
+static bool regdb_has_valid_signature(const u8 *data, unsigned int size)
+{
+       return true;
+}
+
+static void free_regdb_keyring(void)
+{
+}
+#endif /* CONFIG_CFG80211_REQUIRE_SIGNED_REGDB */
+
 static bool valid_regdb(const u8 *data, unsigned int size)
 {
        const struct fwdb_header *hdr = (void *)data;
@@ -673,6 +783,9 @@ static bool valid_regdb(const u8 *data, unsigned int size)
        if (hdr->version != cpu_to_be32(FWDB_VERSION))
                return false;
 
+       if (!regdb_has_valid_signature(data, size))
+               return false;
+
        country = &hdr->country[0];
        while ((u8 *)(country + 1) <= data + size) {
                if (!country->coll_ptr)
@@ -773,7 +886,7 @@ static void regdb_fw_cb(const struct firmware *fw, void *context)
                pr_info("failed to load regulatory.db\n");
                set_error = -ENODATA;
        } else if (!valid_regdb(fw->data, fw->size)) {
-               pr_info("loaded regulatory.db is malformed\n");
+               pr_info("loaded regulatory.db is malformed or signature is missing/invalid\n");
                set_error = -EINVAL;
        }
 
@@ -3535,6 +3648,10 @@ int __init regulatory_init(void)
 {
        int err = 0;
 
+       err = load_builtin_regdb_keys();
+       if (err)
+               return err;
+
        reg_pdev = platform_device_register_simple("regulatory", 0, NULL, 0);
        if (IS_ERR(reg_pdev))
                return PTR_ERR(reg_pdev);
@@ -3611,4 +3728,6 @@ void regulatory_exit(void)
 
        if (!IS_ERR_OR_NULL(regdb))
                kfree(regdb);
+
+       free_regdb_keyring();
 }