Skip to content

Commit 90a53e4

Browse files
committed
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 <[email protected]>
1 parent c8c240e commit 90a53e4

File tree

6 files changed

+182
-1
lines changed

6 files changed

+182
-1
lines changed

net/wireless/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
shipped-certs.c
2+
extra-certs.c

net/wireless/Kconfig

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,36 @@ config CFG80211_CERTIFICATION_ONUS
8383
you are a wireless researcher and are working in a controlled
8484
and approved environment by your local regulatory agency.
8585

86+
config CFG80211_REQUIRE_SIGNED_REGDB
87+
bool "require regdb signature" if CFG80211_CERTIFICATION_ONUS
88+
default y
89+
select SYSTEM_DATA_VERIFICATION
90+
help
91+
Require that in addition to the "regulatory.db" file a
92+
"regulatory.db.p7s" can be loaded with a valid PKCS#7
93+
signature for the regulatory.db file made by one of the
94+
keys in the certs/ directory.
95+
96+
config CFG80211_USE_KERNEL_REGDB_KEYS
97+
bool "allow regdb keys shipped with the kernel" if CFG80211_CERTIFICATION_ONUS
98+
default y
99+
depends on CFG80211_REQUIRE_SIGNED_REGDB
100+
help
101+
Allow the regulatory database to be signed by one of the keys for
102+
which certificates are part of the kernel sources
103+
(in net/wireless/certs/).
104+
105+
This is currently only Seth Forshee's key, who is the regulatory
106+
database maintainer.
107+
108+
config CFG80211_EXTRA_REGDB_KEYDIR
109+
string "additional regdb key directory" if CFG80211_CERTIFICATION_ONUS
110+
depends on CFG80211_REQUIRE_SIGNED_REGDB
111+
help
112+
If selected, point to a directory with DER-encoded X.509
113+
certificates like in the kernel sources (net/wireless/certs/)
114+
that shall be accepted for a signed regulatory database.
115+
86116
config CFG80211_REG_CELLULAR_HINTS
87117
bool "cfg80211 regulatory support for cellular base station hints"
88118
depends on CFG80211_CERTIFICATION_ONUS

net/wireless/Makefile

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,25 @@ cfg80211-$(CONFIG_CFG80211_DEBUGFS) += debugfs.o
1616
cfg80211-$(CONFIG_CFG80211_WEXT) += wext-compat.o wext-sme.o
1717

1818
CFLAGS_trace.o := -I$(src)
19+
20+
cfg80211-$(CONFIG_CFG80211_USE_KERNEL_REGDB_KEYS) += shipped-certs.o
21+
ifneq ($(CONFIG_CFG80211_EXTRA_REGDB_KEYDIR),)
22+
cfg80211-y += extra-certs.o
23+
endif
24+
25+
$(obj)/shipped-certs.c: $(wildcard $(srctree)/$(src)/certs/*.x509)
26+
@echo " GEN $@"
27+
@echo '#include "reg.h"' > $@
28+
@echo 'const u8 shipped_regdb_certs[] = {' >> $@
29+
@for f in $^ ; do hexdump -v -e '1/1 "0x%.2x," "\n"' < $$f >> $@ ; done
30+
@echo '};' >> $@
31+
@echo 'unsigned int shipped_regdb_certs_len = sizeof(shipped_regdb_certs);' >> $@
32+
33+
$(obj)/extra-certs.c: $(CONFIG_CFG80211_EXTRA_REGDB_KEYDIR:"%"=%) \
34+
$(wildcard $(CONFIG_CFG80211_EXTRA_REGDB_KEYDIR:"%"=%)/*.x509)
35+
@echo " GEN $@"
36+
@echo '#include "reg.h"' > $@
37+
@echo 'const u8 extra_regdb_certs[] = {' >> $@
38+
@for f in $^ ; do test -f $$f && hexdump -v -e '1/1 "0x%.2x," "\n"' < $$f >> $@ || true ; done
39+
@echo '};' >> $@
40+
@echo 'unsigned int extra_regdb_certs_len = sizeof(extra_regdb_certs);' >> $@

net/wireless/certs/sforshee.x509

680 Bytes
Binary file not shown.

net/wireless/reg.c

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
#include <linux/ctype.h>
5454
#include <linux/nl80211.h>
5555
#include <linux/platform_device.h>
56+
#include <linux/verification.h>
5657
#include <linux/moduleparam.h>
5758
#include <linux/firmware.h>
5859
#include <net/cfg80211.h>
@@ -659,6 +660,115 @@ static bool valid_country(const u8 *data, unsigned int size,
659660
return true;
660661
}
661662

663+
#ifdef CONFIG_CFG80211_REQUIRE_SIGNED_REGDB
664+
static struct key *builtin_regdb_keys;
665+
666+
static void __init load_keys_from_buffer(const u8 *p, unsigned int buflen)
667+
{
668+
const u8 *end = p + buflen;
669+
size_t plen;
670+
key_ref_t key;
671+
672+
while (p < end) {
673+
/* Each cert begins with an ASN.1 SEQUENCE tag and must be more
674+
* than 256 bytes in size.
675+
*/
676+
if (end - p < 4)
677+
goto dodgy_cert;
678+
if (p[0] != 0x30 &&
679+
p[1] != 0x82)
680+
goto dodgy_cert;
681+
plen = (p[2] << 8) | p[3];
682+
plen += 4;
683+
if (plen > end - p)
684+
goto dodgy_cert;
685+
686+
key = key_create_or_update(make_key_ref(builtin_regdb_keys, 1),
687+
"asymmetric", NULL, p, plen,
688+
((KEY_POS_ALL & ~KEY_POS_SETATTR) |
689+
KEY_USR_VIEW | KEY_USR_READ),
690+
KEY_ALLOC_NOT_IN_QUOTA |
691+
KEY_ALLOC_BUILT_IN |
692+
KEY_ALLOC_BYPASS_RESTRICTION);
693+
if (IS_ERR(key)) {
694+
pr_err("Problem loading in-kernel X.509 certificate (%ld)\n",
695+
PTR_ERR(key));
696+
} else {
697+
pr_notice("Loaded X.509 cert '%s'\n",
698+
key_ref_to_ptr(key)->description);
699+
key_ref_put(key);
700+
}
701+
p += plen;
702+
}
703+
704+
return;
705+
706+
dodgy_cert:
707+
pr_err("Problem parsing in-kernel X.509 certificate list\n");
708+
}
709+
710+
static int __init load_builtin_regdb_keys(void)
711+
{
712+
builtin_regdb_keys =
713+
keyring_alloc(".builtin_regdb_keys",
714+
KUIDT_INIT(0), KGIDT_INIT(0), current_cred(),
715+
((KEY_POS_ALL & ~KEY_POS_SETATTR) |
716+
KEY_USR_VIEW | KEY_USR_READ | KEY_USR_SEARCH),
717+
KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
718+
if (IS_ERR(builtin_regdb_keys))
719+
return PTR_ERR(builtin_regdb_keys);
720+
721+
pr_notice("Loading compiled-in X.509 certificates for regulatory database\n");
722+
723+
#ifdef CONFIG_CFG80211_USE_KERNEL_REGDB_KEYS
724+
load_keys_from_buffer(shipped_regdb_certs, shipped_regdb_certs_len);
725+
#endif
726+
#ifdef CFG80211_EXTRA_REGDB_KEYDIR
727+
if (CONFIG_CFG80211_EXTRA_REGDB_KEYDIR[0] != '\0')
728+
load_keys_from_buffer(extra_regdb_certs, extra_regdb_certs_len);
729+
#endif
730+
731+
return 0;
732+
}
733+
734+
static bool regdb_has_valid_signature(const u8 *data, unsigned int size)
735+
{
736+
const struct firmware *sig;
737+
bool result;
738+
739+
if (request_firmware(&sig, "regulatory.db.p7s", &reg_pdev->dev))
740+
return false;
741+
742+
result = verify_pkcs7_signature(data, size, sig->data, sig->size,
743+
builtin_regdb_keys,
744+
VERIFYING_UNSPECIFIED_SIGNATURE,
745+
NULL, NULL) == 0;
746+
747+
release_firmware(sig);
748+
749+
return result;
750+
}
751+
752+
static void free_regdb_keyring(void)
753+
{
754+
key_put(builtin_regdb_keys);
755+
}
756+
#else
757+
static int load_builtin_regdb_keys(void)
758+
{
759+
return 0;
760+
}
761+
762+
static bool regdb_has_valid_signature(const u8 *data, unsigned int size)
763+
{
764+
return true;
765+
}
766+
767+
static void free_regdb_keyring(void)
768+
{
769+
}
770+
#endif /* CONFIG_CFG80211_REQUIRE_SIGNED_REGDB */
771+
662772
static bool valid_regdb(const u8 *data, unsigned int size)
663773
{
664774
const struct fwdb_header *hdr = (void *)data;
@@ -673,6 +783,9 @@ static bool valid_regdb(const u8 *data, unsigned int size)
673783
if (hdr->version != cpu_to_be32(FWDB_VERSION))
674784
return false;
675785

786+
if (!regdb_has_valid_signature(data, size))
787+
return false;
788+
676789
country = &hdr->country[0];
677790
while ((u8 *)(country + 1) <= data + size) {
678791
if (!country->coll_ptr)
@@ -773,7 +886,7 @@ static void regdb_fw_cb(const struct firmware *fw, void *context)
773886
pr_info("failed to load regulatory.db\n");
774887
set_error = -ENODATA;
775888
} else if (!valid_regdb(fw->data, fw->size)) {
776-
pr_info("loaded regulatory.db is malformed\n");
889+
pr_info("loaded regulatory.db is malformed or signature is missing/invalid\n");
777890
set_error = -EINVAL;
778891
}
779892

@@ -3535,6 +3648,10 @@ int __init regulatory_init(void)
35353648
{
35363649
int err = 0;
35373650

3651+
err = load_builtin_regdb_keys();
3652+
if (err)
3653+
return err;
3654+
35383655
reg_pdev = platform_device_register_simple("regulatory", 0, NULL, 0);
35393656
if (IS_ERR(reg_pdev))
35403657
return PTR_ERR(reg_pdev);
@@ -3611,4 +3728,6 @@ void regulatory_exit(void)
36113728

36123729
if (!IS_ERR_OR_NULL(regdb))
36133730
kfree(regdb);
3731+
3732+
free_regdb_keyring();
36143733
}

net/wireless/reg.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
#ifndef __NET_WIRELESS_REG_H
22
#define __NET_WIRELESS_REG_H
3+
4+
#include <net/cfg80211.h>
5+
36
/*
47
* Copyright 2008-2011 Luis R. Rodriguez <[email protected]>
58
*
@@ -185,4 +188,9 @@ bool reg_dfs_domain_same(struct wiphy *wiphy1, struct wiphy *wiphy2);
185188
*/
186189
int reg_reload_regdb(void);
187190

191+
extern const u8 shipped_regdb_certs[];
192+
extern unsigned int shipped_regdb_certs_len;
193+
extern const u8 extra_regdb_certs[];
194+
extern unsigned int extra_regdb_certs_len;
195+
188196
#endif /* __NET_WIRELESS_REG_H */

0 commit comments

Comments
 (0)