JasonWang's Blog

Linux内核模块签名那些事

字数统计: 1.3k阅读时长: 6 min
2025/01/24

最近有同事反馈一个系统启动失败的问题,根因是系统的驱动模块加载失败导致system_server无法正常启动。lsmod查看,没有有任何的驱动加载,尝试insmod /vendor/lib/modules/cnss2.ko会提示:

1
2
3

insmod: failed to to load cnss2.ko : Key was rejected by service

说明对应模块的签名与内核不一致,导致安装失败了。这里我们就来看看内核模块具体是怎么签名的,模块签名又是如何验证的,以及如何通过工具进行模块的签名检验。

内核模块签名

内核为了增强安全,会在编译阶段生成一个签名并放在模块的末尾,在模块尝试加载时主动对模块的签名进行校验,如果签名不匹配则会拒绝安装。这样可以减少未签名或者恶意的模块安装到内核,从而威胁到内核的正常运行。

内核的签名遵从PKCS7(Public Key Cryptography Standards)加密规范,;加密秘钥支持多种SHA1/SHA224/SHA256等多种长度,可以通过内核配置CONFIG_MODULE_SIG_HASH进行配置。Linux内核从3.7版本开始支持模块的签名校验机制。如果想要开启此功能,需要打开如下几个配置:

1
2
3
4
5
6
7
8
CONFIG_MODULE_SIG=y
CONFIG_MODULE_SIG_FORCE=y
CONFIG_MODULE_SIG_ALL=y
CONFIG_MODULE_SIG_SHA1=y
#根据实际需要选择哈希算法
#CONFIG_MODULE_SIG_SHA224=y
#CONFIG_MODULE_SIG_SHA256=y

查看内核根目录的Makefile脚本可以看到,如果开启了CONFIG_MODULE_SIG_ALL/CONFIG_MODULE_SIG两个配置,则会基于内核生成的证书对系统所有外部模块进行签名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

ifdef CONFIG_MODULE_SIG_ALL
$(eval $(call config_filename,MODULE_SIG_KEY))

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

ifeq ($(CONFIG_MODULE_SIG), y)
PHONY += modules_sign
modules_sign:
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modsign
endif

具体的签名过程可以参考scripts/sign-file.c这个文件;签名完成后,同时会在模块的末尾加一串魔术字符~Module signature appended~\n。我们可以通过如下查看模块状态:

1
2
3

strings gvm_spf_machine_dlkm.ko |tail -n 1

模块签名校验

通过insmod加载内核模块时,核心是通过系统调用__NR_finit_module尝试安装模块驱动,调用路径如下(kernel/module.c):

1
2
3
4
5
6
7
8
9
10
11

int rc = syscall(__NR_finit_module, fd.get(), options.c_str(), flags)
# 内核调用路径
-> finit_module
-> load_module
-> module_sig_check
-> mod_verify_sig
-> verify_pkcs7_signature
-> do_init_module
-> do_mod_ctors
-> do_one_initcall

如果签名校验mod_verify_sig返回错误,内核会返回EKEYREJECTED错误码,这样驱动安装时会打印错误的信息key was rejected by service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

#ifdef CONFIG_MODULE_SIG
static int module_sig_check(struct load_info *info, int flags)
{
int err = -ENODATA;
const unsigned long markerlen = sizeof(MODULE_SIG_STRING) - 1;
const char *reason;
const void *mod = info->hdr;

/*
* Require flags == 0, as a module with version information
* removed is no longer the module that was signed
*/
if (flags == 0 &&
info->len > markerlen &&
memcmp(mod + info->len - markerlen, MODULE_SIG_STRING, markerlen) == 0) {
/* We truncate the module to discard the signature */
info->len -= markerlen;
err = mod_verify_sig(mod, info);
}

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);
}

如何检查模块的签名

在实际的项目开发过程中,我们可能需要查看模块的签名状态,并校验模块的签名是否正常。可以通过modinfo查看某个模块的签名状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# modinfo cnss2.ko
filename: out/target/product/msmnile_gvmq/dlkm/lib/modules/cnss2.ko
license: GPL v2
description: CNSS2 Platform Driver
vermagic: 5.4.219-g328405dec0e5-dirty SMP preempt mod_unload modversions aarch64
name: cnss2
intree: Y
depends: pci-msm-drv
alias: of:N*T*Cqcom,cnss
alias: of:N*T*Cqcom,cnssC*
alias: of:N*T*Cqcom,cnss-qca6290
alias: of:N*T*Cqcom,cnss-qca6290C*
alias: of:N*T*Cqcom,cnss-qca6390
alias: of:N*T*Cqcom,cnss-qca6390C*
alias: of:N*T*Cqcom,cnss-qca6490
alias: of:N*T*Cqcom,cnss-qca6490C*
alias: of:N*T*Cqcom,cnss-kiwi
alias: of:N*T*Cqcom,cnss-kiwiC*
alias: of:N*T*Cqcom,cnss-qca-converged
alias: of:N*T*Cqcom,cnss-qca-convergedC*
alias: pci:v0000168Cd0000003Esv*sd*bc*sc*i*
alias: pci:v000017CBd00001100sv*sd*bc*sc*i*
alias: pci:v000017CBd00001101sv*sd*bc*sc*i*
alias: pci:v000017CBd00001102sv*sd*bc*sc*i*
alias: pci:v000017CBd00001103sv*sd*bc*sc*i*
alias: pci:v000017CBd00001107sv*sd*bc*sc*i*
sig_id: PKCS#7
signer: Build time autogenerated kernel key
sig_key: AD:D1:68:DF:11:5E:02:AE
sig_hashalgo: sha1

如果要检验某个驱动与当前的内核系统签名是否匹配,可以通过一个perl脚本check_mod_sig.pl来进行校验:

  • 首先,需要通过内核提供的scripts/extract-sys-certs.pl脚本从vmlinux中提取证书(也可以从编译产物中获取)
  • 然后跟进证书对模块的签名进行校验,如果返回OK则表面模块驱动的签名与内核镜像的一致
1
2
3
4
5

perl extract-sys-certs.pl vmlinux ./vmlinux.x509

perl check_mod_sig.pl ./vmlinux.x509 cnss.ko

如果我们要去掉内核模块中的签名,可以使用strip(Android平台需要使用arm平台的命令工具prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/aarch64-linux-android/bin/strip)将签名去除,然后通过sign-file这个工具重新进行签名:

1
2
3
4

strip --keep-file-symbols cnss2.ko


仿照内核的Makefile进行模块的手动签名,签名成功后可以看到文件末尾多了~Module signature appended~这个字符串:

1
2
3
4
5

# mod_sign_cmd = scripts/sign-file $(CONFIG_MODULE_SIG_HASH) $(MODULE_SIG_KEY_SRCPREFIX)$(CONFIG_MODULE_SIG_KEY) certs/signing_key.x509

./sign-file sha1 certs/signing_key.pem certs/signing_key.x509 cnss2.ko

参考文献

原文作者:Jason Wang

更新日期:2025-01-24, 18:15:28

版权声明:本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可

CATALOG
  1. 1. 内核模块签名
  2. 2. 模块签名校验
  3. 3. 如何检查模块的签名
  4. 4. 参考文献