Linux IMA 启用与体验

简单认识 IMA

内核完整性子系统可用于检测文件是否被远程或本地更改。它会评估一个文件的度量值(哈希值),并将其与先前存储为扩展属性的“良好”值进行比较(在 ext3、ext4 等支持扩展属性的文件系统上)。其他安全技术(如 SELinux)也提供了类似但互补的机制,可以根据策略尝试保护文件完整性。

内核完整性子系统由两个主要组件组成:

  • IMA(Integrity Measurement Architecture,完整性度量架构)能够基于自定义策略对通过 execve()mmap()open() 系统调用访问的文件进行度量,度量结果可被用于本地/远程证明,或者和已有的参考值比较以控制对文件的访问。
  • EVM(Extended Verification Module,扩展验证模块)能将系统当中某个文件的安全扩展属性,包括 security.imasecurity.selinux 等合起来计算一个哈希值,然后使用 TPM 中存的密钥或其他可信环境中的密钥对其进行签名,签名之后的值存在 security.evm 中,这个签名后的值是不能被篡改的,如果被篡改,再次访问的时候就会验签失败。EVM 的作用就是通过对安全扩展属性计算摘要和签名并将其存储在 security.evm 中,提供对安全扩展属性的离线保护。

Linux IMA 子系统在 Linux 内核中引入了钩子,以支持在文件内容被读取或执行之前,在打开时创建和收集文件的哈希值。IMA 度量子系统于 Linux 2.6.30 中被引入。

根据 IMA wiki 的定义,内核完整性子系统的功能可以被分为三部分:

  • 度量(measure):检测对文件的意外或恶意修改,无论远程还是本地。
  • 评估(appraise):度量文件并与一个存储在扩展属性中的参考值作比较,控制本地文件完整性。
  • 审计(audit):将度量结果写到系统日志中,用于审计。

如果系统中存在 TPM 芯片,度量结果将被扩展到 TPM 芯片的指定 PCR 寄存器中,由于 PCR 扩展的单向性以及 TPM 芯片的硬件安全性,用户无法修改已被扩展的度量结果,这就确保了度量结果的真实性。

如果任何被监视的文件被修改(例如系统更新时),可以进行 IMA 重新度量。为此,需要使用 i_version 选项挂载文件系统。要在文件更改后重新度量它,文件系统必须支持 i_version。例如,要在根文件系统上启用 i_version,可以这样编辑 /etc/fstab 文件:

1
/dev/vda1  /  ext4  noatime,iversion  1 2

IMA 评估

IMA 评估的目的是通过与标准参考值的比较,控制对本地文件的访问。相比于 IMA 度量作为一个“只记录不干涉”的观察员,IMA 评估更像是一位严格的保安人员,它的职责是拒绝对所有“人证不一”的程序的访问。

IMA 首先使用安全扩展属性 security.imasecurity.evm 存储文件完整性度量的参考值:

  • security.ima:存储文件内容的哈希值。
  • security.evm:存储文件扩展属性的哈希值签名。

访问受保护文件时,将会触发内核中的钩子,依次验证文件扩展属性和内容的完整性:

  1. 使用内核 keyring 中的公钥对文件 security.evm 扩展属性中的签名值验签,与当前文件扩展属性的哈希值比较,如果匹配就证明文件的扩展属性是完整的(包括 security.ima)。
  2. 在文件扩展属性完整的前提下,将文件 security.ima 扩展属性的内容与当前文件内容的哈希值比较,如果匹配就允许对文件的访问。

IMA 评估的文件范围和触发条件可以由用户通过 IMA 策略自行配置。

启用 IMA 评估需要两个步骤:

  • 首先,使用内核命令行参数 ima_appraise_tcbima_appraise='fix' 重新启动内核,为文件系统重新标记。接下来需要读取所有要评估的文件,这将需要一些时间。要重新标记整个文件系统,可以运行以下命令:
1
time find / -fstype ext4 -type f -uid 0 -exec dd if='{}' of=/dev/null count=0 status=none \;

一个比较新的 Ubuntu Server 22.04 系统需要花费的时间约为五分钟。

完成后,文件的扩展属性中就能显示存储的哈希值。例如:

1
2
3
4
neko@ubuntu:~$ getfattr -m - -d /sbin/init
getfattr: Removing leading '/' from absolute path names
# file: sbin/init
security.ima=0sATGt7iOfuc/rfwQW/pfk594TctLW
  • 随后使用 ima_appraise_tcpima_appraise=enforce 内核命令行参数重启。

在开启 IMA 评估的情况下,每当访问一个可执行文件或动态库文件,就会调用内核中的钩子,计算文件内容和扩展属性的哈希值,并在内核哈希表中进行搜索,如果匹配就允许文件的执行,否则就拒绝访问。如果启用了审计,还会产生审计事件。

扩展 Linux 操作系统为其增加 IMA 功能

Ubuntu 官方的内核已默认编译 IMA/EVM,并且也已经自动挂载了 securityfs 文件系统,只需在内核启动参数中启用 IMA 即可。

编辑 /etc/default/grub 文件,在 GRUB_CMDLINE_LINUX 参数中可以添加 Linux 启动时的内核命令行参数,不同参数之间用空格分隔。要启用 IMA 只需要附加上 ima_tcb ima_appraise_tcb ima_appraise=fix 即可。如果原来没有参数,改完后应该是这样的:

1
GRUB_CMDLINE_LINUX="ima_tcb ima_appraise_tcb ima_appraise=fix"

与 IMA 相关的内核参数有如下几个:

  • ima=on Fedora/RHEL 可能需要开启
  • ima_appraise=<policy>,IMA 评估策略,其中参数可填以下几个
    • off 关闭 IMA 评估模式,在访问文件时不进行完整性校验,也不为文件生成新的参考值。
    • enforce 开启 IMA 评估强制模式,在访问文件时进行完整性校验,即计算文件哈希值并与参考值比对,如果比对失败就拒绝对文件的访问。IMA 会为新文件生成新的参考值。
    • fix 开启 IMA 修复模式,在该模式下允许更新受保护文件的参考值,允许系统在没有参考值的情况下启动。
    • log 开启 IMA 评估日志模式,在访问文件时进行完整性校验,但即使校验失败也允许执行命令,只进行日志记录。
  • ima_policy=<policy>,IMA 策略,其中参数可填以下几个
    • tcb 度量所有文件执行、动态库映射、内核模块导入以及设备驱动加载,此外,root 用户读文件的行为也会被度量。
    • appraise_tcb 评估 root 拥有的所有文件。
    • secure_boot 对所有内核模块导入、硬件驱动加载、kexec 内核切换以及 IMA 策略进行评估,前提是这些文件都具有 IMA 签名。
    • ima_policy 参数可以同时指定多个值,例如 ima_policy=tcb|appraise_tcb,启动后系统的 IMA 策略就是这两种参数对应的策略的总和。
  • ima_tcb 等价于 ima_policy=tcb
  • ima_appraise_tcb 等价于 ima_policy=appraise_tcb
  • ima_template=<template used> IMA 度量扩展模板,默认值为 ima-ng,可填值为 ima/ima-ng/ima-sig
  • ima_hash=<hash used> IMA 摘要算法,ima 模板 默认为 sha1,Linux 3.13 默认值为 sha256,可填值为 sha1/md5/sha256/sha512/wp512/…
  • ima_audit 审核日志记录
    • 0 基础完整性审计信息(默认)
    • 1 额外完整性审计信息

修改后使用 sudo update-grub 自动生成文件更新/boot/grub/grub.cfg,然后重启电脑。

重启后可使用 sudo cat /sys/kernel/security/ima/ascii_runtime_measurements 查看 IMA 记录的应用度量值,此文件即 IMA 维护的运行时度量列表。内容较长,这里截取一部分展示:

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
10 1d8d532d463c9f8c205d0df7787669a85f93e260 ima-ng sha1:0000000000000000000000000000000000000000 boot_aggregate
10 832cdc51f56ab669516480d2f030cfcb9ca70a08 ima-ng sha1:5a493e4a8ace4e0a7bd608936fd8c07e62df9566 /init
10 36c821e585bfc0c164de21bdf1909216318d85aa ima-ng sha1:8c87d9511c99d2ed6bdfa5fd8ceb1ff5b3b3d8c7 /usr/bin/sh
10 804e66ad9b641c6fe8c5c43b0e555b73445c307c ima-ng sha1:7db501b4ac0906d2bc76154be2e474ca49a83ad3 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
10 0bd657c386fef5386f7898046cc9f480ba7756e5 ima-ng sha1:e7639aa9eca7f73561641e14715e4f8810198ea5 /etc/ld.so.cache
10 36f276698feac02280ddb19e3d3f95172702cb71 ima-ng sha1:8f7d59c6f95b0cf57a8db165033296dda91d1239 /usr/lib/x86_64-linux-gnu/libc.so.6
10 223eb68bfb9f72922506747d3bc4dd76d813b5da ima-ng sha1:65030975e1f3887efd00fbb568f00409b7c256d0 /conf/arch.conf
10 42d3c8ce8d3dc2925f434026ff0e497b23871bf1 ima-ng sha1:1ea4f64494e66cf4c77adc2efb46bbc1c30336f0 /conf/initramfs.conf
10 50c1912e73da8108e18a6b14666102294f1e147c ima-ng sha1:3fd80f5f099941f3bf614f23d4e543d941d35234 /scripts/functions
10 5857a0f9895c4cb340c1bf92feb74299a259b872 ima-ng sha1:76c1f46ea6470be11196982559275dbfc15a63a3 /scripts/init-top/ORDER
10 869a737ab1b837911b2885b315c45cf399f298b0 ima-ng sha1:3740c11188ec7cf144e56b5b18cb87e29f7c32cd /scripts/init-top/all_generic_ide
10 95e1c8d50e45a9fdd89e52707739789ba67a9780 ima-ng sha1:a317b3ba961596354096d6a4f2a7bd5ad81e14f8 /scripts/init-top/blacklist
10 924179623e9a461fa2db9ecc53272cb34fb48f74 ima-ng sha1:54cc0e88d2b49be638e8d71d3e51c4222fd39154 /scripts/init-top/udev
10 d5d666164c8c86467001a3909427e5d483f14793 ima-ng sha1:261efaf6726de2aca2ef3dae20ef106df3c806e4 /usr/bin/udevadm
10 d7a23a92cbea3a2099ecf8b04346d496cd7a70ed ima-ng sha1:79ad88cb982431464f094105ca24b96fed69e2e6 /usr/lib/x86_64-linux-gnu/libkmod.so.2.3.7
10 ecbe008886d9d0e210e6681ec1f6a6aaf1fb4215 ima-ng sha1:b15b01bc41f38f544d6c51ffdb61e3ca50889bf5 /usr/lib/x86_64-linux-gnu/libacl.so.1.1.2301
10 faf969546216c4f4112a9e405ad6cbe4bccd5586 ima-ng sha1:5432d87de94b8853a2c391133ad50cfd5a9e4cd3 /usr/lib/x86_64-linux-gnu/libblkid.so.1.1.0
10 db68c01fdf15b549ba29a1433d175f6d654f5010 ima-ng sha1:acb47886b635c8017505a8a5e4e247cae54a65bc /usr/lib/x86_64-linux-gnu/libcap.so.2.44
10 91a0c1f961cf9153099750ad4df808f57244b045 ima-ng sha1:6744576d271f98f56d2d776fcdb941b6c6c26da7 /usr/lib/x86_64-linux-gnu/libselinux.so.1
10 3c7ef8392e654b1f202cea1d4b8a3714e54776de ima-ng sha1:5b0e9f5a5655f22d89bba1fdafcfb7c93f0b43ce /usr/lib/x86_64-linux-gnu/libzstd.so.1.4.8
10 479a8012721c06d45aedba1791ffab7d995ad30f ima-ng sha1:4f509d391aa126829f746cc3961dc39ffbef21ab /usr/lib/x86_64-linux-gnu/liblzma.so.5.2.5
10 be91a9a2f23eff47dd4c584f35149df9718ec569 ima-ng sha1:0d15dde7c509ef7df42c00e689bbbb9d0604170e /usr/lib/x86_64-linux-gnu/libcrypto.so.3
10 9aff26c79b77dddbf04a8f1fd9ce33a270b6d4bf ima-ng sha1:41e8b494781ca0892a15c4a2d06a6f5dff94d2ba /usr/lib/x86_64-linux-gnu/libpcre2-8.so.0.10.4
10 b7fe20d034f72a48f45e34fdc379f9ab2f9ac12d ima-ng sha1:b5594f6993080f2bbf08bc6e6be28ead5311a12b /etc/udev/udev.conf
10 f2845cdf6b4ee5bf13fc8b4731bf4c84f5214666 ima-ng sha1:d5bff35cbf6f5ba38c740868c4fcad1368d623ed /usr/lib/modprobe.d/aliases.conf
10 acb00d49a996737fa7c88926e7bd67ee8dec5dee ima-ng sha1:090610b7060cf46a4dcc8086483dd6323b6f681b /etc/modprobe.d/amd64-microcode-blacklist.conf
10 6d5209742ec43be01a465a75a18444de8caee9d8 ima-ng sha1:cd74d302e42741adff5d34a3f68e829ae5c25af1 /etc/modprobe.d/blacklist-ath_pci.conf
10 0a8aa3804882c7a55de4bdf3cccf769b756696f3 ima-ng sha1:7ba6392bf4d678cf990aa8dc73e73cf065d33b9f /etc/modprobe.d/blacklist-firewire.conf
10 b8324e7f35414eb733a02130ed6d9967c35d0699 ima-ng sha1:81b456866de0bafa76e180311f73d5e9f3eb327c /etc/modprobe.d/blacklist-framebuffer.conf
10 615f01f57cf280ad5add634d54f5d365b84606f2 ima-ng sha1:d6c6f70959c86cae74d500c08a9f8be2d7e6907e /etc/modprobe.d/blacklist-rare-network.conf
10 2d7de44b674a7b363cfc48cb42349e360fc93c0b ima-ng sha1:fec1343b28435a5cc85cb3a4f67a5556fe5033a4 /etc/modprobe.d/blacklist.conf

表中各列为:

  • 用于扩展度量结果的 PCR(Platform Configuration Register,平台配置寄存器),默认是 10,只在系统装了 TPM 芯片的情况下有意义。
  • 模板哈希值:最终被用于扩展的哈希值,组合了文件内容哈希和文件路径的长度和值。
  • 扩展度量值的模板(在本例中是 ima-ng)。
  • 由文件内容生成的哈希值。
  • 被度量的文件路径。

编写程序观察 IMA 工作

普通程序

随便编写一个程序,编译执行,比如一个简单的 hello world 程序:

1
2
3
4
5
6
7
// hello.c
#include <stdio.h>

int main() {
printf("hello world\n");
return 0;
}

随后再次使用 sudo cat /sys/kernel/security/ima/ascii_runtime_measurements 查看 IMA 记录的应用度量值,仔细找找,可以在末尾几行中找到自己刚刚执行的文件。

1
10 6ff2505abba95a0f71dd83e6a32fbd39b1644c34 ima-ng sha1:383597be6110792b8553fb1463850670b69504ad /home/neko/a.out

使用 cat /sys/kernel/security/ima/runtime_measurements_count 查看 IMA 日志条数,可以看到相比运行前会根据 IMA 记录的条目增加相应数量。

共享库

编写一个共享库,然后尝试使用这个共享库:

1
2
3
4
5
6
7
// shared_hello.c

#include <stdio.h>

void hello() {
printf("hello world\n");
}
1
2
3
4
5
6
// use_hello.c

int main() {
hello();
return 0;
}

使用 gcc -o libhello.so -fPIC -shared shared_hello.cshared_hello.c 编译成动态共享库 libhello.so

-fPIC 参数声明链接库的代码段是可以共享的,-shared 参数声明编译为共享库。Linux 共享库的一个命名的惯例为 libxxx.so

使用 gcc -o hello -L. use_hello.c -lhello 编译 use_hello.c

-L 参数指定到哪个附加路径下面去寻找共享库,现在我们指定在当前目录下面寻找。-l 参数指定链接到哪个共享库上面,参数 -lhello 会自动链接到 libhello.so 这个共享库上面。

使用 export LD_LIBRARY_PATH=/home/neko:$LD_LIBRARY_PATH 将当前目录加入到共享库路径环境变量中,以便程序运行时动态查找并加载动态共享库。

随后再次使用 sudo cat /sys/kernel/security/ima/ascii_runtime_measurements 查看 IMA 记录的应用度量值,仔细找找,可以在末尾几行中找到自己刚刚执行的文件和对应的动态库。

1
2
10 0572cb41970bd27be429dd8e1640aadd788882d2 ima-ng sha1:4a030a72bf7ea3a03cf00b3b848f8ae9ad2f6cb1 /home/neko/libhello.so
10 79ec022287ca4ee4f5472e668f250d592986dea6 ima-ng sha1:489a2e96f977491db0f72a49e005472c96e2e454 /home/neko/hello

使用 cat /sys/kernel/security/ima/runtime_measurements_count 查看 IMA 日志条数,可以看到相比运行前会根据 IMA 记录的条目增加相应数量。

内核模块

编写一个简单的内核模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// kmod_hello.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

/*用于告诉内核该模块拥有 GPL license*/
MODULE_LICENSE("GPL");

/*执行模块初始化工作*/
static int __init hello_init(void) {
printk(KERN_INFO "Hello, module\n");
return 0;
}

/*执行模块析构工作*/
static void __exit hello_exit(void) {
printk(KERN_INFO "Goodbye, module\n");
}

/*该函数注册模块的构造函数*/
module_init(hello_init);
/*该函数注册模块的析构函数*/
module_exit(hello_exit);

编写一个 Makefile 文件以管理模块的编译(注意 Makefile 文件的缩进需要使用 Tab,不能用空格):

1
2
3
4
5
6
7
8
9
// Makefile

obj-m := kmod_hello.o

all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

kmod_hello.cMakefile 放在同一目录下,执行 make 命令编译模块。

注意 Makefile 文件中的命令行(如上例中两句 make)需要用 tab 制表符开头,不能使用空格。如果出现类似 Makefile:4: *** missing separator. Stop. 的错误,可能是因为这一原因,可以重新编辑 Makefile 文件,将命令行开头修改为 tab。

在开启 Secure Boot 后若模块未签名,则可能导致在使用 sudo insmod kmod_hello.ko 加载模块时报错 insmod: ERROR: could not insert module kmod_hello.ko: Operation not permitted。使用 dmesg 查看内核信息时可在末尾发现类似 Lockdown: insmod: unsigned module loading is restricted; see man kernel_lockdown.7 的错误,即因为模块未签名导致加载失败。对内核模块的签名具体请参考【译】如何为 Secure Boot 签名

使用 sudo insmod kmod_hello.ko 加载模块,并使用 sudo dmesg | tail -5 查看 kernel message

1
2
3
// sudo dmesg | tail -5

[ 81.109425] Hello, module

如果在加载模块时出现 insmod: ERROR: could not insert module kmod_hello.ko: Invalid module format 错误。使用 dmesg 查看内核信息时在末尾发现类似 kmod_hello: disagrees about version of symbol module_layout 的错误,是因为模块编译时对应的内核版本与当前运行内核版本不一致导致加载失败;若是类似 module: x86/modules: Skipping invalid relocation target, existing value is nonzero for type 1, loc 00000000981bd3a1, val ffffffffc0986000 的错误,可能是当前内核自身有问题,可以尝试使用包管理器重新安装内核或者使用自行编译的内核。

随后再次使用 sudo cat /sys/kernel/security/ima/ascii_runtime_measurements 查看 IMA 记录的应用度量值,仔细找找,可以在末尾几行中找到刚刚加载的内核模块。

1
10 56e7459158b8b239ac43a820800ba69eae766f6f ima-ng sha1:755d642ae4fab794747b3413643ba603a15fddc1 /home/neko/kmod_hello.ko

使用 cat /sys/kernel/security/ima/runtime_measurements_count 查看 IMA 日志条数,可以看到相比运行前会根据 IMA 记录的条目增加相应数量。

使用 sudo rmmod kmod_hello.ko 卸载模块,并使用 sudo dmesg | tail -5 查看 kernel message

1
2
3
// sudo dmesg

[ 250.705497] Goodbye, module

随后再次使用 sudo cat /sys/kernel/security/ima/ascii_runtime_measurements 查看 IMA 记录的应用度量值,可以发现刚刚卸载的内核模块仍有记录,因为这是 IMA 度量的日志,并不会随着程序的结束或模块卸载被删去。

另外注意 IMA 只是起到度量并记录作用,并不会进行校验与认证。如果两次运行同一个文件名但文件内容不同,会被重新度量并记录。

参考资料

  1. Integrity Measurement Architecture (IMA) / Wiki / Home
  2. 在TPM2.0下使用IMA/EVM - Real Own
  3. linux - insmod: ERROR: could not insert module HelloWorld.ko: Operation not permitted - Stack Overflow
  4. 【译】如何为 Secure Boot 签名
  5. How to use the Linux kernel's Integrity Measurement Architecture
  6. 可信计算