内核引导签名
Secure Boot
是UEFI标准里面用来防病毒的,用来阻止恶意引导程序的加载。 UEFI
里面预先保存有受信任的公钥,比如OEM厂家的或者微软的公钥。有的好像个人也可以自己配置一些公钥(Machine Owner Key (MOK))进去。 如果这个选项被设置了,像grub这些引导程序的efi镜像
就必须经过公钥对应的私钥的签名才能加载了。 所以有的时候安装系统时,想要加载没签名的grub或者grub模块
,要禁用bios
里面的这个secure boot
(有些笔记本电脑重新编译内核,引导失败就是这个原因)。
ubuntu
和redhat
这些发行版,看他们的文档,如果他们发现uefi
里面的secure boot
是打开的,运行时候linux 内核
里面会获取的到UEFI
里面的system_keyring
,那内核就启动签名校验,用这uefi传过来的system_keyring
校验模块的签名。
第三方内核模块,一般不是ubuntu
这些编译的,是后面才编译的,编译的时候不可能拿到ubuntu
官方的私钥来做签名
。这些不签名的驱动,在 secure boot
模式下,就会被内核拒绝加载了。这种第三方驱动,要么把bios
里面的secure boot
关了,这样内核也不检查模块的签名了,要么就把自己使用的私钥对应的公钥
加到UEFI
的数据库里面去。bios
可以自己配置或者MOK
相关的工具可以修改UEFI的公钥数据库。
内核驱动签名
代码:5.10.108
内核配置
内核从3.7后开始支持模块签名,这个功能使能以后,内核只允许安装特定key签名的模块。
内核在编译的时候,启动相关配置(.config)
,内核才会启动内核签名功能
make menuconfig
# Enable loadable module support
# --> Module signature verification # 启用模块签名
# --> Require modules to be validly signed # 对应 congfig 中的 CONFIG_MODULE_SIG_FORCE
# --> Automatically sign all modules (NEW) # 对应 congfig 中的 CONFIG_MODULE_SIG_ALL
# --> Which hash algorithm should modules be signed with? (Sign modules with SHA-256) # 选择对哈希模块签名的哈希算法
# /boot/config-$(uname -r)
# 启用内核签名
CONFIG_MODULE_SIG=y
# 模块签名或不签名都可以使用(CONFIG_MODULE_SIG_FORCE not set)
# 模块必须有正确的签名才能正常使用
CONFIG_MODULE_SIG_FORCE=y
# 对所有模块签名
CONFIG_MODULE_SIG_ALL=y
编译内核生成私钥,公钥
x509.genkey
:生成key pair时的配置项。
signing_key.pem
: 秘钥,旧版内核是signing_key.priv
signing_key.x509
:公钥/数字证书,会被编译进内核,用于签名模块的校验
每编译一次,虽然x509.genkey
每次都相同,但是生成的key pair
是不同的,因为有生成key pair
会使用硬件随机数。
# certs/Makefile
...
ifeq ($(CONFIG_MODULE_SIG_KEY),"certs/signing_key.pem")
ifeq ($(openssl_available),yes)
X509TEXT=$(shell openssl x509 -in "certs/signing_key.pem" -text 2>/dev/null)
$(if $(findstring rsaEncryption,$(X509TEXT)),,$(shell rm -f "certs/signing_key.pem"))
endif
# signing_key.pem 秘钥,旧版内核是signing_key.priv
$(obj)/signing_key.pem: $(obj)/x509.genkey
@$(kecho) "###"
@$(kecho) "### Now generating an X.509 key pair to be used for signing modules."
@$(kecho) "###"
@$(kecho) "### If this takes a long time, you might wish to run rngd in the"
@$(kecho) "### background to keep the supply of entropy topped up. It"
@$(kecho) "### needs to be run as root, and uses a hardware random"
@$(kecho) "### number generator if one is available."
@$(kecho) "###"
$(Q)openssl req -new -nodes -utf8 -$(CONFIG_MODULE_SIG_HASH) -days 36500 \
-batch -x509 -config $(obj)/x509.genkey \
-outform PEM -out $(obj)/signing_key.pem \
-keyout $(obj)/signing_key.pem \
$($(quiet)redirect_openssl)
@$(kecho) "###"
@$(kecho) "### Key pair generated."
@$(kecho) "###"
# x509.genkey 生成key pair时的配置项
$(obj)/x509.genkey:
@$(kecho) Generating X.509 key generation config
# 等价于 @echo "[ req ]" >$@
@echo >$@ "[ req ]"
@echo >>$@ "default_bits = 4096"
@echo >>$@ "distinguished_name = req_distinguished_name"
@echo >>$@ "prompt = no"
@echo >>$@ "string_mask = utf8only"
@echo >>$@ "x509_extensions = myexts"
@echo >>$@
@echo >>$@ "[ req_distinguished_name ]"
@echo >>$@ "#O = Unspecified company"
@echo >>$@ "CN = Build time autogenerated kernel key"
@echo >>$@ "#emailAddress = unspecified.user@unspecified.company"
@echo >>$@
@echo >>$@ "[ myexts ]"
@echo >>$@ "basicConstraints=critical,CA:FALSE"
@echo >>$@ "keyUsage=digitalSignature"
@echo >>$@ "subjectKeyIdentifier=hash"
@echo >>$@ "authorityKeyIdentifier=keyid"
endif # CONFIG_MODULE_SIG_KEY
$(eval $(call config_filename,MODULE_SIG_KEY))
# If CONFIG_MODULE_SIG_KEY isn't a PKCS#11 URI, depend on it
ifeq ($(patsubst pkcs11:%,%,$(firstword $(MODULE_SIG_KEY_FILENAME))),$(firstword $(MODULE_SIG_KEY_FILENAME)))
X509_DEP := $(MODULE_SIG_KEY_SRCPREFIX)$(MODULE_SIG_KEY_FILENAME)
endif
# GCC PR#66871 again.
$(obj)/system_certificates.o: $(obj)/signing_key.x509
targets += signing_key.x509
# signing_key.x509 公钥/数字证书
$(obj)/signing_key.x509: scripts/extract-cert $(X509_DEP) FORCE
$(call if_changed,extract_certs,$(MODULE_SIG_KEY_SRCPREFIX)$(CONFIG_MODULE_SIG_KEY))
endif # CONFIG_MODULE_SIG
ifeq ($(CONFIG_SYSTEM_REVOCATION_LIST),y)
$(eval $(call config_filename,SYSTEM_REVOCATION_KEYS))
$(obj)/revocation_certificates.o: $(obj)/x509_revocation_list
quiet_cmd_extract_certs = EXTRACT_CERTS $(patsubst "%",%,$(2))
cmd_extract_certs = scripts/extract-cert $(2) $@
targets += x509_revocation_list
$(obj)/x509_revocation_list: scripts/extract-cert $(SYSTEM_REVOCATION_KEYS_SRCPREFIX)$(SYSTEM_REVOCATION_KEYS_FILENAME) FORCE
$(call if_changed,extract_certs,$(SYSTEM_REVOCATION_KEYS_SRCPREFIX)$(CONFIG_SYSTEM_REVOCATION_KEYS))
endif
...
# certs/system_certificates.S
...
# certs/signing_key.x509和system_certificates.S会一起被编译为certs/system_certificates.o
# 该object文件的内容会被包含到内核image的.init.rodata节中
__INITRODATA
.align 8
.globl system_certificate_list
system_certificate_list:
__cert_list_start:
#ifdef CONFIG_MODULE_SIG
.incbin "certs/signing_key.x509"
#endif
.incbin "certs/x509_certificate_list"
__cert_list_end:
...
为所有驱动签名
注:编译完驱动后,驱动并未被签名,要执行 make modules_install 后才被签名
# Makefile
...
ifdef CONFIG_MODULE_SIG_ALL
$(eval $(call config_filename,MODULE_SIG_KEY))
# .config 中:
# CONFIG_MODULE_SIG_HASH="sha256"
# CONFIG_MODULE_SIG_KEY="certs/signing_key.pem"
# MODULE_SIG_KEY_SRCPREFIX 未定义为空
# 等价于
# mod_sign_cmd = scripts/sign-file sha256 certs/signing_key.pem certs/signing_key.x509
mod_sign_cmd = scripts/sign-file $(CONFIG_MODULE_SIG_HASH) $(MODULE_SIG_KEY_SRCPREFIX)$(CONFIG_MODULE_SIG_KEY) certs/signing_key.x509
else
mod_sign_cmd = true
endif
export mod_sign_cmd
...
# scripts/Makefile.modsign
...
# 为驱动加签名
quiet_cmd_sign_ko = SIGN [M] $(2)/$(notdir $@)
cmd_sign_ko = $(mod_sign_cmd) $(2)/$(notdir $@)
...
对应自定义驱动,可以使用以下命令签名:
cd kernel_path
scripts/sign-file sha256 certs/signing_key.pem certs/signing_key.x509 yuor_module_path
# 旧版kernel
perl scripts/sign-file sha256 certs/signing_key.priv certs/signing_key.x509 yuor_module_path
驱动加完签名后的结构
<内核模块的内容>
# 签名后添加的内容
<PKCS#7消息>
<模块签名>
<Mark字符串"~Module signature appended~\n"(共28字节,不包括结尾的NULL字符)>
驱动加载代码对签名驱动校验的流程
// kernel/module.c
static int load_module(struct load_info *info, const char __user *uargs,
int flags)
{
...
err = module_sig_check(info, flags);
if (err)
goto free_copy;
...
}
#ifdef CONFIG_MODULE_SIG
static int module_sig_check(struct load_info *info, int flags)
{
...
if (flags == 0 &&
info->len > markerlen &&
// include/linux/module_signature.h
// #define MODULE_SIG_STRING "~Module signature appended~\n"
// 检测模块最后的内容是不是 "~Module signature appended~\n"
memcmp(mod + info->len - markerlen, MODULE_SIG_STRING, markerlen) == 0) {
/* We truncate the module to discard the signature */
// 模块的长度减少markerlen
info->len -= markerlen;
err = mod_verify_sig(mod, info);
}
//除非 err == 0,否则都返回错误
switch (err) {
case 0:
info->sig_ok = true;
return 0;
/* We don't permit modules to be loaded into trusted kernels
* without a valid signature on them, but if we're not
* enforcing, certain errors are non-fatal.
*/
case -ENODATA:
reason = "unsigned module";
break;
case -ENOPKG:
reason = "module with unsupported crypto";
break;
case -ENOKEY:
reason = "module with unavailable key";
break;
/* All other errors are fatal, including nomem, unparseable
* signatures and signature check failures - even if signatures
* aren't required.
*/
default:
return err;
}
if (is_module_sig_enforced()) {
pr_notice("Loading of %s is rejected\n", reason);
return -EKEYREJECTED;
}
return security_locked_down(LOCKDOWN_MODULE_SIGNATURE);
}
#else /* !CONFIG_MODULE_SIG */
static int module_sig_check(struct load_info *info, int flags)
{
return 0;
}
#endif /* !CONFIG_MODULE_SIG */
static bool sig_enforce = IS_ENABLED(CONFIG_MODULE_SIG_FORCE);
module_param(sig_enforce, bool_enable_only, 0644);
bool is_module_sig_enforced(void)
{
return sig_enforce;
}
EXPORT_SYMBOL(is_module_sig_enforced);
// kernel/module_signing.c
...
int mod_verify_sig(const void *mod, struct load_info *info)
{
struct module_signature ms;
size_t sig_len, modlen = info->len;
int ret;
pr_devel("==>%s(,%zu)\n", __func__, modlen);
if (modlen <= sizeof(ms))
return -EBADMSG;
// 获取<模块签名>
memcpy(&ms, mod + (modlen - sizeof(ms)), sizeof(ms));
// 校验<模块签名>
ret = mod_check_sig(&ms, modlen, "module");
if (ret)
return ret;
sig_len = be32_to_cpu(ms.sig_len);
// 模块长度只包含模块本身的内容了
modlen -= sig_len + sizeof(ms);
info->len = modlen;
// 校验 <PKCS#7消息>
return verify_pkcs7_signature(mod, modlen, mod + modlen, sig_len,
VERIFY_USE_SECONDARY_KEYRING,
VERIFYING_MODULE_SIGNATURE,
NULL, NULL);
}
...
// kernel/module_signature.c
int mod_check_sig(const struct module_signature *ms, size_t file_len,
const char *name)
{
if (be32_to_cpu(ms->sig_len) >= file_len - sizeof(*ms))
return -EBADMSG;
if (ms->id_type != PKEY_ID_PKCS7) {
pr_err("%s: not signed with expected PKCS#7 message\n",
name);
return -ENOPKG;
}
if (ms->algo != 0 ||
ms->hash != 0 ||
ms->signer_len != 0 ||
ms->key_id_len != 0 ||
ms->__pad[0] != 0 ||
ms->__pad[1] != 0 ||
ms->__pad[2] != 0) {
pr_err("%s: PKCS#7 signature info has unexpected non-zero params\n",
name);
return -EBADMSG;
}
return 0;
}
// certs/system_keyring.c
...
int verify_pkcs7_signature(const void *data, size_t len,
const void *raw_pkcs7, size_t pkcs7_len,
struct key *trusted_keys,
enum key_being_used_for usage,
int (*view_content)(void *ctx,
const void *data, size_t len,
size_t asn1hdrlen),
void *ctx)
{
struct pkcs7_message *pkcs7;
int ret;
pkcs7 = pkcs7_parse_message(raw_pkcs7, pkcs7_len);
if (IS_ERR(pkcs7))
return PTR_ERR(pkcs7);
ret = verify_pkcs7_message_sig(data, len, pkcs7, trusted_keys, usage,
view_content, ctx);
pkcs7_free_message(pkcs7);
pr_devel("<==%s() = %d\n", __func__, ret);
return ret;
}
EXPORT_SYMBOL_GPL(verify_pkcs7_signature);
...
如何查看驱动是否加入签名
查看驱动程序,最后面数据是否是MODULE_SIG_STRING("~Module signature appended~\n")
# 有驱动签名
root:~$ hexdump -C ./test/lib/modules/5.10.108/kernel/net/ipv4/netfilter/iptable_nat.ko | tail
00001fc0 87 de 91 9d ac 56 f4 73 dc 9b a9 20 63 68 c8 24 |.....V.s... ch.$|
00001fd0 3e 84 30 ed 1d b2 2b f6 d6 a2 f5 6c 2f cd 15 82 |>.0...+....l/...|
00001fe0 0f 85 09 c1 18 ce 6e a7 06 90 8a 75 aa bf 37 22 |......n....u..7"|
00001ff0 8a 43 68 65 45 8e a9 3c 88 cd ee 94 01 f9 e8 5e |.CheE..<.......^|
00002000 a7 ee 4c f6 fe 30 b0 b2 dd e6 09 88 23 42 b8 49 |..L..0......#B.I|
00002010 66 a0 9b 42 81 32 9f 45 35 00 00 02 00 00 00 00 |f..B.2.E5.......|
00002020 00 00 00 02 a9 7e 4d 6f 64 75 6c 65 20 73 69 67 |.....~Module sig|
00002030 6e 61 74 75 72 65 20 61 70 70 65 6e 64 65 64 7e |nature appended~|
00002040 0a |.|
00002041
# 无驱动签名
root:~$ hexdump -C ./test1/lib/modules/5.10.108/kernel/net/ipv4/netfilter/iptable_nat.ko | tail
00001ce0 08 00 00 00 00 00 00 00 18 00 00 00 00 00 00 00 |................|
00001cf0 09 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 |................|
00001d00 00 00 00 00 00 00 00 00 68 0d 00 00 00 00 00 00 |........h.......|
00001d10 49 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |I...............|
00001d20 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00001d30 11 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 |................|
00001d40 00 00 00 00 00 00 00 00 48 16 00 00 00 00 00 00 |........H.......|
00001d50 e3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00001d60 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00001d70
特殊模块签名
猜的不一定是
由于linux 3.7
后才加入签名配置,所以在linux 3.7
版本的签名信息保存到.note.module.sig
段,如下是2.6.32
的签名信息:
root:~$ readelf -x .note.module.sig ./ahci.ko
Hex dump of section '.note.module.sig':
0x00000000 0b000000 60000000 64000000 6d6f6475 ....`...d...modu
0x00000010 6c652e73 69670000 885e0400 11080006 le.sig...^......
0x00000020 05025965 2b82000a 091070a6 64250e63 ..Ye+.....p.d%.c
0x00000030 2d89586b 00ff647b fec64300 74f9a3c2 -.Xk..d{..C.t...
0x00000040 a37b794a 4ec5e2d8 98d75872 e28ce4a7 .{yJN.....Xr....
0x00000050 fcbed2c8 21af00ff 74c2c36c e5de163e ....!...t..l...>
0x00000060 06522257 3a43c974 a9bbf067 7987e46c .R"W:C.t...gy..l
0x00000070 1a154fbd 7b9a6aa8 ..O.{.j.