基于 Linux Kernel 源码深度分析 路径:
security/|include/linux/security.h|kernel/capability.c内核版本:Linux 6.x(master 分支)
- Linux 安全体系概述
- DAC:自主访问控制
- Capability 体系深度解析
- struct cred:凭证管理核心
- LSM 框架架构
- LSM Hook 点分类详解
- call_int_hook / call_void_hook 调用链
- LSM Stacking:多模块共存
- LSM Blob:私有数据管理
- SELinux:类型强制详解
- AppArmor:路径名策略
- BPF-LSM:可编程安全策略
- seccomp:系统调用过滤
- Capability 深度:ambient、bounding、file
- namespace 与安全隔离
- Landlock:用户态沙箱
- 安全子系统全景总结
Linux 的安全体系历经三代演进,从最初的 UNIX DAC 模型逐步走向灵活可扩展的强制访问控制框架:
[Linux 安全模型演进时间轴]
UNIX 诞生(1970s)
|
v
DAC(自主访问控制)
├── owner/group/other 三元组权限位(rwx)
├── SUID/SGID 位:临时提升执行者身份
├── root 用户拥有绝对权力(uid=0 绕过所有检查)
└── 问题:权限粒度粗,root 被攻击则全盘失守
|
v
POSIX Capabilities(Linux 2.2, 1999)
├── 将 root 特权分割为 41 个独立能力位
├── 进程可持有部分特权而不是全部(最小权限原则)
├── 三个集合:effective / permitted / inheritable
└── 问题:仍是 DAC 的细化,缺乏客体标签机制
|
v
LSM 框架(Linux 2.6.0, 2003)
├── 提供通用 hook 插桩点,MAC 模块可在此注入策略
├── SELinux / AppArmor / Smack / TOMOYO 等作为模块接入
├── security_add_hooks() 注册钩子
└── 问题(早期):同一时刻只能加载一个主要 LSM
|
v
LSM Stacking(Linux 4.2+, 正式支持于 5.1+)
├── 支持多个 LSM 同时叠加运行
├── capabilities 永远在最前(LSM_ORDER_FIRST)
├── integrity(IMA/EVM)在最后(LSM_ORDER_LAST)
└── 中间 LSM 按 CONFIG_LSM 顺序排列
|
v
static_call 优化(Linux 5.7+)
├── 用静态调用替换间接函数指针,避免 Spectre v2
├── BPF-LSM:用 BPF 程序实现 hook(Linux 5.7+)
└── 编译期 MAX_LSM_COUNT 限制并发 LSM 数量
|
v
当前(Linux 6.x)
├── Landlock(用户态 MAC 沙箱,Linux 5.13+)
├── IPE(完整性策略执行,Linux 6.9+)
├── 网络沙箱 via Landlock(Linux 6.7+)
└── LSM blob 共享机制成熟
Linux 安全并非单一机制,而是三个层次叠加:
┌─────────────────────────────────────────────────────────────┐
│ 用户空间进程 │
│ 系统调用(open/read/write/socket/...) │
└────────────────────────┬────────────────────────────────────┘
│ 系统调用入口
v
┌─────────────────────────────────────────────────────────────┐
│ Layer 1: DAC(自主访问控制) │
│ ├── inode->i_mode 权限位检查 │
│ ├── uid/gid 匹配(fsuid/fsgid) │
│ └── ACL(POSIX ACL) │
└────────────────────────┬────────────────────────────────────┘
│ DAC 通过后
v
┌─────────────────────────────────────────────────────────────┐
│ Layer 2: Capability 检查 │
│ ├── cap_effective 中是否有所需 capability │
│ ├── security_capable() → LSM hook │
│ └── ns_capable():user namespace 感知 │
└────────────────────────┬────────────────────────────────────┘
│ capability 通过后
v
┌─────────────────────────────────────────────────────────────┐
│ Layer 3: MAC(LSM 强制访问控制) │
│ ├── SELinux:类型强制,AVC 查询 │
│ ├── AppArmor:路径名策略,profile 匹配 │
│ ├── BPF-LSM:可编程策略 │
│ └── Landlock:用户态定义的沙箱规则 │
└─────────────────────────────────────────────────────────────┘
关键原则:所有层次必须全部通过,拒绝任何一层即拒绝操作。DAC 在最前,MAC 在最后,但 MAC 可以拒绝 DAC 允许的操作(反过来不行)。
DAC(Discretionary Access Control,自主访问控制)是 UNIX 的传统权限模型,由文件所有者自行决定访问规则。
[inode 权限位结构]
i_mode(16 位):
┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
│15│14│13│12│11│10│ 9│ 8│ 7│ 6│ 5│ 4│ 3│ 2│ 1│ 0│
└──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
├── [15:12] 文件类型:0100=普通文件,0040=目录,0120=符号链接
├── [11] SUID:执行时使用文件 owner 的 uid
├── [10] SGID:执行时使用文件 group 的 gid
├── [ 9] Sticky:目录下只有 owner 可删除自己的文件
├── [8:6] owner 权限:r=4, w=2, x=1
├── [5:3] group 权限
└── [2:0] others 权限
内核在 fs/namei.c 的 generic_permission() 中实现 DAC 检查,核心逻辑如下:
- 若进程
fsuid == inode->i_uid,使用 owner 权限位 - 若进程
fsgid == inode->i_gid或补充组包含i_gid,使用 group 权限位 - 否则使用 others 权限位
- 检查所需权限(MAY_READ / MAY_WRITE / MAY_EXEC)是否在该权限位集合中
- 若进程持有
CAP_DAC_OVERRIDE(capability 1),可绕过读写检查 - 若进程持有
CAP_DAC_READ_SEARCH(capability 2),可绕过读和目录搜索检查
SUID(Set-UID)位允许程序在执行时以文件 owner 的身份运行,而不是调用者的身份。典型案例是 /usr/bin/passwd,它以 root 身份运行,才能修改 /etc/shadow。
[exec 时 SUID 处理流程]
execve("/usr/bin/passwd", ...)
│
v
bprm_fill_uid()
├── 检查文件 mode & S_ISUID
├── 若有 SUID 位且文件 owner 不是当前 euid:
│ new->euid = inode->i_uid
├── 检查文件 mode & S_ISGID
├── 若有 SGID 位:
│ new->egid = inode->i_gid
└── 调用 commit_creds(new) 提交新凭证
安全风险:SUID root 程序是攻击者的重要目标,任何漏洞都可能导致提权。这也是 capabilities 出现的动因——让 passwd 只持有 CAP_DAC_WRITE 而不是完整 root 权限。
传统 UNIX 的 root 是全能的,拥有 uid=0 即拥有一切。这违反了最小权限原则(principle of least privilege)。POSIX 1003.1e 草案(未通过标准化,但 Linux 实现了其 capability 部分)将 root 权限分解为细粒度的 capability 位。
include/uapi/linux/capability.h 定义了所有 41 个 capability:
// include/uapi/linux/capability.h:115
#define CAP_CHOWN 0 // 任意修改文件 owner
#define CAP_DAC_OVERRIDE 1 // 绕过 DAC 读写执行检查
#define CAP_DAC_READ_SEARCH 2 // 绕过 DAC 读和目录搜索
#define CAP_FOWNER 3 // 绕过文件 owner 检查
#define CAP_FSETID 4 // 设置 SUID/SGID 位
#define CAP_KILL 5 // 向任意进程发送信号
#define CAP_SETGID 6 // 设置任意 GID
#define CAP_SETUID 7 // 设置任意 UID
#define CAP_SETPCAP 8 // 修改 capability
#define CAP_LINUX_IMMUTABLE 9 // 设置 immutable/append-only 属性
#define CAP_NET_BIND_SERVICE 10 // 绑定 <1024 端口
#define CAP_NET_BROADCAST 11 // 广播/多播
#define CAP_NET_ADMIN 12 // 网络管理(路由/防火墙等)
#define CAP_NET_RAW 13 // 原始套接字
#define CAP_IPC_LOCK 14 // 锁定共享内存
#define CAP_IPC_OWNER 15 // 绕过 IPC 所有权检查
#define CAP_SYS_MODULE 16 // 加载/卸载内核模块
#define CAP_SYS_RAWIO 17 // 直接 I/O 访问(ioperm/iopl)
#define CAP_SYS_CHROOT 18 // chroot()
#define CAP_SYS_PTRACE 19 // ptrace() 任意进程
#define CAP_SYS_PACCT 20 // 进程审计
#define CAP_SYS_ADMIN 21 // 最危险的超级权限(挂载/配额等)
#define CAP_SYS_BOOT 22 // reboot()
#define CAP_SYS_NICE 23 // 实时调度/优先级
#define CAP_SYS_RESOURCE 24 // 覆盖资源限制
#define CAP_SYS_TIME 25 // 修改系统时钟
#define CAP_SYS_TTY_CONFIG 26 // 配置 TTY
#define CAP_MKNOD 27 // 创建设备文件(mknod)
#define CAP_LEASE 28 // 文件租约
#define CAP_AUDIT_WRITE 29 // 写入审计日志
#define CAP_AUDIT_CONTROL 30 // 配置审计
#define CAP_SETFCAP 31 // 设置文件 capability
#define CAP_MAC_OVERRIDE 32 // 覆盖 MAC 策略(LSM 定义语义)
#define CAP_MAC_ADMIN 33 // MAC 管理权限
#define CAP_SYSLOG 34 // 控制内核 syslog
#define CAP_WAKE_ALARM 35 // 唤醒系统
#define CAP_BLOCK_SUSPEND 36 // 阻止系统挂起
#define CAP_AUDIT_READ 37 // 通过多播读取审计日志
#define CAP_PERFMON 38 // perf_events 及性能观测
#define CAP_BPF 39 // BPF 程序操作
#define CAP_CHECKPOINT_RESTORE 40 // 检查点/恢复
// include/uapi/linux/capability.h:423
#define CAP_LAST_CAP CAP_CHECKPOINT_RESTORE [Capability 功能分类]
文件系统类:
├── CAP_CHOWN(0) - 修改任意文件所有权
├── CAP_DAC_OVERRIDE(1) - 绕过文件访问权限
├── CAP_DAC_READ_SEARCH(2) - 绕过读/搜索权限
├── CAP_FOWNER(3) - 绕过所有权验证
├── CAP_FSETID(4) - 设置 setuid/setgid 位
├── CAP_LEASE(28) - 文件租约
└── CAP_MKNOD(27) - 创建特殊设备节点
进程管理类:
├── CAP_KILL(5) - 向任意进程发送信号
├── CAP_SETGID(6) - 修改任意 GID
├── CAP_SETUID(7) - 修改任意 UID
├── CAP_SETPCAP(8) - 修改 capability 集合
├── CAP_SYS_PTRACE(19) - trace 任意进程
├── CAP_SYS_NICE(23) - 实时调度权限
└── CAP_CHECKPOINT_RESTORE(40) - CRIU 支持
网络类:
├── CAP_NET_BIND_SERVICE(10) - 绑定特权端口
├── CAP_NET_BROADCAST(11) - 广播
├── CAP_NET_ADMIN(12) - 网络配置
└── CAP_NET_RAW(13) - 原始套接字
系统管理类:
├── CAP_SYS_MODULE(16) - 内核模块加载(极危险)
├── CAP_SYS_RAWIO(17) - 直接硬件访问
├── CAP_SYS_CHROOT(18) - chroot
├── CAP_SYS_ADMIN(21) - 超级管理权限(最危险)
├── CAP_SYS_BOOT(22) - 重启系统
├── CAP_SYS_RESOURCE(24) - 覆盖资源限制
└── CAP_SYS_TIME(25) - 修改时钟
安全/审计类:
├── CAP_LINUX_IMMUTABLE(9) - 设置不可变属性
├── CAP_IPC_LOCK(14) - 锁定内存
├── CAP_AUDIT_WRITE(29) - 写审计日志
├── CAP_AUDIT_CONTROL(30) - 配置审计系统
├── CAP_AUDIT_READ(37) - 读审计日志
├── CAP_MAC_OVERRIDE(32) - 覆盖 MAC
├── CAP_MAC_ADMIN(33) - MAC 管理
├── CAP_SYSLOG(34) - 控制内核日志
├── CAP_PERFMON(38) - 性能监控
├── CAP_BPF(39) - BPF 操作
└── CAP_SETFCAP(31) - 文件 capability
// include/uapi/linux/capability.h:31
#define _LINUX_CAPABILITY_VERSION_3 0x20080522
#define _LINUX_CAPABILITY_U32S_3 2
// capability 用两个 u32 存储,共 64 位,目前使用低 41 位
// include/uapi/linux/capability.h:431
#define CAP_TO_INDEX(x) ((x) >> 5) // x / 32 → u32 数组下标
#define CAP_TO_MASK(x) (1U << ((x) & 31)) // x % 32 → 该 u32 内的位掩码内核内部使用 kernel_cap_t(定义在 include/linux/capability.h),实际为包含 u32[2] 的结构体。
struct cred 是 Linux 进程凭证的核心数据结构,定义在 include/linux/cred.h:113:
// include/linux/cred.h:113
struct cred {
atomic_long_t usage; // 引用计数
/* UID/GID 套组 */
kuid_t uid; // 真实 UID(real)
kgid_t gid; // 真实 GID
kuid_t suid; // 保存的 UID(saved)
kgid_t sgid; // 保存的 GID
kuid_t euid; // 有效 UID(effective):文件访问时使用
kgid_t egid; // 有效 GID
kuid_t fsuid; // 文件系统 UID(VFS 操作专用)
kgid_t fsgid; // 文件系统 GID
unsigned securebits; // SUID-less 安全位(SECBIT_*)
/* Capability 集合 */
kernel_cap_t cap_inheritable; // 可继承集合(I)
kernel_cap_t cap_permitted; // 许可集合(P)
kernel_cap_t cap_effective; // 有效集合(E)
kernel_cap_t cap_bset; // bounding set(B)
kernel_cap_t cap_ambient; // ambient set(A)
/* 密钥环 */
unsigned char jit_keyring;
struct key *session_keyring;
struct key *process_keyring;
struct key *thread_keyring;
struct key *request_key_auth;
/* LSM 私有数据 */
void *security; // LSM 安全指针(开启 CONFIG_SECURITY)
/* 用户账户 */
struct user_struct *user; // 真实 UID 对应的 user 结构
struct user_namespace *user_ns; // capability 所在的 user namespace
struct ucounts *ucounts; // per-user 资源计数
struct group_info *group_info; // 补充组列表
/* RCU 删除 */
union {
int non_rcu;
struct rcu_head rcu;
};
} __randomize_layout; [进程 UID 状态机]
┌──────────┬──────────────────────────────────────────┐
│ 字段 │ 含义及用途 │
├──────────┼──────────────────────────────────────────┤
│ uid │ 真实 UID:登录身份,不随 setuid 变化 │
│ gid │ 真实 GID │
│ euid │ 有效 UID:权限检查时使用(如 DAC) │
│ egid │ 有效 GID │
│ suid │ 保存 UID:可通过 setuid() 恢复 │
│ sgid │ 保存 GID │
│ fsuid │ 文件系统 UID:VFS 操作权限检查,默认=euid │
│ fsgid │ 文件系统 GID,默认=egid │
└──────────┴──────────────────────────────────────────┘
非特权进程调用 setuid(x):
x == uid → euid = x,suid 不变
x == suid → euid = x
特权进程调用 setuid(x):
uid = euid = suid = x(完全放弃特权)
fsuid 通常等于 euid,但 NFS 服务器可以单独设置 fsuid
使得服务器以客户端 UID 的身份访问文件,但不影响其他权限检查
[Capability 集合关系]
ambient (A)
│ 继承至子进程(不依赖 exec 的文件 capability)
│
┌──────┴──────────────────────────────────────────┐
│ bounding set (B) │
│ ┌─────────────────────────────────────────┐ │
│ │ permitted (P) │ │
│ │ ┌─────────────────────────────────┐ │ │
│ │ │ effective (E) │ │ │
│ │ │ ⊆ permitted │ │ │
│ │ └─────────────────────────────────┘ │ │
│ │ │ │
│ │ inheritable (I) │ │
│ │ ⊆ permitted ∪ bounding │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
集合语义:
- E(effective):当前实际有效的 capability,内核检查此集合
- P(permitted):进程拥有但可能未激活的 capability 上限
- I(inheritable):exec 时可传递给新程序的 capability
- B(bounding):限制 P 的上界,子进程继承后只降不升
- A(ambient):Linux 4.3 引入,无需文件 capability 的继承机制
不变量(cred.h:172):
ambient ⊆ (permitted ∩ inheritable)
struct cred 实现写时复制(Copy-on-Write),保证凭证的不可变性:
[凭证 COW 流程]
current->cred (const, 只读)
│
│ prepare_creds() ─── include/linux/cred.h:156
│ ├── 分配新的 struct cred
│ ├── 从 current->cred 深度复制所有字段
│ └── 返回可修改的新副本
v
new_cred (可修改)
│
│ 修改 new_cred->euid / new_cred->cap_effective 等
│
v
commit_creds(new_cred) ─── include/linux/cred.h:158
│
├── 原子地将 current->cred 替换为 new_cred
├── 通过 RCU 安全地释放旧凭证
└── 触发必要的安全钩子(如 setuid 审计)
abort_creds(new_cred) ─── 放弃修改,释放新凭证副本
关键函数签名(include/linux/cred.h):
struct cred *prepare_creds(void)— 行 156int commit_creds(struct cred *)— 行 158void abort_creds(struct cred *)— 行 159
线程安全:current->cred 通过 RCU 保护,其他线程访问时需持有 RCU 读锁(rcu_read_lock())。只有当前线程可以写自己的凭证。
struct task_struct {
...
const struct cred __rcu *real_cred; // 客观凭证(该进程是被操作对象时)
const struct cred __rcu *cred; // 主观凭证(该进程是操作主体时)
...
};正常情况下两者相同。override_creds() 可以临时替换主观凭证(include/linux/cred.h:179),用于内核代码临时以不同身份执行操作,如 NFS 服务器代码。
LSM(Linux Security Module)框架是 Linux 内核的安全 hook 基础设施,允许安全模块在内核关键操作点注入访问控制决策,而无需修改内核核心代码。
[LSM 框架架构]
┌─────────────────────────────────────────────────────────────┐
│ 内核核心代码 │
│ VFS / 网络协议栈 / IPC / 进程管理 │
│ │ │
│ │ security_xxx() 接口函数 │
│ v │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ security/security.c │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ static_calls_table(静态调用表) │ │ │
│ │ │ ┌─────────┬─────────┬─────────┬──────────┐ │ │ │
│ │ │ │ capable │inode_ │file_ │socket_ │ │ │ │
│ │ │ │ [0][1] │perm[0] │open[0] │connect[0]│ │ │ │
│ │ │ └────┬────┴────┬────┴────┬────┴─────┬────┘ │ │ │
│ │ └───────┼─────────┼─────────┼──────────┼──────┘ │ │
│ └──────────┼─────────┼─────────┼──────────┼──────────┘ │
│ │ │ │ │ │
│ ┌───────┘ ┌──────┘ ┌──────┘ ┌──────┘ │
│ v v v v │
│ capabilities SELinux AppArmor BPF-LSM │
│ (内置) (可选) (可选) (可选) │
└─────────────────────────────────────────────────────────────┘
每个 LSM 注册的 hook 以 security_hook_list 表示(include/linux/lsm_hooks.h:95):
// include/linux/lsm_hooks.h:95
struct security_hook_list {
struct lsm_static_call *scalls; // 指向静态调用表中的槽位
union security_list_options hook; // 回调函数(通过 union 按名存放)
const struct lsm_id *lsmid; // 所属 LSM 的标识
} __randomize_layout;struct lsm_id(include/linux/lsm_hooks.h:81)标识每个 LSM:
// include/linux/lsm_hooks.h:81
struct lsm_id {
const char *name; // LSM 名称字符串(如 "selinux", "apparmor")
u64 id; // 来自 uapi/linux/lsm.h 的数字 ID
};LSM 在 init() 函数中调用 security_add_hooks() 注册所有 hook(include/linux/lsm_hooks.h:142):
// include/linux/lsm_hooks.h:142
extern void security_add_hooks(struct security_hook_list *hooks, int count,
const struct lsm_id *lsmid);注册过程(security/security.c 中的实现):
- 遍历 hooks 数组(每项对应一个 hook 名称)
- 找到
static_calls_table中对应 hook 名称的静态调用槽 - 从后往前填充(最后注册的 LSM 占据最前的槽位,先执行)
- 启用对应的
static_key_false,使后续调用不走if (0)分支
[security_add_hooks() 填充静态调用表]
static_calls_table.capable[MAX_LSM_COUNT - 1] (最后注册的)
static_calls_table.capable[MAX_LSM_COUNT - 2]
...
static_calls_table.capable[0] (最先注册的,如 capabilities)
调用时从 [0] 开始向后扫描,遇到第一个非默认返回值即停止
// include/linux/lsm_hooks.h:186
#define DEFINE_LSM(lsm) \
static struct lsm_info __lsm_##lsm \
__used __section(".lsm_info.init") \
__aligned(sizeof(unsigned long))struct lsm_info 包含 LSM 的完整元信息(include/linux/lsm_hooks.h:170):
// include/linux/lsm_hooks.h:170
struct lsm_info {
const struct lsm_id *id;
enum lsm_order order; // LSM_ORDER_FIRST / MUTABLE / LAST
unsigned long flags; // LSM_FLAG_LEGACY_MAJOR / EXCLUSIVE
struct lsm_blob_sizes *blobs; // 请求的 blob 大小
int *enabled; // 由 CONFIG_LSM 控制
int (*init)(void); // 主初始化函数
// ... 各阶段 initcall 回调
};enum lsm_order 决定 LSM 的执行顺序:
LSM_ORDER_FIRST = -1:仅 capabilities 使用LSM_ORDER_MUTABLE = 0:SELinux / AppArmor / BPF-LSM 等LSM_ORDER_LAST = 1:仅 IMA/EVM 使用
LSM hook 定义在 include/linux/lsm_hook_defs.h,按宏 LSM_HOOK(返回类型, 默认值, 名称, 参数...) 声明。全部 hook 约 250 个,覆盖内核所有关键操作路径。
[Task Hook 调用场景]
进程 fork/exec/exit ─────────────────────────────────────┐
│
task_alloc ← fork 时分配任务 blob │
task_free ← 进程退出释放 blob │
bprm_creds_for_exec ← execve 前准备新凭证 │
bprm_check_security ← execve 安全最终检查 │
bprm_committed_creds ← 新凭证提交后通知 │
│
capget/capset ← 读写 capability 集合 │
capable ← 能力检查(最频繁的 hook 之一) │
ptrace_access_check ← ptrace 访问检查 │
ptrace_traceme ← 被 trace 检查 └─
来自 include/linux/lsm_hook_defs.h:36:
LSM_HOOK(int, 0, ptrace_access_check, struct task_struct *child, unsigned int mode)
LSM_HOOK(int, 0, ptrace_traceme, struct task_struct *parent)
LSM_HOOK(int, 0, capget, const struct task_struct *target, ...)
LSM_HOOK(int, 0, capset, struct cred *new, const struct cred *old, ...)
LSM_HOOK(int, 0, capable, const struct cred *cred, struct user_namespace *ns,
int cap, unsigned int opts) [inode Hook 覆盖的文件系统操作]
inode_alloc_security ← 新 inode 创建(分配安全 blob)
inode_free_security ← inode 释放
inode_init_security ← 设置 xattr 安全标签(如 SELinux context)
inode_create ← open(O_CREAT) 创建文件
inode_link ← 创建硬链接
inode_unlink ← 删除文件
inode_symlink ← 创建符号链接
inode_mkdir ← 创建目录
inode_rmdir ← 删除目录
inode_rename ← 重命名
inode_permission ← VFS 权限检查(最核心的 hook)
inode_setattr ← 修改文件属性(chmod/chown/truncate)
inode_getattr ← stat 系统调用
inode_setxattr ← 设置扩展属性
inode_getxattr ← 获取扩展属性
inode_listxattr ← 列出扩展属性
inode_removexattr ← 删除扩展属性
inode_readlink ← 读符号链接
inode_follow_link ← 跟随符号链接(防止时序攻击)
来自 include/linux/lsm_hook_defs.h:115-158:
LSM_HOOK(int, 0, inode_permission, struct inode *inode, int mask)
LSM_HOOK(int, 0, inode_setattr, struct mnt_idmap *idmap,
struct dentry *dentry, struct iattr *attr)
LSM_HOOK(int, 0, inode_setxattr, struct mnt_idmap *idmap,
struct dentry *dentry, const char *name, const void *value,
size_t size, int flags) [file Hook 调用时机]
file_open ← open() 系统调用成功后(文件描述符创建时)
file_receive ← 通过 UNIX 域套接字接收文件描述符
file_ioctl ← ioctl() 系统调用
mmap_addr ← mmap() 地址检查(防止 mmap 到低地址)
mmap_file ← mmap() 映射文件权限检查
file_mprotect ← mprotect() 权限修改
file_lock ← flock/fcntl 文件锁
file_send_sigiotask ← 发送 SIGIO 异步通知
file_truncate ← 截断文件(open(O_TRUNC) 或 truncate())
[网络操作 Hook 分布]
套接字生命周期:
socket_create ← socket() 系统调用
socket_bind ← bind() 系统调用
socket_connect ← connect() 系统调用
socket_listen ← listen() 系统调用
socket_accept ← accept() 系统调用
socket_sendmsg ← send/sendto/sendmsg 系统调用
socket_recvmsg ← recv/recvfrom/recvmsg 系统调用
数据包路径:
socket_sock_rcv_skb ← 入包过滤(网络层)
inet_conn_request ← TCP SYN 接收
inet_csk_clone ← TCP 连接克隆
Netfilter 集成:
sk_clone_security ← 套接字克隆时复制安全标签
secmark_relabel_packet ← 数据包重标签(SELinux SECMARK)
[IPC Hook 分类]
消息队列:
msg_queue_alloc_security ← 创建消息队列
msg_queue_msgctl ← msgctl() 操作
msg_queue_msgsnd ← msgsnd() 发送
msg_queue_msgrcv ← msgrcv() 接收
共享内存:
shm_alloc_security ← shmget() 创建
shm_shmat ← shmat() 附加
信号量:
sem_alloc_security ← semget() 创建
sem_semop ← semop() 操作
进程间信号:
task_kill ← kill() / tkill() / tgkill()
security/security.c:447 定义了两个核心分发宏:
// security/security.c:447
#define call_void_hook(HOOK, ...) \
do { \
LSM_LOOP_UNROLL(__CALL_STATIC_VOID, HOOK, __VA_ARGS__); \
} while (0)
// security/security.c:462
#define call_int_hook(HOOK, ...) \
({ \
__label__ OUT; \
int RC = LSM_RET_DEFAULT(HOOK); \
\
LSM_LOOP_UNROLL(__CALL_STATIC_INT, RC, HOOK, OUT, __VA_ARGS__); \
OUT: \
RC; \
})LSM_LOOP_UNROLL 在编译期展开为对每个静态调用槽的检查:
// security/security.c:440
#define __CALL_STATIC_VOID(NUM, HOOK, ...) \
do { \
if (static_branch_unlikely(&SECURITY_HOOK_ACTIVE_KEY(HOOK, NUM))) { \
static_call(LSM_STATIC_CALL(HOOK, NUM))(__VA_ARGS__); \
} \
} while (0);
#define __CALL_STATIC_INT(NUM, R, HOOK, LABEL, ...) \
do { \
if (static_branch_unlikely(&SECURITY_HOOK_ACTIVE_KEY(HOOK, NUM))) { \
R = static_call(LSM_STATIC_CALL(HOOK, NUM))(__VA_ARGS__); \
if (R != LSM_RET_DEFAULT(HOOK)) \
goto LABEL; /* 非默认返回值:提前退出 */ \
} \
} while (0); [call_int_hook 执行流程]
RC = 默认值(通常为 0 = 允许)
┌─── 检查槽位 [0] 是否激活?
│ YES → 调用 LSM[0].hook()
│ RC = 返回值
│ 若 RC != 默认值 → goto OUT(提前结束)
│ NO → 跳过
│
├─── 检查槽位 [1] 是否激活?
│ ...(同上)
│
└─── 所有槽位检查完毕
OUT:
return RC;
关键语义:
call_int_hook在任何 LSM 返回非默认值(通常是错误码,如-EACCES)时立即停止- 第一个拒绝就生效(fail-safe)
call_void_hook则调用所有激活的 hook,不能中途停止
// security/security.c:634
int security_capable(const struct cred *cred,
struct user_namespace *ns,
int cap, unsigned int opts)
{
return call_int_hook(capable, cred, ns, cap, opts);
}调用路径:
capable(CAP_NET_BIND_SERVICE) // kernel/capability.c:414
→ ns_capable(&init_user_ns, cap) // kernel/capability.c:361
→ ns_capable_common(ns, cap, CAP_OPT_NONE) // :331
→ security_capable(current_cred(), ns, cap, opts)
→ call_int_hook(capable, ...)
→ capabilities_capable() // 总是第一个检查
→ selinux_capable() // 若 SELinux 启用
→ apparmor_capable() // 若 AppArmor 启用
ns_capable_common 的关键逻辑(kernel/capability.c:331):
static bool ns_capable_common(struct user_namespace *ns, int cap, unsigned int opts)
{
int capable;
if (unlikely(!cap_valid(cap))) {
pr_crit("capable() called with invalid cap=%u\n", cap);
BUG();
}
capable = security_capable(current_cred(), ns, cap, opts);
if (capable == 0) {
current->flags |= PF_SUPERPRIV; // 标记使用了超级权限
return true;
}
return false;
}传统函数指针调用在现代 CPU 上有严重的间接分支预测问题,尤其是 Spectre v2 漏洞被发现后,内核需要对所有间接跳转进行 retpoline 硬化。
static_call 机制将间接调用替换为直接调用:
传统方式(间接函数指针):
call *(%rax) ← 需要 retpoline 保护,约 15-30 cycle 开销
static_call 方式:
call selinux_capable ← 直接调用,约 1-2 cycle
(函数地址在运行时通过修改机器码写入,初始化后不再改变)
static_branch_unlikely:
test %rdi, %rdi ← 几乎免费的分支预测(未激活的 LSM slot)
(基于 jump_label 机制,未激活时是一个 NOP 指令)
LSM stacking 允许多个 LSM 同时运行。策略:所有 LSM 全部通过才允许操作。
[LSM Stacking 执行模型]
操作请求
│
v
┌──────────────────────────────────────────────┐
│ capabilities(LSM_ORDER_FIRST,永远第一个) │
│ 检查 cap_effective 是否有所需 capability │
└─────────────────────┬────────────────────────┘
│ PASS
v
┌──────────────────────────────────────────────┐
│ SELinux(若启用) │
│ AVC 查询:ssid + tsid + tclass → allow? │
└─────────────────────┬────────────────────────┘
│ PASS
v
┌──────────────────────────────────────────────┐
│ AppArmor(若启用) │
│ 路径名 + profile 规则 → allow? │
└─────────────────────┬────────────────────────┘
│ PASS
v
┌──────────────────────────────────────────────┐
│ BPF-LSM(若挂载了 BPF 程序) │
│ 执行 BPF 程序,返回 0=allow / <0=deny │
└─────────────────────┬────────────────────────┘
│ PASS
v
┌──────────────────────────────────────────────┐
│ IMA/EVM(LSM_ORDER_LAST) │
│ 完整性验证(度量、评估、强制) │
└─────────────────────┬────────────────────────┘
│ 全部 PASS
v
操作允许
CONFIG_LSM 内核配置选项指定 LSM 的加载顺序,默认值为:
CONFIG_LSM="landlock,lockdown,yama,integrity,selinux,smack,tomoyo,apparmor"
lsm_order 枚举(include/linux/lsm_hooks.h:148):
// include/linux/lsm_hooks.h:148
enum lsm_order {
LSM_ORDER_FIRST = -1, // capabilities 专用
LSM_ORDER_MUTABLE = 0, // 普通 LSM
LSM_ORDER_LAST = 1, // IMA/EVM 专用
};某些 LSM 互斥:同一时间只能有一个"主要" MAC LSM 处于强制模式。
// include/linux/lsm_hooks.h:145
#define LSM_FLAG_LEGACY_MAJOR BIT(0) // 旧式主要 LSM(SELinux/AppArmor/Smack 之一)
#define LSM_FLAG_EXCLUSIVE BIT(1) // 与其他主要 LSM 互斥每个 LSM 需要在内核对象(inode/cred/file 等)上附加自己的私有安全数据。传统方式是每个对象包含一个 void *security 指针,每个 LSM 独占它。LSM stacking 要求多个 LSM 共享同一指针,因此引入了"blob"机制。
// include/linux/lsm_hooks.h:104
struct lsm_blob_sizes {
unsigned int lbs_cred; // struct cred->security 中的偏移量/大小
unsigned int lbs_file; // struct file->f_security 中的偏移量/大小
unsigned int lbs_ib; // InfiniBand 安全 blob
unsigned int lbs_inode; // struct inode->i_security
unsigned int lbs_sock; // struct sock->sk_security
unsigned int lbs_superblock; // struct super_block->s_security
unsigned int lbs_ipc; // IPC 对象安全 blob
unsigned int lbs_key; // key->security
unsigned int lbs_msg_msg; // msg_msg->security
unsigned int lbs_perf_event; // perf_event->security
unsigned int lbs_task; // task_struct->security
unsigned int lbs_xattr_count; // xattr 槽位数
unsigned int lbs_tun_dev; // TUN 设备安全 blob
unsigned int lbs_bdev; // 块设备安全 blob
unsigned int lbs_bpf_map; // bpf_map->security
unsigned int lbs_bpf_prog; // bpf_prog->aux->security
unsigned int lbs_bpf_token; // bpf_token->security
}; [安全 blob 内存布局示例(cred->security)]
┌────────────────────────────────────────┐
│ offset 0: SELinux 数据 │ ← selinux_blob_sizes.lbs_cred 字节
│ struct cred_security_struct │
│ ├── osid (u32) │
│ ├── sid (u32) │
│ ├── exec_sid (u32) │
│ └── ... │
├────────────────────────────────────────┤
│ offset N: AppArmor 数据 │ ← apparmor_blob_sizes.lbs_cred 字节
│ ... │
├────────────────────────────────────────┤
│ offset M: BPF-LSM 数据 │
│ ... │
└────────────────────────────────────────┘
访问方式(SELinux 示例,security/selinux/include/objsec.h:182):
static inline struct cred_security_struct *selinux_cred(const struct cred *cred)
{
return cred->security + selinux_blob_sizes.lbs_cred;
}
框架在初始化时汇总所有 LSM 的 blob 大小需求(security/security.c:207):
// security/security.c:207
int lsm_cred_alloc(struct cred *cred, gfp_t gfp)
{
return lsm_blob_alloc(&cred->security, blob_sizes.lbs_cred, gfp);
}// security/security.c:221(inode)
static int lsm_inode_alloc(struct inode *inode, gfp_t gfp)
{
inode->i_security = kmem_cache_zalloc(lsm_inode_cache, gfp);
...
}
// security/security.c:242(task)
int lsm_task_alloc(struct task_struct *task)
{
return lsm_blob_alloc(&task->security, blob_sizes.lbs_task, GFP_KERNEL);
}SELinux(Security Enhanced Linux)由 NSA 开发,实现了强制访问控制中的类型强制(Type Enforcement)模型。
[SELinux 安全模型层次]
┌─────────────────────────────────────────────────────┐
│ SELinux 策略(Policy) │
│ │
│ 用户(user) │
│ └── 角色(role) │
│ └── 类型(type)/ 域(domain) │
│ └── 级别(level)[MLS 模式] │
└─────────────────────────────────────────────────────┘
│
│ 安全上下文格式:user:role:type:level
│ 例:system_u:system_r:httpd_t:s0
│
v
┌─────────────────────────────────────────────────────┐
│ 类型强制(TE)规则 │
│ │
│ allow httpd_t httpd_config_t : file { read open } │
│ ↑ ↑ ↑ ↑ ↑ │
│ 规则 源域 目标类型 类 权限集合 │
└─────────────────────────────────────────────────────┘
AVC 是 SELinux 的核心性能优化,缓存最近的访问决策以避免每次都查询策略数据库。
[AVC 查询流程]
操作请求:httpd_t 读取 passwd_t 文件
│
v
avc_has_perm(ssid=httpd_t, tsid=passwd_t, tclass=file, requested=READ)
─── security/selinux/include/avc.h:140
│
v
┌─────────────────────────────────────────────────────┐
│ AVC 缓存查找 │
│ key = (ssid, tsid, tclass) │
│ 命中?→ 返回缓存的 av_decision │
└──────────────────┬──────────────────────────────────┘
│ 未命中
v
┌─────────────────────────────────────────────────────┐
│ 慢路径:策略查询 │
│ security_compute_av(ssid, tsid, tclass, avd) │
│ 查询策略数据库(selinux_state.policy) │
└──────────────────┬──────────────────────────────────┘
│
v
av_decision:
├── allowed (u32): 允许的权限位图
├── auditallow (u32): 需要审计的允许操作
└── auditdeny (u32): 需要审计的拒绝操作
allowed & requested == requested → 允许
allowed & requested != requested → 拒绝
│
v
avc_audit() ─── security/selinux/include/avc.h:123
若需要审计(允许/拒绝),写入 audit 日志:
"avc: denied { read } for pid=1234 comm="httpd" ..."
// security/selinux/include/avc.h:140
int avc_has_perm(u32 ssid, u32 tsid, u16 tclass, u32 requested,
struct common_audit_data *auditdata);
// 无审计版本(性能敏感路径使用)
int avc_has_perm_noaudit(u32 ssid, u32 tsid, u16 tclass, u32 requested,
unsigned int flags, struct av_decision *avd);
// AVC_STRICT: 忽略 permissive 模式
// AVC_EXTENDED_PERMS: 更新扩展权限缓存selinux_audit_data(security/selinux/include/avc.h:48):
struct selinux_audit_data {
u32 ssid; // 源 SID(Security Identifier)
u32 tsid; // 目标 SID
u16 tclass; // 目标安全类(file/socket/process 等)
u32 requested; // 请求的权限位图
u32 audited; // 实际被审计的权限
u32 denied; // 被拒绝的权限
int result; // 结果(0=允许,<0=拒绝)
} __randomize_layout;SELinux 为每类内核对象维护独立的安全结构(security/selinux/include/objsec.h):
// objsec.h:40
struct cred_security_struct {
u32 osid; // 上次 execve 之前的 SID
u32 sid; // 当前 SID(进程标签)
u32 exec_sid; // execve 时要转换到的 SID
u32 create_sid; // fscreate SID(新建文件的默认 SID)
u32 keycreate_sid; // 密钥创建 SID
u32 sockcreate_sid; // 套接字创建 SID
};
// objsec.h:74
struct inode_security_struct {
struct inode *inode; // 反向指针
u32 task_sid; // 创建该 inode 的任务的 SID
u32 sid; // inode 自身的 SID(文件标签)
u16 sclass; // 安全类(文件/目录/套接字等)
unsigned char initialized; // 是否已初始化
spinlock_t lock;
};SELinux 策略通过 selinuxfs 加载,挂载在 /sys/fs/selinux/:
[SELinux 策略加载路径]
用户空间:
semanage / setfiles / load_policy
│ write policy binary
v
/sys/fs/selinux/load
│
v
sel_write_load() ← selinuxfs 写处理函数
│
v
security_load_policy() ← security/selinux/ss/services.c
│
├── 解析二进制策略
├── 构建类型表、规则表、SID 映射
├── 通知 AVC 缓存失效(avc_ss_reset())
└── 原子替换 selinux_state.policy
安全上下文示例:
- 进程:
system_u:system_r:httpd_t:s0(user:role:type:level) - 文件:
system_u:object_r:httpd_config_t:s0 - 策略规则:
allow httpd_t httpd_config_t:file { read getattr open }
SELinux 运行模式:
├── enforcing:拒绝违反策略的操作,并记录日志
├── permissive:允许所有操作,但记录应该被拒绝的操作
└── disabled:完全关闭 SELinux
permissive 模式用于策略调试,getenforce/setenforce 控制
也可以为特定域设置 permissive(permissive httpd_t)
[AppArmor vs SELinux 对比]
┌──────────────────┬───────────────────┬───────────────────────┐
│ 特性 │ SELinux │ AppArmor │
├──────────────────┼───────────────────┼───────────────────────┤
│ 标签机制 │ inode 标签(SID) │ 路径名匹配 │
│ 策略对象 │ 类型/角色/用户 │ profile(按程序) │
│ 策略语言 │ TE/MLS/RBAC │ 正则表达式路径规则 │
│ 文件系统依赖 │ 需要 xattr │ 不需要 xattr │
│ 策略复杂度 │ 高 │ 低(更易配置) │
│ 硬链接处理 │ 正确(标签固定) │ 可能绕过(路径欺骗) │
│ namespace 感知 │ 有限 │ 良好 │
└──────────────────┴───────────────────┴───────────────────────┘
AppArmor 的基本单元是 profile(配置文件),每个程序可以有一个 profile:
[AppArmor Profile 结构]
/etc/apparmor.d/usr.sbin.nginx:
/usr/sbin/nginx {
# 能力
capability net_bind_service,
capability setuid,
# 文件规则
/etc/nginx/** r, # 读 nginx 配置
/var/log/nginx/** rw, # 读写日志
/var/www/html/** r, # 读 web 内容
/run/nginx.pid rw, # PID 文件
deny /etc/shadow r, # 明确拒绝读取密码文件
# 网络规则
network tcp,
# exec 规则
/usr/sbin/nginx ix, # 自身执行(继承 profile)
}
profile 名称通常是可执行文件路径
// security/apparmor/include/policy.h:73
enum profile_mode {
APPARMOR_ENFORCE, // 强制模式:拒绝违规操作
APPARMOR_COMPLAIN, // 投诉模式:允许但记录(调试用)
APPARMOR_KILL, // 杀死模式:违规则杀死进程
APPARMOR_UNCONFINED, // 无限制模式:不受约束
APPARMOR_USER, // 用户模式:投诉模式的变体
};// security/apparmor/include/file.h:42
struct aa_file_ctx {
spinlock_t lock;
struct aa_label __rcu *label; // 文件打开时的标签快照
u32 allow; // 打开时允许的权限位图
};文件打开时(hook_file_open),AppArmor 将当前进程 label 和允许的权限缓存在 aa_file_ctx 中。后续的 file_permission 检查只需比对缓存,不必重新查询 profile。
open("/etc/passwd", O_RDONLY)
│
v
security_file_open(file) ← security/security.c
│ call_int_hook(file_open, ...)
v
apparmor_file_open(file)
│
├── 获取当前进程 label:aa_get_task_label()
├── 构建路径名:aa_path_name()
├── 查询 profile 规则:aa_path_perm()
│ ├── 遍历标签中的所有 profile
│ ├── aa_str_perms():DFA 状态机匹配路径名
│ └── 比较请求权限 vs 规则允许权限
└── 若拒绝:aa_audit_file() 记录拒绝日志
返回 -EACCES
路径匹配使用 DFA(确定有限自动机),由正则表达式编译而来,运行时高效匹配。
AppArmor 支持 namespace(命名空间),允许在容器内定义独立的策略:
AppArmor namespace 结构:
├── 根命名空间(系统全局)
│ └── 可以有多个 profile
├── 容器命名空间(/sys/kernel/security/apparmor/policy/namespaces/)
│ ├── ns_name/ ← 为特定容器创建
│ └── 该 ns 内的 profile 相互隔离
└── 嵌套命名空间(namespace 内可嵌套)
BPF-LSM(Linux 5.7+)允许用户空间通过 BPF 程序编写安全策略,并在内核 LSM hook 点执行。这打破了以往"安全模块必须编译进内核"的限制。
[BPF-LSM 架构]
用户空间(安全策略工程师)
├── 编写 C 程序,使用 libbpf
├── 声明 SEC("lsm/file_open") 函数
└── 调用 bpf(BPF_PROG_LOAD, BPF_PROG_TYPE_LSM, ...)
│
v 内核 BPF 验证器
├── 类型检查(BTF)
├── 指令限制(循环、内存访问)
├── 验证安全性(无有害操作)
└── JIT 编译为机器码
│
v
bpf(BPF_LINK_CREATE, ...) ← attach 到指定 LSM hook
│
v
static_calls_table.file_open[N] = &bpf_lsm_trampoline
│ (通过 BPF trampoline 调用 JIT 代码)
v
在每次 file_open 时执行 BPF 程序
// include/uapi/linux/bpf.h(BPF 程序类型)
BPF_PROG_TYPE_LSM // LSM hook BPF 程序
// BPF 程序可以 attach 的 hook 点(bpf_lsm_* 系列)
// 每个 LSM hook 都有对应的 bpf_lsm_xxx 函数,通过 BTF 暴露
// 示例:BPF-LSM 程序拦截文件打开
SEC("lsm/file_open")
int BPF_PROG(restrict_open, struct file *file)
{
// 获取文件路径
struct path *path = &file->f_path;
// ... 安全检查 ...
return 0; // 允许
// return -EACCES; // 拒绝
}Linux 5.12 引入了 sleepable BPF 程序,允许 LSM BPF hook 执行可能阻塞的操作:
普通 BPF(不可 sleep):
├── 执行时禁止阻塞(抢占禁用、RCU 读锁等)
├── 不能使用 bpf_copy_from_user() 等需要 page fault 的辅助函数
└── 速度快,但功能受限
Sleepable BPF(LINUX_5.12+):
├── 以 BPF_F_SLEEPABLE 标志加载
├── 可以调用 bpf_copy_from_user()(需要处理缺页)
├── 可以访问 bpf_ringbuf_output() 等
└── 用于需要用户空间内存读取的安全检查
// include/uapi/linux/capability.h:414
#define CAP_BPF 39
/*
* CAP_BPF 允许:
* - 创建所有类型的 BPF map
* - 高级验证器特性(有界循环、间接变量访问等)
* - 加载 BPF Type Format (BTF) 数据
* - 获取 xlated/JIT 代码
* - 使用 bpf_spin_lock() helper
*
* 加载追踪程序:需要 CAP_PERFMON + CAP_BPF
* 加载网络程序:需要 CAP_NET_ADMIN + CAP_BPF
* 加载 LSM 程序:需要 CAP_BPF(+ 可能需要 CAP_PERFMON for trusted)
*/seccomp(secure computing)是与 LSM 框架并列的另一安全机制,专注于系统调用层面的过滤。它不检查对象(文件/网络),而是检查进程是否被允许调用特定的系统调用。
[seccomp 与 LSM 的关系]
系统调用入口
│
v
syscall_enter_from_user_mode()
│
├─→ seccomp 检查(先于 LSM)
│ │ 若 seccomp 拒绝 → SIGKILL / SIGSYS / 返回错误
│ │ 若 seccomp 允许 → 继续
│ v
├─→ 系统调用处理函数(如 sys_open)
│ │
│ └─→ security_xxx() → LSM 检查
│
v
返回用户空间
// include/uapi/linux/seccomp.h:10
#define SECCOMP_MODE_DISABLED 0 // 未启用
#define SECCOMP_MODE_STRICT 1 // strict 模式
#define SECCOMP_MODE_FILTER 2 // filter 模式(BPF)
// 内部(kernel/seccomp.c:35)
#define SECCOMP_MODE_DEAD (SECCOMP_MODE_FILTER + 1) // 进程将被杀死strict 模式(Mode 1):只允许 read、write、exit、sigreturn 四个系统调用,其他调用立即触发 SIGKILL。
filter 模式(Mode 2):通过用户定义的 BPF 程序过滤每个系统调用。
// include/uapi/linux/seccomp.h:62
struct seccomp_data {
int nr; // 系统调用号
__u32 arch; // 架构标识(AUDIT_ARCH_X86_64 等)
__u64 instruction_pointer; // 发起系统调用时的指令指针(RIP)
__u64 args[6]; // 系统调用的 6 个参数
};BPF 过滤器以 seccomp_data 作为输入,返回 32 位动作码。
// include/uapi/linux/seccomp.h:38
#define SECCOMP_RET_KILL_PROCESS 0x80000000U // 杀死整个进程(SIGKILL)
#define SECCOMP_RET_KILL_THREAD 0x00000000U // 杀死当前线程(SIGKILL)
#define SECCOMP_RET_KILL SECCOMP_RET_KILL_THREAD
#define SECCOMP_RET_TRAP 0x00030000U // 发送 SIGSYS(gvisor 用此实现虚拟化)
#define SECCOMP_RET_ERRNO 0x00050000U // 返回错误码(低 16 位为 errno)
#define SECCOMP_RET_USER_NOTIF 0x7fc00000U // 通知用户空间监控进程
#define SECCOMP_RET_TRACE 0x7ff00000U // 传递给 ptrace tracer
#define SECCOMP_RET_LOG 0x7ffc0000U // 允许但记录日志
#define SECCOMP_RET_ALLOW 0x7fff0000U // 无条件允许
// 值越小越严格(从最严格到最宽松的顺序)
// 多个 filter 叠加时,取最严格的(最小的)返回值 [seccomp action 优先级(从最严格到最宽松)]
KILL_PROCESS (0x80000000) ← 最高优先级
KILL_THREAD (0x00000000)
TRAP (0x00030000)
ERRNO (0x00050000)
USER_NOTIF (0x7fc00000)
TRACE (0x7ff00000)
LOG (0x7ffc0000)
ALLOW (0x7fff0000) ← 最低优先级
// kernel/seccomp.c:224
struct seccomp_filter {
refcount_t refs; // 引用计数(任务附加、fork、通知者各计一)
refcount_t users; // 直接使用者计数
bool log; // 是否记录所有非 ALLOW 动作
bool wait_killable_recv; // 通知接收时进入 killable 睡眠
struct action_cache cache; // arch/syscall → action 的缓存
struct seccomp_filter *prev; // 链接到更老的 filter(树形结构)
struct bpf_prog *prog; // BPF 程序(实际过滤逻辑)
struct notification *notif; // USER_NOTIF 相关信息
struct mutex notify_lock; // 通知锁
wait_queue_head_t wqh; // USER_NOTIF 等待队列
};filter 以树的形式组织:current->seccomp.filter 指向最新安装的 filter,prev 链接到父 filter。fork 后子进程共享父进程的 filter 树。
[seccomp 使用场景]
Chrome 浏览器:
└── 渲染进程通过 seccomp 限制系统调用
仅允许绘图、IPC 等少量调用
Docker/containerd:
└── 容器内进程默认禁止约 44 个"危险"系统调用
如 kexec_load, create_module, mount 等
systemd(sandboxing):
└── SystemCallFilter= 配置项限制服务进程的系统调用
OpenSSH:
└── 子进程在 pre-auth 阶段启用 seccomp filter
防止漏洞利用访问危险系统调用
Linux 5.0 引入的 SECCOMP_RET_USER_NOTIF 允许用户空间进程充当"syscall 代理":
[USER_NOTIF 工作流程]
被监控进程 监控进程(如容器运行时)
──────────── ──────────────────────
write(fd, ...)
│
├→ seccomp 返回 USER_NOTIF
│ 阻塞等待
│ SECCOMP_IOCTL_NOTIF_RECV
│ ← 读取通知(系统调用信息)
│ 决策:允许/拒绝/修改返回值
│ SECCOMP_IOCTL_NOTIF_SEND
│ → 发送响应
↑
write() 返回(监控进程指定的返回值)
传统 SUID root 的问题:程序获得完整 root 权限。File capability(setcap 命令)允许非 SUID 程序获得特定能力:
[文件 capability 设置与执行]
设置(用户空间):
setcap cap_net_bind_service+ep /usr/bin/node
↑ ↑ ↑ ↑
命令 capability + ep = effective+permitted
xattr 存储(security.capability):
struct vfs_cap_data {
__le32 magic_etc; // 版本 + 标志
struct {
__le32 permitted; // 文件的许可 capability
__le32 inheritable; // 文件的可继承 capability
} data[VFS_CAP_U32]; // 两个 u32(64 位)
};
// include/uapi/linux/capability.h:74
exec 时 capability 计算(POSIX 规则):
P'(permitted) = (P(inheritable) & F(inheritable)) | (F(permitted) & P(bounding))
P'(effective) = F(effective) ? P'(permitted) : P'(effective) & P(permitted)
P'(inheritable) = P(inheritable)
P'(ambient) = (file 有 capability) ? 0 : P(ambient)
其中:
P = exec 前进程的 capability
F = 文件的 capability
P' = exec 后进程的 capability
Ambient set 是 Linux 4.3 引入的特性,解决了传统 capability 继承机制的问题:非 root 进程无法通过 exec 传递 capability 给子进程(除非文件有对应的 file capability)。
[Ambient Set 的必要性]
问题:
root shell (cap_net_admin+ep)
└── su user_A
└── 执行 network_manager(无 SUID,无 file capability)
→ 没有 cap_net_admin!无法管理网络
解决(Ambient Set):
root shell
└── prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, CAP_NET_ADMIN, ...)
└── su user_A
└── 执行 network_manager
→ ambient 自动添加到 permitted 和 effective!
不变量(include/linux/cred.h:172):
cap_ambient ⊆ (cap_permitted ∩ cap_inheritable)
(必须同时在 P 和 I 中才能放入 ambient)
Bounding set 的作用:
├── 限制进程通过 exec 获得的能力上界
├── 即使文件有 file capability,也无法超出 bounding set
└── 一旦从 bounding set 中移除某个 capability,该进程(及其子孙)永远无法获得
操作:
prctl(PR_CAPBSET_DROP, CAP_SYS_MODULE) ← 从 bounding set 删除(不可逆)
prctl(PR_CAPBSET_READ, CAP_SYS_MODULE) ← 查询是否在 bounding set 中
容器隔离应用:
容器运行时在 fork/exec 容器主进程前,
从 bounding set 删除大量危险 capability(如 CAP_SYS_MODULE、CAP_SYS_RAWIO),
即使容器内程序有对应 file capability 也无效
capable(CAP_NET_ADMIN) // kernel/capability.c:414
= ns_capable(&init_user_ns, CAP_NET_ADMIN) // :416
ns_capable(ns, cap) // kernel/capability.c:361
→ ns_capable_common(ns, cap, CAP_OPT_NONE) // :331
→ security_capable(current_cred(), ns, cap, opts) // security/security.c:634
→ call_int_hook(capable, cred, ns, cap, opts)
→ cap_capable(cred, ns, cap, opts) // security/commoncap.c
├── 找到 cred 所在的 user_ns 与目标 ns 的关系
├── 遍历 user_ns 层次向上找
├── 检查 cred->cap_effective 是否包含 cap
└── 若在目标 ns 的祖先 ns 中有效 → 允许
→ selinux_capable(...) // 若 SELinux 启用
→ avc_has_perm(...) // 检查 capability 类的权限
CAP_OPT_* 标志(include/linux/security.h:72):
CAP_OPT_NONE = 0x0:普通检查CAP_OPT_NOAUDIT = BIT(1):不记录审计(用于探测性检查)CAP_OPT_INSETID = BIT(2):在 setid 调用中(影响 SELinux 决策)
user namespace 是 Linux 容器化的基础,允许非特权用户在新 namespace 内拥有完整的 capability 集合:
[user namespace 与 capability 的映射]
宿主机(init_user_ns):
│ root (uid=0) → 全部 capability
│ 普通用户 → 无 capability
│
└── 容器(新 user_ns,uid 映射 0→10000):
│ 容器内 uid=0 → 在该 user_ns 范围内拥有全部 capability
│ 但这些 capability 在宿主机的 init_user_ns 视角下是受限的
│
└── 嵌套 user_ns:
容器内可再创建 user_ns(需要 CAP_SYS_ADMIN in 父 ns)
ns_capable(user_ns, CAP_NET_ADMIN) 的语义:
检查当前进程的 cred 是否在 user_ns 或其祖先 ns 中有 CAP_NET_ADMIN
[ns_capable 的查找算法]
当前进程的 cred->user_ns = process_ns
目标 user_namespace = target_ns
算法:
ns = process_ns
while (ns != target_ns) {
if (ns 比 target_ns "低"(是其子孙)) ns = ns->parent
若不在目标 ns 的祖先链上 → 无权限
}
// 到达目标 ns 或其祖先
检查 cred->cap_effective 是否有对应 bit
结论:
- 子 ns 中的 capability 不对父 ns 有效
- 父 ns 中的 capability 对子 ns 中的操作有效
- 这保证了容器隔离:容器内的 root 无法影响宿主机
[主要逃逸路径与防护]
路径 1: capability 滥用
├── CAP_SYS_ADMIN 允许挂载文件系统 → 可以访问宿主机文件系统
├── 防护:容器运行时从 bounding set 删除 CAP_SYS_ADMIN
└── 使用 seccomp 过滤 mount() 系统调用
路径 2: 文件系统逃逸
├── 通过 /proc/pid/root 符号链接跨越 chroot
├── 防护:AppArmor/SELinux 限制路径访问
└── Landlock 沙箱限制文件系统视图
路径 3: 设备访问
├── /dev/mem、/dev/kmem 提供内核内存访问
├── 防护:seccomp 过滤 open() 特定路径
└── SELinux device 类策略限制设备文件访问
路径 4: 内核漏洞利用
├── 利用内核漏洞直接修改 cred->euid/cap_effective
├── 防护:KASLR、KPTI、Stack Canary
└── BPF 验证器防止恶意 BPF 程序
路径 5: Spectre/Meltdown
├── 旁道攻击读取内核内存
├── 防护:KPTI、Retpoline、IBRS/STIBP
└── seccomp SECCOMP_FILTER_FLAG_SPEC_ALLOW 控制推测执行
[mount namespace + Landlock 双重隔离]
mount namespace:
├── 容器有独立的文件系统视图(挂载点)
├── 但不阻止通过某些 fd 访问宿主机文件
└── 需要 CAP_SYS_ADMIN 创建(user ns 中可以)
Landlock(叠加在 mount ns 之上):
├── 基于 inode 对象(不受路径名变化影响)
├── 规则绑定到 inode 引用,而非路径字符串
└── 即使攻击者重新绑定路径,inode 规则仍然有效
Landlock(Linux 5.13+)是一个独特的 LSM:它允许非特权用户(无需任何 capability)定义并应用安全沙箱规则给自己的进程。这打破了以往 MAC 策略必须由 root 定义的限制。
[Landlock vs 传统 LSM]
传统 LSM(SELinux/AppArmor):
├── 策略由系统管理员定义
├── 进程无法修改适用于自己的策略
└── 需要特权才能管理策略
Landlock:
├── 任何进程可以限制自己(或子进程)
├── 策略只能收紧(不能放宽)——不可逆
├── 不需要任何 capability
└── 适合用户态程序自我沙箱化(如 Firefox、PDF 阅读器)
// security/landlock/ruleset.h:119
struct landlock_ruleset {
struct rb_root root_inode; // inode 规则的红黑树(文件系统规则)
struct rb_root root_net_port; // 网络端口规则的红黑树(Linux 6.7+)
struct landlock_hierarchy *hierarchy; // 规则层次(ptrace 保护)
union {
struct work_struct work_free; // 延迟释放(无锁路径)
struct {
struct mutex lock; // 并发修改保护
refcount_t usage; // 引用计数
u32 num_rules; // 规则数量
u32 num_layers; // 层数(merged 后的 domain 使用)
struct access_masks access_masks[]; // 每层的访问掩码(FAM)
};
};
}; [Landlock 核心概念]
1. Ruleset(规则集):
├── 用户通过 landlock_create_ruleset() 系统调用创建
├── 指定哪类访问会被限制(access_mask_fs / access_mask_net)
└── 类似于策略模板(空的,需要添加规则)
2. Rule(规则):
├── 通过 landlock_add_rule() 添加到 ruleset
├── 每条规则绑定一个文件路径(转换为 inode 对象)
└── 指定该路径允许的访问权限子集
3. Domain(域):
├── 通过 landlock_restrict_self() 将 ruleset 应用于当前进程
├── 进程的 domain 是所有历史应用的 ruleset 的合并
└── 每次 restrict_self 增加一个"层",只能进一步限制
层次合并(merge):
domain = ruleset_1 ∩ ruleset_2 ∩ ... ∩ ruleset_N
(访问权限取交集,越来越严格)
// security/landlock/access.h:49
struct access_masks {
access_mask_t fs : LANDLOCK_NUM_ACCESS_FS; // 文件系统访问权限位
access_mask_t net : LANDLOCK_NUM_ACCESS_NET; // 网络访问权限位
access_mask_t scope : LANDLOCK_NUM_SCOPE; // 作用域限制
};文件系统访问权限(LANDLOCK_ACCESS_FS_*):
LANDLOCK_ACCESS_FS_EXECUTE - 执行文件
LANDLOCK_ACCESS_FS_WRITE_FILE - 写文件
LANDLOCK_ACCESS_FS_READ_FILE - 读文件
LANDLOCK_ACCESS_FS_READ_DIR - 列目录
LANDLOCK_ACCESS_FS_REMOVE_DIR - 删除目录
LANDLOCK_ACCESS_FS_REMOVE_FILE - 删除文件
LANDLOCK_ACCESS_FS_MAKE_CHAR - 创建字符设备
LANDLOCK_ACCESS_FS_MAKE_DIR - 创建目录
LANDLOCK_ACCESS_FS_MAKE_REG - 创建普通文件
LANDLOCK_ACCESS_FS_MAKE_SOCK - 创建 UNIX 套接字
LANDLOCK_ACCESS_FS_MAKE_FIFO - 创建 FIFO
LANDLOCK_ACCESS_FS_MAKE_BLOCK - 创建块设备
LANDLOCK_ACCESS_FS_MAKE_SYM - 创建符号链接
LANDLOCK_ACCESS_FS_REFER - 重命名(跨目录)(ABI v2)
LANDLOCK_ACCESS_FS_TRUNCATE - 截断文件(ABI v3)
LANDLOCK_ACCESS_FS_IOCTL_DEV - 设备 ioctl(ABI v5)
// security/landlock/net.h:18
int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
const u16 port, access_mask_t access_rights);网络访问权限(LANDLOCK_ACCESS_NET_*):
LANDLOCK_ACCESS_NET_BIND_TCP - TCP 绑定指定端口
LANDLOCK_ACCESS_NET_CONNECT_TCP - TCP 连接指定端口
使用示例:
创建 ruleset,允许 TCP 连接 443 和 80 端口
→ 进程无法连接其他 TCP 端口
→ 即使进程持有 CAP_NET_BIND_SERVICE 也被 Landlock 覆盖
// security/landlock/fs.h:29
struct landlock_inode_security {
struct landlock_object __rcu *object; // 指向 Landlock 对象(弱引用)
};
// security/landlock/fs.h:51
struct landlock_file_security {
access_mask_t allowed_access; // 文件打开时可用的访问权限
// (用于后续 file_permission 检查)
}; [Landlock 访问决策流程]
open("/home/user/doc.txt", O_RDONLY)
│
v
hook_file_open()
│
├── 获取当前进程的 domain(cred->security 中的 Landlock 域)
├── 若 domain 为 NULL → 无限制,允许
│
├── 构建 landlock_id(inode 对象指针)
├── landlock_find_rule(domain, id)
│ └── rb_find() 在 domain->root_inode 红黑树中查找
│
├── 若找到规则:检查规则允许的 access 是否包含所需权限
├── 若未找到:检查各层是否对该类访问有限制
│ 若有限制但无白名单规则 → 拒绝
└── 拒绝时返回 -EACCES 并可选记录审计日志
Landlock 的层次(hierarchy)机制保证:受沙箱约束的进程无法通过 ptrace 逃逸。当进程 A 尝试 ptrace 进程 B 时,内核检查 A 的 Landlock domain 是否是 B 的 domain 的祖先:
A domain ⊆ B domain → A 的约束 ≥ B → A 可以 ptrace B
A domain ⊄ B domain → A 的约束更宽松 → 拒绝(防止逃逸)
┌─────────────────────────────────────────────────────────────────────┐
│ Linux 内核安全全景图 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 用户空间进程(uid/gid/pid/ns...) │
│ │ 系统调用 │
│ v │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ seccomp(系统调用过滤层) │ │
│ │ MODE_STRICT:仅 4 个系统调用 │ │
│ │ MODE_FILTER:BPF 程序决策(KILL/TRAP/ERRNO/ALLOW) │ │
│ └───────────────────────┬───────────────────────────────────────┘ │
│ │ 系统调用进入内核处理函数 │
│ v │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ DAC(自主访问控制) │ │
│ │ inode->i_mode + uid/gid 匹配 + ACL │ │
│ └───────────────────────┬───────────────────────────────────────┘ │
│ │ │
│ v │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ LSM 框架(call_int_hook / call_void_hook) │ │
│ │ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │
│ │ │capabilities│ │ SELinux │ │ AppArmor │ │ BPF-LSM │ │ │
│ │ │(ORDER_FIRST)│ │(TE/AVC)│ │(路径名)│ │(可编程) │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────────┘ │ │
│ │ ┌──────────┐ ┌──────────┐ │ │
│ │ │ Landlock │ │ IMA/EVM │ │ │
│ │ │(用户沙箱)│ │(ORDER_LAST)│ │ │
│ │ └──────────┘ └──────────┘ │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ namespace 隔离(PID/Mount/Network/User/IPC/UTS/Time) │
│ struct cred(uid/gid/capability/LSM 私有数据) │
└─────────────────────────────────────────────────────────────────────┘
┌────────────────┬──────────┬──────────┬─────────┬──────────┬──────────┐
│ 特性 │ DAC │ Capability│ SELinux │ AppArmor │ Landlock │
├────────────────┼──────────┼──────────┼─────────┼──────────┼──────────┤
│ 控制粒度 │ 粗 │ 中 │ 极细 │ 细 │ 细 │
│ 策略定义者 │ 文件owner│ 管理员 │ 管理员 │ 管理员 │ 进程自身 │
│ 需要特权 │ 否 │ 否 │ 是 │ 是 │ 否 │
│ 标签机制 │ 无 │ 无 │ inode │ 路径名 │ inode │
│ xattr 依赖 │ ACL 需要 │ 文件CAP │ 需要 │ 不需要 │ 不需要 │
│ 性能影响 │ 极小 │ 极小 │ 中 │ 中 │ 小 │
│ 配置复杂度 │ 简单 │ 简单 │ 复杂 │ 中等 │ 简单 │
│ 容器化支持 │ 好 │ 好 │ 好 │ 好 │ 极好 │
└────────────────┴──────────┴──────────┴─────────┴──────────┴──────────┘
内核安全相关关键文件:
头文件:
include/uapi/linux/capability.h - Capability 定义(0-40号)
include/linux/cred.h - struct cred 定义
include/linux/lsm_hooks.h - LSM 框架数据结构
include/linux/lsm_hook_defs.h - 所有 hook 点声明
include/linux/security.h - security_xxx() 接口
include/uapi/linux/seccomp.h - seccomp 用户空间接口
核心实现:
security/security.c - LSM 框架核心,call_int_hook 等
kernel/capability.c - capable() / ns_capable() 实现
kernel/seccomp.c - seccomp 过滤器实现
kernel/cred.c - prepare_creds / commit_creds
SELinux:
security/selinux/hooks.c - SELinux hook 实现
security/selinux/avc.c - AVC 缓存
security/selinux/include/avc.h - AVC 接口
security/selinux/include/objsec.h - SELinux 对象安全结构
AppArmor:
security/apparmor/lsm.c - AppArmor hook 实现
security/apparmor/include/policy.h - profile 定义
security/apparmor/include/file.h - 文件安全结构
Landlock:
security/landlock/ruleset.h - struct landlock_ruleset
security/landlock/fs.h - 文件系统 blob 结构
security/landlock/access.h - 访问权限类型定义
security/landlock/net.h - 网络沙箱接口
[最小权限最佳实践]
服务进程(如 web 服务器):
1. 使用 file capability(setcap cap_net_bind_service+ep /usr/sbin/nginx)
而非运行整个服务为 root
2. 配置 AppArmor/SELinux profile 限制文件访问路径
3. 使用 seccomp filter 过滤不需要的系统调用
4. 使用 Landlock 进一步限制文件系统访问
容器化工作负载:
1. 使用 user namespace,容器内 root ≠ 宿主机 root
2. drop 所有 capability,只保留必需的(--cap-drop ALL --cap-add NET_BIND_SERVICE)
3. 应用 seccomp profile(Docker 默认禁止约 44 个系统调用)
4. 使用 read-only rootfs,writable 只挂载必要目录
高安全场景(金融/政府):
1. SELinux enforcing 模式,精细化 TE 策略
2. IMA(完整性测量)+ EVM(扩展验证)
3. dm-verity 保护根文件系统
4. KASLR + KPTI + retpoline 全系列缓解措施
5. audit 子系统记录所有特权操作
[各安全机制的性能代价]
DAC 检查:
└── ~0 额外开销(与文件访问本身的缓存 miss 相比可忽略)
Capability 检查(capable()):
└── ~5-10 ns(主要是 security_capable() 的 static_call)
SELinux AVC 命中:
└── ~50-100 ns(hash 查找 + 缓存读)
SELinux AVC 未命中:
└── ~1-5 μs(策略数据库查询)
AppArmor DFA 匹配:
└── ~100-200 ns(路径名长度相关)
BPF-LSM:
└── ~10-100 ns(JIT 程序执行,取决于复杂度)
Landlock 规则查找:
└── ~50-100 ns(红黑树查找,O(log N))
seccomp filter(BPF 程序):
└── ~50-200 ns(取决于规则复杂度)
优化建议:
├── 使用 AVC allow 规则(而非 dontaudit)减少 audit 开销
├── BPF-LSM 优先用 per-CPU map 避免锁竞争
└── seccomp 把最常用的 syscall 放在 filter 最前面(BPF 短路求值)
| 字段 | 类型 | 含义 |
|---|---|---|
| uid | kuid_t | 真实 UID |
| gid | kgid_t | 真实 GID |
| suid | kuid_t | 保存的 UID |
| sgid | kgid_t | 保存的 GID |
| euid | kuid_t | 有效 UID |
| egid | kgid_t | 有效 GID |
| fsuid | kuid_t | 文件系统 UID |
| fsgid | kgid_t | 文件系统 GID |
| cap_inheritable | kernel_cap_t | 可继承 capability 集合 |
| cap_permitted | kernel_cap_t | 许可 capability 集合 |
| cap_effective | kernel_cap_t | 有效 capability 集合 |
| cap_bset | kernel_cap_t | bounding set |
| cap_ambient | kernel_cap_t | ambient set |
| security | void* | LSM 私有 blob |
| user_ns | struct user_namespace* | capability 所在 namespace |
| 字段 | 类型 | 含义 |
|---|---|---|
| nr | int | 系统调用号 |
| arch | u32 | 架构(AUDIT_ARCH_*) |
| instruction_pointer | u64 | 发起系统调用时的 RIP |
| args[6] | u64 | 系统调用参数(最多6个) |
| 字段 | 类型 | 含义 |
|---|---|---|
| root_inode | struct rb_root | 文件系统 inode 规则红黑树 |
| root_net_port | struct rb_root | 网络端口规则红黑树 |
| hierarchy | struct landlock_hierarchy* | 层次信息(ptrace 保护) |
| num_rules | u32 | 规则总数 |
| num_layers | u32 | 已合并的层数 |
| access_masks[] | struct access_masks | 每层的访问权限掩码 |
由 Claude Code 分析生成