4. 内核模块签名机制概述
内核模块是 Linux 内核在运行时动态扩展功能的基本单元,它运行在最高特权级(Ring 0 / EL0 内核态),一旦被加载,就拥有与内核本体几乎相同的权限。正因如此,内核模块既是驱动生态的基石,也是攻击者最想利用的入口之一。内核模块签名机制的核心目标,就是回答一个问题:这段即将进入内核的二进制代码,是否来自可信来源,并且在传输与存储过程中没有被篡改?
一、为什么需要签名:从信任边界说起
在没有签名校验的系统中,任何具备 root 权限的进程都可以通过 insmod 或 init_module 系统调用把任意 .ko 文件塞进内核。这意味着一次提权成功后,攻击者可以植入 rootkit、挂钩系统调用、隐藏进程与文件,且这种修改在内核内存中运行,常规的用户态检测手段很难发现。签名机制在“模块二进制”与“内核加载器”之间建立了一道密码学信任边界。
具体而言,签名解决三类问题:
- 来源真实性:确认模块由持有私钥的可信方(厂商、OEM、SoC 供应商)构建,而非第三方伪造。
- 完整性:确认模块在分发、OTA、刷机过程中未被篡改,哪怕改动一个字节也会导致校验失败。
- 策略可控性:让内核能够按策略决定“允许加载 / 拒绝加载 / 仅告警”,从而支撑安全启动链的完整性。
二、签名验证流程:从 .ko 到内核信任链
内核模块签名采用“分离签名”的方式:签名数据并不嵌入模块代码段,而是附加在模块文件末尾,形成 module_signature 结构。加载时,内核先解析模块 ELF,再定位签名块,最后用内置公钥验证。
典型流程可以概括为以下步骤:
- 构建阶段:对模块的规范化内容计算摘要(通常是 SHA-256 或 SHA-512)。
- 签名阶段:用私钥对摘要进行签名,生成 RSA/ECDSA 签名值。
- 附加阶段:通过
scripts/sign-file工具把算法 ID、密钥 ID、签名值写入模块尾部。 - 加载阶段:内核读取模块,提取签名块,用密钥环中的公钥验证签名。
- 策略判定:根据
CONFIG_MODULE_SIG_FORCE等配置决定强制拒绝还是仅记录告警。
下面是一个典型的签名与查看命令:
# 使用私钥对模块签名(SHA-256 + RSA)
scripts/sign-file sha256 signing_key.pem signing_cert.pem my_driver.ko
# 查看模块尾部签名信息
tail -c 200 my_driver.ko | hexdump -C
# 加载时观察内核日志
dmesg | grep -i "module verification"
# 输出示例:
# my_driver: module verification failed: signature and/or required key missing
# my_driver: loading out-of-tree module taints kernel
验证失败时,内核会给出明确提示。若启用了强制模式,加载会直接返回 -EKEYREJECTED;若未强制,模块仍可加载,但内核会被标记为 tainted,影响后续问题定位与厂商支持。
三、Android 与标准 Linux 的差异
标准 Linux 发行版通常把模块签名作为“可选加固项”,很多发行版默认不强制,甚至允许用户自行签名。Android 则把模块签名嵌入到整机安全启动链中,要求更严格、更系统化。
| 对比维度 | 标准 Linux | Android |
|---|---|---|
| 强制策略 | 默认宽松,CONFIG_MODULE_SIG_FORCE 可选 |
通常强制校验,与 Verified Boot 绑定 |
| 信任根 | 内核内置公钥或用户密钥环 | 设备厂商 / SoC 供应商密钥,逐级签名 |
| 模块来源 | 发行版仓库、第三方 DKMS | 多为 GKI 与 vendor 模块,来源受控 |
| 加载限制 | root 可加载未签名模块 | 未签名模块通常被拒绝,需解锁或关闭校验 |
| 与启动链关系 | 相对独立 | boot / vendor_boot / dtbo 逐级验证,模块签名是其中一环 |
Android 的另一个显著差异是 GKI(Generic Kernel Image) 与 vendor 模块的分离。Google 提供通用内核镜像,厂商通过 vendor_boot 与 vendor_dlkm 分区加载自己的模块。这些模块必须使用厂商密钥签名,并且其公钥需要被内核信任,否则在强制校验下无法加载。
四、常见误区与排障复盘
误区一:签名后模块内容被加密。签名只保证真实性与完整性,模块仍是明文,可被反汇编分析。
误区二:只要签名就能加载。若公钥未被内核信任,或签名算法不被支持,验证仍会失败。
误区三:关闭校验没有代价。关闭 CONFIG_MODULE_SIG_FORCE 会降低安全启动链强度,并可能触发 taint,影响厂商支持与 OTA 兼容性。
排障时建议按以下顺序检查:
- 确认模块尾部是否存在
module_signature结构; - 确认签名使用的算法与内核配置一致(如 SHA-256 + RSA-2048);
- 确认对应公钥已编译进内核或加入可信密钥环;
- 查看
dmesg中的module verification failed具体原因; - 检查是否因
CONFIG_MODULE_SIG_FORCE导致直接拒绝。
理解签名机制,是后续掌握 Android 模块加载策略、GKI 兼容性与安全启动链的基础。签名不是一道孤立的校验,而是从构建、分发到加载的完整信任传递过程。