Linux内核分析之安全模块-00
This language version is unavailable; showing the other language.
52.1 LSM 钩子机制与安全审计
LSM 的框架本体是"一张钩子头表 + 每模块一条注册链 + 一组调用点"。调用点遍布 VFS/进程/IPC/网络子系统,覆盖所有"对象访问"语义;每个钩子返回 0(放行)或负错误码(拒绝)。本节解剖钩子结构、调用点分布与审计出口。
52.1.1 security_hook_list:钩子的注册形态
// include/linux/lsm_hooks.h:95(节选)
struct security_hook_list {
struct lsm_static_call *scalls; /* 静态调用点 (直补丁优化) */
union security_list_options hook; /* 本模块的判定函数 */
const struct lsm_id *lsmid; /* 归属哪个 LSM */
} __randomize_layout;
/*
* Security blob size or offset data. (lsm_hooks.h:108 区)
*/
struct lsm_blob_sizes {
unsigned int lbs_cred; /* 本模块在 cred 里的附加数据 */
unsigned int lbs_file;
unsigned int lbs_inode; /* 在 inode 里的附加数据 */
unsigned int lbs_sock;
unsigned int lbs_superblock;
unsigned int lbs_ipc;
unsigned int lbs_key;
unsigned int lbs_msg_msg;
unsigned int lbs_perf_event;
...
};
框架的三件套:
[1] security_hook_heads: 全局钩子头表
(file_open/inode_create/bprm_check/task_kill...
每个钩子点一个链表头, 模块注册即挂链)
[2] lsm_blob_sizes: 安全数据的"分租"协议 —
每模块申报要在 cred/inode/file 里加多少字节,
框架统一布线 (52.3 节) — 多模块堆叠的数据
隔离基础
[3] static_call 直补丁: 单模块钩子点不再走链表
遍历, 直接补丁到目标函数 (15 章 static_branch
家族的 LSM 应用) — 钩子开销从间接调用降到
直接调用
52.1.2 调用点分布:哪里有安全决策
// security/security.c:2737 / :1625 / :818(三类代表调用点)
int security_file_open(struct file *file)
int security_inode_create(struct inode *dir, struct dentry *dentry, umode_t mode)
int security_bprm_check(struct linux_binprm *bprm)
钩子点的分类地图 (~250 个):
对象生命周期:
inode_alloc/free inode 诞生/消亡时分配安全数据
file_alloc/free file 同上
cred_prepare/transfer 凭证复制/转移 (55 章)
访问判定 (每次操作调用):
file_open / file_permission / file_read/write
inode_permission / inode_permission_mmap
mmap_file (可执行映射判定!) / file_truncate
进程语义:
bprm_check (exec 的第一道门) / bprm_creds_for_exec
task_create/kill/setpgid/PRCTL...
ptrace_access_check (调试器的门, 25.3 节 GUP 对照)
IPC/网络/挂载:
msg_queue_sem_shm_* sysv IPC (28 章)
socket_create/connect/recv_msg
sb_mount / move_mount (33.2 节挂载)
设计纪律: 钩子点 = "DAC 通过后仍有安全语义"处 —
与功能代码正交, 内核主路径只多一次间接调用
每次操作的成本:security_file_open 在 open 路径一次(多模块时逐个调用);热路径(read/write)的钩子已被削减为少量(file_permission 在部分路径绕过)——框架以"钩子粒度向冷路径倾斜"控制开销。
52.1.3 审计出口:拒绝事件的去向
LSM 的审计通道 (三层):
[1] 模块内部审计:
SELinux → avc: denied 日志 (permissive 下不拒但记)
AppArmor → aa_audit 消息 (54 章)
[2] 通用审计: audit_log_start → 48.2.2 节
NETLINK_AUDIT → auditd (事件不可丢语义)
[3] permissive 模式: SELinux/AppArmor 可设
"只审计不强制" — 策略调试期的标准姿势
(先观察会拒什么, 再收紧)
排查口径:
dmesg | grep -i "avc.*denied" SELinux 拒绝明细
journalctl -k | grep apparmor AppArmor 拒绝
auditctl -l / ausearch -m AVC 审计规则与事件
(每次"程序莫名打不开文件"的第一排查动作)
小结
LSM 框架以"钩子头表 + lsm_blob_sizes 分租 + static_call 直补丁"三件套在约 250 个调用点提供 MAC 插槽:调用点覆盖对象生命周期、访问判定、进程语义与 IPC/网络/挂载,DAC 通过后仍需逐模块放行;审计经模块日志与 48 章审计总线三层出口,permissive 模式支撑策略调试。它是 47.1 节"框架留白"哲学在安全领域的复刻。下一节看多模块堆叠与 BPF LSM。
52.2 多 LSM 堆叠与 BPF LSM
历史上 LSM 一次只能启用一个"主模块"(SELinux 或 AppArmor 二选一)。现代内核支持多模块堆叠(lsm= 启动参数列出全部启用者)与 BPF LSM(钩子逻辑用 eBPF 程序实现,无需改内核)。本节解剖堆叠的数据布线、启用顺序语义与 BPF LSM 的编程面。
52.2.1 堆叠:顺序与数据布线
堆叠的三要素:
[1] 启动参数: lsm=integrity,apparmor,selinux,bpf
(顺序有意义: 钩子按列出的序执行;
capability 总在第一 — 55 章的 POSIX 能力
是其他模块的地基)
[2] 钩子链: 每个钩子点的链表按 lsm= 顺序串接
全部启用模块的函数 — 逐个执行, 任一拒绝即拒
[3] blob 分租 (52.1.1 节 lsm_blob_sizes):
SELinux 申报 lbs_cred=X1, AppArmor 申报 X2,
框架把 cred 尾部扩出 X1+X2 字节并记录各自
偏移 → 每模块在共享对象里有自己的"储物柜"
(3-40 章各结构的 security 指针字段即入口)
判定语义: 全票通过制 (任一 DENY 即 DENY),
无优先级仲裁 — 模块间互不感知, 简单但严格
52.2.2 BPF LSM:策略即程序
// include/linux/bpf_lsm.h:25 区(桩生成宏)
__weak noinline RET bpf_lsm_##NAME(__VA_ARGS__) /* 每个钩子一个 BPF 可附着桩 */
BPF LSM 的编程模型:
BPF_PROG_TYPE_LSM 程序附着到任意 LSM 钩子:
SEC("lsm/file_open")
int BPF_PROG(check_open, struct file *file)
{
if (可疑文件) return -EPERM; /* 拒绝 */
return 0; /* 放行 */
}
特性:
- 程序可读钩子参数的完整内核结构 (BTF 类型安全)
- 可与用户态通信 (ringbuf/map, 37 章 fd 家族)
- 可只读监控 (不返回拒绝值 = 审计探针)
- 受 CAP_BPF + CAP_MAC_ADMIN 门控
与传统模块的对比: 无需编译内核/重启, 策略即
代码可热更新; 但表达能力受 BPF 验证器限制
(循环/指针规则) — "策略即数据"到"策略即
程序"的演进, 与 45 章 epoll 之于 select 的
地位类似
堆叠+组合的典型阵型:容器宿主 = capability + apparmor + bpf(POSIX 能力打底、容器 profile、自定义策略探针);联邦系统 = capability + selinux + integrity。/sys/kernel/security/lsm 显示当前启用序。
小结
堆叠把 LSM 从"单选"推向"组合":lsm= 顺序决定钩子链执行序、lsm_blob_sizes 分租保证各模块数据隔离、全票通过制无需仲裁;BPF LSM 以 BPF_PROG_TYPE_LSM 程序附着任意钩子,把策略从"编译进内核的模块"演进为"热更新的程序"(验证器约束下的内核编程)。下一节看所有 MAC 都要读取的主体上下文——cred 结构与能力位。
52.3 安全上下文与 cred 结构
进程的"安全身份"浓缩在 struct cred:真实的/有效的/保存的 UID-GID、五个能力位集合、securebits、以及 LSM 的安全 blob。本节逐字段解析凭证结构、写时复制语义与能力位全表——为 55 章的 Capability 详解与 user namespace 映射打基础。
52.3.1 cred:进程凭证快照
// include/linux/cred.h:113(节选)
struct cred {
atomic_long_t usage;
kuid_t uid; /* real UID of the task */
kuid_t gid;
kuid_t suid; /* saved UID */
kgid_t sgid;
kuid_t euid; /* effective UID */
kgid_t egid;
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
unsigned securebits;
kernel_cap_t cap_inheritable; /* 子进程可继承的能力 */
kernel_cap_t cap_permitted; /* 允许拥有的能力池 */
kernel_cap_t cap_effective; /* 当前生效的能力 */
kernel_cap_t cap_bset; /* 能力边界集(只减不增) */
kernel_cap_t cap_ambient; /* 非特权继承能力 */
...
void *security; /* LSM blob 指针 (52.1 节分租) */
struct user_namespace *user_ns; /* 所属用户命名空间 (9 章) */
...
};
四种 UID 的分工 (setuid 程序的经典语义):
uid (real) 谁启动了我 (登录身份, kill 权判定)
euid (effective) 权限判定用的是它 (DAC/能力检查)
suid (saved) setuid 程序"降权后存着"的原身份
(需要时提回去 —passwd 改密码的机制)
fsuid (fs) VFS 操作专用的 euid (NFS 等按连接
切换身份而不动 euid)
三个能力集合的关系 (55 章展开):
e ⊆ p ⊆ (bset ∪ ...)
exec setuid-root: euid=0, cap_effective = permitted
exec 普通程序: e 集清空 (能力掉线), ambient 除外
cred 的不可变快照语义:运行中的 cred 对象不可原地修改——prepare_creds() 复制一份、改完 commit_creds() 原子切换(引用计数+RCU,17 章)。读者(权限检查路径)拿到的 cred 永远是完整一致的快照。
52.3.2 能力位与检查入口
// kernel/capability.c:414 与 :361
bool capable(int cap) /* 对 init_user_ns 检查 */
bool ns_capable(struct user_namespace *ns, int cap) /* 对指定 ns 检查 */
// include/uapi/linux/capability.h
#define CAP_CHOWN 0 /* :15 */
#define CAP_NET_ADMIN 12 /* :205 */
#define CAP_SYS_ADMIN 21 /* :281 — "万能钥匙", 慎授! */
#define CAP_LAST_CAP CAP_CHECKPOINT_RESTORE /* :423 */
能力把"root 的特权"拆成约 40 个独立位:CAP_SYS_ADMIN 是历史遗留的"万能位"(挂载/容器/大量敏感操作都查它——攻击面的最爱,收紧是长期方向);细粒度位如 CAP_NET_ADMIN(48 章 rtnetlink 配置)、CAP_SYS_TIME 等。ns_capable(user_ns, cap) 把检查限定在命名空间内——容器内的 CAP_SYS_ADMIN 只在容器 user_ns 里有效(9 章隔离 + 55.3 节映射的联合)。
能力检查的调用点抽样 (全书已见):
rtnetlink 配置 ns_capable(net->user_ns, CAP_NET_ADMIN) (48.2 节)
审计规则 CAP_AUDIT_* (48.2.2 节)
BPF LSM 编程 CAP_BPF + CAP_MAC_ADMIN (52.2 节)
wakeup_kswapd 等 内核内部路径不经能力 (直接信任)
DAC 判定 euid/fsuid + 权限位 (33 章路径查找)
52.3.3 LSM blob 与 cred 的接合
安全上下文的完整栈 (一个进程的"身份"):
task_struct
└─ cred (52.3.1)
├─ uid 组 (4 种 UID/GID)
├─ 能力 5 集 (55 章详解)
├─ user_ns (55.3 节映射)
├─ securebits
└─ security blob (LSM 分租区)
├─ SELinux: sid (安全 ID → 53 章标签)
├─ AppArmor: profile 引用 (54 章)
└─ (BPF LSM 无 blob — 策略在程序里)
判定序回顾 (52 章 README):
DAC (euid/fsuid vs 权限位)
→ Capability (e 集 vs 所需位)
→ LSM 各模块 (blob 里的上下文 vs 策略)
→ seccomp (56 章: 系统调用号过滤, 另一道门)
小结
struct cred 以"四 UID + 五能力集 + securebits + LSM blob + user_ns"承载进程安全身份:不可变快照语义(prepare/commit 原子切换)保证检查者视角一致;能力位把 root 特权拆成约 40 个可独立授予的位(CAP_SYS_ADMIN 万能位的收敛是长期方向);LSM blob 分租(52.1 节)让每个模块在 cred 里有私有上下文。cred 是 53-56 章全部安全机制的"主体"载体。下一章进入最大的 LSM 消费者——SELinux。
52.4 IMA 完整性测量架构
前两节的安全机制(SELinux/AppArmor/BPF LSM)回答"这个操作允不允许";本节的 IMA(Integrity Measurement Architecture,完整性测量架构)回答另一个维度的问题——"这个被执行/被加载的东西是不是它该是的样子"。它包含两个互补机制:测量(把每个执行/加载文件的哈希追加进一条防篡改的度量链,锚定在 TPM 的 PCR10——供远程证明与事后取证)与评估(appraisal,用签名/哈希比对判定文件可信与否,不可信即拒绝执行)。配套的 EVM 则用 HMAC/签名保护文件的元数据 xattr 不被离线篡改。全部行号针对本树实际布局,结合 Linux 7.0.10 内核源码逐行分析。
完整性的三个时间点 (IMA 在其中占两个):
静态: Secure Boot 固件链 —— 只管"启动前"
固件→bootloader→内核 逐级验签 (1.6 节提及)
运行: IMA 测量+评估 —— 管"内核起来之后"
每次 exec/mmap/模块加载/固件加载/kexec...
测量: 记入度量链 (谁跑了什么, 事后可审计)
评估: 签名比对 (没签过的就不许跑)
远程证明: 远端验证者索取度量链+PCR10 引用
( TPM 签名 PCR → 证明度量链未被捏造 )
52.4.1 钩子面:enum ima_hooks 与触发点
// security/integrity/ima/ima.h:313-333
#define __ima_hooks(hook) \
hook(NONE, none) \
hook(FILE_CHECK, file) \
hook(MMAP_CHECK, mmap) \
hook(MMAP_CHECK_REQPROT, mmap_reqprot) \
hook(BPRM_CHECK, bprm) \
hook(CREDS_CHECK, creds) \
hook(POST_SETATTR, post_setattr) \
hook(MODULE_CHECK, module) \
hook(FIRMWARE_CHECK, firmware) \
hook(KEXEC_KERNEL_CHECK, kexec_kernel) \
hook(KEXEC_INITRAMFS_CHECK, kexec_initramfs) \
hook(POLICY_CHECK, policy) \
hook(KEXEC_CMDLINE, kexec_cmdline) \
hook(KEY_CHECK, key) \
hook(CRITICAL_DATA, critical_data) \
hook(SETXATTR_CHECK, setxattr_check) \
hook(MAX_CHECK, none)
钩子面的覆盖 (对应内核中"代码进入系统"的每条路):
FILE_CHECK / MMAP_CHECK open/mmap 可执行 (52.1 节
security_file_open 的消费端)
BPRM_CHECK / CREDS_CHECK exec 与凭证切换
MODULE_CHECK 模块加载 (init_module)
FIRMWARE_CHECK 固件装载 (42 章请求固件)
KEXEC_KERNEL/INITRAMFS kexec 换核的输入 (18.1 节)
KEY_CHECK 新钥匙入环 (可信密钥环)
CRITICAL_DATA 关键数据结构快照 (SELinux
策略/内核 nonce 等 — 容器合规)
POLICY_CHECK IMA 自身新策略的评估
(自指: 给 IMA 换策略的文件本身也要被评估)
每个 hook 对应 UAPI 的 func= 值 (fun=FILE_CHECK
等 — 策略规则的 func 字段, 52.4.4 节)
52.4.2 测量:process_measurement 与度量链
// security/integrity/ima/ima_main.c:653-668(文件钩子入口)
* ima_file_check - based on policy, collect/store measurement.
static int ima_file_check(struct file *file, int mask)
{
struct lsm_prop prop;
security_current_getlsmprop_subj(&prop);
return process_measurement(file, current_cred(), &prop, NULL, 0,
mask & (MAY_READ | MAY_WRITE | MAY_EXEC |
MAY_APPEND), FILE_CHECK, 0, false);
}
// :236
static int process_measurement(struct file *file, const struct cred *cred, ...)
// :584
static int ima_bprm_check(struct linux_binprm *bprm) /* exec 钩子 */
// security/integrity/ima/ima.h:188-204(每 inode 的度量元数据)
struct ima_iint_cache {
struct mutex mutex; /* protects: version, flags, digest */
struct integrity_inode_attributes real_inode;
unsigned long flags;
unsigned long measured_pcrs; /* 已扩展过哪些 PCR (防重复度量) */
unsigned long atomic_flags;
enum integrity_status ima_file_status:4; /* 文件度量/评估状态机 */
enum integrity_status ima_mmap_status:4;
enum integrity_status ima_bprm_status:4;
enum integrity_status ima_read_status:4;
enum integrity_status ima_creds_status:4;
struct ima_digest_data *ima_hash; /* 最近一次哈希 (缓存) */
};
static inline struct ima_iint_cache *
ima_inode_get_iint(const struct inode *inode)
{
struct ima_iint_cache **iint_sec;
...
iint_sec = inode->i_security + ima_blob_sizes.lbs_inode;
return *iint_sec;
}
iint 缓存正是 52.1.1 节 blob 分租协议的客户:IMA 在 lsm_blob_sizes.lbs_inode 声明自己的字节数,内核把 inode->i_security 尾部划给它——每 inode 一份度量元数据(五个状态机位段对应五类钩子,measured_pcrs 位图防止同一 PCR 被重复扩展——TPM 扩展是慢操作且不可逆)。
度量链的追加与存储 (ima_queue.c + TPM):
process_measurement 命中策略:
[1] 算文件哈希 (sha256 默认, ima_crypto.c)
[2] 组装模板条目: {digest, 文件名}
[3] ima_add_template_entry → 追加进度量链
度量链 = 时序不可逆列表 (只追加)
[4] TPM 扩展: PCR10 = H(PCR10 ‖ H(条目))
(单向"堆叠" — 从 PCR 现值无法反推历史,
但可以重放全链验证 PCR10 当前值)
[5] /sys/kernel/security/ima/ascii_runtime_measurements
可读的度量日志 (ima_fs.c:408-411 的导出文件)
ima_h_table (ima.h:300 区): 度量条目的哈希索引表
(len/violations 计数 — violations 记录
"评估违例被度量"的特殊条目)
防绕过的关键: 重复执行同一文件只度量一次?
不 — measured_pcrs 位图按 PCR 防重复扩展,
但状态变化 (文件被改写) 会使 iint 失效重量
度量与评估在 process_measurement() 里是一体两面:同一函数按策略分别给出 measure 动作(追加度量链)与 appraise 动作(比对签名);任何一步失败且处于 enforcing 时返回 -EACCES(ima_main.c:666 注释"On integrity error ... return -EACCES")。
52.4.3 评估(appraisal):签名比对与拒绝执行
// security/integrity/ima/ima_appraise.c:25/51(启动参数与 Secure Boot 联动)
core_param(ima_appraise, ima_appraise_cmdline_default, charp, 0);
...
pr_info("Secure boot enabled: ignoring ima_appraise=%s option", ...)
评估的判定材料 — 文件 xattr security.ima:
类型一: 纯哈希
xattr 存文件的期望哈希 → 评估=现算哈希比对
(防"文件被改", 但攻击者若能改文件也能改 xattr
— 除非 fs 只读/被 EVM 保护 → 见 52.4.4)
类型二: 签名 (appraise_type=imasig)
xattr 存 {哈希+非对称签名}, 验签钥匙在
INTEGRITY_KEYRING_IMA (integrity.h 的四个钥匙环:
EVM/IMA/PLATFORM(MOK)/MACHINE — Secure Boot
的机器钥匙链入 MACHINE 环)
modsig 变体: 签名附加在文件尾部 (ELF append) —
免 xattr、对只读文件系统友好 (ima_modsig.c)
启动参数语义 (ima_appraise=):
off 不评估
fix 无标签则度量并"顺手补写"正确的 xattr
(初始化部署期)
log 违例只记不拒 (调试)
enforce 拒绝 (默认: Secure Boot 开启时强制 —
:51 的"忽略降级参数"分支, 防 SB 开着却
用启动参数悄悄关评估)
评估的失败动作与 52.1.3 节审计相接:违例产生度量链上的特殊条目(violations 计数)+ audit 记录,enforcing 下拒绝 open/mmap/exec。与 Secure Boot 的耦合是系统级设计:SB 链保证"内核与钥匙环可信",IMA 评估保证"内核之后加载的一切可信"——信任链从固件延伸到运行期。
52.4.4 EVM:元数据的 HMAC/签名封印
评估只管文件内容哈希;文件的元数据(属主、模式、selinux xattr 等)可以被离线/在线篡改而哈希不变。EVM(Extended Verification Module)补上这一环:
// security/integrity/evm/evm_main.c:178 与 :497(锚点)
static enum integrity_status evm_verify_hmac(struct dentry *dentry, ...)
static int evm_protect_xattr(struct mnt_idmap *idmap, ...)
EVM 的两代形态:
EVM HMAC 模式 (对称):
security.evm xattr = HMAC(密钥, {inode 元数据全组:
uid/gid/mode/... + security.selinux +
security.ima + ...})
密钥在内核钥匙环 (EVM key) — 离线挂载改
元数据 → HMAC 对不上 → 评估失败
(密钥封在 TPM/initramfs, 离线者拿不到)
EVM 签名模式 (非对称, 数字签名):
security.evm = 私钥签名 — 验证公钥在
INTEGRITY_KEYRING_EVM; 支持离线签名部署镜像
evm_protect_xattr (:497): 保护对象包括对
security.* xattr 本身的修改 — "改 IMA 签名"
这条逃逸路被元数据 HMAC 再封一层
两者与 IMA 评估的协作:
open 文件 → IMA 评估内容哈希/签名
→ EVM 评估元数据 HMAC/签名
任一失败 → enforcing 下 -EACCES
(内容可信 + 元数据可信 = 完整可信)
52.4.5 策略、运行接口与 kexec 携带
策略语法 (用户态可读格式, ima_policy.c:1138 区解析):
measure func=FILE_CHECK mask=MAY_EXEC
fowner=0 uid=0 template=ima-ng
appraise func=MODULE_CHECK appraise_type=imasig
appraise func=CRITICAL_DATA label=selinux
运行接口 (/sys/kernel/security/ima/, ima_fs.c):
ascii_runtime_measurements 度量日志 (人读)
binary_runtime_measurements 二进制度量日志
runtime_measurements_count 条目计数
violations 违例计数
policy 写入新策略 (评估:
POLICY_CHECK 自指)
ascii_binary_metrics 算法统计
kexec 携带 (ima_kexec.c):
换核时把当前度量链尾部传递给新内核 —
度量的连续性跨越 kexec (审计不断代)
容器/合规场景 (CRITICAL_DATA):
measure func=CRITICAL_DATA 把 SELinux 策略
哈希/内核关键配置纳入度量 — 合规审计
(Fedora/RHEL 的合规模式实践)
观测:
dmesg | grep -i ima 初始化/策略/评估态
cat /sys/.../ascii_runtime_measurements | wc -l
度量条目数; 每条 = PCR 扩展一次
与本卷的收束:IMA 的 iint 缓存复用 52.1 节 blob 分租、钩子面复用 52.1.2 节调用点、评估钥匙环与 Secure Boot/MOK 相接、CRITICAL_DATA 与容器合规相接、kexec 携带呼应 18.1 节 kexec 副本、审计走 48.2.2 节总线。安全部分的完整图景:DAC 定基线(52.3)→ 能力细粒度(55)→ MAC 定策略(53/54)→ seccomp 缩面(56)→ IMA/EVM 保证"跑的东西本身可信"——五个维度互不替代。
小结
IMA 以"钩子面(16 类 ima_hooks)→ 每inode 的 ima_iint_cache 元数据(52.1 节 blob 分租的客户)→ process_measurement 一体两面的测量与评估 → TPM PCR10 度量链"构成运行期完整性闭环:测量供远程证明与取证(kexec 跨代携带),评估按 security.ima xattr 的哈希/签名/ modsig 拒绝不可信内容;EVM 以 HMAC/签名封印元数据堵住"改 xattr 绕过"的侧门;策略与度量经 selinuxfs 式的 IMA fs 暴露。它与 Secure Boot 相接构成从固件到运行期的完整信任链。