Linux内核模块签名与吊销:从Secure Boot到运行时模块验证全栈实践

内核模块是Linux可扩展性的核心机制,但不受限制的模块加载也意味着系统安全的巨大敞口。本文从CONFIG_MODULE_SIG的签名格式解析出发,深入讲解内核模块签名验证的底层原理、与UEFI Secure Boot的信任链构建,以及如何实现模块吊销机制,覆盖从开发签名到生产部署的完整路径。


一、为什么内核模块需要签名?

Linux允许通过insmod/modprobe在运行时加载内核模块(.ko文件),这段代码以内核态(ring 0)运行,拥有完整的系统权限。如果没有验证机制,攻击者可以通过以下方式植入恶意模块:

  • 利用内核漏洞(如堆溢出)加载内核态Rootkit(ldiska / adore-ng变种)
  • 替换/lib/modules/目录下的合法模块文件
  • 通过用户态漏洞获得root权限后加载特权模块

内核模块签名机制通过在模块二进制上附加数字签名,确保任何加载的模块都经过开发者或组织授权,且未被篡改。它构成了可信计算基(TCB)中内核完整性的最后一道防线。


二、模块签名格式详解

内核模块签名采用追加式签名——签名数据不是嵌入ELF结构内部,而是附加在.ko文件的尾部。这种设计保持了ELF加载器的二进制兼容性,签名验证在模块加载前单独执行。

2.1 签名数据结构

┌─────────────────────────────────────┐
│        Standard .ko ELF Data        │
├─────────────────────────────────────┤
│  PKCS#7 Signed Data Structure       │
│  ┌───────────────────────────────┐  │
│  │ Signer's CN (e.g. "Linux")    │  │
│  │ Key ID (SHA-256 fingerprint)  │  │
│  │ Signature (RSA/ECDSA)         │  │
│  └───────────────────────────────┘  │
├─────────────────────────────────────┤
│  Magic: "~Module signature~\n"     │
└─────────────────────────────────────┘

核心代码位于kernel/module_signature.c:

struct module_signature {
    u8  algo;       // Hash algorithm: 0x04=SHA256, 0x0b=SHA512
    u8  hash;       // Signature scheme
    u8  id_type;    // 0=PGP, 1=X509
    u8  signer_len; // Signer CN length
    u8  key_id_len; // Key ID length
    u8  __pad[3];
    u32 sig_len;    // Signature length (big-endian)
    // Followed by: signer + key_id + signature
};

2.2 签名生成流程

构建内核时,scripts/sign-file工具对每个.ko进行签名:

# 签名命令格式
scripts/sign-file <hash_algo> <private_key> <x509_cert> <module.ko>

# 示例:使用SHA-512和自签名证书签名
scripts/sign-file sha512 signing_key.pem signing_key.x509 my_driver.ko

签名过程分为三步:

  1. 计算哈希:对ELF文件内容(不含尾部签名区域)计算SHA-512哈希
  2. PKCS#7封装:使用私钥对哈希签名,封装为PKCS#7格式,附带签名者DN和密钥ID
  3. 写入尾部:将签名结构体以~Module signature appended~\n魔术字结尾追加到文件

  4. 三、运行时签名验证路径

    模块加载时,签名验证发生在load_module() → module_sig_check()调用链中:

    // kernel/module/main.c (简化流程)
    static int load_module(struct load_info *info, const char *uargs, int flags)
    {
        // 1. 解析签名尾部
        err = module_sig_check(info, flags);
        if (err)
            goto free_copy;
        
        // 2. 完整的模块加载流程
        err = layout_and_allocate(info, mod);
        err = complete_formation(mod, info);
        err = prepare_binary(info, mod);
        err = finalize(mod, info);
        
        return err;
    }

    module_sig_check()的核心逻辑:

    static int module_sig_check(struct load_info *info, int flags)
    {
        int err = -ENODATA;
        bool sig_enforce = sig_enforce_kernel();
        
        // 查找尾部魔术字 "~Module signature appended~\n"
        sig = (void *)(info->hdr + info->len) - sizeof(magic);
        if (memcmp(sig, MODULE_SIG_STRING, sizeof(magic) - 1))
            goto no_signature;
        
        // 解析签名头
        err = mod_verify_sig(info, &ms);
        if (err)
            return err;
        
        // 建立签名者身份与密钥的绑定关系
        err = verify_pkcs7_signature(pks7, key);
        if (err) {
            if (sig_enforce) {
                pr_err("Module signature verification failed\n");
                return err; // 拒绝加载
            }
            // 非强制模式:标记-module但允许加载
            mark_unsupported_module(info->mod);
        }
        return 0;
    }

    强制模式生效时,任何签名验证失败都会导致模块加载返回-EKEYREJECTED("Required key not available")。


    四、密钥管理与配置

    4.1 密钥类型

    内核维护三组密钥环:

    密钥环 用途 配置
    Built-in Trusted Keys 内核编译时内置的签名公钥 CONFIG_SYSTEM_TRUSTED_KEYS
    Platform Key Ring 平台级密钥(UEFI Secure Boot db/KEK) CONFIG_LOAD_UEFI_KEYS
    Imported/MOK Key Ring 从MOK(Machine Owner Key)导入 CONFIG_SECONDARY_TRUSTSOURCE

    4.2 Keyctl 实用操作

    # 查看系统信任密钥环
    keyctl show %:.system_keyring
    
    # 查看模块签名密钥
    keyctl list %:.builtin_trusted_keys
    
    # 验证已签名模块
    modinfo my_driver.ko | grep sig_
    # 输出: signer: Linux Kernel Key
    #       sig_key: AB:CD:EF:...:12
    #       sig_hashalgo: sha512

    4.3 构建配置

    CONFIG_MODULE_SIG=y                    # 启用签名验证
    CONFIG_MODULE_SIG_FORCE=y              # 强制模式(无签名=拒绝加载)
    CONFIG_MODULE_SIG_ALL=y                # 构建时自动签名所有模块
    CONFIG_MODULE_SIG_SHA512=y             # 使用SHA-512哈希
    CONFIG_MODULE_SIG_KEY="certs/signing_key.pem"  # 签名私钥路径
    CONFIG_SYSTEM_TRUSTED_KEYRING=y        # 启用信任密钥环
    CONFIG_SYSTEM_EXTRA_CERTIFICATE=y      # 额外X.509证书

    五、模块吊销机制

    密钥吊销是PKI体系中最棘手的环节。内核从5.2版本开始逐步完善了吊销支持。

    5.1 黑名单密钥环(Blacklist Keyring)

    CONFIG_SYSTEM_BLACKLIST_KEYRING提供了一组"不信任"的密钥/哈希列表。任何签名密钥ID匹配黑名单的模块,无论签名是否有效,都会被拒绝加载:

    // certs/blacklist.c
    static struct key *blacklist_keyring;
    
    static int blacklist_init(void)
    {
        // 加载 compiled-in 黑名单
        const u8 *p = blacklist_hashes;
        const u8 *end = p + blacklist_hashes_size;
        
        while (p < end) {
            xas_for_each_mark(&xa, entry, ULONG_MAX) {
                // 每个条目格式: <desc_len><desc><hash_algo><hash>
                keyring_instantiate(" blacklist_keyring",
                    "asymmetric: blacklisted key",
                    hash, hash_len, KEY_POS_VIEW);
            }
        }
    }

    5.2 模块完整性黑名单

    除了密钥级吊销,还可以按模块的预期哈希值进行"精准打击":

    # 生成模块的预期SHA-256哈希
    sha256sum /lib/modules/$(uname -r)/my_driver.ko
    
    # 将哈希添加到内核命令行(需在编译时启用CONFIG_MODULE_SIG_BLACKLIST)
    module.sig_enforce=1
    module_blacklist=<sha256_hash>

    5.3 与dm-verity的联动

    在生产环境中,模块吊销通常与dm-verity(设备映射器完整性校验)协同:

    启动链验证:
    ┌─────────────────┐
    │ UEFI Secure Boot  │ → 验证bootloader
    ├─────────────────┤
    │ Kernel Image      │ → 验证内核映像
    ├─────────────────┤
    │ Initramfs         │ → 验证初始文件系统
    ├─────────────────┤
    │ dm-verity Rootfs  │ → 验证根文件系统完整性
    ├─────────────────┤
    │ Module Signing    │ → 验证内核加载的模块
    └─────────────────┘

    这一系列机制构成了从固件到应用的完整信任链。


    六、自定义签名实战

    以下演示使用自签名证书为外部模块(如DKMS驱动)签名的完整流程:

    步骤1:创建签名密钥对

    # 生成X.509自签名证书
    openssl req -new -x509 -newkey rsa:4096 \
      -keyout MOK.priv -outform DER -out MOK.der \
      -nodes -days 36500 -subj "/CN=MyOrganization Module Signing Key/"
    
    # 转换为PKCS#12(用于后续MOK导入)
    openssl pkcs12 -export -in MOK.der -inkey MOK.priv \
      -out MOK.p12 -name "MyOrg Module Signing"

    步骤2:导入MOK(Machine Owner Key)

    # 通过 mokutil 启动 UEFI MOK 注册
    mokutil --import MOK.der
    # 输入密码后重启,在UEFI MOK Manager蓝色界面确认注册
    
    # 验证导入
    mokutil --list-enrolled

    步骤3:构建并签名外部模块

    # 假设一个DKMS模块已经安装
    cd /var/lib/dkms/my-driver/
    
    # 使用MOK密钥签名所有已编译的模块
    find . -name "*.ko" -exec \
      /lib/modules/$(uname -r)/build/scripts/sign-file \
      sha512 /root/MOK.priv /root/MOK.der {} \;
    
    # 验证签名
    modinfo /var/lib/dkms/my-driver/1.0/$(uname -r)/x86_64/module/my-driver.ko | grep signer

    步骤4:配置吊销

    # 假设需要将某个泄露的密钥哈希加入黑名单
    # 编辑 certs/blacklist.conf
    echo "<key_id>" >> certs/blacklist.conf
    
    # 重新编译内核,黑名单将被嵌入内核映像
    CONFIG_SYSTEM_BLACKLIST_KEYRING=y
    CONFIG_SYSTEM_BLACKLIST_HASH_LIST="certs/blacklist.conf"

    七、常见陷阱与排错

    7.1 "Required key not available" 错误

    % modprobe my_module
    modprobe: ERROR: could not insert 'my_module': Required key not available

    原因排查:

    1. 签名缺失或损坏:模块根本没有签名或签名格式错误
    2. 密钥不匹配:签名使用的公钥不在当前系统的信任密钥环中
    3. 哈希算法不匹配:内核配置要求SHA-512但模块使用SHA-256签名
    4. # 获取详细的调试信息
      dmesg | tail -20 | grep -i "module\|signature\|key"
      
      # 查看模块签名元数据
      modinfo -F signer -F sig_key -F sig_hashalgo <module>.ko

      7.2 Secure Boot导致的"Lockdown"状态

      当UEFI Secure Boot启用时,内核进入Lockdown模式,这会限制一系列特权操作:

      lockdown=integrity:  禁止用户空间直接读写物理内存
      lockdown=confidentiality: 禁止I/O端口访问、禁止kexec

      在Lockdown模式下,无法加载未签名模块,即使CONFIG_MODULE_SIG_FORCE未启用。

      7.3 DKMS与DKMS自动签名

      DKMS(Dynamic Kernel Module Support)未原生集成签名。需要借助dkms-signer工具或手动钩子:

      # /etc/dkms/template-dkms-makepkg.conf
      POST_BUILD="../dkms-signing.sh $kernelver $arch"

      八、前沿发展与替代方案

      8.1 eBPF作为模块的替代方案

      eBPF通过内核内省验证器(Verifier)从根本上拒绝危险操作,在安全性上远超传统模块。但eBPF无法直接替代需要访问私有内核数据结构的驱动场景。

      8.2 Rust for Linux

      Rust模块借助语言级别的内存安全保证,减少了触发签名验证失败的内部路径。签名是信任链的最后一道,Rust是减少需要信任的攻击面的第一道。

      8.3 综合能力对比

      机制 保护范围 实现复杂度 灵活性
      Module Signing 模块完整性/来源 中 高(支持多种用途)
      dm-verity 文件系统完整性 高 低(只读)
      Lockdown 运行时特权边界 低 中
      eBPF Verifier 程序安全性 高 高

      总结

      Linux内核模块签名是生产环境中保障内核安全的重要基础设施。完整的实施路径包括:正确配置构建系统(CONFIG_MODULE_SIG_FORCE + SHA-512)、与Secure Boot建立信任链、在密钥泄露时通过黑名单密钥环快速吊销、并与dm-verity等机制协同构建纵深防御。随着Rust和eBPF的逐步成熟,未来的内核安全将是语言安全、运行时验证和签名加密的多层融合体系。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部