linux 内核签名


内核引导签名

Secure Boot是UEFI标准里面用来防病毒的,用来阻止恶意引导程序的加载。 UEFI里面预先保存有受信任的公钥,比如OEM厂家的或者微软的公钥。有的好像个人也可以自己配置一些公钥(Machine Owner Key (MOK))进去。 如果这个选项被设置了,像grub这些引导程序的efi镜像就必须经过公钥对应的私钥的签名才能加载了。 所以有的时候安装系统时,想要加载没签名的grub或者grub模块,要禁用bios里面的这个secure boot(有些笔记本电脑重新编译内核,引导失败就是这个原因)。

ubunturedhat这些发行版,看他们的文档,如果他们发现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

Linux kernel模块内核签名问题解决方法

驱动加完签名后的结构

<内核模块的内容>

# 签名后添加的内容
<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);
...

深入分析Linux kernel安全特性: 内核模块签名

如何查看驱动是否加入签名

查看驱动程序,最后面数据是否是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.

Linux内核模块常见问题


  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux内核安全技术主要是为了保护Linux操作系统的核心部分,防止攻击者利用漏洞或恶意代码对系统进行破坏或入侵。以下是一些常见的Linux内核安全技术: 1. 内核漏洞修复:Linux内核经常会发现漏洞,并及时发布修复补丁。及时更新内核版本可以提高系统的安全性。 2. 安全模块:Linux内核支持加载安全模块,如SELinux(Security-Enhanced Linux)和AppArmor。这些安全模块可以限制进程的权限,防止恶意进程对系统进行攻击。 3. 内核参数设置:通过调整内核参数,可以增强系统的安全性。例如,可以限制进程能够使用的系统资源,限制网络连接数等。 4. 内核审计:Linux内核提供了审计功能,可以记录系统中发生的安全事件和操作。通过审计日志,可以追踪和分析系统的安全问题。 5. 内核模块安全:Linux内核模块是内核的扩展,可以加载和卸载。为了防止恶意模块的加载,可以使用模块签名和模块黑名单等技术。 6. 内核沙箱:内核沙箱是一种隔离机制,可以将不可信的代码或进程限制在一个安全的环境中运行,以防止对系统的恶意操作。 7. 内核安全审计:通过对内核代码进行审计,可以发现潜在的安全问题和漏洞,并及时修复。 8. 内核安全加固:对Linux内核进行加固,可以通过禁用不必要的功能、限制系统调用、配置访问控制等方式来提高系统的安全性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值