最近有同事反馈一个系统启动失败的问题,根因是系统的驱动模块加载失败导致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_cmdifeq ($(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; if (flags == 0 && info->len > markerlen && memcmp (mod + info->len - markerlen, MODULE_SIG_STRING, markerlen) == 0 ) { info->len -= markerlen; err = mod_verify_sig(mod, info); } switch (err) { case 0 : info->sig_ok = true ; return 0 ; case -ENODATA: reason = "unsigned module" ; break ; case -ENOPKG: reason = "module with unsupported crypto" ; break ; case -ENOKEY: reason = "module with unavailable key" ; break ; 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 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,cnssalias : of:N*T*Cqcom,cnssC*alias : of:N*T*Cqcom,cnss-qca6290alias : of:N*T*Cqcom,cnss-qca6290C*alias : of:N*T*Cqcom,cnss-qca6390alias : of:N*T*Cqcom,cnss-qca6390C*alias : of:N*T*Cqcom,cnss-qca6490alias : of:N*T*Cqcom,cnss-qca6490C*alias : of:N*T*Cqcom,cnss-kiwialias : of:N*T*Cqcom,cnss-kiwiC*alias : of:N*T*Cqcom,cnss-qca-convergedalias : 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 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
参考文献