mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
5005 字
14 分钟
安全机制
2024-08-03

第 15 章:Cgroups 与 Namespaces中,我们看到了 Linux 如何通过资源隔离构建容器的边界。但隔离只是安全的一个维度——一个拥有 root 权限的容器进程仍然可以摧毁宿主机。真正的安全需要权限最小化访问控制,这正是本章的主题。

Linux 安全机制经历了从简单到复杂的演进:从 Unix 时代粗糙的 root/非 root 二元模型,到 POSIX Capabilities 的权限拆分,再到 SELinux/AppArmor 的强制访问控制(MAC),以及 Seccomp-BPF 的系统调用过滤。每一层都在弥补上一层的不足,共同构建了纵深防御体系。

本章将从传统 Unix 权限模型出发,逐层深入 Linux 安全子系统的设计与实现,剖析内核如何通过 cred 结构体管理进程凭证、通过 LSM 框架实现可插拔的安全策略、通过 Seccomp-BPF 限制系统调用面,最终理解 Linux 安全的”洋葱模型”。

一、传统 Unix 权限模型#

1.1 rwx 权限位与 uid/gid#

Unix 权限模型诞生于 1970 年代的 Multics 和早期 Unix,其核心思想极其简洁:每个文件有一个属主(owner)和一个属组(group),权限分为**读(r)、写(w)、执行(x)**三组,分别对应属主、属组和其他用户。

$ ls -l /etc/shadow
-rw-r----- 1 root shadow 1234 Apr 19 10:00 /etc/shadow

这行输出说明:/etc/shadow 的属主是 root,拥有读写权限;属组是 shadow,只有读权限;其他用户无任何权限。

内核在执行权限检查时,按以下优先级依次判断:

  1. 若进程的 euid(有效用户 ID)等于文件的 uid(属主 ID),则使用属主权限位
  2. 若进程的 egid(有效组 ID)或补充组中包含文件的 gid(属组 ID),则使用属组权限位
  3. 否则使用其他用户权限位

一旦命中某条规则,就不再继续检查。这意味着如果文件的属主权限位为空,但其他用户权限位有权限,属主反而无法访问——这是一个经常被忽视的细节。

1.2 进程凭证:真实 ID 与有效 ID#

Unix 进程有四组用户/组 ID,它们的含义各不相同:

ID 类型含义典型用途
真实 UID/GID(ruid/rgid)标识”谁启动了这个进程”审计日志、信号发送权限判断
有效 UID/GID(euid/egid)标识”进程当前以谁的身份运行”文件访问权限检查
保存的 set-user/group-ID(suid/sgid)保存 setuid 之前的 euid/egidsetuid 程序恢复原始身份
文件系统 UID/GID(fsuid/fsgid)专用于文件系统访问检查NFS 等需要区分的场景(已基本废弃)
// include/linux/cred.h — 进程凭证结构体(简化)
struct cred {
kuid_t uid; /* 真实 UID */
kgid_t gid; /* 真实 GID */
kuid_t euid; /* 有效 UID */
kgid_t egid; /* 有效 GID */
kuid_t suid; /* 保存的 set-user-ID */
kgid_t sgid; /* 保存的 set-group-ID */
kuid_t fsuid; /* 文件系统 UID(通常 = euid) */
kgid_t fsgid; /* 文件系统 GID(通常 = egid) */
// ... Capabilities、安全上下文等
};
Note

在现代内核中,UID/GID 使用 kuid_t/kgid_t 封装而非裸 uid_t/gid_t,这是为了支持用户命名空间中的 ID 映射——同一个数值在不同命名空间中可能代表不同的用户。

1.3 setuid 机制:权限提升的经典方案#

setuid 是 Unix 最古老的权限提升机制。当一个可执行文件设置了 setuid 位时,执行该文件的进程的 euid 会被设置为文件的属主 ID,而非启动者的 ruid。

最典型的例子是 passwd 命令:

$ ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 68208 Apr 19 10:00 /usr/bin/passwd

权限位中的 s 就是 setuid 标志。普通用户执行 passwd 时,进程的 euid 变为 0(root),从而获得修改 /etc/shadow 的权限。但 passwd 程序内部会仔细校验用户身份,只允许修改自己的密码。

setuid 的工作流程如下:

sequenceDiagram participant User as 普通用户(ruid=1000) participant Passwd as passwd进程 participant Kernel as 内核 participant Shadow as /etc/shadow User->>Kernel: execve("/usr/bin/passwd") Kernel->>Kernel: 检测到 setuid 位 Kernel->>Kernel: euid = 文件属主uid = 0 Note over Passwd: ruid=1000, euid=0 Passwd->>Kernel: open("/etc/shadow", O_RDWR) Kernel->>Kernel: 权限检查: euid=0 → root权限 → 允许 Kernel-->>Passwd: fd 返回成功 Passwd->>Passwd: 校验旧密码、修改新密码 Passwd->>Shadow: 写入新密码

setuid 的风险是的:如果 setuid 程序存在漏洞(如缓冲区溢出),攻击者就能以 root 身份执行任意代码。历史上无数安全漏洞都源于 setuid 程序的缺陷。这也是 Linux 引入 Capabilities、SELinux 等机制的核心动机——减少以 root 身份运行的代码量

1.4 传统模型的根本缺陷#

Unix 权限模型有两个根本性缺陷:

  1. root 全能问题:UID 0 拥有一切权限,无法拆分。一个只需绑定 80 端口的 Web 服务器,却同时获得了加载内核模块、修改系统时间、格式化磁盘等所有权限。这违反了最小权限原则

  2. 自主访问控制(DAC)的局限:文件属主可以自由修改权限位,恶意程序可以利用这一点绕过安全策略。例如,一个被入侵的进程可以 chmod 777 敏感文件,DAC 无法阻止。

Linux 从 2.2 内核开始,通过 Capabilities、LSM 框架、SELinux 等机制逐步弥补这些缺陷。

二、Linux Capabilities:拆分 root 权限#

2.1 从全能 root 到权限碎片#

Linux Capabilities 的核心思想是将 root 的全能权限拆分为细粒度的能力单元。每个 Capability 代表一类特权操作,进程可以只拥有它需要的 Capability,而不必拥有全部 root 权限。

截至 Linux 6.x 内核,共定义了约 40 个 Capabilities,以下是最常用的几个:

Capability控制的操作典型用途
CAP_NET_BIND_SERVICE绑定 1024 以下端口Web 服务器绑定 80/443
CAP_NET_RAW使用原始套接字ping、tcpdump
CAP_SYS_ADMIN大量管理操作(挂载、namespace 等)容器运行时、系统管理
CAP_SYS_PTRACE跟踪进程内存调试器(gdb)、strace
CAP_SYS_CHROOT修改根目录chroot 隔离
CAP_KILL向非自有进程发信号进程管理工具
CAP_DAC_OVERRIDE绕过文件权限检查备份工具
CAP_SETUID / CAP_SETGID修改进程 UID/GIDsetuid 程序
CAP_SETPCAP修改进程 Capabilities权限管理工具
CAP_SYS_RESOURCE绕过资源限制系统监控工具
Warning

CAP_SYS_ADMIN 被戏称为”新的 root”,因为它控制的操作多达数十种(挂载文件系统、管理 namespace、配置 BMC 等)。在容器环境中,给予 CAP_SYS_ADMIN 几乎等同于给予 root 权限。应尽量避免使用。

2.2 Capabilities 在内核中的实现#

Capabilities 的内核实现围绕 cred 结构体展开。每个进程有五组 Capability 集合:

// include/linux/cred.h — cred 中的 Capabilities 字段
struct cred {
// ... uid/gid 等
kernel_cap_t cap_inheritable; /* 可继承的 Capabilities */
kernel_cap_t cap_permitted; /* 允许使用的 Capabilities */
kernel_cap_t cap_effective; /* 当前生效的 Capabilities */
kernel_cap_t cap_bounding; /* Bounding 集合(上限) */
kernel_cap_t cap_ambient; /* 环境Capabilities(非setuid时继承) */
// ...
};

五组集合的含义和关系:

集合含义何时生效
Inheritableexecve 时可传递给子进程的 Capabilities仅当文件也有对应 Permitted 时才传递
Permitted进程允许拥有的 Capabilities 上限超出此集合的 Capability 无法获取
Effective进程当前实际生效的 Capabilities内核做权限检查时查询此集合
Bounding进程可获得的 Capabilities 的绝对上限任何情况下都无法突破
Ambient非 setuid 程序 execve 时自动继承的 Capabilities解决非 root 程序的 Capability 继承问题

它们之间的关系可以用以下流程图表示:

flowchart TD subgraph execve时的Capability计算 A[父进程的 Capabilities] --> B{目标文件是否有<br/>文件 Capabilities?} B -->|有文件 Cap| C[按文件 Cap 规则计算<br/>P'(permitted) = F(permitted) ∪ F(inheritable) ∩ P(inheritable)<br/>E'(effective) = F(effective) ? P'(permitted) : 空] B -->|无文件 Cap| D[Ambient 继承<br/>P'(permitted) = Ambient ∪ P(inheritable) ∩ Bounding<br/>E'(effective) = Ambient] C --> E[新进程的 Permitted/Effective] D --> E E --> F[Bounding 集合不变<br/>新 Bounding = 旧 Bounding] end style A fill:#3498db,color:#fff style E fill:#2ecc71,color:#fff style F fill:#e74c3c,color:#fff

2.3 文件 Capabilities#

除了进程 Capabilities,Linux 还支持为可执行文件设置 Capabilities(类似 setuid 的效果,但粒度更细):

# 给 nginx 赋予绑定特权端口的 Capability,而无需 root
$ sudo setcap cap_net_bind_service=+ep /usr/sbin/nginx
# 查看文件的 Capabilities
$ getcap /usr/sbin/nginx
/usr/sbin/nginx = cap_net_bind_service+ep
# 移除文件的 Capabilities
$ sudo setcap -r /usr/sbin/nginx

文件 Capability 的标记格式为 cap_xxx=+eip,其中:

  • e(effective):执行时自动生效
  • i(inheritable):可被子进程继承
  • p(permitted):允许使用

内核在 execve() 时根据文件 Capabilities 和进程 Capabilities 的交集计算新进程的 Capabilities。这比 setuid 安全得多——即使程序被攻破,攻击者也只能获得特定 Capability 对应的权限,而非全部 root 权限。

2.4 capsh 与 libcap:Capabilities 管理工具#

libcap 提供了用户态的 Capabilities 管理工具:

# 查看当前进程的 Capabilities
$ capsh --print
Current: = cap_chown,cap_dac_override,...+eip
Bounding set =cap_chown,cap_dac_override,...
# 以特定 Capability 启动进程
$ capsh --caps="cap_net_bind_service+ep" -- -c "./myserver"
# 降权:移除所有 Capabilities 后运行
$ capsh --drop=all -- -c "id"
uid=0(root) gid=0(root) groups=0(root) # UID 仍是 root
# 但没有任何 Capability,很多特权操作会被拒绝

2.5 内核源码:capability.c#

Capabilities 的核心逻辑在 kernel/capability.c 中:

// kernel/capability.c — 系统调用实现
// 获取进程 Capabilities
SYSCALL_DEFINE2(capget, cap_user_header_t, header, cap_user_data_t, dataptr)
{
struct __user_cap_header_struct cap_header;
// ... 从 current->cred 中读取各集合
}
// 设置进程 Capabilities
SYSCALL_DEFINE2(capset, cap_user_header_t, header, const cap_user_data_t, data)
{
// ... 权限检查:新集合不能超出 Permitted 和 Bounding
// ... 提交新的 cred
}
// 权限检查:进程是否拥有指定 Capability
bool has_capability(struct task_struct *t, int cap)
{
return cap_raised(t->cred->cap_effective, cap);
}

当内核执行特权操作时,会调用 capable() 函数检查当前进程是否拥有对应的 Capability:

// kernel/capability.c — 核心权限检查
bool capable(int cap)
{
return ns_capable(&init_user_ns, cap);
}
bool ns_capable(struct user_namespace *ns, int cap)
{
if (unlikely(!cap_valid(cap))) return false;
if (security_capable(ns, current_cred(), cap) == 0) {
// LSM 钩子也通过后,标记已使用(用于审计)
current->flags |= PF_SUPERPRIV;
return true;
}
return false;
}

注意 capable() 不仅检查 Capability 集合,还会调用 LSM 钩子(security_capable()),这意味着 SELinux 等安全模块可以在 Capability 检查通过后进一步限制权限。

三、cred 结构体:进程凭证的核心#

3.1 cred 的完整结构#

cred 结构体是 Linux 安全的基石,它集中管理了进程的所有身份和权限信息:

// include/linux/cred.h — 完整的 cred 结构体(关键字段)
struct cred {
atomic_t usage; /* 引用计数 */
kuid_t uid; /* 真实 UID */
kgid_t gid; /* 真实 GID */
kuid_t euid; /* 有效 UID */
kgid_t egid; /* 有效 GID */
kuid_t suid; /* 保存的 set-user-ID */
kgid_t sgid; /* 保存的 set-group-ID */
kuid_t fsuid; /* 文件系统 UID */
kgid_t fsgid; /* 文件系统 GID */
unsigned securebits; /* SUID 相关安全位 */
kernel_cap_t cap_inheritable; /* 可继承 Capabilities */
kernel_cap_t cap_permitted; /* 允许 Capabilities */
kernel_cap_t cap_effective; /* 生效 Capabilities */
kernel_cap_t cap_bounding; /* Bounding 集合 */
kernel_cap_t cap_ambient; /* 环境 Capabilities */
unsigned char jit_keyring; /* JIT 密钥环 */
struct key *session_keyring; /* 会话密钥环 */
struct key *process_keyring; /* 进程密钥环 */
struct key *thread_keyring; /* 线程密钥环 */
struct key *request_key_auth; /* 密钥授权 */
void *security; /* LSM 安全上下文 */
struct user_struct *user; /* 用户资源统计 */
struct user_namespace *user_ns; /* 用户命名空间 */
struct group_info *group_info; /* 补充组信息 */
struct rcu_head rcu; /* RCU 回收 */
};

其中最关键的字段是 security——这是一个不透明指针,由 LSM 框架使用。对于 SELinux,它指向 struct cred_security_struct,包含安全 ID(SID)和角色/类型/级别信息;对于 AppArmor,它指向 struct aa_label,包含 profile 标签。

3.2 cred 的不可变性与 RCU#

cred 结构体遵循不可变(immutable)设计:一旦创建就不能修改。当进程需要改变凭证时(如 setuid()capset()),内核会复制一份新的 cred,修改新副本,然后通过 RCU 机制原子地替换旧 cred:

// kernel/cred.c — 准备新的凭证
struct cred *prepare_creds(void)
{
struct task_struct *task = current;
const struct cred *old;
struct cred *new;
new = kmem_cache_alloc(cred_jar, GFP_KERNEL);
if (!new) return NULL;
old = task->cred;
memcpy(new, old, sizeof(struct cred)); // 复制旧凭证
atomic_set(&new->usage, 1);
// ... 初始化引用计数、RCU 等
return new;
}
// 提交新凭证
int commit_creds(struct cred *new)
{
struct task_struct *task = current;
const struct cred *old = task->cred;
// ... 各种合法性检查
rcu_assign_pointer(task->cred, new); // RCU 原子替换
put_cred(old); // 释放旧凭证
return 0;
}

这种设计的好处是:其他 CPU 上正在读取旧 cred 的代码(如权限检查)可以安全地继续使用旧版本,不会被半修改的状态所困惑。RCU 宽限期结束后,旧 cred 才被回收。

3.3 cred 与线程共享#

同一进程内的线程共享 cred——它们通过 task_struct->cred 指向同一个 cred 结构体。当某个线程调用 setuid() 修改凭证时,所有线程的凭证都会改变(因为它们共享同一个 cred 指针)。

task_struct 还有一个 real_cred 字段,指向”真实凭证”(objective credential),用于审计和信号权限判断。cred 则是”有效凭证”(subjective credential),用于访问控制检查。两者通常相同,但在临时改变身份(如 nfsd 代为访问文件)时会不同。

四、LSM 框架:可插拔的安全基础设施#

4.1 LSM 的设计哲学#

Linux Security Module(LSM)框架是 Linux 安全子系统的骨架。它的核心设计原则是可插拔——内核提供安全钩子(hooks),具体的安全策略由可加载的安全模块实现。

LSM 不实现任何安全策略本身,它只提供基础设施:

  • 在内核关键操作点插入安全钩子(如 security_inode_permission()security_file_open()
  • 管理 credinodefile 等内核对象的安全上下文
  • 提供**堆叠(stacking)**机制,允许多个安全模块协同工作
// include/linux/lsm_hook_defs.h — 部分 LSM 钩子定义
LSM_HOOK(int, 0, inode_permission, struct inode *inode, int mask)
LSM_HOOK(int, 0, file_open, struct file *file)
LSM_HOOK(int, 0, task_create, unsigned long clone_flags)
LSM_HOOK(int, 0, task_kill, struct task_struct *p, int signum)
LSM_HOOK(int, 0, sb_mount, const char *dev_name, struct path *path, ...)
LSM_HOOK(int, 0, capget, struct task_struct *target, ...)
// ... 约 200+ 个钩子

4.2 LSM 钩子的调用流程#

以文件访问为例,完整的权限检查流程如下:

flowchart TD A[进程调用 open] --> B[VFS: inode_permission] B --> C{DAC 检查<br/>rwx 权限位} C -->|拒绝| D[返回 EACCES] C -->|通过| E{Capability 检查<br/>capable?} E -->|需要特权且无 Cap| D E -->|通过或无需特权| F{LSM 钩子<br/>security_inode_permission} F -->|SELinux 拒绝| D F -->|AppArmor 拒绝| D F -->|通过| G[允许访问] style C fill:#3498db,color:#fff style E fill:#f39c12,color:#fff style F fill:#e74c3c,color:#fff style G fill:#2ecc71,color:#fff

注意检查的顺序:先 DAC(rwx),再 Capability,最后 LSM。这意味着 LSM 可以在 DAC 检查通过后进一步收紧权限,但不能放宽 DAC 拒绝的访问(除非进程拥有 CAP_DAC_OVERRIDE)。

4.3 主要的 LSM 模块#

Linux 内核内置了多个 LSM 模块:

模块类型特点默认启用
SELinuxMAC基于类型强制(TE),策略最严格RHEL/CentOS/Fedora
AppArmorMAC基于路径,配置简单Ubuntu/SUSE
SmackMAC简化版 MAC,面向嵌入式Tizen
TomoyoMAC基于路径,学习模式少量使用
LoadPin完整性限制内核模块加载来源Chrome OS
Yama补充限制 ptrace 附加部分发行版
BPF-LSMMAC基于 eBPF 的动态安全策略5.7+ 内核

可以通过 /sys/kernel/security/lsm 查看当前启用的安全模块:

$ cat /sys/kernel/security/lsm
lockdown,capability,yama,selinux,bpf

五、SELinux:强制访问控制的标杆#

5.1 DAC 与 MAC 的根本区别#

SELinux 实现的是强制访问控制(MAC),与传统 Unix 的**自主访问控制(DAC)**有根本区别:

特性DACMAC
控制者资源属主安全管理员(系统策略)
可否绕过属主可自由修改权限进程无法绕过策略
权限粒度rwx 三位细粒度(读/写/创建/删除/…等数十种)
安全属性uid/gid安全上下文(user:role:type
典型实现Unix 权限位SELinux、AppArmor

在 MAC 模型下,即使进程的 euid 是 root,如果 SELinux 策略不允许访问某个文件,访问也会被拒绝。root 不再是万能的。

5.2 安全上下文#

SELinux 的核心概念是安全上下文(Security Context),格式为 user:role:type:level

# 查看文件的安全上下文
$ ls -Z /var/www/html/index.html
system_u:object_r:httpd_sys_content_t:s0 /var/www/html/index.html
# 查看进程的安全上下文
$ ps -Z -C nginx
system_u:system_r:httpd_t:s0 1234 ? 00:00:00 nginx

四个字段的含义:

  • User:SELinux 用户(不同于 Linux UID),如 system_uunconfined_u
  • Role:角色,如 system_robject_r,用于 RBAC
  • Type:类型(进程叫 domain),这是 SELinux 策略的核心
  • Level:MLS/MCS 级别,如 s0s0-s0:c0.c1023

Type Enforcement(TE) 是 SELinux 最核心的访问控制机制。策略规则定义了”哪个 type 的进程可以访问哪个 type 的资源,以及允许什么操作”:

# 允许 httpd_t 域读取 httpd_sys_content_t 类型的文件
allow httpd_t httpd_sys_content_t:file { read getattr open };
# 允许 httpd_t 域连接 TCP 套接字
allow httpd_t httpd_port_t:tcp_socket { name_connect };

5.3 AVC:访问向量缓存#

SELinux 的策略规则数量巨大(通常数十万条),每次访问都遍历策略表会严重影响性能。因此 SELinux 引入了 AVC(Access Vector Cache),缓存策略查询结果:

// security/selinux/avc.c — AVC 查询
int avc_has_perm(u32 ssid, u32 tsid, u16 tclass, u32 requested,
struct common_audit_data *auditdata)
{
struct avc_node *node;
// 1. 先查 AVC 缓存
node = avc_lookup(ssid, tsid, tclass);
if (node) {
// 缓存命中,直接返回
if (node->ae.avd.allowed & requested)
return 0; // 允许
}
// 2. 缓存未命中,查询安全服务器(SS)
rc = security_compute_av(ssid, tsid, tclass, &avd);
// 3. 将结果插入 AVC 缓存
avc_insert(ssid, tsid, tclass, &avd);
return rc;
}

AVC 的统计信息可以通过 /selinux/avc/cache_statsavcstat 命令查看:

$ avcstat
lookups hits misses allocations reclaims frees
87654321 87654000 321 321 0 0

5.4 SELinux 的三种模式#

模式行为用途
Enforcing强制执行策略,违规操作被拒绝生产环境
Permissive记录违规但不拒绝(仅审计日志)策略调试
Disabled完全禁用 SELinux不推荐
# 查看当前模式
$ sestatus
SELinux status: enabled
SELinux mode: Enforcing
# 临时切换到 Permissive 模式(重启后恢复)
$ sudo setenforce 0
# 永久修改模式(修改 /etc/selinux/config)
$ sudo sed -i 's/SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config

5.5 SELinux 内核架构#

SELinux 的内核实现位于 security/selinux/ 目录,核心组件包括:

security/selinux/
├── avc.c # 访问向量缓存
├── hooks.c # LSM 钩子实现(2000+ 行)
├── ss/ # 安全服务器(策略引擎)
│ ├── policydb.c # 策略数据库
│ ├── services.c # 策略查询服务
│ ├── sidtab.c # SID 表
│ └── ebitmap.c # 扩展位图
├── selinuxfs.c # /sys/fs/selinux 文件系统
├── xfrm.c # 网络安全关联
└── netlabel.c # 网络标签

当 LSM 钩子被调用时,hooks.c 中的回调函数会:

  1. cred->security 获取进程的 SID(Security ID)
  2. 从目标对象(inode、socket 等)获取其 SID
  3. 调用 AVC 查询策略
  4. 根据结果允许或拒绝

六、AppArmor:基于路径的 MAC#

6.1 与 SELinux 的对比#

AppArmor 是 SELinux 的替代方案,在 Ubuntu 和 SUSE 中默认启用。两者最根本的区别在于标识对象的方式

特性SELinuxAppArmor
对象标识inode 标签(类型)文件路径
策略复杂度高(需标记所有文件)低(按路径匹配)
学习模式有(aa-genprof)
适用场景高安全需求易用性优先
策略语言m4 宏 + TE 规则简化的 profile 语法
可移动介质标签持久路径可能变化

AppArmor 基于路径的设计意味着:如果文件被移动或重命名,策略可能不再匹配。但这也使得策略编写和维护更简单——不需要为每个文件设置安全标签。

6.2 AppArmor Profile 示例#

/etc/apparmor.d/usr.sbin.nginx
#include <tunables/global>
/usr/sbin/nginx {
#include <abstractions/base>
#include <abstractions/nameservice>
/etc/nginx/** r,
/var/log/nginx/* rw,
/var/www/html/** r,
/usr/sbin/nginx mr,
network inet tcp,
network inet6 tcp,
capability net_bind_service,
capability setgid,
capability setuid,
}

这个 profile 定义了 nginx 进程可以访问的文件路径和网络权限。任何未明确允许的操作都会被拒绝。

# 查看 AppArmor 状态
$ sudo aa-status
apparmor module is loaded.
14 profiles are loaded.
14 profiles are in enforce mode.
0 profiles are in complain mode.
# 生成新 profile(学习模式)
$ sudo aa-genprof /usr/sbin/mysqld
# 将 profile 设为 complain 模式(仅记录不拒绝)
$ sudo aa-complain /usr/sbin/nginx
# 将 profile 设为 enforce 模式
$ sudo aa-enforce /usr/sbin/nginx

七、Seccomp-BPF:系统调用过滤#

7.1 Seccomp 的起源与演进#

Seccomp(Secure Computing Mode)最初是一个极其简单的沙箱机制:进程进入 seccomp 模式后,只能调用 read()write()_exit()sigreturn() 四个系统调用,其他任何系统调用都会导致进程被 SIGKILL 杀死。

这种”严格模式”过于极端,实际用途有限(主要用于 JIT 编译器的计算沙箱)。Linux 3.5 引入了 Seccomp-BPF,允许通过 BPF 程序自定义系统调用过滤规则,极大地增强了灵活性。

7.2 Seccomp-BPF 的工作原理#

Seccomp-BPF 允许进程安装一个 BPF 过滤程序,在每次系统调用进入内核时执行。BPF 程序可以检查系统调用号、参数,并返回以下动作之一:

返回动作含义
SECCOMP_RET_ALLOW允许系统调用继续执行
SECCOMP_RET_KILL_PROCESS杀死整个进程
SECCOMP_RET_KILL_THREAD杀死当前线程
SECCOMP_RET_TRAP发送 SIGSYS 信号,可用于优雅处理
SECCOMP_RET_ERRNO返回指定的 errno 值,不执行系统调用
SECCOMP_RET_TRACE交给 ptrace 跟踪器决定
SECCOMP_RET_LOG允许但记录日志
// 示例:只允许 read, write, exit, sigreturn,拒绝其他
#include <linux/seccomp.h>
#include <linux/filter.h>
#include <linux/audit.h>
struct sock_filter filter[] = {
/* 加载系统调用号 */
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)),
/* 允许 read */
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_read, 0, 1),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
/* 允许 write */
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_write, 0, 1),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
/* 允许 exit */
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_exit, 0, 1),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
/* 允许 exit_group */
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_exit_group, 0, 1),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
/* 其他系统调用返回 ERRNO(EPERM) */
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | EPERM),
};

7.3 内核实现:kernel/seccomp.c#

Seccomp-BPF 的内核实现位于 kernel/seccomp.c,核心流程如下:

// kernel/seccomp.c — 系统调用入口检查
int __secure_computing(const struct seccomp_data *sd)
{
int mode = current->seccomp.mode;
int data;
switch (mode) {
case SECCOMP_MODE_STRICT:
// 严格模式:只允许 4 个系统调用
return __seccomp_mode_strict(sd);
case SECCOMP_MODE_FILTER:
// BPF 过滤模式:执行 BPF 程序
return __seccomp_mode_filter(sd);
default:
BUG();
}
}
// BPF 过滤模式
static int __seccomp_mode_filter(const struct seccomp_data *sd)
{
struct seccomp_filter *f;
u32 ret;
// 遍历所有已安装的过滤器(支持多层叠加)
for (f = current->seccomp.filter; f; f = f->prev) {
// 执行 BPF 程序
ret = bpf_prog_run_pin_on_cpu(f->prog, sd);
// 根据返回动作处理
switch (ret & SECCOMP_RET_ACTION_FULL) {
case SECCOMP_RET_ALLOW:
return 0;
case SECCOMP_RET_KILL_PROCESS:
do_exit(SIGKILL);
case SECCOMP_RET_ERRNO:
return -(int)(ret & SECCOMP_RET_DATA);
// ... 其他动作
}
}
return 0;
}

Seccomp 检查发生在系统调用入口的最早阶段——在参数复制、Capability 检查、LSM 检查之前。这意味着即使进程拥有足够的 Capability 和 SELinux 权限,如果 Seccomp 过滤器拒绝了该系统调用,调用也不会被执行。

7.4 Seccomp 与容器安全#

Seccomp-BPF 是容器安全的关键组件。Docker 和 Kubernetes 默认都会为容器配置 Seccomp profile,限制可用的系统调用:

// Docker 默认 Seccomp profile 片段(禁止的危险系统调用)
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": ["SCMP_ARCH_X86_64"],
"syscalls": [
{ "names": ["read", "write", "open", "close", "..."], "action": "SCMP_ACT_ALLOW" },
{ "names": ["mount", "umount2", "ptrace", "kexec_load", "..."], "action": "SCMP_ACT_KILL" }
]
}

通过 Seccomp,容器可用的系统调用从约 400 个减少到约 50-70 个,大幅缩小了攻击面。

7.5 libseccomp:高级 Seccomp API#

直接编写 BPF 程序繁琐且易错。libseccomp 提供了更友好的 API:

#include <seccomp.h>
int main() {
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL); // 默认拒绝
// 允许特定系统调用
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
// 允许 open 但限制参数
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 1,
SCMP_A1(SCMP_CMP_EQ, O_RDONLY)); // 只允许只读打开
// 加载过滤器(不可逆!)
seccomp_load(ctx);
seccomp_release(ctx);
// 此后只能调用 read/write/exit 和只读 open
}

八、审计子系统:Audit#

8.1 Audit 的定位与作用#

Linux Audit 子系统不控制访问权限,而是记录安全相关事件,用于事后审计和入侵检测。它与 SELinux、Capabilities 互补——后两者负责”阻止”,Audit 负责”记录”。

Audit 可以记录的事件包括:

  • 系统调用(可按调用号、返回值过滤)
  • 文件访问(可按路径、inode 监控)
  • 用户登录/注销
  • SELinux AVC 拒绝
  • Capability 使用
  • 网络连接

8.2 Audit 的架构#

flowchart LR subgraph 内核空间 A[系统调用入口] --> B[Audit 框架] C[SELinux AVC] --> B D[Capability 检查] --> B B --> E[Audit 规则引擎] E --> F[netlink 套接字] end subgraph 用户空间 F --> G[auditd 守护进程] G --> H[/var/log/audit/audit.log] G --> I[ausearch/aureport] end style B fill:#e74c3c,color:#fff style E fill:#f39c12,color:#fff style G fill:#3498db,color:#fff

8.3 Audit 规则与使用#

# 查看 Audit 状态
$ sudo auditctl -s
enabled 1
failure 1
pid 1234
rate_limit 0
backlog_limit 8192
lost 0
backlog 0
# 添加文件监控规则
$ sudo auditctl -w /etc/shadow -p wa -k shadow_changes
# -w: 监控路径 -p: 权限(w=写,a=属性修改) -k: 规则标签
# 添加系统调用监控规则
$ sudo auditctl -a always,exit -F arch=b64 -S mount -S umount2 -k mount_ops
# 搜索审计日志
$ sudo ausearch -k shadow_changes
----
type=SYSCALL msg=audit(1713513600.123:456): arch=c000003e syscall=2 success=yes exit=3
a0=7ffd1234 a1=241 a2=1b6 a3=0 items=1 ppid=1234 pid=5678
auid=1000 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0
tty=pts0 ses=1 comm="vim" exe="/usr/bin/vim"
key="shadow_changes"
# 生成审计报告
$ sudo aureport --summary

审计日志中的 auid(Audit UID)特别重要——它记录的是原始登录用户,即使后续通过 su/sudo 切换了身份,auid 也不会改变,确保了审计追踪的完整性。

九、Linux 安全的纵深防御体系#

9.1 多层安全检查的完整流程#

以一个 open() 系统调用为例,追踪它穿越所有安全层的完整路径:

flowchart TD A["用户态: open('/etc/shadow', O_RDWR)"] --> B["系统调用入口"] B --> C{"Seccomp-BPF<br/>过滤器"} C -->|拒绝| D["SIGKILL / ERRNO"] C -->|允许| E{"Capability 检查<br/>capable()?"} E -->|需要特权且无 Cap| F["返回 EPERM"] E -->|通过或无需特权| G{"DAC 检查<br/>rwx 权限位"} G -->|拒绝| H["返回 EACCES"] G -->|通过| I{"LSM 钩子<br/>security_inode_permission()"} I -->|SELinux 拒绝| J["返回 EACCES + AVC 日志"] I -->|AppArmor 拒绝| K["返回 EACCES + AA 日志"] I -->|通过| L["Audit 记录<br/>(如果配置了规则)"] L --> M["执行文件操作"] style C fill:#9b59b6,color:#fff style E fill:#f39c12,color:#fff style G fill:#3498db,color:#fff style I fill:#e74c3c,color:#fff style M fill:#2ecc71,color:#fff

这个流程体现了纵深防御的核心思想:每一层都是独立的防线,即使某一层被绕过,其他层仍然可以阻止攻击。

9.2 各安全机制的协作关系#

安全机制防御层级核心能力局限性
Unix 权限第一层基础的读/写/执行控制root 全能、属主可自由修改
Capabilities第二层拆分 root 权限配置复杂、CAP_SYS_ADMIN 过于宽泛
SELinux/AppArmor第三层强制访问控制策略复杂、可能影响兼容性
Seccomp-BPF第四层限制系统调用面只能过滤系统调用、无法检查参数内容
Audit审计层事后追溯不阻止攻击、日志量大

9.3 容器场景下的安全配置#

容器安全是以上所有机制的综合应用:

# Kubernetes Pod 安全上下文示例
apiVersion: v1
kind: Pod
spec:
securityContext:
runAsNonRoot: true # 禁止 root 运行
runAsUser: 1000 # 指定 UID
fsGroup: 2000 # 文件系统 GID
seccompProfile:
type: RuntimeDefault # 使用容器运行时的默认 Seccomp profile
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false # 禁止 setuid 提权
readOnlyRootFilesystem: true # 只读根文件系统
capabilities:
drop: ["ALL"] # 移除所有 Capabilities
add: ["NET_BIND_SERVICE"] # 仅添加需要的 Capability

这种”最小权限”配置将容器的攻击面压缩到极致:非 root 用户、无 Capability、Seccomp 限制、只读文件系统。

十、动手实践#

实践 1:探索 Capabilities#

# 1. 查看当前 shell 的 Capabilities
$ capsh --print
# 2. 查看系统文件的 Capabilities
$ getcap -r /usr/bin/ 2>/dev/null
/usr/bin/ping = cap_net_raw+ep
/usr/bin/newuidmap = cap_setuid+ep
/usr/bin/newgidmap = cap_setgid+ep
# 3. 给文件设置 Capability
$ sudo setcap cap_net_bind_service=+ep ./myserver
$ getcap ./myserver
./myserver = cap_net_bind_service+ep
# 4. 以受限 Capability 运行命令
$ capsh --drop=all -- -c "cat /etc/shadow"
cat: /etc/shadow: Permission denied
# 即使是 root,没有 CAP_DAC_OVERRIDE 也无法读取
# 5. 查看进程的 Capabilities(通过 /proc)
$ cat /proc/$$/status | grep Cap
CapInh: 0000000000000000
CapPrm: 0000003fffffffff
CapEff: 0000003fffffffff
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000
# 解码 Capability 位图
$ capsh --decode=0000003fffffffff
0x0000003fffffffff=cap_chown,cap_dac_override,cap_dac_read_search,...

实践 2:探索 SELinux#

# 1. 查看 SELinux 状态
$ sestatus
SELinux status: enabled
SELinux mount: /sys/fs/selinux
SELinux root directory: /etc/selinux
Loaded policy name: targeted
Current mode: enforcing
Mode from config file: enforcing
Policy MLS status: enabled
Policy deny_unknown status: allowed
Memory protection checking: actual (secure)
Max kernel policy version: 33
# 2. 查看文件和进程的安全上下文
$ ls -Z /var/www/html/
$ ps -Z -C httpd
# 3. 查看 SELinux 拒绝日志
$ sudo ausearch -m avc -ts recent
# 或使用 sealert 获取可读解释
$ sudo sealert -a /var/log/audit/audit.log
# 4. 临时切换到 Permissive 模式
$ sudo setenforce 0
$ getenforce
Permissive
# 5. 恢复 Enforcing 模式
$ sudo setenforce 1

实践 3:探索 Seccomp#

# 1. 查看进程的 Seccomp 状态
$ cat /proc/$$/status | grep Seccomp
Seccomp: 0 # 0=禁用, 1=严格, 2=过滤
# 2. 查看 Docker 容器的 Seccomp profile
$ docker inspect --format='{{.HostConfig.SecurityOpt}}' mycontainer
# 3. 使用 strace 验证 Seccomp 过滤
$ docker run --rm -it alpine sh -c "strace -e mount mount /dev/sda1 /mnt"
# 应该看到 mount 被拒绝(EPERM 或 SIGSYS)
# 4. 使用 libseccomp 的 scmp_bpf_sim 工具测试规则
$ scmp_bpf_sim -a x86_64 -s openat -a 0 <<< 'allow if syscall == read'

实践 4:探索 Audit#

# 1. 查看 Audit 状态
$ sudo auditctl -s
# 2. 添加监控规则
$ sudo auditctl -w /etc/passwd -p wa -k passwd_changes
# 3. 触发审计事件
$ sudo chmod 644 /etc/passwd
# 4. 搜索审计日志
$ sudo ausearch -k passwd_changes | head -20
# 5. 生成审计报告
$ sudo aureport -x --summary # 按可执行文件汇总
$ sudo aureport -f --summary # 按文件汇总
$ sudo aureport -u --summary # 按用户汇总
# 6. 删除监控规则
$ sudo auditctl -W /etc/passwd -p wa -k passwd_changes

实践 5:探索 AppArmor#

# 1. 查看 AppArmor 状态
$ sudo aa-status
# 2. 查看进程的 AppArmor profile
$ cat /proc/$$/attr/current
unconfined # 未被限制
# 3. 列出所有已加载的 profile
$ sudo aa-status --profiles
# 4. 查看特定 profile 的详情
$ sudo aa-status --profile /usr/sbin/nginx

实践 6:综合实验——构建最小权限进程#

# 1. 创建一个只能绑定特权端口的受限进程
$ sudo setcap cap_net_bind_service=+ep ./webserver
$ ./webserver # 可以绑定 80 端口,但无其他特权
# 2. 验证它无法执行其他特权操作
$ sudo setcap cap_net_bind_service=+ep ./webserver
$ ./webserver &
$ cat /proc/$(pgrep webserver)/status | grep Cap
# 只有 cap_net_bind_service 被设置
# 3. 使用 strace 观察权限检查
$ strace -e trace=open,openat ./webserver 2>&1 | head

参考资料#

内核源码#

路径说明
kernel/capability.cCapabilities 系统调用实现
kernel/cred.c进程凭证管理
include/linux/cred.hcred 结构体定义
security/selinux/SELinux 完整实现
security/selinux/avc.c访问向量缓存
security/selinux/hooks.cSELinux LSM 钩子
security/selinux/ss/安全服务器(策略引擎)
security/apparmor/AppArmor 完整实现
kernel/seccomp.cSeccomp-BPF 实现
include/linux/seccomp.hSeccomp 数据结构
kernel/audit.cAudit 核心实现
include/linux/lsm_hook_defs.hLSM 钩子定义
security/security.cLSM 框架核心

经典书籍与文档#

资料作者说明
《SELinux by Example》Frank Mayer 等SELinux 策略编写的权威指南
《Linux Server Security》Michael D. BauerLinux 安全加固实践
Linux Kernel Documentation — Security内核文档Documentation/security/ 目录
SELinux User’s and Administrator’s GuideRed HatRHEL 官方 SELinux 文档
AppArmor DocumentationSUSE/CanonicalAppArmor 官方文档
seccomp(2) man pageMichael KerriskSeccomp 系统调用手册
capabilities(7) man pageMichael KerriskCapabilities 详细手册

在线资源#

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

安全机制
https://blog.souloss.com/posts/linux-internals/security-mechanisms/
作者
Souloss
发布于
2024-08-03
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时