基于内核源码版本:Linux 主线(含 nfsd-7.0-2 合并) 源码根目录:
security/integrity/
- 概述与背景
- 子系统整体架构
- IMA:完整性度量架构
- EVM:扩展验证模块
- IMA 与 EVM 的协作机制
- 签名验证链
- 数据结构全景
- 内核钩子挂载点
- 启动流程与初始化
- 安全属性与 xattr 类型系统
- ToMToU 时序攻击防御
- 黑名单与 fsverity 集成
- 调试与运维接口
- 设计缺陷与限制
- 参考文件索引
- 度量日志详解:ima_add_template_entry 深度分析
- 哈希计算子系统:ima_crypto 深度分析
- IMA 策略解析:规则文本到结构体的转换
- 内核模块内嵌签名:modsig 机制
- EVM xattr 保护链的精确语义
- kexec 场景下的度量日志传递
- 非对称密钥度量:ima_asymmetric_keys
- 远程证明场景深度分析
- Integrity Policy Enforcement(IPE)新框架
- IMA 审计子系统集成
- EVM 可移植签名与镜像分发
- 性能优化分析
- 安全边界与威胁模型
- 实战配置与调试方法
- 扩展参考文件索引
IMA(Integrity Measurement Architecture)与 EVM(Extended Verification Module)是 Linux 内核安全子系统中的两个互补组件,共同构成文件完整性保护体系。它们均位于 security/integrity/ 目录下,通过 LSM(Linux Security Module)钩子嵌入内核的文件访问路径。
IMA 最初由 IBM 的 Reiner Sailer、Mimi Zohar 等人于 2005–2008 年为 IBM 设计,旨在:
- 在文件被访问前计算其哈希值,并将结果扩展到 TPM(可信平台模块)的 PCR(Platform Configuration Register)中,建立不可篡改的度量日志;
- 在策略要求时,将度量结果与预存的参考值(xattr 中的哈希或数字签名)比对,决定是否允许访问;
- 通过 Linux Audit 子系统记录完整性相关事件。
EVM 作为 IMA 的配套模块,解决了 IMA 本身无法防止的一类攻击:攻击者可以直接篡改文件的扩展属性(如 security.ima、security.selinux),绕过 IMA 的评估。EVM 通过对所有安全相关 xattr 的集合计算 HMAC(密钥散列消息认证码)或数字签名,并将结果存入 security.evm,保护这些 xattr 不被离线或在线篡改。
两者的组合构成了一个完整的信任链:
硬件信任根(TPM/Secure Boot)
|
v
内核镜像验证
|
v
IMA策略加载
|
v
文件访问时: EVM验证xattr完整性
|
v
IMA验证文件内容
|
v
允许/拒绝访问
security/integrity/
├── integrity.h # 公共数据结构:evm_ima_xattr_data, ima_digest_data,
│ # signature_v2_hdr, integrity 状态枚举
├── digsig.c # 数字签名验证:integrity_digsig_verify()
├── digsig_asymmetric.c # 非对称密钥验证
├── ima/
│ ├── ima.h # IMA 内部定义:ima_iint_cache, ima_template_desc,
│ │ # ima_rule_entry 等核心结构
│ ├── ima_main.c # LSM 钩子实现,process_measurement() 主流程
│ ├── ima_policy.c # 策略引擎:ima_rule_entry, 默认规则集
│ ├── ima_template.c # 模板框架:builtin_templates, 字段注册
│ ├── ima_template_lib.c # 模板字段实现:d-ng, n-ng, sig 等
│ ├── ima_appraise.c # 评估实现:ima_appraise_measurement()
│ ├── ima_crypto.c # 哈希计算:ima_calc_file_hash()
│ ├── ima_queue.c # 度量队列:ima_measurements 链表
│ ├── ima_fs.c # securityfs 接口:ascii_runtime_measurements
│ └── ima_init.c # 初始化:TPM、密钥环、模板
└── evm/
├── evm.h # EVM 内部定义:evm_iint_cache, xattr_list
├── evm_main.c # EVM 核心逻辑:evm_verifyxattr(), evm_protect_xattr()
├── evm_crypto.c # HMAC 计算:evm_calc_hmac_or_hash()
├── evm_secfs.c # securityfs 接口:evm 状态、密钥加载
└── evm_posix_acl.c # POSIX ACL 与 EVM 集成
文件系统操作
(open/exec/mmap)
|
v
LSM 安全钩子
(ima_file_check 等)
|
v
process_measurement() [ima_main.c:236]
+--------------------------------------------+
| 1. ima_get_action() -> 查询策略 |
| 决定 MEASURE/APPRAISE/AUDIT |
| |
| 2. ima_inode_get() -> 获取/创建 |
| per-inode 缓存 ima_iint_cache |
| |
| 3. ima_read_xattr() |
| 读取 security.ima |
| |
| 4. ima_collect_measurement() |
| 计算文件哈希 |
| |
| 5. ima_store_measurement() ------> TPM PCR 扩展
| 追加度量日志条目 ------> /sys/kernel/security/ima/
| |
| 6. ima_appraise_measurement() |
| -> evm_verifyxattr() [EVM] <------- security.evm HMAC验证
| -> xattr_verify() <------- security.ima 内容比对
| |
| 7. ima_audit_measurement() ------> Linux Audit
+--------------------------------------------+
|
v
返回 0 (允许) 或 -EACCES (拒绝)
IMA 的三种操作模式通过 ima.h 中定义的位标志加以区分,每种模式在 process_measurement() 的不同阶段触发:
// security/integrity/ima/ima.h:131
#define IMA_MEASURE 0x00000001 // 度量:计算哈希,记录到度量日志,扩展TPM PCR
#define IMA_MEASURED 0x00000002 // 已度量(缓存标志)
#define IMA_APPRAISE 0x00000004 // 评估:将当前哈希与参考值比对
#define IMA_APPRAISED 0x00000008 // 已评估(缓存标志)
#define IMA_AUDIT 0x00000040 // 审计:通过 Linux Audit 记录事件
#define IMA_AUDITED 0x00000080 // 已审计(缓存标志)
#define IMA_HASH 0x00000100 // 仅计算哈希(用于更新 xattr)度量(Measure):对文件内容计算哈希,将结果追加到内存中的 ima_measurements 链表,同时将条目哈希扩展到 TPM 的 PCR10(默认),形成不可回退的历史记录。即使文件后续被修改,已经记录的度量值仍留存于 PCR 中。
评估(Appraise):在允许文件访问前,计算文件当前哈希,与存储在 security.ima xattr 中的参考哈希或数字签名进行比对。若不符则根据 ima_appraise 模式(enforce/log/fix)决定是否拒绝访问。
审计(Audit):调用 ima_audit_measurement(),通过 integrity_audit_msg() 发送 AUDIT_INTEGRITY_DATA 类型的审计事件,记录文件名、哈希算法和哈希值到 Linux Audit 日志。
每个被 IMA 追踪的 inode 均关联一个 ima_iint_cache 结构,定义于 security/integrity/ima/ima.h:188:
// security/integrity/ima/ima.h:188
struct ima_iint_cache {
struct mutex mutex; // 保护 version/flags/digest 的互斥锁
struct integrity_inode_attributes real_inode; // inode 版本/属性快照
unsigned long flags; // 度量/评估/审计状态位图
unsigned long measured_pcrs; // 已度量的 PCR 位图(支持多 PCR)
unsigned long atomic_flags; // 原子操作标志(CHANGE_XATTR等)
enum integrity_status ima_file_status:4; // FILE_CHECK 评估结果
enum integrity_status ima_mmap_status:4; // MMAP_CHECK 评估结果
enum integrity_status ima_bprm_status:4; // BPRM_CHECK 评估结果
enum integrity_status ima_read_status:4; // 读取操作评估结果
enum integrity_status ima_creds_status:4; // 凭证操作评估结果
struct ima_digest_data *ima_hash; // 已计算的哈希值
};ima_iint_cache 通过 LSM blob 机制与 inode 关联,而非嵌入 struct inode。访问方式如下:
// security/integrity/ima/ima.h:204
static inline struct ima_iint_cache *
ima_inode_get_iint(const struct inode *inode)
{
struct ima_iint_cache **iint_sec;
if (unlikely(!inode->i_security))
return NULL;
iint_sec = inode->i_security + ima_blob_sizes.lbs_inode;
return *iint_sec;
}atomic_flags 字段中各位的含义(ima.h:180):
#define IMA_CHANGE_XATTR 0 // security.ima xattr 已被修改
#define IMA_UPDATE_XATTR 1 // 需要在文件关闭时更新 security.ima
#define IMA_CHANGE_ATTR 2 // inode 属性已变更(setattr 调用)
#define IMA_DIGSIG 3 // xattr 包含数字签名(非哈希)
#define IMA_MAY_EMIT_TOMTOU 4 // 可能产生 ToMToU 违规报告
#define IMA_EMITTED_OPENWRITERS 5 // 已报告 open_writers 违规integrity_status 枚举(通过 linux/integrity.h 暴露):
INTEGRITY_PASS = 0 // 验证通过
INTEGRITY_PASS_IMMUTABLE // 通过(不可变签名)
INTEGRITY_FAIL // 验证失败
INTEGRITY_FAIL_IMMUTABLE // 不可变签名验证失败
INTEGRITY_NOLABEL // 缺少 security.evm
INTEGRITY_NOXATTRS // 无安全 xattr(新文件)
INTEGRITY_UNKNOWN // 未知/未验证
缓存生命周期管理:当文件以写模式关闭时,ima_check_last_writer()(ima_main.c:183)负责检测文件内容是否发生变化(通过 STATX_CHANGE_COOKIE),若变化则清除 IMA_DONE_MASK 位,强制下次访问重新度量/评估:
// security/integrity/ima/ima_main.c:183
static void ima_check_last_writer(struct ima_iint_cache *iint,
struct inode *inode, struct file *file)
{
if (atomic_read(&inode->i_writecount) == 1) {
// 最后一个写者关闭,重置状态
iint->flags &= ~(IMA_DONE_MASK | IMA_NEW_FILE);
iint->measured_pcrs = 0;
}
}process_measurement() 是 IMA 所有功能的核心调度函数,位于 security/integrity/ima/ima_main.c:236。其完整签名:
static int process_measurement(struct file *file, const struct cred *cred,
struct lsm_prop *prop, char *buf, loff_t size,
int mask, enum ima_hooks func,
enum kernel_read_file_id read_id,
bool bprm_is_check)参数说明:
func:触发类型,包括FILE_CHECK、MMAP_CHECK、BPRM_CHECK、MODULE_CHECK、FIRMWARE_CHECK、POLICY_CHECK、CRITICAL_DATA等;mask:访问权限掩码(MAY_READ、MAY_EXEC等);bprm_is_check:是否来自AT_EXECVE_CHECK路径(用户态脚本解释器完整性检查)。
执行流程(简化):
process_measurement()
|
+-- 1. 快速排除:非普通文件或策略未启用 -> 返回 0
|
+-- 2. ima_get_action() -> 从策略规则集确定 action 位图
| 返回: IMA_MEASURE | IMA_APPRAISE | IMA_AUDIT 的组合
|
+-- 3. 加 inode_lock
|
+-- 4. ima_inode_get() -> 获取或创建 ima_iint_cache
|
+-- 5. ima_rdwr_violation_check() -> 检查 ToMToU 违规
|
+-- 6. 解 inode_lock
|
+-- 7. 根据缓存标志跳过已完成的操作(IMA_DONE_MASK)
|
+-- 8. ima_read_xattr() -> 读取 security.ima
|
+-- 9. ima_collect_measurement() -> 计算当前文件哈希
|
+-- 10. ima_store_measurement() -> 写入度量日志 + TPM PCR 扩展
|
+-- 11. ima_appraise_measurement() -> 评估:调用 EVM + 哈希比对
|
+-- 12. ima_audit_measurement() -> 审计日志
|
+-- 13. 检查哈希算法是否在 allowed_algos 允许集合内
|
+-- 14. 若持有数字签名且有写操作,返回 -EACCES(保护签名文件)
|
+-- 返回 0 或 -EACCES
度量日志通过 securityfs 暴露于用户空间:
/sys/kernel/security/ima/ascii_runtime_measurements:人类可读的文本格式/sys/kernel/security/ima/binary_runtime_measurements:二进制格式,可直接用于远程证明(Remote Attestation)
度量列表的内存表示:
// security/integrity/ima/ima.h:114
struct ima_queue_entry {
struct hlist_node hnext; // 哈希冲突链表节点(用于去重查询)
struct list_head later; // 全局度量列表节点(ima_measurements)
struct ima_template_entry *entry; // 实际度量数据
};
// security/integrity/ima/ima.h:106
struct ima_template_entry {
int pcr; // 目标 PCR 编号(默认 10)
struct tpm_digest *digests; // 各 TPM bank 的摘要(SHA1/SHA256等)
struct ima_template_desc *template_desc; // 使用的模板描述符
u32 template_data_len; // 模板数据总长度
struct ima_field_data template_data[]; // 变长模板字段数组
};TPM PCR 扩展操作:ima_store_measurement() 调用 ima_add_template_entry(),后者对模板条目计算哈希后调用 TPM 驱动的 tpm_pcr_extend(),将哈希值扩展到目标 PCR:
PCR_new = SHA1(PCR_old || measurement_hash)
默认 PCR 编号由内核配置 CONFIG_IMA_MEASURE_PCR_IDX 决定,通常为 10。ima.h:29 中定义了合法 PCR 常量:
enum tpm_pcrs { TPM_PCR0 = 0, TPM_PCR8 = 8, TPM_PCR10 = 10 };度量日志的文本格式示例(ascii_runtime_measurements):
10 abc123... ima-ng sha256:def456... /usr/bin/bash
10 789abc... ima-ng sha256:123def... /lib/x86_64-linux-gnu/libc.so.6
每行格式:<PCR编号> <模板哈希> <模板名称> <字段1> <字段2> ...
去重机制:ima_measurements 是全局有序链表,同时维护一个哈希表 ima_htable(大小 IMA_MEASURE_HTABLE_SIZE = 1 << IMA_HASH_BITS = 1024,ima.h:35-36)用于快速查重。
IMA 策略通过规则链表实现,核心数据结构是 ima_rule_entry(ima_policy.c:100):
// security/integrity/ima/ima_policy.c:100
struct ima_rule_entry {
struct list_head list;
int action; // MEASURE / DONT_MEASURE / APPRAISE / AUDIT 等
unsigned int flags; // 条件字段标志(IMA_FUNC / IMA_UID / IMA_FSMAGIC 等)
enum ima_hooks func; // 触发钩子类型(FILE_CHECK / BPRM_CHECK 等)
int mask; // 访问权限掩码
unsigned long fsmagic; // 文件系统魔数(用于排除 /proc、sysfs 等)
uuid_t fsuuid; // 文件系统 UUID
kuid_t uid; // 触发进程 UID 条件
kgid_t gid; // 触发进程 GID 条件
kuid_t fowner; // 文件所有者 UID 条件
kgid_t fgroup; // 文件所有者 GID 条件
bool (*uid_op)(...); // UID 比较函数(uid_eq / 大于 / 小于)
bool (*fowner_op)(...); // fowner 比较函数
int pcr; // 目标 PCR 编号
unsigned int allowed_algos; // 允许的哈希算法位图
struct { void *rule; char *args_p; int type; } lsm[MAX_LSM_RULES];
char *fsname; // 文件系统名称(用于按类型过滤)
char *fs_subtype; // 文件系统子类型
struct ima_rule_opt_list *keyrings; // 密钥环白名单(KEY_CHECK)
struct ima_rule_opt_list *label; // 数据标签(CRITICAL_DATA)
struct ima_template_desc *template; // 规则关联的模板
};条件标志位(ima_policy.c:25 起):
#define IMA_FUNC 0x0001 // 基于 func 类型匹配
#define IMA_MASK 0x0002 // 基于访问掩码匹配(MAY_READ/MAY_EXEC)
#define IMA_FSMAGIC 0x0004 // 基于文件系统魔数匹配
#define IMA_UID 0x0008 // 基于进程 UID 匹配
#define IMA_FOWNER 0x0010 // 基于文件所有者匹配
#define IMA_FSUUID 0x0020 // 基于文件系统 UUID 匹配
#define IMA_INMASK 0x0040 // inode_mask 而非 file_mask
#define IMA_EUID 0x0080 // 基于进程 EUID 匹配
#define IMA_PCR 0x0100 // 指定 PCR 编号
#define IMA_FSNAME 0x0200 // 基于文件系统名称匹配
#define IMA_KEYRINGS 0x0400 // 基于密钥环名称匹配
#define IMA_LABEL 0x0800 // 基于数据标签匹配
#define IMA_GID 0x2000 // 基于进程 GID 匹配
#define IMA_FGROUP 0x8000 // 基于文件所有者 GID 匹配
#define IMA_FS_SUBTYPE 0x10000 // 基于文件系统子类型匹配内置默认规则集:
dont_measure_rules(排除规则,ima_policy.c:150):
{.action = DONT_MEASURE, .fsmagic = PROC_SUPER_MAGIC, ...} // 排除 /proc
{.action = DONT_MEASURE, .fsmagic = SYSFS_MAGIC, ...} // 排除 /sys
{.action = DONT_MEASURE, .fsmagic = DEBUGFS_MAGIC, ...} // 排除 debugfs
{.action = DONT_MEASURE, .fsmagic = TMPFS_MAGIC, ...} // 排除 tmpfs 文件
{.action = DONT_MEASURE, .fsmagic = EFIVARFS_MAGIC, ...} // 排除 EFI 变量
// ... 共 13 条排除规则default_measurement_rules(默认度量规则,ima_policy.c:181,ima_policy=tcb):
{.action = MEASURE, .func = MMAP_CHECK, .mask = MAY_EXEC, ...}
{.action = MEASURE, .func = BPRM_CHECK, .mask = MAY_EXEC, ...}
{.action = MEASURE, .func = FILE_CHECK, .mask = MAY_READ,
.uid = GLOBAL_ROOT_UID, .uid_op = &uid_eq, ...} // root 读取的文件
{.action = MEASURE, .func = MODULE_CHECK, ...}
{.action = MEASURE, .func = FIRMWARE_CHECK, ...}
{.action = MEASURE, .func = POLICY_CHECK, ...}secure_boot_rules(安全启动规则,ima_policy.c:245,强制要求数字签名):
{.action = APPRAISE, .func = MODULE_CHECK,
.flags = IMA_FUNC | IMA_DIGSIG_REQUIRED | IMA_MODSIG_ALLOWED |
IMA_CHECK_BLACKLIST},
{.action = APPRAISE, .func = FIRMWARE_CHECK,
.flags = IMA_FUNC | IMA_DIGSIG_REQUIRED},
{.action = APPRAISE, .func = KEXEC_KERNEL_CHECK,
.flags = IMA_FUNC | IMA_DIGSIG_REQUIRED},
{.action = APPRAISE, .func = POLICY_CHECK,
.flags = IMA_FUNC | IMA_DIGSIG_REQUIRED},策略链表与 RCU:三条策略链表并存(ima_policy.c:264):
ima_default_rules:内核编译时确定的默认规则ima_policy_rules:用户通过 securityfs 加载的自定义规则ima_temp_rules:规则更新期间的临时列表
当前活跃规则集由 ima_rules RCU 指针指向,策略切换通过 rcu_assign_pointer() 完成。
非操作标志(ima.h:143-150)控制评估行为:
#define IMA_DIGSIG_REQUIRED 0x01000000 // 强制要求数字签名(不接受纯哈希)
#define IMA_PERMIT_DIRECTIO 0x02000000 // 允许 O_DIRECT 访问
#define IMA_NEW_FILE 0x04000000 // 新创建文件豁免
#define IMA_FAIL_UNVERIFIABLE_SIGS 0x10000000 // 不可验证签名视为失败
#define IMA_MODSIG_ALLOWED 0x20000000 // 允许内核模块内嵌签名
#define IMA_CHECK_BLACKLIST 0x40000000 // 启用黑名单检查
#define IMA_VERITY_REQUIRED 0x80000000 // 强制要求 fsverity 签名IMA 使用模板描述符定义度量日志条目的格式,允许在不同字段组合之间灵活选择。
内置模板集合(security/integrity/ima/ima_template.c:19):
static struct ima_template_desc builtin_templates[] = {
{.name = "ima", .fmt = "d|n"},
{.name = "ima-ng", .fmt = "d-ng|n-ng"}, // 默认,支持多种哈希算法
{.name = "ima-sig", .fmt = "d-ng|n-ng|sig"}, // 含数字签名
{.name = "ima-ngv2", .fmt = "d-ngv2|n-ng"}, // v2 摘要格式
{.name = "ima-sigv2",.fmt = "d-ngv2|n-ng|sig"},
{.name = "ima-buf", .fmt = "d-ng|n-ng|buf"}, // 含任意缓冲区数据
{.name = "ima-modsig",.fmt = "d-ng|n-ng|sig|d-modsig|modsig"},
{.name = "evm-sig",
.fmt = "d-ng|n-ng|evmsig|xattrnames|xattrlengths|xattrvalues|iuid|igid|imode"},
{.name = "", .fmt = ""}, // 自定义格式占位符
};支持的模板字段(ima_template.c:36):
static const struct ima_template_field supported_fields[] = {
{.field_id = "d", ...}, // 传统摘要(仅 SHA1/MD5)
{.field_id = "n", ...}, // 传统文件名
{.field_id = "d-ng", ...}, // 带算法前缀的摘要(sha256:abc...)
{.field_id = "d-ngv2", ...}, // v2 摘要(含额外元数据)
{.field_id = "n-ng", ...}, // 完整路径文件名
{.field_id = "sig", ...}, // security.ima 签名内容
{.field_id = "buf", ...}, // 任意数据缓冲区
{.field_id = "d-modsig", ...}, // 模块内嵌签名摘要
{.field_id = "modsig", ...}, // 模块内嵌签名内容
{.field_id = "evmsig", ...}, // EVM 签名
{.field_id = "iuid", ...}, // inode UID
{.field_id = "igid", ...}, // inode GID
{.field_id = "imode", ...}, // inode 权限位
{.field_id = "xattrnames",...}, // 被保护 xattr 名称列表
{.field_id = "xattrlengths",...},// 被保护 xattr 长度列表
{.field_id = "xattrvalues",...}, // 被保护 xattr 值列表
};模板描述符结构(ima.h:98):
struct ima_template_desc {
struct list_head list; // 链入 defined_templates 链表
char *name; // 模板名称(如 "ima-ng")
char *fmt; // 字段格式字符串(如 "d-ng|n-ng")
int num_fields; // 字段数量
const struct ima_template_field **fields; // 字段函数指针数组
};字段最大数量限制:IMA_TEMPLATE_NUM_FIELDS_MAX = 15(ima.h:39)。
字段 ID 最大长度:IMA_TEMPLATE_FIELD_ID_MAX_LEN = 16(ima.h:38)。
通过内核启动参数 ima_template=<name> 或 ima_template_fmt=<格式> 可在启动时选择模板。ima-ng 是现代系统的推荐选择,因为字段 d-ng 格式为 <algo>:<hex_digest>,而旧的 ima 模板仅支持 SHA1/MD5。
模板字段的解析通过 template_desc_init_fields()(ima_template.c:208)实现,以 | 为分隔符分割格式字符串,逐个查找并注册字段。
IMA 评估的核心函数是 ima_appraise_measurement(),位于 security/integrity/ima/ima_appraise.c:481。
评估模式(由内核参数 ima_appraise= 控制,ima_appraise.c:27):
if (strncmp(str, "off", 3) == 0)
appraisal_state = 0; // 关闭评估
else if (strncmp(str, "log", 3) == 0)
appraisal_state = IMA_APPRAISE_LOG; // 仅记录,不拒绝
else if (strncmp(str, "fix", 3) == 0)
appraisal_state = IMA_APPRAISE_FIX; // 自动修复(重写 xattr)
else if (strncmp(str, "enforce", 7) == 0)
appraisal_state = IMA_APPRAISE_ENFORCE; // 强制拒绝(编译期默认)安全启动启用时,enforce 模式无法被覆盖(ima_appraise.c:49)。
评估流程(ima_appraise_measurement() 内部):
ima_appraise_measurement()
|
+-- 1. 检查 xattr 支持(IOP_XATTR)
|
+-- 2. evm_verifyxattr(dentry, XATTR_NAME_IMA, ...)
| -> 用 EVM 验证 security.ima 自身的完整性
| -> 返回 INTEGRITY_PASS / INTEGRITY_FAIL / INTEGRITY_NOLABEL 等
|
+-- 3. switch(evm_status):
| PASS / PASS_IMMUTABLE -> 继续
| NOLABEL -> 缺少 security.evm -> "missing-HMAC" -> goto out
| FAIL -> EVM 验证失败 -> "invalid-HMAC" -> goto out
|
+-- 4. xattr_verify() -> 验证 security.ima 内容
| switch(xattr_value->type):
| IMA_XATTR_DIGEST: 哈希比对(memcmp)
| IMA_XATTR_DIGEST_NG: 含算法ID的哈希比对
| EVM_IMA_XATTR_DIGSIG: 数字签名验证
| -> integrity_digsig_verify(INTEGRITY_KEYRING_IMA)
| IMA_VERITY_DIGSIG: fsverity 数字签名验证(v3格式)
|
+-- 5. fix 模式:若验证失败且未使用数字签名,重写 security.ima
|
+-- 6. ima_set_cache_status() -> 更新 iint 中对应 func 的状态
|
+-- 返回 status(INTEGRITY_PASS 或失败状态)
IMA_DIGSIG_REQUIRED 强制签名:当策略规则设置了此标志时,仅有哈希的 xattr 不被接受,必须提供数字签名:
// security/integrity/ima/ima_appraise.c:293
case IMA_XATTR_DIGEST_NG:
if (*status != INTEGRITY_PASS_IMMUTABLE) {
if (iint->flags & IMA_DIGSIG_REQUIRED) {
*cause = "IMA-signature-required";
*status = INTEGRITY_FAIL;
break;
}
}IMA_DIGSIG 原子标志(ima.h:183):当 xattr 类型为 EVM_IMA_XATTR_DIGSIG 时置位。该位被 process_measurement() 检查:若文件持有数字签名而又有写操作请求,直接返回 -EACCES(ima_main.c:454),防止签名被意外覆盖:
// security/integrity/ima/ima_main.c:454
if ((mask & MAY_WRITE) && test_bit(IMA_DIGSIG, &iint->atomic_flags) &&
!(iint->flags & IMA_NEW_FILE))
rc = -EACCES;哈希算法检测(ima_get_hash_algo(),ima_appraise.c:179):从 xattr 内容自动推断所使用的哈希算法,实现向后兼容:
IMA_XATTR_DIGEST:通过长度判断(17 字节 = MD5,21 字节含零填充 = MD5,否则 SHA1);IMA_XATTR_DIGEST_NG:第一字节为算法 ID(enum hash_algo);EVM_IMA_XATTR_DIGSIG:从签名头部signature_v2_hdr.hash_algo读取;- 默认回退到
ima_hash_algo(系统全局哈希算法,默认 SHA1,ima_main.c:40)。
IMA 的评估功能依赖 security.ima xattr 的准确性,但 xattr 本身存储在文件系统中,攻击者若能获取系统的离线访问权限(例如直接挂载磁盘),可以直接修改 xattr,绕过 IMA 的保护。EVM 通过为所有安全相关 xattr 的集合计算 HMAC 或数字签名,使任何单个 xattr 的篡改都能被检测到。
EVM 保护的 xattr 集合(evm_main.c:38):
security.selinux (若启用 SELinux)
security.SMACK64 (若启用 Smack)
security.SMACK64EXEC
security.SMACK64TRANSMUTE
security.SMACK64MMAP
security.apparmor (若启用 AppArmor)
security.ima (若启用 IMA Appraise)
security.capability (文件能力,始终保护)
这个保护链条形成一个闭环:security.evm 保护 security.ima,security.ima 保护文件内容。
EVM 同样为每个被追踪的 inode 维护一个缓存结构(security/integrity/evm/evm.h:39):
struct evm_iint_cache {
unsigned long flags; // 状态标志
enum integrity_status evm_status:4; // 最近一次 EVM 验证结果
struct integrity_inode_attributes metadata_inode; // 元数据 inode 属性快照
};标志位:
#define EVM_NEW_FILE 0x00000001 // 新创建的文件
#define EVM_IMMUTABLE_DIGSIG 0x00000002 // 使用了可移植/不可变数字签名evm_iint_cache 通过 LSM blob 机制与 inode 关联(evm.h:47):
static inline struct evm_iint_cache *evm_iint_inode(const struct inode *inode)
{
if (unlikely(!inode->i_security))
return NULL;
return inode->i_security + evm_blob_sizes.lbs_inode;
}security.evm xattr 存储 EVM 的验证数据,其类型通过 evm_ima_xattr_type 枚举区分(security/integrity/integrity.h:23):
enum evm_ima_xattr_type {
IMA_XATTR_DIGEST = 0x01, // IMA 传统哈希
EVM_XATTR_HMAC = 0x02, // EVM HMAC(需要 HMAC 密钥)
EVM_IMA_XATTR_DIGSIG = 0x03, // IMA/EVM 数字签名(v2,基于文件哈希)
IMA_XATTR_DIGEST_NG = 0x04, // IMA 带算法ID的哈希
EVM_XATTR_PORTABLE_DIGSIG = 0x05, // EVM 可移植数字签名(不含主机特定信息)
IMA_VERITY_DIGSIG = 0x06, // IMA fsverity 签名(v3)
IMA_XATTR_LAST
};HMAC 内容结构(integrity.h:44):
struct evm_xattr {
struct evm_ima_xattr_data_hdr data; // type 字段(EVM_XATTR_HMAC)
u8 digest[SHA1_DIGEST_SIZE]; // 20 字节 HMAC-SHA1 值
} __packed;HMAC 计算覆盖(在 evm_crypto.c 的 evm_calc_hmac_or_hash() 中实现):
HMAC-SHA1(evmkey,
security.selinux_value
|| security.SMACK64_value
|| ...
|| security.ima_value
|| security.capability_value
|| inode.ino
|| inode.generation
|| inode.uid (编码为 init_user_ns 下的原始值)
|| inode.gid
|| inode.mode
[|| inode.i_sb->s_uuid (若启用 CONFIG_EVM_ATTR_FSUUID)]
)
其中 inode 元数据部分由 hmac_add_misc() 处理(evm_crypto.c:143):
static void hmac_add_misc(struct shash_desc *desc, struct inode *inode,
char type, char *digest)
{
struct h_misc {
unsigned long ino;
__u32 generation;
uid_t uid;
gid_t gid;
umode_t mode;
} hmac_misc;
// 可移植签名不包含 ino/generation(跨主机兼容性)
if (type != EVM_XATTR_PORTABLE_DIGSIG) {
hmac_misc.ino = inode->i_ino;
hmac_misc.generation = inode->i_generation;
}
// uid/gid 必须编码为 init_user_ns 下的值,防止 fuse 命名空间攻击
hmac_misc.uid = from_kuid(&init_user_ns, inode->i_uid);
hmac_misc.gid = from_kgid(&init_user_ns, inode->i_gid);
hmac_misc.mode = inode->i_mode;
crypto_shash_update(desc, (const u8 *)&hmac_misc, sizeof(hmac_misc));
if ((evm_hmac_attrs & EVM_ATTR_FSUUID) && type != EVM_XATTR_PORTABLE_DIGSIG)
crypto_shash_update(desc, (u8 *)&inode->i_sb->s_uuid, UUID_SIZE);
crypto_shash_final(desc, digest);
}为何包含 inode 元数据:防止"剪切粘贴"攻击——攻击者将一个文件的 security.evm 复制到另一个文件。由于 HMAC 输入包含 inode 号和 UUID,这种攻击会被检测到。
可移植签名(EVM_XATTR_PORTABLE_DIGSIG):不包含主机特定信息,因此签名可以在不同机器的相同文件之间复用,适合镜像分发场景。
evm_calc_hmac_or_hash() 是 EVM 加密计算的核心,位于 security/integrity/evm/evm_crypto.c:221:
evm_calc_hmac_or_hash()
|
+-- 1. 检查:inode 是否支持 xattr,是否在 init_user_ns
|
+-- 2. init_desc(type, algo) -> 初始化 shash_desc
| HMAC 类型: 使用 hmac_tfm(已用 evmkey 设置密钥)
| HASH 类型: 使用 evm_tfm[algo](无密钥)
|
+-- 3. 遍历 evm_config_xattrnames 链表
| 对每个 enabled 的 xattr:
| a. 若当前正在设置该 xattr(req_xattr_name 匹配),
| 使用传入的新值(避免重新读取,保证一致性)
| b. 否则调用 vfs_getxattr_alloc() 读取现有值
| c. crypto_shash_update(desc, xattr_value, size)
|
+-- 4. hmac_add_misc() -> 追加 inode 元数据并 finalize
|
+-- 5. 若为 PORTABLE_DIGSIG,检查 IMA xattr 是否存在
| (可移植签名必须包含 security.ima)
|
+-- 返回 HMAC/hash 结果到 data->digest
两个公开接口(evm_crypto.c:316-330):
evm_calc_hmac():计算 HMAC,类型为EVM_XATTR_HMAC;evm_calc_hash():计算哈希(用于数字签名验证的数据摘要)。
evm_verify_hmac() 是 EVM 验证的核心函数,位于 security/integrity/evm/evm_main.c:178:
evm_verify_hmac()
|
+-- 1. 检查缓存:iint->evm_status 若已为 PASS/PASS_IMMUTABLE,直接返回
|
+-- 2. 读取 security.evm(vfs_getxattr_alloc)
| 失败 -> INTEGRITY_FAIL
| ENODATA:
| 有受保护 xattr -> INTEGRITY_NOLABEL(缺签名)
| 无受保护 xattr -> INTEGRITY_NOXATTRS(新文件)
|
+-- 3. switch(xattr_data->type):
|
| EVM_XATTR_HMAC:
| 检查长度(必须等于 sizeof(struct evm_xattr) = 21B)
| evm_calc_hmac() -> 重新计算 HMAC
| crypto_memneq() 恒时比对
| 不匹配 -> rc = -EINVAL
|
| EVM_XATTR_PORTABLE_DIGSIG: (evm_immutable = 1)
| fallthrough
| EVM_IMA_XATTR_DIGSIG:
| 检查长度(> sizeof(signature_v2_hdr))
| evm_calc_hash() -> 计算 xattr 集合哈希
| integrity_digsig_verify(INTEGRITY_KEYRING_EVM, ...) -> 验证签名
| 成功且文件可写 -> evm_update_evmxattr()(升级为 HMAC,优化性能)
| PORTABLE_DIGSIG 成功 -> INTEGRITY_PASS_IMMUTABLE
|
+-- 4. 缓存结果到 iint->evm_status
|
+-- 返回 evm_status
evm_verifyxattr() 公共接口(evm_main.c:421):
enum integrity_status evm_verifyxattr(struct dentry *dentry,
const char *xattr_name,
void *xattr_value, size_t xattr_value_len)
{
if (!evm_key_loaded() || !evm_protected_xattr(xattr_name))
return INTEGRITY_UNKNOWN;
return evm_verify_hmac(dentry, xattr_name, xattr_value, xattr_value_len);
}
EXPORT_SYMBOL_GPL(evm_verifyxattr);此函数由 IMA 的 ima_appraise_measurement() 在 ima_appraise.c:534 处调用,是两个子系统的关键接口点。
EVM 保护 xattr 修改:evm_protect_xattr()(evm_main.c:497)实现为 inode_setxattr 钩子,在任何受保护 xattr 被修改之前检查当前 EVM 状态,若 EVM 签名为不可变类型(EVM_IMMUTABLE_DIGSIG)则拒绝修改。
EVM 支持两种密钥机制,分别对应 HMAC 和数字签名两种保护模式。
EVM 初始化标志(security/integrity/evm/evm.h:20):
#define EVM_INIT_HMAC 0x0001 // HMAC 密钥已加载
#define EVM_INIT_X509 0x0002 // X.509 证书密钥环已就绪
#define EVM_ALLOW_METADATA_WRITES 0x0004 // 允许在无密钥时写入元数据
#define EVM_SETUP_COMPLETE 0x80000000 // 用户态已完成密钥加载HMAC 密钥:密钥值在 evm_crypto.c 中以全局数组形式保存(最大 128 字节):
// security/integrity/evm/evm_crypto.c:25-27
#define EVMKEY "evm-key"
#define MAX_KEY_SIZE 128
static unsigned char evmkey[MAX_KEY_SIZE];HMAC 算法固定为 hmac(sha1)(evm_crypto.c:39):
static const char evm_hmac[] = "hmac(sha1)";通过 evm_set_key() 加载,加载成功后设置 EVM_INIT_HMAC 标志。
EVM 密钥环:用于数字签名验证的公钥存储于 INTEGRITY_KEYRING_EVM(索引 0),在初始化时由 evm_load_x509() 加载(当启用 CONFIG_EVM_LOAD_X509 时)。
evm_hmac_disabled() 状态检查(evm_main.c:120):
static bool evm_hmac_disabled(void)
{
if (evm_initialized & EVM_INIT_HMAC)
return false;
if (!(evm_initialized & EVM_SETUP_COMPLETE))
return false;
return true;
}若 HMAC 密钥未加载且 EVM_SETUP_COMPLETE 已设置,EVM 验证错误将被视为不可恢复的失败。这防止了系统在 "EVM 初始化中" 状态被绕过。
evm_key_loaded()(evm_main.c:108):
static bool evm_key_loaded(void)
{
return (bool)(evm_initialized & EVM_KEY_MASK);
}其中 EVM_KEY_MASK = EVM_INIT_HMAC | EVM_INIT_X509(evm.h:25)。
IMA 与 EVM 通过 ima_appraise_measurement() -> evm_verifyxattr() 这条调用链形成严格的串联验证:
文件访问请求
|
v
process_measurement() [ima_main.c]
|
v
ima_appraise_measurement() [ima_appraise.c:481]
|
+------> evm_verifyxattr(dentry, "security.ima", xattr_value, len)
| [evm_main.c:421]
| |
| v
| evm_verify_hmac() [evm_main.c:178]
| |
| +-- 读取 security.evm
| +-- 重算 HMAC(所有安全xattr + inode元数据)
| +-- 比对 -> 返回 integrity_status
|
| <- INTEGRITY_PASS / INTEGRITY_FAIL / ...
|
+-- 若 EVM 验证通过:
| xattr_verify() [ima_appraise.c:278]
| -> 验证 security.ima 内容(哈希比对或签名验证)
|
+-- 若 EVM 验证失败:
拒绝访问(enforce 模式),或记录日志(log 模式)
xattr 修改时的自动更新:当受保护 xattr 发生变化时,evm_inode_post_setxattr() 钩子触发 evm_update_evmxattr(),自动重新计算并更新 security.evm,保持 EVM 与实际 xattr 内容的一致性。
堆叠文件系统中的 EVM 元数据跟踪(ima_main.c:342):
metadata_inode = d_inode(d_real(file_dentry(file), D_REAL_METADATA));
if (evm_metadata_changed(inode, metadata_inode))
iint->flags &= ~(IMA_APPRAISED | IMA_APPRAISED_SUBMASK);在 overlayfs 等堆叠文件系统中,IMA 通过比较元数据 inode 的变化来检测 EVM 保护的 xattr 是否被修改,并在变化时清除评估缓存,强制重新验证。
协作不变式:
- EVM 先于 IMA 验证。若
security.evm验证失败,security.ima即使内容正确也不会被信任; IMA_NEW_FILE标志允许新创建的文件在无 xattr 时通过评估,EVM 通过INTEGRITY_NOXATTRS状态与之配合;- 数字签名类型的
security.evm在成功验证后,若文件系统可写,EVM 会将其升级为 HMAC 格式(节省每次验证的签名运算开销); - 文件以写模式关闭后,
IMA_UPDATE_XATTR标志被设置,在下次适当时机触发security.ima的重新写入(ima_update_xattr(),ima_appraise.c:618)。
密钥信任体系
+------------------------------------------+
| 硬件/固件信任根 |
| (TPM EK / Secure Boot PK / MOK) |
+------------------+-----------------------+
|
v
+----------------------------------------------+
| 内核密钥环(keyring) |
| |
| .system_keyring 内核内置证书 |
| .platform UEFI db 证书 |
| .machine MOK 证书 |
| .ima IMA 内容签名验证密钥 |
| .evm EVM xattr 签名验证密钥 |
+----------------------------------------------+
|
v
integrity_digsig_verify(INTEGRITY_KEYRING_IMA/EVM,
sig, siglen, digest, digestlen)
[security/integrity/digsig.c]
|
v
asymmetric_verify() -> RSA/ECDSA 签名验证
数字签名格式(signature_v2_hdr,integrity.h:92):
+------------------------------------------+
| type (1B) | version (1B) | hash_algo |
+------------------------------------------+
| keyid (4B, big-endian) |
+------------------------------------------+
| sig_size (2B, big-endian) |
+------------------------------------------+
| sig[] (RSA/ECDSA 签名数据,可变长度) |
+------------------------------------------+
IMA 签名版本:
v2: type=EVM_IMA_XATTR_DIGSIG(0x03)
直接对文件哈希签名
v3: type=IMA_VERITY_DIGSIG(0x06)
对 ima_file_id 结构的哈希签名
(通过间接签名提高安全性,防止类型混淆攻击)
ima_file_id 结构(integrity.h:109):
+------------------------------------------+
| hash_type (1B, evm_ima_xattr_type) |
| hash_algorithm (1B, hash_algo) |
| hash[HASH_MAX_DIGESTSIZE] |
+------------------------------------------+
密钥环索引(integrity.h:120):
#define INTEGRITY_KEYRING_EVM 0 // EVM xattr 签名验证
#define INTEGRITY_KEYRING_IMA 1 // IMA 文件内容签名验证
#define INTEGRITY_KEYRING_PLATFORM 2 // 平台密钥(UEFI/MOK)
#define INTEGRITY_KEYRING_MACHINE 3 // 机器密钥(MOK 扩展)
#define INTEGRITY_KEYRING_MAX 4kexec 内核验证(ima_appraise.c:349):kexec 内核镜像的签名验证优先使用 INTEGRITY_KEYRING_IMA,若失败则回退到 INTEGRITY_KEYRING_PLATFORM,确保在 IMA 密钥环未包含相关证书时仍能通过 UEFI 验证:
rc = integrity_digsig_verify(INTEGRITY_KEYRING_IMA, ...);
if (IS_ENABLED(CONFIG_INTEGRITY_PLATFORM_KEYRING) && rc &&
func == KEXEC_KERNEL_CHECK)
rc = integrity_digsig_verify(INTEGRITY_KEYRING_PLATFORM, ...);struct inode
+-- i_security --> [LSM blob 区域]
|
+-- [ima_blob_sizes.lbs_inode 偏移]
| +-> struct ima_iint_cache *
| |-- mutex
| |-- real_inode (integrity_inode_attributes)
| |-- flags (IMA_MEASURE | IMA_APPRAISED 等)
| |-- measured_pcrs (已度量PCR位图)
| |-- atomic_flags (IMA_DIGSIG | IMA_UPDATE_XATTR)
| |-- ima_file_status / ima_mmap_status / ...
| +-- ima_hash --> struct ima_digest_data
| |-- algo (hash_algo 枚举)
| |-- length
| |-- xattr (sha1/ng 联合体)
| +-- digest[] (哈希字节)
|
+-- [evm_blob_sizes.lbs_inode 偏移]
+-> struct evm_iint_cache
|-- flags (EVM_NEW_FILE | EVM_IMMUTABLE_DIGSIG)
|-- evm_status (integrity_status 枚举)
+-- metadata_inode
全局度量链表:
ima_measurements (list_head)
+-> struct ima_queue_entry
|-- hnext (hlist_node,在 ima_htable 中用于去重)
|-- later (list_head,链接下一条目)
+-- entry --> struct ima_template_entry
|-- pcr (TPM PCR 编号)
|-- digests[] (各 TPM bank 的模板摘要)
|-- template_desc --> struct ima_template_desc
| |-- name ("ima-ng")
| |-- fmt ("d-ng|n-ng")
| |-- num_fields
| +-- fields[]
|-- template_data_len
+-- template_data[] (每个字段的 ima_field_data)
+-> struct ima_field_data
|-- data (字段原始字节)
+-- len
策略规则链表:
ima_rules (RCU 指针) --> ima_default_rules / ima_policy_rules
+-> struct ima_rule_entry
|-- action (MEASURE / APPRAISE / AUDIT / DONT_*)
|-- flags (IMA_FUNC | IMA_UID | IMA_FSMAGIC 等条件标志)
|-- func (FILE_CHECK / BPRM_CHECK / MODULE_CHECK 等)
|-- mask (MAY_READ / MAY_EXEC 等)
|-- fsmagic (文件系统魔数)
|-- uid / gid / fowner / fgroup (身份条件)
|-- pcr (目标 PCR)
|-- allowed_algos (允许哈希算法位图)
|-- lsm[] (LSM 标签条件,最多 6 条)
+-- template --> struct ima_template_desc
xattr 类型体系(security.ima / security.evm):
struct evm_ima_xattr_data
|-- type (enum evm_ima_xattr_type)
| |-- IMA_XATTR_DIGEST (0x01) 传统 SHA1/MD5 哈希
| |-- EVM_XATTR_HMAC (0x02) HMAC-SHA1 (security.evm 用)
| |-- EVM_IMA_XATTR_DIGSIG (0x03) RSA/ECDSA 签名 v2
| |-- IMA_XATTR_DIGEST_NG (0x04) 带算法 ID 的哈希
| |-- EVM_XATTR_PORTABLE_DIGSIG (0x05) 可移植签名
| +-- IMA_VERITY_DIGSIG (0x06) fsverity 签名 v3
+-- data[]
struct signature_v2_hdr (type=0x03/0x05/0x06 时的数据格式)
|-- type (1B)
|-- version (1B, 2=文件哈希签名, 3=ima_file_id哈希签名)
|-- hash_algo (1B, enum hash_algo)
|-- keyid (4B, BE)
|-- sig_size (2B, BE)
+-- sig[] (可变)
IMA 通过 LSM 框架在以下关键路径上插入检查:
操作 LSM 钩子 IMA 函数
----------------------------------------------------------------------
open()/read() security_file_open() -> ima_file_check()
process_measurement(FILE_CHECK)
execve() security_bprm_check() -> ima_bprm_check()
process_measurement(BPRM_CHECK)
bprm_creds_for_exec (同上,AT_EXECVE_CHECK) -> ima_bprm_creds_for_exec()
凭证检查 security_creds_check() -> ima_creds_check()
process_measurement(CREDS_CHECK)
mmap(PROT_EXEC) security_mmap_file() -> ima_file_mmap()
process_measurement(MMAP_CHECK)
mprotect(PROT_EXEC) security_vm_enough_memory() -> ima_file_mprotect()
内核模块加载 security_kernel_read_file() -> ima_kernel_read_file()
process_measurement(MODULE_CHECK)
固件加载 同上 -> process_measurement(FIRMWARE_CHECK)
kexec 内核 同上 -> process_measurement(KEXEC_KERNEL_CHECK)
IMA 策略文件 同上 -> process_measurement(POLICY_CHECK)
关键数据度量 直接调用 -> process_buffer_measurement(CRITICAL_DATA)
文件关闭 security_file_free() -> ima_file_free()
ima_check_last_writer()
setxattr security_inode_setxattr -> evm_inode_setxattr() [EVM]
evm_protect_xattr()
post-setxattr security_inode_post_setxattr -> evm_inode_post_setxattr()
evm_update_evmxattr()
removexattr security_inode_removexattr -> evm_inode_removexattr()
setattr security_inode_setattr -> evm_inode_setattr()
内核启动参数解析阶段(__setup 宏,早于 initcall)
|
+-- ima_hash=sha256 -> hash_setup() [ima_main.c:66]
+-- ima_template=ima-ng -> ima_template_setup() [ima_template.c:104]
+-- ima_policy=tcb -> policy_setup() [ima_policy.c:285]
+-- ima_appraise=enforce -> ima_appraise_parse_cmdline() [ima_appraise.c:27]
+-- evm=fix -> evm_set_fixmode() [evm_main.c:76]
|
v
security_initcall(ima_init)
|
+-- ima_iintcache_init() -> 注册 LSM blob(per-inode 缓存)
+-- ima_init_crypto() -> 分配哈希算法 tfm 句柄
+-- ima_init_template() -> 解析模板格式字符串,构建字段数组
+-- ima_fs_init() -> 创建 /sys/kernel/security/ima/ 目录树
+-- ima_load_kexec_buffer() -> 恢复 kexec 传递的度量日志
+-- ima_init_template_list() -> 注册所有内置模板
+-- ima_add_boot_aggregate() -> 度量 TPM PCR0-7,生成首条度量日志
|
v
security_initcall(evm_init)
|
+-- evm_init_config() -> 构建受保护 xattr 名称链表
+-- evm_init_secfs() -> 创建 /sys/kernel/security/evm 接口
+-- evm_load_x509() -> 加载 EVM 公钥证书到 .evm 密钥环
|
v
用户态初始化(init 进程)
|
+-- 通过 keyctl 将 HMAC 密钥写入内核:
| keyctl add encrypted .evm "new default user:kmk 32" @u
|
+-- 通知内核密钥加载完成:
echo 1 > /sys/kernel/security/evm (启用 EVM_INIT_HMAC)
echo 0x80000000 > /sys/kernel/security/evm (设置 EVM_SETUP_COMPLETE)
boot_aggregate:IMA 的第一个度量条目,读取 TPM 的 PCR0-7(记录了固件启动过程),将其哈希值作为 boot_aggregate 条目记录到度量日志,将 IMA 度量链与固件完整性度量链连接起来,形成连续的信任链。外部验证者可以通过读取 TPM quote 和 IMA 日志,验证整个启动链的完整性。
security.ima 的可能内容(xattr_value->type):
类型 内容格式 用途
------------------------------------------------------------------------
IMA_XATTR_DIGEST (0x01) [type(1B)] + SHA1/MD5 哈希 传统格式,向后兼容
IMA_XATTR_DIGEST_NG (0x04) [type(1B)] + [algo(1B)] + 哈希 现代格式,支持任意算法
EVM_IMA_XATTR_DIGSIG (0x03) [signature_v2_hdr] + RSA签名 IMA 数字签名(推荐)
IMA_VERITY_DIGSIG (0x06) [signature_v2_hdr(v3)] + 签名 fsverity 数字签名
security.evm 的可能内容:
类型 内容格式 用途
--------------------------------------------------------------------------
EVM_XATTR_HMAC (0x02) [type(1B)] + HMAC-SHA1(20B) HMAC 保护(需密钥)
EVM_IMA_XATTR_DIGSIG (0x03) [signature_v2_hdr] + 签名 EVM 签名(在线验证)
EVM_XATTR_PORTABLE_DIGSIG (0x05) [signature_v2_hdr] + 签名 可移植签名(镜像分发)
ima_digest_data 的序列化格式(xattr 存储格式,integrity.h:51):
struct ima_digest_data {
u8 algo; // enum hash_algo(哈希算法编号)
u8 length; // digest 字节数
union {
struct { u8 unused; u8 type; } sha1; // 传统格式(IMA_XATTR_DIGEST)
struct { u8 type; u8 algo; } ng; // NG 格式(IMA_XATTR_DIGEST_NG)
u8 data[2];
} xattr;
u8 digest[]; // 实际哈希字节(变长)
} __packed;ima_fix_xattr() 在 fix 模式下重写 security.ima 时(ima_appraise.c:88),会根据算法选择合适的序列化格式:
if (algo <= HASH_ALGO_SHA1) {
offset = 1;
iint->ima_hash->xattr.sha1.type = IMA_XATTR_DIGEST;
} else {
offset = 0;
iint->ima_hash->xattr.ng.type = IMA_XATTR_DIGEST_NG;
iint->ima_hash->xattr.ng.algo = algo;
}哈希算法对应关系:
enum hash_algo 值 算法名称 摘要长度
-----------------------------------------
0 MD4 16B
1 MD5 16B
2 SHA1 20B
3 RIPE-MD-160 20B
4 SHA256 32B
5 SHA384 48B
6 SHA512 64B
...(共 HASH_ALGO__LAST 种,位图宽度为 unsigned int,至少 32 位)
static_assert 语句(ima_policy.c:134)确保 unsigned int 足以容纳所有哈希算法的位图:
static_assert(
8 * sizeof(unsigned int) >= HASH_ALGO__LAST,
"The bitfield allowed_algos in ima_rule_entry is too small...");ToMToU(Time-of-Measure to Time-of-Use)是完整性验证系统的经典问题:文件在被度量之后、被使用之前被修改,导致度量值与实际使用内容不一致。
检测读写冲突(ima_rdwr_violation_check(),ima_main.c:137):
// 场景1:文件被写者打开,但已有读者在使用并已被度量
if (mode & FMODE_WRITE) {
if (atomic_read(&inode->i_readcount) && IS_IMA(inode)) {
if (test_and_clear_bit(IMA_MAY_EMIT_TOMTOU, &iint->atomic_flags))
send_tomtou = true; // 触发 ToMToU 违规报告
}
}
// 场景2:文件被读者打开准备度量,但已有写者在修改
if (must_measure)
set_bit(IMA_MAY_EMIT_TOMTOU, &iint->atomic_flags);
if (inode_is_open_for_write(inode) && must_measure) {
if (!test_and_set_bit(IMA_EMITTED_OPENWRITERS, &iint->atomic_flags))
send_writers = true; // 触发 open_writers 违规报告
}mmap 写者检测(mmap_violation_check(),ima_main.c:106):
if ((func == MMAP_CHECK || func == MMAP_CHECK_REQPROT) &&
mapping_writably_mapped(file->f_mapping)) {
rc = -ETXTBSY;
integrity_audit_msg(AUDIT_INTEGRITY_DATA, inode, *pathname,
"mmap_file", "mmapped_writers", rc, 0);
}违规记录:所有违规均通过 ima_add_violation() 记录到度量日志,条目的 "violation" 字段标记为 "ToMToU" 或 "open_writers",对应的 TPM PCR 也会被扩展,使远程验证方能感知到违规事件。
mprotect 攻击阻断(ima_file_mprotect(),ima_main.c:525):文件可以先以读写方式 mmap,然后通过 mprotect(PROT_EXEC) 绕过 MMAP_CHECK。IMA 的此钩子专门检测这种升级操作,若策略要求评估 mmap 执行权限,则直接返回 -EPERM 拒绝。
ima_check_blacklist()(ima_appraise.c:447)在评估前检查文件哈希是否在系统黑名单中:
int ima_check_blacklist(struct ima_iint_cache *iint,
const struct modsig *modsig, int pcr)
{
if (!(iint->flags & IMA_CHECK_BLACKLIST))
return 0;
if (iint->flags & IMA_MODSIG_ALLOWED && modsig) {
ima_get_modsig_digest(modsig, &hash_algo, &digest, &digestsize);
rc = is_binary_blacklisted(digest, digestsize);
} else if (iint->flags & IMA_DIGSIG_REQUIRED && iint->ima_hash)
rc = is_binary_blacklisted(iint->ima_hash->digest,
iint->ima_hash->length);
if ((rc == -EPERM) && (iint->flags & IMA_MEASURE))
process_buffer_measurement(&nop_mnt_idmap, NULL, digest, digestsize,
"blacklisted-hash", NONE,
pcr, NULL, false, NULL, 0);
return rc;
}命中黑名单时,哈希值被记录到度量日志(可供远程审计),并返回 -EPERM 阻止执行。
fsverity 是内核基于 Merkle 树的文件完整性机制。IMA 通过 IMA_VERITY_DIGSIG(类型 0x06)与 fsverity 集成:
security.ima存储对 fsverity descriptor 摘要的签名(v3 格式);calc_file_id_hash()计算ima_file_id结构的哈希,该结构包含 fsverity 使用的哈希类型和算法;- 签名验证针对此间接哈希进行(
ima_appraise.c:363),防止不同类型签名之间的混淆攻击:
case IMA_VERITY_DIGSIG:
sig = (typeof(sig))xattr_value;
if (sig->version != 3) { // 强制要求 v3 格式
*cause = "invalid-signature-version";
*status = INTEGRITY_FAIL;
break;
}
// 对 ima_file_id 结构计算间接哈希
rc = calc_file_id_hash(IMA_VERITY_DIGSIG, iint->ima_hash->algo,
iint->ima_hash->digest,
container_of(&hash.hdr, struct ima_digest_data, hdr));
rc = integrity_digsig_verify(INTEGRITY_KEYRING_IMA,
(const char *)xattr_value,
xattr_len, hash.digest, hash.hdr.length);IMA_VERITY_REQUIRED 与 IMA_DIGSIG_REQUIRED 的组合(ima_appraise.c:293):
if (iint->flags & IMA_DIGSIG_REQUIRED) {
if (iint->flags & IMA_VERITY_REQUIRED)
*cause = "verity-signature-required";
else
*cause = "IMA-signature-required";
*status = INTEGRITY_FAIL;
}/sys/kernel/security/ima/
|-- ascii_runtime_measurements 只读,文本格式度量日志
| 格式:<pcr> <template_hash> <template_name> <fields...>
| 示例:10 abc123 ima-ng sha256:def456 /usr/bin/bash
|
|-- binary_runtime_measurements 只读,二进制格式度量日志
| 用于远程证明工具(如 attest)
|
|-- runtime_measurements_count 只读,度量条目数量
|
|-- violations 只读,违规计数(ToMToU 等)
|
|-- policy 读写,IMA 策略规则(需 CAP_MAC_ADMIN)
| 写入格式示例:
| measure func=FILE_CHECK mask=MAY_READ uid=0 template=ima-ng
| appraise func=MODULE_CHECK appraise_type=imasig
|
+-- boot_aggregate 只读,boot_aggregate 条目内容
/sys/kernel/security/evm 读写,EVM 初始化状态
读取:当前 evm_initialized 值
写入 1: 启用 HMAC 模式(需先加载 .evm 密钥)
写入 2: 启用 X.509 模式
写入 4: 允许元数据写入(EVM_ALLOW_METADATA_WRITES)
写入 0x80000000:设置 EVM_SETUP_COMPLETE(用户态声明初始化完成)
action [condition ...] [option ...]
action 关键字:
measure | dont_measure | appraise | dont_appraise | audit | hash | dont_hash
condition 关键字:
func=<FILE_CHECK|BPRM_CHECK|MMAP_CHECK|MODULE_CHECK|FIRMWARE_CHECK|
KEXEC_KERNEL_CHECK|POLICY_CHECK|CRITICAL_DATA|KEY_CHECK>
mask=<MAY_READ|MAY_WRITE|MAY_EXEC|MAY_APPEND>
fsmagic=<十六进制魔数>
fsuuid=<UUID>
uid=[<|>]<数值>
euid=[<|>]<数值>
gid=[<|>]<数值>
fowner=<UID>
fgroup=<GID>
fsname=<文件系统名称>
obj_type=<SELinux类型>
subj_type=<SELinux类型>
keyrings=<key1|key2|...>
label=<数据标签>
option 关键字:
template=<模板名称>
pcr=<编号>
appraise_type=imasig 要求数字签名(IMA_DIGSIG_REQUIRED)
appraise_type=imasig|modsig 允许内嵌签名
appraise_type=check_blacklist 启用黑名单检查
appraise_algos=sha256,sha512 限制允许的哈希算法
permit_directio 允许 O_DIRECT 访问
| 参数 | 可选值 | 说明 |
|---|---|---|
ima= |
on|off |
在 kdump 内核中控制 IMA 启用(ima_main.c:48) |
ima_hash= |
sha1|sha256|... |
全局哈希算法(ima_main.c:66) |
ima_template= |
ima|ima-ng|ima-sig|... |
度量模板(ima_template.c:104) |
ima_template_fmt= |
格式字符串 | 自定义模板格式(ima_template.c:142) |
ima_policy= |
tcb|appraise_tcb|secure_boot|critical_data|fail_securely |
初始策略集(ima_policy.c:285) |
ima_appraise= |
off|log|fix|enforce |
评估模式(ima_appraise.c:27) |
ima_tcb |
(无值) | 使用 ORIGINAL_TCB 策略(ima_policy.c:279) |
evm= |
fix |
EVM fix 模式(evm_main.c:76) |
1. HMAC 密钥安全性:EVM HMAC 使用的密钥在内核内存中以明文形式保存(evmkey[] 数组,evm_crypto.c:27),任何能读取内核内存的攻击者(如通过内核漏洞)可以提取密钥,进而伪造任意文件的 security.evm。这也是为何 EVM 数字签名模式(EVM_IMA_XATTR_DIGSIG)在安全性上优于 HMAC 模式。
2. HMAC 算法固化:EVM HMAC 固定使用 hmac(sha1)(evm_crypto.c:39),SHA1 在理论上已不再被视为抗碰撞安全。未来迁移到 SHA256 涉及 xattr 格式的不兼容变更,实际上形成了技术债。
3. 文件系统覆盖层限制:在堆叠文件系统(如 overlayfs)上,IMA 需要处理真实 inode 与覆盖层 inode 不一致的情况(ima_main.c:329),增加了复杂性和性能开销。
4. 策略评估开销:每次文件访问都需要线性遍历策略规则链表(通过 ima_match_policy()),对规则数量较多的系统有性能影响,且没有索引优化。
5. xattr 支持限制:IMA/EVM 依赖文件系统的 xattr 支持。不支持 xattr 的文件系统(如 FAT32、某些网络文件系统)无法使用评估功能。IOP_XATTR 标志检查(ima_appraise.c:497)处理这种情况,但只能降级为仅度量模式。
6. EVM 与用户命名空间:EVM HMAC 计算要求 inode 的用户命名空间为 init_user_ns(evm_crypto.c:239),否则返回 -EOPNOTSUPP,这限制了 EVM 在容器环境(非特权 fuse 挂载)中的适用性。
7. 测量日志增长无界:度量日志 ima_measurements 链表在系统运行期间只增不减,在长期运行的系统或度量策略过于宽泛的情况下可能消耗大量内核内存。没有内置的日志轮转或截断机制。
8. IMA_HASH_BITS 的哈希表固定大小:度量日志哈希表 ima_htable 的大小固定为 1024(ima.h:36),在大量文件被度量时可能出现哈希冲突,影响去重性能。
| 文件路径 | 关键内容 |
|---|---|
security/integrity/ima/ima.h |
ima_iint_cache(L188), ima_template_desc(L98), ima_template_entry(L106), ima_queue_entry(L114), IMA 标志位定义(L131-), ima_algo_desc(L53) |
security/integrity/ima/ima_main.c |
process_measurement()(L236), ima_file_mmap()(L485), ima_bprm_check()(L584), ima_check_last_writer()(L183), ima_rdwr_violation_check()(L137), mmap_violation_check()(L106), ima_file_mprotect()(L525) |
security/integrity/ima/ima_policy.c |
ima_rule_entry(L100), 条件标志位定义(L25-), dont_measure_rules(L150), default_measurement_rules(L181), default_appraise_rules(L197), secure_boot_rules(L245), critical_data_rules(L257), policy_setup()(L285) |
security/integrity/ima/ima_template.c |
builtin_templates[](L19), supported_fields[](L36), template_desc_init_fields()(L208), ima_init_template_list()(L259), ima_template_setup()(L104) |
security/integrity/ima/ima_appraise.c |
ima_appraise_measurement()(L481), xattr_verify()(L278), ima_check_blacklist()(L447), ima_get_hash_algo()(L179), ima_fix_xattr()(L88), ima_must_appraise()(L74), ima_update_xattr()(L618) |
security/integrity/evm/evm_main.c |
evm_verify_hmac()(L178), evm_verifyxattr()(L421), evm_protect_xattr()(L497), evm_config_default_xattrnames[](L38), evm_verify_current_integrity()(L440), evm_find_protected_xattrs()(L131) |
security/integrity/evm/evm_crypto.c |
evm_calc_hmac_or_hash()(L221), hmac_add_misc()(L143), evm_set_key()(L53), init_desc()(L75), evm_calc_hmac()(L316), evm_calc_hash()(L325) |
security/integrity/evm/evm.h |
evm_iint_cache(L39), EVM 初始化标志(L20-), evm_digest(L64), xattr_list(L29), EVM_KEY_MASK(L25) |
security/integrity/integrity.h |
evm_ima_xattr_data(L33), evm_xattr(L44), ima_digest_data(L51), ima_max_digest_data(L77), signature_v2_hdr(L92), ima_file_id(L109), 密钥环索引(L120), integrity_digsig_verify()声明(L132) |
ima_add_template_entry() 是度量日志追加和 TPM PCR 扩展的核心执行函数,位于 security/integrity/ima/ima_queue.c:166。
// security/integrity/ima/ima_queue.c:166
int ima_add_template_entry(struct ima_template_entry *entry, int violation,
const char *op, struct inode *inode,
const unsigned char *filename)整个操作在 ima_extend_list_mutex(静态互斥锁)保护下执行,确保:
- 度量列表的追加与 TPM PCR 扩展的原子性;
- 防止并发写入产生竞态条件,导致 PCR 计算与日志不一致。
流程如下:
ima_add_template_entry()
|
+-- mutex_lock(&ima_extend_list_mutex)
|
+-- 检查 ima_measurements_suspended(系统关机准备阶段暂停度量)
| -> 若暂停,返回 -ENODEV
|
+-- 去重检查(CONFIG_IMA_DISABLE_HTABLE 控制是否跳过):
| ima_lookup_digest_entry(digest, pcr)
| -> 若已存在相同 (摘要, PCR) 组合,返回 -EEXIST
|
+-- ima_add_digest_entry(entry, update_htable=true)
| -> kmalloc_obj(*qe) 分配 ima_queue_entry
| -> list_add_tail_rcu(&qe->later, &ima_measurements)
| -> hlist_add_head_rcu(&qe->hnext, &ima_htable.queue[key])
| -> atomic_long_inc(&ima_htable.len)
| -> 更新 binary_runtime_size(用于 kexec 传递)
|
+-- ima_pcr_extend(digests_arg, entry->pcr)
| -> tpm_pcr_extend(ima_tpm_chip, pcr, digests_arg)
| -> violation 时使用全零 digests(使 PCR 值无效)
|
+-- mutex_unlock(&ima_extend_list_mutex)
|
+-- integrity_audit_msg(AUDIT_INTEGRITY_PCR, ...)
| 记录度量操作到审计日志
当参数 violation=1 时(如 ToMToU 违规),系统不扩展真实度量值,而是用全零数组 digests(在 ima_queue.c:26 预分配)扩展 PCR,使 PCR 值"无效化"。这是有意为之的设计:
正常度量: PCR_new = Hash(PCR_old || template_hash)
违规度量: PCR_new = Hash(PCR_old || 0x000...000)
通过这种方式,任何参与远程证明的验证方在重放度量日志时,会因 PCR 期望值不匹配而得知系统曾发生完整性违规事件。违规条目仍会被添加到度量列表,携带 violation 字段说明违规类型。
度量列表和哈希表均使用 RCU(Read-Copy-Update)机制管理:
写操作(追加): 持有 ima_extend_list_mutex,使用 list_add_tail_rcu()
读操作(遍历): 调用 rcu_read_lock(),使用 list_for_each_entry_rcu()
ima_fs.c 中的 ima_measurements_start() 通过 seq_file 接口实现对度量日志的并发安全遍历:
// security/integrity/ima/ima_fs.c:77
static void *ima_measurements_start(struct seq_file *m, loff_t *pos)
{
loff_t l = *pos;
struct ima_queue_entry *qe;
/* we need a lock since we walk the list, not running number */
rcu_read_lock();
list_for_each_entry_rcu(qe, &ima_measurements, later) {
if (!l--) {
rcu_read_unlock();
return qe;
}
}
rcu_read_unlock();
return NULL;
}二进制日志中每条记录的序列化布局(get_binary_runtime_size(),ima_queue.c:81):
+------------------+
| pcr (4B) | TPM PCR 编号,u32
+------------------+
| digest (20B) | 模板摘要(SHA1,固定 TPM_DIGEST_SIZE)
+------------------+
| template_name_sz | 模板名称长度,int
+------------------+
| template_name[] | 模板名称字符串(可变长)
+------------------+
| template_data_sz | template_data 总长度,u32
+------------------+
| template_data[] | 各字段序列化数据(可变长)
+------------------+
ima_canonical_fmt 标志(ima_fs.c:29)控制字节序:大端系统默认启用规范格式(强制小端存储),使二进制日志在不同架构间具备可移植性,这对跨平台的远程证明至关重要。
IMA 的哈希计算支持两种不同的加密 API 路径,以适应不同文件大小场景的性能需求。
security/integrity/ima/ima_crypto.c 实现了双路径哈希计算:
// security/integrity/ima/ima_crypto.c:26-27
static unsigned long ima_ahash_minsize; // ahash 启用的最小文件大小阈值
static unsigned int ima_bufsize = PAGE_SIZE; // 缓冲区大小(默认一页)- shash(同步):
crypto_shash+crypto_shash_update(),适用于小文件。实现简单,无需 scatter/gather 链表; - ahash(异步):
crypto_ahash+ scatter/gather 列表,适用于大文件(ima_ahash_minsize控制阈值,默认 0 表示不使用)。通过减少内存拷贝提升大文件哈希性能。
选择逻辑:
ima_calc_file_hash()
|
+-- 若 ima_ahash_minsize > 0 且文件大小 >= ima_ahash_minsize:
| ima_calc_file_ahash() -> 使用 ahash(scatter/gather)
|
+-- 否则:
ima_calc_file_shash() -> 使用 shash(直接 update)
现代 TPM 2.0 支持多个 PCR bank(不同哈希算法),IMA 为每个 bank 都计算对应的模板摘要:
// security/integrity/ima/ima.h:63-64
extern int ima_sha1_idx __ro_after_init;
extern int ima_hash_algo_idx __ro_after_init;
extern struct ima_algo_desc *ima_algo_array __ro_after_init;ima_algo_array 在初始化时构建,包含所有需要为 TPM bank 计算摘要的算法列表。每个 ima_template_entry 的 digests[] 数组对应每个 bank 的摘要值,在 PCR 扩展时分别使用对应 bank 的算法摘要。
ima_add_boot_aggregate()(ima_init.c:42)是 IMA 初始化时的第一个重要操作:
// security/integrity/ima/ima_init.c:42
static int __init ima_add_boot_aggregate(void)
{
// 若 TPM 存在,读取 PCR0-7 计算 boot_aggregate
if (ima_tpm_chip) {
result = ima_calc_boot_aggregate(hash_hdr);
// 失败则使用违规条目(PCR 值全ff)
}
// ...
result = ima_alloc_init_template(&event_data, &entry, NULL);
result = ima_store_template(entry, violation, NULL,
boot_aggregate_name,
CONFIG_IMA_MEASURE_PCR_IDX);
}TPM 2.0 的哈希算法选择:TPM 2.0 可能禁用了 SHA1 bank(取决于固件配置)。IMA 的偏好顺序为:
- 优先使用
ima_hash_algo(系统配置的 IMA 哈希算法)对应的 PCR bank; - 若不可用,回退到 TPM 2.0 要求的 SHA256 bank;
- 若仍不可用,使用 TPM 1.2 要求的 SHA1 bank;
- 若 TPM 芯片不存在,hash 值全零,并添加违规条目(使 PCR 无效化,表明不存在硬件信任根)。
IMA 策略文件中的文本规则通过 ima_parse_rule()(ima_policy.c 中未展示行号)转换为 ima_rule_entry 结构体。
策略解析借助内核的 match_token() 机制,将规则字符串按 token 逐个匹配:
// 策略 token 枚举(ima_policy.c 中的 policy_opt 枚举)
enum policy_opt {
Opt_measure, Opt_dont_measure,
Opt_appraise, Opt_dont_appraise,
Opt_audit,
Opt_func, Opt_mask, Opt_fsmagic,
Opt_uid, Opt_euid, Opt_gid, Opt_egid,
Opt_fowner, Opt_fgroup,
Opt_fsuuid, Opt_fsname,
Opt_obj_user, Opt_obj_role, Opt_obj_type,
Opt_subj_user, Opt_subj_role, Opt_subj_type,
Opt_pcr, Opt_template,
Opt_appraise_type, Opt_appraise_algos,
Opt_permit_directio,
Opt_keyrings, Opt_label,
// ...
};LSM 条件(如 obj_type=、subj_type=)通过 security_filter_rule_init() 委托给 LSM(SELinux/AppArmor 等)进行规则预编译,生成 ima_rule_entry.lsm[].rule 指针,在实际匹配时调用 security_filter_rule_match()。
策略文件通过写入 /sys/kernel/security/ima/policy 加载(ima_fs.c),该接口使用 ima_write_mutex 进行序列化。策略一旦加载就不可撤销(只允许添加,不允许删除),这是安全设计的核心约束:
策略加载时序:
用户态写入规则文本
|
v
ima_parse_rule() 解析并构建 ima_rule_entry
|
v
list_add_tail() 追加到 ima_temp_rules
|
写入结束后:
v
ima_update_policy() 将 temp_rules 移交给 policy_rules
|
v
rcu_assign_pointer(ima_rules, &ima_policy_rules)
(原子切换,不影响正在进行的策略评估)
ima_match_policy()(ima_policy.c)遍历活跃规则链表,对每条规则调用 ima_match_rules() 判断是否命中:
ima_match_rules() 匹配顺序:
1. IMA_FUNC: func == rule->func
2. IMA_MASK: mask & rule->mask(访问掩码按位与)
3. IMA_FSMAGIC: inode->i_sb->s_magic == rule->fsmagic
4. IMA_FSUUID: uuid_equal(&inode->i_sb->s_uuid, &rule->fsuuid)
5. IMA_UID: rule->uid_op(cred_uid, rule->uid)
6. IMA_GID: rule->gid_op(cred_gid, rule->gid)
7. IMA_EUID: rule->uid_op(cred_euid, rule->uid)
8. IMA_FOWNER: rule->fowner_op(vfsuid, rule->fowner)
9. IMA_FGROUP: rule->fgroup_op(vfsgid, rule->fgroup)
10. LSM 标签: security_filter_rule_match() 逐个验证
11. IMA_FSNAME: strcmp(inode->i_sb->s_type->name, rule->fsname)
12. IMA_KEYRINGS: ima_match_keyring(eventname, rule->keyrings)
13. IMA_LABEL: ima_match_label(rule->label, data_label)
以上所有条件均满足 -> 规则命中
DONT_MEASURE / DONT_APPRAISE 规则的优先级处理:ima_get_action() 在遇到第一个匹配的规则后不立即停止,而是继续遍历以收集所有匹配规则的 action 位集合,但若遇到 DONT_* 规则会清除对应的 action 位,实现按优先级的豁免。
内核配置选项直接控制默认评估规则的生成(ima_policy.c:226):
CONFIG_IMA_APPRAISE_REQUIRE_MODULE_SIGS -> MODULE_CHECK 要求 IMA_DIGSIG_REQUIRED
CONFIG_IMA_APPRAISE_REQUIRE_FIRMWARE_SIGS -> FIRMWARE_CHECK 要求签名
CONFIG_IMA_APPRAISE_REQUIRE_KEXEC_SIGS -> KEXEC_KERNEL_CHECK 要求签名
CONFIG_IMA_APPRAISE_REQUIRE_POLICY_SIGS -> POLICY_CHECK 要求签名
CONFIG_IMA_APPRAISE_SIGNED_INIT -> root 所有的文件必须有签名
这些配置项在编译时固化到 build_appraise_rules[] 数组中,用户无法通过运行时策略覆盖这些强制要求。
内核模块除了可以使用 security.ima xattr 外,还支持将 PKCS#7 签名直接附加在模块文件末尾(即"内嵌签名",module signature)。IMA 通过 ima_modsig.c 实现对此类签名的读取和验证。
// security/integrity/ima/ima_modsig.c:18
struct modsig {
struct pkcs7_message *pkcs7_msg; // 解析后的 PKCS#7 消息
enum hash_algo hash_algo; // 签名使用的哈希算法
// 'd-modsig' 模板字段的内容(内嵌签名的文件摘要)
const u8 *digest;
u32 digest_size;
// 'modsig' 模板字段的内容(原始 PKCS#7 数据)
int raw_pkcs7_len;
u8 raw_pkcs7[] __counted_by(raw_pkcs7_len);
};// security/integrity/ima/ima_modsig.c:40
int ima_read_modsig(enum ima_hooks func, const void *buf, loff_t buf_len,
struct modsig **modsig)读取流程:
- 检查文件末尾是否存在
MODULE_SIG_STRING标记("~Module signature appended~\n"); - 解析末尾的
struct module_signature头部,获取签名长度; - 调用
pkcs7_parse_message()解析 PKCS#7 包; - 通过
pkcs7_get_digest()从 PKCS#7 消息中提取文件哈希(作为d-modsig字段); - 整个原始 PKCS#7 数据作为
modsig模板字段。
IMA_MODSIG_ALLOWED 标志(ima.h:148)允许策略规则在 xattr 签名不可用时退而使用内嵌签名:
验证优先级(当同时存在 xattr 和 modsig 时):
1. xattr 类型为 EVM_IMA_XATTR_DIGSIG -> 使用 xattr 签名
2. xattr 缺失且 IMA_MODSIG_ALLOWED -> 使用内嵌 modsig
3. 两者均缺失且 IMA_DIGSIG_REQUIRED -> 返回 INTEGRITY_FAIL
在度量日志模板 ima-modsig 中,两种签名信息可以同时记录:
ima-modsig 模板字段布局:
d-ng : 文件内容哈希(sha256:abcdef...)
n-ng : 文件名(/lib/modules/.../foo.ko)
sig : security.ima xattr 内容(若存在)
d-modsig: PKCS#7 消息中的文件摘要
modsig : 原始 PKCS#7 数据(用于外部验证)
evm_protected_xattr_common()(evm_main.c:299)实现了 xattr 名称与保护列表的双重匹配:
// 匹配方式1:完整名称匹配
// xattr->name = "security.selinux",req_xattr_name = "security.selinux" -> 匹配
// 匹配方式2:去掉 "security." 前缀后的短名称匹配
// xattr->name = "security.selinux",req_xattr_name = "selinux" -> 也匹配
if (strncmp(req_xattr_name,
xattr->name + XATTR_SECURITY_PREFIX_LEN,
strlen(req_xattr_name)) == 0)
found = 1;这种设计允许通过短名称(如 "ima" 而非 "security.ima")调用 EVM,提高了 API 的灵活性。
evm_read_protected_xattrs()(evm_main.c:351)提供了读取所有受保护 xattr 的值、名称和长度的通用接口,供 evm-sig 模板字段(xattrnames, xattrlengths, xattrvalues)使用:
// 类型参数:
// 'n' -> 返回 xattr 名称,以 '|' 分隔
// 'l' -> 返回各 xattr 的 u32 长度列表
// 'v' -> 返回各 xattr 的原始字节值evm-sig 模板将所有受保护 xattr 的完整内容记录到度量日志,使远程验证方能在不访问实际文件系统的情况下重新验证 EVM 哈希。
部分文件系统(如 cifs、nfs 的某些版本)不支持 EVM HMAC,内核通过超级块标志位 SB_I_EVM_HMAC_UNSUPPORTED 标记此类文件系统:
// security/integrity/evm/evm_main.c:154
static int is_unsupported_hmac_fs(struct dentry *dentry)
{
struct inode *inode = d_backing_inode(dentry);
if (inode->i_sb->s_iflags & SB_I_EVM_HMAC_UNSUPPORTED) {
pr_info_once("%s not supported\n", inode->i_sb->s_type->name);
return 1;
}
return 0;
}当文件系统不支持 HMAC 且 EVM_INIT_X509 未启用时,evm_verify_hmac() 对不支持的文件系统返回 INTEGRITY_UNKNOWN(而非 FAIL),允许系统继续运行(evm_main.c:199-201)。
任意安全 xattr 的修改路径:
系统调用 setxattr(2)
|
v
vfs_setxattr()
|
v
security_inode_setxattr() [LSM 钩子]
|
v
evm_inode_setxattr() [evm_main.c]
|
+-- evm_protect_xattr(dentry, xattr_name, value, size)
| |
| +-- 检查是否受保护
| +-- 若为 EVM_IMMUTABLE_DIGSIG 类型 -> 返回 -EPERM
| +-- 检查 EVM 当前状态是否允许修改
|
v
若通过,实际写入 xattr
|
v
security_inode_post_setxattr() [LSM 后钩子]
|
v
evm_inode_post_setxattr() [evm_main.c]
|
v
evm_update_evmxattr() -> 重新计算 HMAC,更新 security.evm
这个双钩子(pre + post)设计保证了 security.evm 在每次受保护 xattr 修改后都能同步更新,维持 EVM 保护的完整性。
在 kexec -l 加载新内核再 kexec -e 执行时,系统会切换到新内核,旧内核的所有内存数据(包括 IMA 度量日志)理论上应当丢失。然而,远程证明要求度量日志在整个运行历史中保持完整,包括跨越 kexec 边界的历史。
CONFIG_IMA_KEXEC 选项启用跨 kexec 度量日志传递机制,将度量日志序列化到一个预分配的内存段,由 kexec 传递给新内核。
// security/integrity/ima/ima_kexec.c:22-27
static struct seq_file ima_kexec_file; // 用于序列化度量日志的 seq_file
static size_t kexec_segment_size; // kexec 段大小
static void *ima_kexec_buffer; // 内核内存中的缓冲区kexec load 时(ima_alloc_kexec_file_buf()):
- 分配大小为
kexec_segment_size的 vmalloc 缓冲区; - 预留
sizeof(struct ima_kexec_hdr)作为头部空间; - 触发关键数据度量事件
kexec_load(记录当前度量日志大小等元数据)。
kexec execute 时:
ima_update_kexec_buffer()将度量日志序列化写入缓冲区;- 头部写入
ima_kexec_hdr(版本、缓冲区大小、条目数量); - 缓冲区地址通过 kexec 的
KEXEC_SEGMENT_LOAD传递给新内核。
新内核启动时(ima_load_kexec_buffer(),ima_init.c),通过 arch_ima_initmem_init() 找到 kexec 传来的度量日志缓冲区,调用 ima_restore_measurement_list() 将旧日志条目逐一恢复到新内核的 ima_measurements 链表。
日志传递数据结构:
struct ima_kexec_hdr {
u16 version; // 2(当前版本)
u16 _reserved0;
u32 _reserved1;
u64 buffer_size; // 包含头部的总缓冲区大小
u64 count; // 度量条目数量
};
重要约束:
- 恢复的条目使用
ima_restore_measurement_entry(),该函数调用ima_add_digest_entry(entry, 0),第二个参数update_htable=0,表示不更新哈希表(因为历史条目不需要去重,也不需要 TPM 扩展); - 日志传递期间不进行 PCR 扩展(新内核的 PCR 从 0 开始),但新内核会再次度量 boot_aggregate,并记录
kexec_cmdline等 kexec 相关信息。
ima_post_key_create_or_update()(ima_asymmetric_keys.c:29)是 IMA 对内核密钥子系统的度量钩子,在每次非对称密钥创建或更新时被调用。
void ima_post_key_create_or_update(struct key *keyring, struct key *key,
const void *payload, size_t payload_len,
unsigned long flags, bool create)此钩子仅对非对称密钥(key->type == &key_type_asymmetric)生效,度量密钥本身的 payload(DER 编码的公钥数据)。度量采用 process_buffer_measurement() 而非文件度量路径,模板字段 buf 存储密钥的二进制数据。
这一机制允许远程验证方追踪系统在运行期间加载了哪些公钥,对于密钥滥用检测和合规审计至关重要。
在系统初始化早期,内核密钥环可能已经加载了密钥,但 IMA 的密钥环(.ima 等)尚未就绪。为此,ima_queue_keys.c 实现了一个临时队列:
// 初始化阶段的密钥度量流程:
ima_post_key_create_or_update()
|
+-- ima_should_queue_key() -> 若 .ima 密钥环未就绪,返回 true
|
+-- ima_queue_key() -> 将密钥 payload 加入待处理队列
|
... 系统继续初始化 ...
|
+-- ima_process_queued_keys() -> .ima 密钥环就绪后批量处理
-> 对队列中的每个密钥调用 process_buffer_measurement()KEY_CHECK 功能的策略规则示例:
measure func=KEY_CHECK keyrings=.ima|.builtin_trusted_keys template=ima-buf
此规则会度量所有加入 .ima 和 .builtin_trusted_keys 密钥环的密钥,将其 DER 编码记录到度量日志。
远程证明(Remote Attestation)允许外部验证方(Verifier)确信被验证方(Attester,即运行中的系统)处于已知的、可信的状态。IMA/TPM 实现的远程证明流程如下:
被验证方(Attester) 验证方(Verifier)
| |
|<-- 挑战(nonce)-------------------|
| |
+-- TPM_Quote(nonce, PCR[10]) |
| -> TPM 硬件签名的 PCR 快照 |
| |
+-- IMA 度量日志(binary) |
| /sys/kernel/security/ima/ |
| binary_runtime_measurements |
| |
|---- PCR Quote + 度量日志 ---------->|
| |
| +-- 1. 验证 TPM Quote 签名(AIK证书链)
| +-- 2. 重放度量日志:
| | 对每条日志条目计算模板哈希
| | 按顺序扩展到本地 PCR10 副本
| +-- 3. 比对重放结果与 Quote 中的 PCR10
| | 若不一致 -> 日志被篡改
| +-- 4. 逐条验证度量日志条目:
| 查找已知可信文件哈希白名单
| 若发现陌生哈希 -> 报警
TPM PCR 的核心安全属性:
+------------------------------------+
| TPM PCR 特性 |
+------------------------------------+
| 1. 只能扩展(extend),不能直接写入 |
| 2. 扩展是加密绑定的: |
| PCR[n] = Hash(PCR[n] || new_val) |
| 3. 系统重启前不会重置 |
| (PCR 初始值为全零或全0x3a) |
| 4. TPM_Quote 用 AIK 私钥对 |
| 选定 PCRs 值进行签名 |
+------------------------------------+
这意味着:即使攻击者能修改内存中的 IMA 度量日志,也无法修改 TPM 硬件中已经写入的 PCR 值。远程证明通过比对日志重放结果与 PCR 快照来检测日志篡改。
IMA 度量日志的设计保证了以下性质:
仅追加性(Append-only):ima_measurements 链表只允许追加,不允许删除或修改(ima_queue.c:28:LIST_HEAD(ima_measurements),只通过 list_add_tail_rcu() 追加)。
TPM 绑定性:每条条目追加到日志的同时,其摘要被扩展到 PCR,因此日志顺序与 PCR 历史严格对应。攻击者无法在不改变 PCR 值的情况下重排或删除日志条目。
违规可见性:ToMToU 违规、黑名单命中等事件均被记录为特殊条目,且会向 PCR 扩展全零值(使 PCR 进入"被污染"状态),远程验证方能从 PCR 值判断是否发生过违规。
与内核 IMA 配套的用户态工具:
工具 功能
-----------------------------------------------------------------
tpm2-tools TPM 2.0 命令行工具(tpm2_quote, tpm2_pcrread 等)
ima-evm-utils IMA/EVM xattr 管理(evmctl 命令)
- evmctl ima_hash 生成并写入 security.ima 哈希
- evmctl sign 生成并写入数字签名
- evmctl verify 验证签名
- evmctl ima_fix 修复度量标签
keylime 完整的远程证明框架(包含 agent/verifier/registrar)
attest-tools 基础远程证明工具
evmctl 签名流程:
# 1. 生成 RSA 私钥
openssl genrsa -out ima-signing-key.pem 2048
# 2. 生成 X.509 证书(公钥)
openssl req -new -x509 -key ima-signing-key.pem -out ima-cert.pem
# 3. 将公钥证书添加到 .ima 密钥环
evmctl import ima-cert.pem /proc/keys
# 4. 为文件计算并写入 IMA 签名
evmctl sign --imasig --key ima-signing-key.pem /usr/bin/bash
# 5. 为系统所有文件批量签名(需要 CAP_SYS_ADMIN)
find /usr/bin -type f | xargs -I{} evmctl sign --imasig --key ima-signing-key.pem {}IPE 是 Microsoft 贡献的新一代完整性策略执行框架(security/ipe/),解决了 IMA 的几个局限性:
| 特性 | IMA | IPE |
|---|---|---|
| 策略语言 | 基于属性的规则 | 更灵活的属性+操作组合 |
| dm-verity 支持 | 通过 fsmagic 间接支持 | 原生 dm-verity 属性 |
| fsverity 支持 | 通过 IMA_VERITY_REQUIRED | 原生 fsverity 属性 |
| initramfs 感知 | 无 | 内置 initramfs 检测 |
| 策略签名 | 通过 POLICY_CHECK 规则 | PKCS#7 签名策略 |
| LSM blob 使用 | 仅 inode | superblock + bdev + inode |
IPE 注册的钩子(security/ipe/ipe.c:48):
static struct security_hook_list ipe_hooks[] __ro_after_init = {
LSM_HOOK_INIT(bprm_check_security, ipe_bprm_check_security),
LSM_HOOK_INIT(bprm_creds_for_exec, ipe_bprm_creds_for_exec),
LSM_HOOK_INIT(mmap_file, ipe_mmap_file),
LSM_HOOK_INIT(file_mprotect, ipe_file_mprotect),
LSM_HOOK_INIT(kernel_read_file, ipe_kernel_read_file),
LSM_HOOK_INIT(kernel_load_data, ipe_kernel_load_data),
LSM_HOOK_INIT(initramfs_populated, ipe_unpack_initramfs),
// dm-verity 支持
LSM_HOOK_INIT(bdev_free_security, ipe_bdev_free_security),
LSM_HOOK_INIT(bdev_setintegrity, ipe_bdev_setintegrity),
// fsverity 内置签名支持
LSM_HOOK_INIT(inode_setintegrity, ipe_inode_setintegrity),
};ipe_eval_ctx(security/ipe/eval.h)集中描述一次策略评估的上下文:
struct ipe_eval_ctx {
enum ipe_op_type op; // 操作类型(执行/mmap/内核读取等)
enum ipe_hook_type hook; // 触发钩子类型
const struct inode *ino; // 目标 inode
const struct file *file; // 目标文件(可空)
bool initramfs; // 是否来自 initramfs
// dm-verity 相关(若启用 CONFIG_IPE_PROP_DM_VERITY)
const struct ipe_bdev *ipe_bdev;
// fsverity 相关(若启用 CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG)
const struct ipe_inode *ipe_inode;
};IPE 策略采用文本格式,每行表示一条规则:
policy_name=<策略名称>
policy_version=<主版本>.<次版本>.<修订>
DEFAULT action=<ALLOW|DENY>
op=<操作> [属性=值 ...] action=<ALLOW|DENY>
支持的操作类型:
EXECUTE -> 文件执行(bprm_check)
FIRMWARE -> 固件加载
KMODULE -> 内核模块加载
KEXEC_IMAGE -> kexec 内核加载
KEXEC_INITRAMFS -> kexec initramfs 加载
POLICY -> IPE 策略文件加载
X_ONLY -> 仅执行权限文件
支持的属性(property):
boot_verified=<TRUE|FALSE> # 来自 dm-verity 验证的块设备
dmverity_signature=<TRUE|FALSE> # 有 dm-verity 签名
dmverity_roothash=<哈希值> # dm-verity 根哈希精确匹配
fsverity_signature=<TRUE|FALSE> # 有 fsverity 内置签名
fsverity_digest=<哈希算法:哈希值> # fsverity 摘要精确匹配
示例策略:
policy_name=secure_production
policy_version=1.0.0
DEFAULT action=DENY
op=EXECUTE boot_verified=TRUE action=ALLOW
op=EXECUTE fsverity_signature=TRUE action=ALLOW
op=KMODULE dmverity_roothash=sha256:abc123def456... action=ALLOW
op=FIRMWARE action=ALLOW
IPE 充分利用 LSM blob 机制,为三个层次的内核对象维护状态:
ipe_superblock(超级块级别):记录文件系统是否来自 initramfs,通过 initramfs_populated 钩子设置。
ipe_bdev(块设备级别):记录块设备的 dm-verity 完整性信息(根哈希、验证状态),通过 bdev_setintegrity 钩子由 dm-verity 驱动设置。
ipe_inode(inode 级别):记录文件是否持有 fsverity 内置签名,通过 inode_setintegrity 钩子设置。
dm-verity 设备挂载流程:
dm_table_add_target() -> dm-verity 初始化
|
v
verity_postresume() -> 验证根哈希
|
v
security_bdev_setintegrity(bdev, BLK_INTEGRITY_TYPE_DM_VERITY, ...)
|
v
ipe_bdev_setintegrity() -> 填充 ipe_bdev 结构体
|
之后该块设备上的文件在 IPE 评估时:
v
build_ipe_bdev_ctx() -> ctx->ipe_bdev 指向该设备的完整性信息
|
v
ipe_evaluate_event() 可基于 dmverity_roothash 等属性做决策
IMA 通过 integrity_audit_msg()(security/integrity/integrity.h 声明)发送审计消息。使用的审计事件类型:
AUDIT_INTEGRITY_DATA (1409) -> 文件哈希、签名验证结果
AUDIT_INTEGRITY_METADATA (1410)-> 文件元数据完整性事件
AUDIT_INTEGRITY_STATUS (1411) -> 完整性状态变更
AUDIT_INTEGRITY_HASH (1412) -> 哈希值记录
AUDIT_INTEGRITY_PCR (1413) -> PCR 扩展操作
AUDIT_INTEGRITY_RULE (1414) -> 策略规则加载
AUDIT_INTEGRITY_EVM_XATTR (1415) -> EVM xattr 相关事件
AUDIT_INTEGRITY_POLICY_RULE (1416) -> 策略规则评估
典型的 IMA 审计消息格式(/var/log/audit/audit.log):
# 文件评估失败(签名验证失败)
type=INTEGRITY_DATA msg=audit(1234567890.123:456):
pid=1234 uid=0 auid=0 ses=1
op="appraise_data"
cause="invalid-signature"
comm="bash"
name="/usr/bin/bash"
dev="sda1"
ino=654321
res=0
# PCR 扩展成功
type=INTEGRITY_PCR msg=audit(1234567890.456:789):
pid=0 uid=0 auid=4294967295 ses=4294967295
op="add_template_entry"
cause="hash_added"
template="ima-ng"
pcr=10
res=1
# ToMToU 违规
type=INTEGRITY_DATA msg=audit(1234567890.789:012):
op="invalid_pcr"
cause="ToMToU"
name="/tmp/suspicious_file"
res=0
ima_audit_measurement()(ima_main.c 中声明,实际在 ima_api.c 实现)将度量结果格式化为审计消息:
ima_audit_measurement()
|
+-- 检查 iint->flags & IMA_AUDITED(避免重复审计)
|
+-- 构造哈希值的十六进制字符串表示
|
+-- integrity_audit_msg(
| AUDIT_INTEGRITY_RULE,
| inode,
| pathname,
| "audit_file", <- 操作名
| hash_string, <- 哈希值字符串
| 0, 1 <- 返回码,成功标志
| )
|
+-- 设置 IMA_AUDITED 标志(缓存,避免重复)
标准 EVM HMAC 包含主机特定信息(inode 号、生成号),因此 HMAC 值不可跨主机使用。可移植签名(EVM_XATTR_PORTABLE_DIGSIG,类型 0x05)解决了这个问题,允许在镜像打包阶段生成签名,在任意目标主机上验证。
可移植签名的 HMAC 输入不包含 ino、generation 和 s_uuid(evm_crypto.c 中 hmac_add_misc() 判断 type != EVM_XATTR_PORTABLE_DIGSIG),仅包含:
可移植签名覆盖内容:
+ security.selinux 值
+ security.ima 值
+ security.capability 值
+ ... 其他受保护 xattr ...
+ uid(来自 init_user_ns)
+ gid
+ mode
(不包含 ino, generation, s_uuid)
此外,可移植签名必须包含 security.ima(在 evm_calc_hmac_or_hash() 中检查,ima xattr 不存在时返回错误),这确保了文件内容的完整性也被纳入保护范围。
镜像打包阶段(构建系统):
1. 为每个文件生成 security.ima 哈希或签名
2. 用 EVM 私钥对(xattr集合 + 元数据)签名
3. 将签名写入 security.evm(类型 EVM_XATTR_PORTABLE_DIGSIG)
4. 打包成镜像/RPM/DEB
部署到目标主机:
1. 文件解包,xattr 随文件写入目标文件系统
2. inode 号和生成号可能与构建时完全不同
3. 目标主机 EVM 验证时忽略 ino/gen,验证通过
运行时优化(evm_verify_hmac,第268行):
若文件系统可写且签名验证通过:
-> evm_update_evmxattr()
-> 将 security.evm 从可移植签名升级为 HMAC
-> 后续验证使用更快的 HMAC 路径
当 security.evm 为 EVM_XATTR_PORTABLE_DIGSIG 类型时,iint->flags |= EVM_IMMUTABLE_DIGSIG(evm_main.c:265)。该标志通过 evm_protect_xattr() 阻止对任何受保护 xattr 的修改,实现"一旦签名,不得修改"的安全语义,这是只读系统(只读根文件系统、容器不可变镜像)的理想方案。
ima_iint_cache 中的状态标志(IMA_MEASURED、IMA_APPRAISED 等)是 IMA 最重要的性能优化:
// process_measurement() 中的快速路径
// security/integrity/ima/ima_main.c
if (iint->flags & action & IMA_DO_MASK)
action &= ~(iint->flags & IMA_DO_MASK); // 清除已完成的操作
// 若所有操作都已在缓存中完成,则跳过整个度量/评估流程
if (!action)
goto out_locked;对于频繁访问的文件(如 /usr/bin/bash),第一次访问后结果被缓存,后续访问只需检查缓存标志,避免了重复的哈希计算和 TPM 操作。
ima_ahash_minsize 参数(通过 /sys/module/ima/parameters/ahash_minsize 可调)控制使用异步哈希的文件大小阈值。对于大文件:
- shash:需要将文件内容逐块读入内核缓冲区(默认
ima_bufsize = PAGE_SIZE),频繁地 copy+update; - ahash:使用 scatter/gather 列表,避免内核缓冲区中间拷贝,直接操作页缓存页面。
evm_verify_hmac() 在成功验证数字签名后,若文件系统可写,会将 security.evm 从签名格式升级为 HMAC 格式(evm_main.c:267-274)。数字签名验证需要非对称加密运算(RSA/ECDSA),而 HMAC 验证仅需 SHA1 哈希,速度快数个数量级。这种"验证一次,升级缓存"的策略对于大量文件的系统启动时间有显著优化效果。
策略规则链表使用 RCU 保护,允许多个 CPU 同时进行只读策略匹配而不需要获取锁:
// ima_policy.c 中的策略读取路径
rcu_read_lock();
ima_rules = rcu_dereference(ima_rules_ptr);
list_for_each_entry_rcu(entry, ima_rules, list) {
// 并发安全的策略匹配
}
rcu_read_unlock();策略更新(写入 policy 文件)持有 ima_write_mutex,并通过 rcu_assign_pointer() 原子切换活跃策略指针。
威胁 IMA 保护 EVM 保护
----------------------------------------------------------------
离线篡改文件内容 security.ima 比对 N/A
离线篡改 security.ima N/A security.evm HMAC
离线篡改安全 xattr N/A security.evm HMAC
剪切粘贴 security.evm N/A ino+gen+uuid 绑定
运行时 ToMToU 攻击 违规记录+PCR N/A
mprotect 绕过 MMAP_CHECK ima_file_mprotect N/A
未签名二进制执行 IMA_DIGSIG_REQUIRED N/A
黑名单哈希执行 is_binary_blacklisted N/A
以下情形超出 IMA/EVM 的保护范围:
-
内核漏洞:攻击者利用内核漏洞直接读取内核内存,可提取
evmkey和 IMA 哈希,伪造任意 xattr 内容; -
TPM 绕过:TPM 仅记录历史,不阻止文件访问。若
ima_appraise=off,度量继续但不拒绝访问; -
物理攻击:TPM PCR 仅在系统运行期间保持,重启后重置。攻击者可通过"冷启动"绕过 TPM 的证明能力;
-
TOCTOU 窗口:
ima_collect_measurement()和ima_store_measurement()之间存在极短的窗口期,理论上存在竞态,但 IMA 的 ToMToU 检测机制可以感知并记录此类违规; -
文件系统不支持 xattr:若文件系统不支持 xattr,
security.ima和security.evm无法存储,IMA 评估退化为仅度量; -
密钥环污染:若攻击者能向
.ima密钥环添加恶意公钥,则可以为任意文件生成"合法"签名。内核通过restrict_link_to_ima限制密钥添加来源(必须由已知的构建密钥或次级密钥签名)。
IMA 签名 v3(version=3 的 EVM_IMA_XATTR_DIGSIG)通过 ima_file_id 结构防止类型混淆攻击(type confusion):
v2 签名(version=2)的被签内容:
digest = H(文件内容)
v3 签名(version=3)的被签内容:
intermediate_hash = H(ima_file_id)
其中 ima_file_id = {
hash_type = IMA_VERITY_DIGSIG (0x06),
hash_algorithm = sha256,
hash[32] = <fsverity 文件摘要>
}
v3 的优势:即使攻击者将一个 fsverity 文件的签名复制到另一个普通 IMA 文件,由于 hash_type 字段不同,验证会失败,有效防止跨类型的签名重放攻击。
适合开发/测试环境的度量配置(仅记录,不拒绝):
# 启动参数
ima_policy=tcb
# 检查度量日志是否生效
cat /sys/kernel/security/ima/ascii_runtime_measurements | head -5
# 查看度量条目总数
cat /sys/kernel/security/ima/runtime_measurements_count
# 查看违规次数
cat /sys/kernel/security/ima/violations# 1. 启动参数(/etc/default/grub 中 GRUB_CMDLINE_LINUX_DEFAULT)
# ima_policy=appraise_tcb ima_appraise=enforce evm=fix
# 2. 为系统文件生成 IMA 哈希
find /usr/bin /usr/lib -type f | xargs -I{} evmctl ima_hash {}
# 3. 为关键二进制生成数字签名
evmctl sign --imasig --key /etc/keys/ima-signing-key.pem /usr/bin/bash
# 4. 初始化 EVM HMAC 密钥
keyctl add encrypted user:kmk "new default user:master 32" @u
keyctl add encrypted user:evm "new user:kmk 32" @u
cat /proc/keys # 确认密钥已加载
# 5. 启用 EVM(在 /etc/init.d/ 或 systemd 早期服务中执行)
echo 1 > /sys/kernel/security/evm
echo 0x80000000 > /sys/kernel/security/evm # 声明初始化完成
# 6. 验证 EVM 状态
cat /sys/kernel/security/evm # 应显示 0x80000003问题:文件访问返回 -EACCES,ima_appraise=enforce
# 查看 audit 日志中的 INTEGRITY_DATA 事件
ausearch -m INTEGRITY_DATA | grep -A5 "cause="
# 常见原因:
# cause="missing-HMAC" -> security.evm 缺失,需要 evmctl ima_hash + EVM 密钥
# cause="invalid-HMAC" -> security.evm 计算不匹配,可能是 xattr 被篡改
# cause="no-security-ima" -> security.ima 缺失,需要 evmctl ima_hash
# cause="invalid-hash" -> 文件内容已被修改
# cause="IMA-signature-required" -> 策略要求签名但只有哈希问题:EVM 初始化后大量文件验证失败
# 临时允许元数据写入(修复 security.evm 的过渡期使用)
echo 4 > /sys/kernel/security/evm # EVM_ALLOW_METADATA_WRITES
# 批量修复 EVM 标签
find /etc /usr/lib -type f | xargs -I{} evmctl ima_fix --imasig {}
# 确认修复后关闭 ALLOW_METADATA_WRITES(重新设置 EVM_SETUP_COMPLETE)
echo 0x80000000 > /sys/kernel/security/evm问题:度量日志过大,消耗大量内存
# 查看当前度量条目数
cat /sys/kernel/security/ima/runtime_measurements_count
# 添加针对性的 dont_measure 规则减少度量量
# 例如:不度量频繁读取的配置文件
echo "dont_measure func=FILE_CHECK fsmagic=0xef53 fowner=0" > \
/sys/kernel/security/ima/policy# 追踪 process_measurement 调用
echo 'p:ima_probe security/integrity/ima/ima_main.c:process_measurement' > \
/sys/kernel/debug/tracing/kprobe_events
echo 1 > /sys/kernel/debug/tracing/events/kprobes/ima_probe/enable
echo 1 > /sys/kernel/debug/tracing/tracing_on
# 执行测试操作
ls /usr/bin/ > /dev/null
# 查看追踪结果
cat /sys/kernel/debug/tracing/trace | grep ima_probe| 文件路径 | 关键函数/数据 |
|---|---|
security/integrity/ima/ima_queue.c |
ima_add_template_entry()(L166), ima_add_digest_entry()(L100), ima_pcr_extend()(L145), ima_restore_measurement_entry()(L223), ima_htable(L36), ima_measurements_suspended(L52) |
security/integrity/ima/ima_crypto.c |
ima_calc_file_hash()内部实现, ima_init_ima_crypto()(L70), ima_ahash_minsize(L26), ima_bufsize(L32), ima_sha1_idx(L60), ima_algo_array(L68) |
security/integrity/ima/ima_init.c |
ima_add_boot_aggregate()(L42), boot_aggregate_name(L24), ima_tpm_chip(L25) |
security/integrity/ima/ima_kexec.c |
ima_measure_kexec_event()(L38), ima_alloc_kexec_file_buf()(L56), ima_kexec_hdr结构 |
security/integrity/ima/ima_modsig.c |
ima_read_modsig()(L40), modsig结构体(L18), PKCS#7 解析流程 |
security/integrity/ima/ima_asymmetric_keys.c |
ima_post_key_create_or_update()(L29), 密钥度量钩子 |
security/integrity/ima/ima_fs.c |
ima_measurements_start()(L77), ima_canonical_fmt(L29), securityfs 文件操作 |
security/integrity/digsig.c |
integrity_digsig_verify()(L61), keyring_name[](L24), v1/v2/v3 签名分发 |
security/integrity/evm/evm_main.c |
evm_read_protected_xattrs()(L351), evm_protected_xattr_common()(L299), is_unsupported_hmac_fs()(L154), evm_inode_post_setxattr()声明 |
security/integrity/evm/evm_secfs.c |
EVM securityfs 接口实现,evm_write_key() |
security/integrity/evm/evm_posix_acl.c |
EVM 与 POSIX ACL 的集成 |
security/ipe/ipe.c |
ipe_hooks[](L48), LSM blob 定义(L14), ipe_sb()(L29) |
security/ipe/eval.c |
ipe_evaluate_event(), build_ipe_sb_ctx(), build_ipe_bdev_ctx() |
security/ipe/policy.c |
ipe_policy_lock(L17), ipe_free_policy()(L47), 策略版本管理 |
security/ipe/hooks.c |
ipe_bprm_check_security()(L30), ipe_bprm_creds_for_exec()(L53) |
security/integrity/platform_certs/load_uefi.c |
UEFI 平台证书加载到 .platform 密钥环 |
security/integrity/platform_certs/machine_keyring.c |
MOK(Machine Owner Key)密钥环管理 |
由 Claude Code 分析生成