Skip to content

Latest commit

 

History

History
2121 lines (1723 loc) · 86.6 KB

File metadata and controls

2121 lines (1723 loc) · 86.6 KB

Linux 安全框架(LSM)深度解析

基于 Linux Kernel 源码深度分析 路径:security/ | include/linux/security.h | kernel/capability.c 内核版本:Linux 6.x(master 分支)


目录

  1. Linux 安全体系概述
  2. DAC:自主访问控制
  3. Capability 体系深度解析
  4. struct cred:凭证管理核心
  5. LSM 框架架构
  6. LSM Hook 点分类详解
  7. call_int_hook / call_void_hook 调用链
  8. LSM Stacking:多模块共存
  9. LSM Blob:私有数据管理
  10. SELinux:类型强制详解
  11. AppArmor:路径名策略
  12. BPF-LSM:可编程安全策略
  13. seccomp:系统调用过滤
  14. Capability 深度:ambient、bounding、file
  15. namespace 与安全隔离
  16. Landlock:用户态沙箱
  17. 安全子系统全景总结

1. Linux 安全体系概述

1.1 安全模型的演进

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 共享机制成熟

1.2 三层安全模型的配合

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 允许的操作(反过来不行)。


2. DAC:自主访问控制

2.1 inode 权限位

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 权限

2.2 权限检查算法

内核在 fs/namei.cgeneric_permission() 中实现 DAC 检查,核心逻辑如下:

  1. 若进程 fsuid == inode->i_uid,使用 owner 权限位
  2. 若进程 fsgid == inode->i_gid 或补充组包含 i_gid,使用 group 权限位
  3. 否则使用 others 权限位
  4. 检查所需权限(MAY_READ / MAY_WRITE / MAY_EXEC)是否在该权限位集合中
  5. 若进程持有 CAP_DAC_OVERRIDE(capability 1),可绕过读写检查
  6. 若进程持有 CAP_DAC_READ_SEARCH(capability 2),可绕过读和目录搜索检查

2.3 SUID/SGID 机制

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 权限。


3. Capability 体系深度解析

3.1 Capability 的设计动机

传统 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

3.2 Capability 分类

  [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

3.3 kernel_cap_t 存储结构

// 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] 的结构体。


4. struct cred:凭证管理核心

4.1 完整结构体解析

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;

4.2 UID/GID 六元组详解

  [进程 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 的身份访问文件,但不影响其他权限检查

4.3 Capability 五元组

  [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)

4.4 COW:prepare_creds / commit_creds

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) — 行 156
  • int commit_creds(struct cred *) — 行 158
  • void abort_creds(struct cred *) — 行 159

线程安全current->cred 通过 RCU 保护,其他线程访问时需持有 RCU 读锁(rcu_read_lock())。只有当前线程可以写自己的凭证。

4.5 task_struct 中的两个凭证指针

struct task_struct {
    ...
    const struct cred __rcu  *real_cred;  // 客观凭证(该进程是被操作对象时)
    const struct cred __rcu  *cred;       // 主观凭证(该进程是操作主体时)
    ...
};

正常情况下两者相同。override_creds() 可以临时替换主观凭证(include/linux/cred.h:179),用于内核代码临时以不同身份执行操作,如 NFS 服务器代码。


5. LSM 框架架构

5.1 框架总体设计

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                   │
  │  (内置)      (可选)  (可选)   (可选)                  │
  └─────────────────────────────────────────────────────────────┘

5.2 struct security_hook_list

每个 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_idinclude/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
};

5.3 security_add_hooks() 注册机制

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 中的实现):

  1. 遍历 hooks 数组(每项对应一个 hook 名称)
  2. 找到 static_calls_table 中对应 hook 名称的静态调用槽
  3. 从后往前填充(最后注册的 LSM 占据最前的槽位,先执行)
  4. 启用对应的 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] 开始向后扫描,遇到第一个非默认返回值即停止

5.4 LSM 生命周期与 DEFINE_LSM

// 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 使用

6. LSM Hook 点分类详解

LSM hook 定义在 include/linux/lsm_hook_defs.h,按宏 LSM_HOOK(返回类型, 默认值, 名称, 参数...) 声明。全部 hook 约 250 个,覆盖内核所有关键操作路径。

6.1 Task/Capability 类 Hook

  [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)

6.2 inode 类 Hook

  [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)

6.3 file 类 Hook

  [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())

6.4 网络类 Hook

  [网络操作 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)

6.5 IPC 类 Hook

  [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()

7. call_int_hook / call_void_hook 调用链

7.1 宏展开原理

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);

7.2 int hook 的语义:fail-safe 原则

  [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,不能中途停止

7.3 典型调用示例:security_capable()

// 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;
}

7.4 静态调用(static_call)的性能优势

传统函数指针调用在现代 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 指令)

8. LSM Stacking:多模块共存

8.1 叠加机制

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
                    操作允许

8.2 CONFIG_LSM 与顺序控制

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 专用
};

8.3 独占标志

某些 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 互斥

9. LSM Blob:私有数据管理

9.1 Blob 的设计动机

每个 LSM 需要在内核对象(inode/cred/file 等)上附加自己的私有安全数据。传统方式是每个对象包含一个 void *security 指针,每个 LSM 独占它。LSM stacking 要求多个 LSM 共享同一指针,因此引入了"blob"机制。

9.2 lsm_blob_sizes

// 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
};

9.3 Blob 布局与访问

  [安全 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);
}

9.4 inode/file/task 的 blob 分配

// 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);
}

10. SELinux:类型强制详解

10.1 SELinux 核心概念

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 }  │
  │  ↑      ↑        ↑              ↑      ↑             │
  │  规则  源域    目标类型        类     权限集合       │
  └─────────────────────────────────────────────────────┘

10.2 AVC(Access Vector Cache)

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" ..."

10.3 avc_has_perm() 的完整接口

// 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_datasecurity/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;

10.4 SELinux 内核对象安全结构

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;
};

10.5 策略加载与安全上下文

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 }

10.6 permissive 模式

  SELinux 运行模式:
  ├── enforcing:拒绝违反策略的操作,并记录日志
  ├── permissive:允许所有操作,但记录应该被拒绝的操作
  └── disabled:完全关闭 SELinux

  permissive 模式用于策略调试,getenforce/setenforce 控制
  也可以为特定域设置 permissive(permissive httpd_t)

11. AppArmor:路径名策略

11.1 与 SELinux 的本质区别

  [AppArmor vs SELinux 对比]

  ┌──────────────────┬───────────────────┬───────────────────────┐
  │  特性            │  SELinux          │  AppArmor             │
  ├──────────────────┼───────────────────┼───────────────────────┤
  │  标签机制        │ inode 标签(SID) │ 路径名匹配            │
  │  策略对象        │ 类型/角色/用户    │ profile(按程序)      │
  │  策略语言        │ TE/MLS/RBAC      │ 正则表达式路径规则     │
  │  文件系统依赖    │ 需要 xattr       │ 不需要 xattr           │
  │  策略复杂度      │ 高               │ 低(更易配置)         │
  │  硬链接处理      │ 正确(标签固定) │ 可能绕过(路径欺骗)   │
  │  namespace 感知  │ 有限             │ 良好                   │
  └──────────────────┴───────────────────┴───────────────────────┘

11.2 Profile 与 Confinement

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 名称通常是可执行文件路径

11.3 profile_mode(security/apparmor/include/policy.h:73

// security/apparmor/include/policy.h:73
enum profile_mode {
    APPARMOR_ENFORCE,     // 强制模式:拒绝违规操作
    APPARMOR_COMPLAIN,    // 投诉模式:允许但记录(调试用)
    APPARMOR_KILL,        // 杀死模式:违规则杀死进程
    APPARMOR_UNCONFINED,  // 无限制模式:不受约束
    APPARMOR_USER,        // 用户模式:投诉模式的变体
};

11.4 aa_file_ctx 与文件打开时的标签缓存

// 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。

11.5 apparmor_file_open() Hook 调用链

  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(确定有限自动机),由正则表达式编译而来,运行时高效匹配。

11.6 namespace 感知

AppArmor 支持 namespace(命名空间),允许在容器内定义独立的策略:

  AppArmor namespace 结构:
  ├── 根命名空间(系统全局)
  │     └── 可以有多个 profile
  ├── 容器命名空间(/sys/kernel/security/apparmor/policy/namespaces/)
  │     ├── ns_name/  ← 为特定容器创建
  │     └── 该 ns 内的 profile 相互隔离
  └── 嵌套命名空间(namespace 内可嵌套)

12. BPF-LSM:可编程安全策略

12.1 BPF-LSM 的革命性意义

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 程序

12.2 BPF_PROG_TYPE_LSM 程序类型

// 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;  // 拒绝
}

12.3 Sleepable LSM BPF

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() 等
  └── 用于需要用户空间内存读取的安全检查

12.4 CAP_BPF 与权限控制

// 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)
 */

13. seccomp:系统调用过滤

13.1 seccomp 的定位

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
  返回用户空间

13.2 seccomp 模式

// 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):只允许 readwriteexitsigreturn 四个系统调用,其他调用立即触发 SIGKILL

filter 模式(Mode 2):通过用户定义的 BPF 程序过滤每个系统调用。

13.3 struct seccomp_data(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 位动作码。

13.4 返回值(Action)语义

// 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) ← 最低优先级

13.5 struct seccomp_filter

// 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 树。

13.6 seccomp 在实际应用中的使用

  [seccomp 使用场景]

  Chrome 浏览器:
  └── 渲染进程通过 seccomp 限制系统调用
      仅允许绘图、IPC 等少量调用

  Docker/containerd:
  └── 容器内进程默认禁止约 44 个"危险"系统调用
      如 kexec_load, create_module, mount 等

  systemd(sandboxing):
  └── SystemCallFilter= 配置项限制服务进程的系统调用

  OpenSSH:
  └── 子进程在 pre-auth 阶段启用 seccomp filter
      防止漏洞利用访问危险系统调用

13.7 USER_NOTIF:用户空间拦截

Linux 5.0 引入的 SECCOMP_RET_USER_NOTIF 允许用户空间进程充当"syscall 代理":

  [USER_NOTIF 工作流程]

  被监控进程              监控进程(如容器运行时)
  ────────────            ──────────────────────
  write(fd, ...)
      │
      ├→ seccomp 返回 USER_NOTIF
      │  阻塞等待
      │                   SECCOMP_IOCTL_NOTIF_RECV
      │                       ← 读取通知(系统调用信息)
      │                   决策:允许/拒绝/修改返回值
      │                   SECCOMP_IOCTL_NOTIF_SEND
      │                       → 发送响应
      ↑
      write() 返回(监控进程指定的返回值)

14. Capability 深度:ambient、bounding、file

14.1 File Capability(文件能力)

传统 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

14.2 Ambient Set(环境能力集)

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)

14.3 Bounding Set(能力边界集)

  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 也无效

14.4 capable() / ns_capable() 完整调用栈

  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 决策)

15. namespace 与安全隔离

15.1 user namespace 与 capability 的关系

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

15.2 capability 在 namespace 层次中的传播

  [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 无法影响宿主机

15.3 容器逃逸防护

  [主要逃逸路径与防护]

  路径 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 控制推测执行

15.4 mount namespace 与 Landlock 的协作

  [mount namespace + Landlock 双重隔离]

  mount namespace:
  ├── 容器有独立的文件系统视图(挂载点)
  ├── 但不阻止通过某些 fd 访问宿主机文件
  └── 需要 CAP_SYS_ADMIN 创建(user ns 中可以)

  Landlock(叠加在 mount ns 之上):
  ├── 基于 inode 对象(不受路径名变化影响)
  ├── 规则绑定到 inode 引用,而非路径字符串
  └── 即使攻击者重新绑定路径,inode 规则仍然有效

16. Landlock:用户态沙箱

16.1 Landlock 的独特定位

Landlock(Linux 5.13+)是一个独特的 LSM:它允许非特权用户(无需任何 capability)定义并应用安全沙箱规则给自己的进程。这打破了以往 MAC 策略必须由 root 定义的限制。

  [Landlock vs 传统 LSM]

  传统 LSM(SELinux/AppArmor):
  ├── 策略由系统管理员定义
  ├── 进程无法修改适用于自己的策略
  └── 需要特权才能管理策略

  Landlock:
  ├── 任何进程可以限制自己(或子进程)
  ├── 策略只能收紧(不能放宽)——不可逆
  ├── 不需要任何 capability
  └── 适合用户态程序自我沙箱化(如 Firefox、PDF 阅读器)

16.2 struct landlock_ruleset(核心数据结构)

// 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)
        };
    };
};

16.3 Landlock 的三个核心概念

  [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
  (访问权限取交集,越来越严格)

16.4 access_masks 与 layer 机制

// 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)

16.5 网络沙箱(Linux 6.7+)

// 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 覆盖

16.6 landlock_inode_security(inode 安全 blob)

// 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 检查)
};

16.7 规则查找算法

  [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 并可选记录审计日志

16.8 Landlock 与 ptrace 保护

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 的约束更宽松 → 拒绝(防止逃逸)

17. 安全子系统全景总结

17.1 完整安全层次图

  ┌─────────────────────────────────────────────────────────────────────┐
  │                       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 私有数据)                      │
  └─────────────────────────────────────────────────────────────────────┘

17.2 各安全机制对比

  ┌────────────────┬──────────┬──────────┬─────────┬──────────┬──────────┐
  │  特性          │ DAC      │ Capability│ SELinux │ AppArmor │ Landlock │
  ├────────────────┼──────────┼──────────┼─────────┼──────────┼──────────┤
  │  控制粒度      │ 粗       │ 中       │ 极细    │ 细       │ 细       │
  │  策略定义者    │ 文件owner│ 管理员   │ 管理员  │ 管理员   │ 进程自身 │
  │  需要特权     │ 否       │ 否       │ 是      │ 是       │ 否       │
  │  标签机制      │ 无       │ 无       │ inode   │ 路径名   │ inode    │
  │  xattr 依赖   │ ACL 需要 │ 文件CAP  │ 需要    │ 不需要   │ 不需要   │
  │  性能影响      │ 极小     │ 极小     │ 中      │ 中       │ 小       │
  │  配置复杂度    │ 简单     │ 简单     │ 复杂    │ 中等     │ 简单     │
  │  容器化支持    │ 好       │ 好       │ 好      │ 好       │ 极好     │
  └────────────────┴──────────┴──────────┴─────────┴──────────┴──────────┘

17.3 关键源文件索引

  内核安全相关关键文件:

  头文件:
  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            - 网络沙箱接口

17.4 实际系统中的安全策略建议

  [最小权限最佳实践]

  服务进程(如 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 子系统记录所有特权操作

17.5 性能代价分析

  [各安全机制的性能代价]

  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 短路求值)

附录:核心数据结构速查

A.1 struct cred 字段速查(include/linux/cred.h:113)

字段 类型 含义
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

A.2 seccomp_data 字段(include/uapi/linux/seccomp.h:62)

字段 类型 含义
nr int 系统调用号
arch u32 架构(AUDIT_ARCH_*)
instruction_pointer u64 发起系统调用时的 RIP
args[6] u64 系统调用参数(最多6个)

A.3 landlock_ruleset 关键字段(security/landlock/ruleset.h:119)

字段 类型 含义
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 分析生成