summaryrefslogtreecommitdiff
path: root/net/wireless/reg.c
diff options
context:
space:
mode:
authorJohannes Berg <johannes.berg@intel.com>2017-09-13 22:21:08 +0200
committerJohannes Berg <johannes.berg@intel.com>2017-10-11 14:24:24 +0200
commit90a53e4432b12288316efaa5f308adafb8d304b0 (patch)
tree0d284c2cc1e9c2749f0216031066ed64bd0417f2 /net/wireless/reg.c
parentc8c240e284b3d821011b4f680b3eaa99569b3756 (diff)
cfg80211: implement regdb signature checking
Currently CRDA implements the signature checking, and the previous commits added the ability to load the whole regulatory database into the kernel. However, we really can't lose the signature checking, so implement it in the kernel by loading a detached signature (regulatory.db.p7s) and check it against built-in keys. Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Diffstat (limited to 'net/wireless/reg.c')
-rw-r--r--net/wireless/reg.c121
1 files changed, 120 insertions, 1 deletions
diff --git a/net/wireless/reg.c b/net/wireless/reg.c
index ebf8267ffbc9..58319c82ecb3 100644
--- a/net/wireless/reg.c
+++ b/net/wireless/reg.c
@@ -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();
}