12.1.2 完整性完整性的英文是“integrity”。在维基百科上integrity 的定义是“Integrity is the quality of being honest and having strong moral principles; moral uprightness. It is generally a personal choice to uphold oneself to consistently moral and ethical standards.”在此含义下integrity 对应的汉语词汇是“诚信”。人类社会的诚信很难移植到计算机的世界中于是 integrity 就变成了“完整性”关注的焦点变成了文件内容是否被篡改。其实在不改变程序内容的情况下运行程序的进程的“诚信”仍有可能出问题。比如某些安全防护差的浏览器在浏览不良网站时会被植入木马做一些用户不希望它做的事情。在 1975 年的论文“Integrity Considerations for Secure Computer Systems”中研究完整性的先驱 K. J. Biba 提出了第一个计算机领域的完整性保护方案方案涉及进程和文件分级。当高级别进程读到了低级别的文件进程本身的完整性级别也要随之降低。就像一个品德高尚的人读了一些低级趣味的书刊就有可能受到不良影响而去做一些与其身份不符的事情所以他的品德级别应该随之降低。由于实现很复杂integrity 在计算机领域基本等同于完整性即关注于文件内容是否改变。衡量的手段就是用哈希算法计算出文件内容的哈希值。然后比较这个哈希值有没有改动。12.1.3 哈希用于完整性校验的哈希算法是密码哈希算法即 cryptographic hash function。它具有以下特性● 对任意输入都能很容易地计算出哈希值。● 不可能从哈希值反推出输入。● 不可能改变输入而不改变输入所产生的哈希值。● 同一哈希值不可能对应两个不同输入。密码哈希算法是包括数字签名在内的信息安全应用的基础。12.1.4 IMA/EVM回顾一下可信计算希望将人类社会中的信任模型移植入计算机领域但是信任不好度量于是就退一步使用完整性。完整性的本义是诚信诚信在计算机中无法实现就再退一步将文件的完整性衡量变为判断文件内容是否被篡改。实现这一目标的工具就是密码哈希算法。Linux 内核中的 IMA 和 EVM 专注于文件的完整性保护这种保护的基础是密码哈希算法。虽然 IMA/EVM 是可信计算的一个组成模块但是 IMA/EVM 的设计者增加了一些功能使得它们在没有可信计算硬件——TPM 的情况下也可以发挥一些完整性保护的作用。1.IMAIMA 从 Linux 2.6.30 开始进入 Linux 内核主线。功能包括1收集collect— 度量文件即计算文件的哈希值。2存储store— 将度量值放入内核维护的一个列表中。并且如果 TPM 硬件存在 IMA 会申请 TPM 中的一个 PCRPlatform Configuration Register。IMA 的存储功能会扩展这个PCR即将度量值和这个 PCR 中的值进行计算将计算结果存回 PCR。3证明attest—— 如果 TPM 硬件存在使用 TPM 对 TPM 分配给 IMA 的 PCR 的值签名。在此基础上远程证明成为可能㊀。4评估appraise— 事先将文件的完整性度量值存储在文件扩展属性 security.ima 中内核针对这个值判断文件内容是否被篡改。IMA 的前三个功能是同一批进入内核的第四个功能从 Linux 3.7 开始正式进入主线。2.EVMEVM 从 Linux 3.3 开始正式进入主线。它的功能只有一个保护protect保护文件的安全相关的扩展属性。安全相关的扩展属性包括SELinux 用到的“security.selinux”SMACK 用到的“security.SMACK64”前面提到的 IMA 用到的“security.ima”capability 用到的“security.capability”。这些扩展属性的值是相关安全机制的基础。这些值如果被篡改安全就无从谈起。无论 IMA 还是 EVM都不是用来防护系统运行中恶意软件对文件的破坏的系统运行中对文件的保护是由其他机制比如 LSM来实施的。IMA 和 EVM 针对的是离线攻击的威胁。比如攻击者在关机的状态下将硬盘拆出插到另一台计算机中修改其中的文件然后把硬盘插回原来的计算机。12.2 架构12.2.1 钩子同 LSM 一样IMA/EVM 也定义了一些钩子函数。与 LSM 不一样的是IMA/EVM 没有一个类似 security_ops 的结构体来承载这些钩子。因为 LSM 有若干可以互相替换的模块:SELinux、SMACK、Tomoyo、AppArmor、Yama而 IMA/EVM 只此一家别无分店。IMA 的钩子函数包括● ima_file_mmap● ima_bprm_check● ima_file_check● ima_module_check● ima_inode_post_setattr● ima_inode_set_xattr● ima_inode_removexattrEVM 的钩子函数都和 inode 的属性attr或者 inode 的扩展属性xattr有关包括● evm_inode_setattr● evm_inode_post_setattr● evm_inode_init_security● evm_inode_setxattr● evm_inode_post_setxattr● evm_inode_removexattr● evm_inode_post_removexattr同 LSM 很类似这些钩子函数在系统调用的关键路径上被调用。有些 IMA/EVM 钩子函数和 LSM 的钩子函数在同一处被调用比如 ima_file_mmap 就在 security_mmap_file 中被调用。12.2.2 策略IMA/EVM 同其他 Linux 中的子系统一样也是定义机制执行策略机制和策略分离。EVM 功能比较单一不需要策略的支持。下面简单描述一下 IMA 的策略rule format: action [condition ...]action: measure | dont_measure | appraise | dont_appraise | audit行为action有五种measure、dont_measure、appraise、dont_appraise、audit。measure的含义是度量将文件的完整性度量值存储在内核的一个链表中如果有 TPM 硬件存在还会将此度量值映射到 TPM 的某个 PCR 中。appraise 的含义是评估将文件现在的完整性度量值和存储于文件扩展属性“security.ima”中的度量值做比较返回一个结果。audit 的含义是审计生成一条审计日志消息传给内核审计子系统。condition: [ base | lsm ] [option] base: [[func] [mask] [fsmagic] [fsuuid] [uid] [fowner]] lsm: [[subj_user] [subj_role] [subj_type] [obj_user] [obj_role] [obj_type]] option: [[appraise_type]]如果对所有文件都做度量或评估无疑会对系统性能产生较大影响。所以 IMA 对文件做度量或评估一定要有所选择。选择的条件有两个一个是“base”另一个是“lsm”。先看“base”func: BPRM_CHECK | MMAP_CHECK | FILE_CHECK | MODULE_CHECK mask: MAY_READ | MAY_WRITE | MAY_APPEND | MAY_EXEC fsmagic: hex value fsuuid: file system UUID (e.g 8bcbe394-4f13-4144-be8e-5aa9ea2ce2f6) uid: decimal value fowner:decimal valuefunc 的五个值BPRM_CHECK 对应钩子函数 ima_bprm_checkMMAP_CHECK 对应钩子函数 imap_file_mmapFILE_CHECK 对应钩子函数 imap_file_checkMODULE_CHECK 对应钩子函数 imap_module_check。fsmagic 对应内核文件系统 super_block 结构体中的成员 s_magic的值比如 proc 文件系统的 super_block 的 s_magic 的值是 0x9fa0。全部的值可以在/usr/include/linux/magic.h 中查到。其他都很简单不解释了。这里的 lsm 实际上专指 SELinuxlsm 相关的条件实际上就是和 selinux 相关的条件因为只有 selinux 有 subj_user、subj_role、subj_type、obj_user、obj_role、obj_type分别代表主体用户、主体角色、主体类型、客体用户、客体角色、客体类型。option 只有一个appraise_type。appraise_type 只有一个值imasig它只会影响到 appraise “行为”。12.2.3 扩展属性前面提到IMA 的功能包括收集、存储、证明、评估。前三个功能是计算文件的完整性度量值存入内核列表如果有 TPM 硬件就同时将度量值映射到 TPM 的 PCR 中这个值可以用来远程证明。这三个功能不涉及扩展属性。第四个功能与文件的扩展属性有关。评估的含义是在进程存取文件之前内核先判断文件的完整性度量值和预先存入到文件的扩展属性“security.ima”中的值是否一致如果一致则允许存取操作不一致则拒绝。EVM 的功能是保证几个安全相关的扩展属性的完整性在内核安全相关的函数中会先验证扩展属性的完整性然后进行安全操作。EVM 用扩展属性“security.evm”存储相关数据。那么谁来保证 EVM 的完整性呢答案是使用加密算法。12.2.4 密钥回顾一下IMA 要做完整性保护完整性的基础是文件的哈希值这个哈希值被存入了文件的扩展属性“security.ima”中。为了保护这个文件扩展属性的完整性又对包括这个扩展属性在内的几个安全相关的扩展属性值进行一个运算将运算结果存入“security.evm”中。然后呢为了保护扩展属性“security.evm”的完整性再引入一个扩展属性显然是不行的。为了保护完整性度量值内核使用了加密算法。加密算法需要密钥IMA/EVM 用到了三组密钥evm-key、_evm、_ima。下面逐一叙述。1evm-key扩展属性 security.evm 值包含几个安全相关扩展属性值的加密处理后的运算结果一种处理方式是使用 HMAC。HMAC 是密钥相关的哈希运算消息认证码Hash-based Message Authentication Code。HMAC 运算利用哈希算法以一个密钥和一个消息为输入生成一个消息摘要作为输出。上述加密运算使用的密钥是内核中一个名称为“evm-key”类型为“encrypted”的密钥。关于密钥管理请参考第 16 章。EVM 子系统将安全相关的扩展属性的值合在一起作为消息输入将密钥“evm-key”作为密钥输入算出当前的 HMAC 值用这个值和之前存储在扩展属性 security.evm 中的值进行比较确定文件的安全相关扩展属性的完整性是否被破坏。2_evm另一个保护安全相关扩展属性值的方式是使用数字签名。数字签名涉及公钥和私钥。在扩展属性 security.evm 的值中包含一个数字签名EVM 子系统在内核钥匙链keyring“_evm”中寻找用于验证签名的公钥。3_imaEVM 是用来保护 IMA 的。EVM 自身依靠数字签名或 HMAC 保护那么如果直接用签名来保护 IMA不就可以省略 EVM 了吗后来IMA 做了一些扩展在 security.ima 中直接存储一个和文件完整性校验值相关的数字签名这样在没有 EVM 的情况下IMA 自己就可以保证自己的完整性。验证签名的公钥存储在内核钥匙链keyring“_ima”上。12.2.5 用户态工具1.签名的生成读到这里不知读者有没有疑问扩展属性 security.evm 和 security.ima 中的值是什么时候填入的密钥是怎么产生的又是什么时候载入内核的先说 IMA扩展属性 security.ima 的值有两种形式一种是非加密哈希另一种是数字签名。前者可以由内核在进程访问文件时填入当时的哈希值条件是内核启动时有参数“ima_appraisefix”。后者要由用户态工具写入。再说 evm扩展属性 security.evm 的值有两种形式一种是 HMAC另一种是数字签名。前者可以由内核在进程访问文件时填入当时计算的结果条件是内核启动时有参数“evmfix”。后者要由用户态工具写入。这个用户态工具是 evmctl当然用户也可以编写别的工具做同样的事情。下面举几个例子。计算哈希存入 ima 并且产生 evm 签名evmctl sign --imahash test.txt产生 ima 签名和 evm 签名evmctl sign --imasig test.txt产生 ima 签名evmctl ima_sign test.txt也就是说在数字签名的情况下内核只掌握公钥只能验证签名不能生成签名。签名都是本次系统启动之前的某个时刻生成的。2.密钥的生成1evm-key密钥的生成也需要用户态工具首先是 evm-key。在 TPM 硬件存在的情况下“encrypted”密钥可以依托“trusted”密钥产生keyctl add trusted kmk new 32 ukeyctl add encrypted evm-key new trusted:kmk 32 u第一条命令是让内核借助 TPM 产生一个类型为“trusted”描述为“kmk”长度为 32B 的密钥。第二条命令是让内核借助刚才产生的“kmk”密钥产生一个类型为“encrypted”描述为“evm-key”长度为 32B 的密钥。有 TPM 硬件时内核才能产生类型为“trusted”的密钥。在没有 TPM 硬件时“encrypted”密钥只能依托“user”密钥产生keyctl add user kmk dd if/dev/urandom bs1 count32 2/dev/null u keyctl add encrypted evm-key new user:kmk 32 u第一条命令让内核产生一个类型为“user”描述为“kmk”长度为 32B内容为自/dev/urandom 读出的随机数的密钥。第二条命令是让内核借助刚才产生的“kmk”密钥产生一个类型为“encrypted”描述为“evm-key”长度为 32B 的密钥。2_ima 和_evm用户可以用 openssl 生成公私钥对然后将公钥加载到内核中ima_id$(keyctl newring _ima u)evmctl import /etc/keys/pubkey_ima.pem $ima_idevm_id$(keyctl newring _evm u)evmctl import /etc/keys/pubkey_evm.pem $evm_id3.密钥加载evm-key、用于数字签名的 ima 公钥和用于数字签名的 evm 公钥都要保存在内核中。它们需要在系统启动的早期被加载入内核。通常这样的工作在 initramfs 中完成。相应的命令可以是加载evm-key。假设有 TPM 硬件使用 trusted 密钥作为 encrypted 密钥的后台支持假设密钥相关的数据被存入/etc/keys/目录下的文件keyctl add trusted kmk load cat /etc/keys/kmk ukeyctl add encrypted evm-key load cat /etc/keys/evm-key u若没有 TPM 硬件只能使用 user 密钥作为 encrypted 密钥的后台支持 cat /etc/keys/kmk | keyctl padd user kmk u keyctl add encrypted evm-key load cat /etc/keys/evm-key u 加载完 evm-key 后要通知一下内核内核 evm 子系统就可以用 evm-key 进行相关计算了。 echo 1 /sys/kernel/security/evm 两个公钥的加载类似下面只列出一个 # search for EVM keyring evm_idkeyctl search u keyring _evm 2/dev/null if [ -z $evm_id ]; then evm_idkeyctl newring _evm u fi # import EVM X509 certificate evmctl import /etc/keys/x509_evm.der $evm_id