在第 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,只有读权限;其他用户无任何权限。
内核在执行权限检查时,按以下优先级依次判断:
- 若进程的 euid(有效用户 ID)等于文件的 uid(属主 ID),则使用属主权限位
- 若进程的 egid(有效组 ID)或补充组中包含文件的 gid(属组 ID),则使用属组权限位
- 否则使用其他用户权限位
一旦命中某条规则,就不再继续检查。这意味着如果文件的属主权限位为空,但其他用户权限位有权限,属主反而无法访问——这是一个经常被忽视的细节。
1.2 进程凭证:真实 ID 与有效 ID
Unix 进程有四组用户/组 ID,它们的含义各不相同:
| ID 类型 | 含义 | 典型用途 |
|---|---|---|
| 真实 UID/GID(ruid/rgid) | 标识”谁启动了这个进程” | 审计日志、信号发送权限判断 |
| 有效 UID/GID(euid/egid) | 标识”进程当前以谁的身份运行” | 文件访问权限检查 |
| 保存的 set-user/group-ID(suid/sgid) | 保存 setuid 之前的 euid/egid | setuid 程序恢复原始身份 |
| 文件系统 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、安全上下文等};在现代内核中,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 的工作流程如下:
setuid 的风险是的:如果 setuid 程序存在漏洞(如缓冲区溢出),攻击者就能以 root 身份执行任意代码。历史上无数安全漏洞都源于 setuid 程序的缺陷。这也是 Linux 引入 Capabilities、SELinux 等机制的核心动机——减少以 root 身份运行的代码量。
1.4 传统模型的根本缺陷
Unix 权限模型有两个根本性缺陷:
-
root 全能问题:UID 0 拥有一切权限,无法拆分。一个只需绑定 80 端口的 Web 服务器,却同时获得了加载内核模块、修改系统时间、格式化磁盘等所有权限。这违反了最小权限原则。
-
自主访问控制(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/GID | setuid 程序 |
CAP_SETPCAP | 修改进程 Capabilities | 权限管理工具 |
CAP_SYS_RESOURCE | 绕过资源限制 | 系统监控工具 |
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时继承) */ // ...};五组集合的含义和关系:
| 集合 | 含义 | 何时生效 |
|---|---|---|
| Inheritable | execve 时可传递给子进程的 Capabilities | 仅当文件也有对应 Permitted 时才传递 |
| Permitted | 进程允许拥有的 Capabilities 上限 | 超出此集合的 Capability 无法获取 |
| Effective | 进程当前实际生效的 Capabilities | 内核做权限检查时查询此集合 |
| Bounding | 进程可获得的 Capabilities 的绝对上限 | 任何情况下都无法突破 |
| Ambient | 非 setuid 程序 execve 时自动继承的 Capabilities | 解决非 root 程序的 Capability 继承问题 |
它们之间的关系可以用以下流程图表示:
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 --printCurrent: = cap_chown,cap_dac_override,...+eipBounding 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 — 系统调用实现
// 获取进程 CapabilitiesSYSCALL_DEFINE2(capget, cap_user_header_t, header, cap_user_data_t, dataptr){ struct __user_cap_header_struct cap_header; // ... 从 current->cred 中读取各集合}
// 设置进程 CapabilitiesSYSCALL_DEFINE2(capset, cap_user_header_t, header, const cap_user_data_t, data){ // ... 权限检查:新集合不能超出 Permitted 和 Bounding // ... 提交新的 cred}
// 权限检查:进程是否拥有指定 Capabilitybool 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()) - 管理
cred、inode、file等内核对象的安全上下文 - 提供**堆叠(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 钩子的调用流程
以文件访问为例,完整的权限检查流程如下:
注意检查的顺序:先 DAC(rwx),再 Capability,最后 LSM。这意味着 LSM 可以在 DAC 检查通过后进一步收紧权限,但不能放宽 DAC 拒绝的访问(除非进程拥有 CAP_DAC_OVERRIDE)。
4.3 主要的 LSM 模块
Linux 内核内置了多个 LSM 模块:
| 模块 | 类型 | 特点 | 默认启用 |
|---|---|---|---|
| SELinux | MAC | 基于类型强制(TE),策略最严格 | RHEL/CentOS/Fedora |
| AppArmor | MAC | 基于路径,配置简单 | Ubuntu/SUSE |
| Smack | MAC | 简化版 MAC,面向嵌入式 | Tizen |
| Tomoyo | MAC | 基于路径,学习模式 | 少量使用 |
| LoadPin | 完整性 | 限制内核模块加载来源 | Chrome OS |
| Yama | 补充 | 限制 ptrace 附加 | 部分发行版 |
| BPF-LSM | MAC | 基于 eBPF 的动态安全策略 | 5.7+ 内核 |
可以通过 /sys/kernel/security/lsm 查看当前启用的安全模块:
$ cat /sys/kernel/security/lsmlockdown,capability,yama,selinux,bpf五、SELinux:强制访问控制的标杆
5.1 DAC 与 MAC 的根本区别
SELinux 实现的是强制访问控制(MAC),与传统 Unix 的**自主访问控制(DAC)**有根本区别:
| 特性 | DAC | MAC |
|---|---|---|
| 控制者 | 资源属主 | 安全管理员(系统策略) |
| 可否绕过 | 属主可自由修改权限 | 进程无法绕过策略 |
| 权限粒度 | 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.htmlsystem_u:object_r:httpd_sys_content_t:s0 /var/www/html/index.html
# 查看进程的安全上下文$ ps -Z -C nginxsystem_u:system_r:httpd_t:s0 1234 ? 00:00:00 nginx四个字段的含义:
- User:SELinux 用户(不同于 Linux UID),如
system_u、unconfined_u - Role:角色,如
system_r、object_r,用于 RBAC - Type:类型(进程叫 domain),这是 SELinux 策略的核心
- Level:MLS/MCS 级别,如
s0、s0-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_stats 或 avcstat 命令查看:
$ avcstatlookups hits misses allocations reclaims frees87654321 87654000 321 321 0 05.4 SELinux 的三种模式
| 模式 | 行为 | 用途 |
|---|---|---|
| Enforcing | 强制执行策略,违规操作被拒绝 | 生产环境 |
| Permissive | 记录违规但不拒绝(仅审计日志) | 策略调试 |
| Disabled | 完全禁用 SELinux | 不推荐 |
# 查看当前模式$ sestatusSELinux status: enabledSELinux mode: Enforcing
# 临时切换到 Permissive 模式(重启后恢复)$ sudo setenforce 0
# 永久修改模式(修改 /etc/selinux/config)$ sudo sed -i 's/SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config5.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 中的回调函数会:
- 从
cred->security获取进程的 SID(Security ID) - 从目标对象(inode、socket 等)获取其 SID
- 调用 AVC 查询策略
- 根据结果允许或拒绝
六、AppArmor:基于路径的 MAC
6.1 与 SELinux 的对比
AppArmor 是 SELinux 的替代方案,在 Ubuntu 和 SUSE 中默认启用。两者最根本的区别在于标识对象的方式:
| 特性 | SELinux | AppArmor |
|---|---|---|
| 对象标识 | inode 标签(类型) | 文件路径 |
| 策略复杂度 | 高(需标记所有文件) | 低(按路径匹配) |
| 学习模式 | 无 | 有(aa-genprof) |
| 适用场景 | 高安全需求 | 易用性优先 |
| 策略语言 | m4 宏 + TE 规则 | 简化的 profile 语法 |
| 可移动介质 | 标签持久 | 路径可能变化 |
AppArmor 基于路径的设计意味着:如果文件被移动或重命名,策略可能不再匹配。但这也使得策略编写和维护更简单——不需要为每个文件设置安全标签。
6.2 AppArmor Profile 示例
#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-statusapparmor 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 的架构
8.3 Audit 规则与使用
# 查看 Audit 状态$ sudo auditctl -senabled 1failure 1pid 1234rate_limit 0backlog_limit 8192lost 0backlog 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() 系统调用为例,追踪它穿越所有安全层的完整路径:
这个流程体现了纵深防御的核心思想:每一层都是独立的防线,即使某一层被绕过,其他层仍然可以阻止攻击。
9.2 各安全机制的协作关系
| 安全机制 | 防御层级 | 核心能力 | 局限性 |
|---|---|---|---|
| Unix 权限 | 第一层 | 基础的读/写/执行控制 | root 全能、属主可自由修改 |
| Capabilities | 第二层 | 拆分 root 权限 | 配置复杂、CAP_SYS_ADMIN 过于宽泛 |
| SELinux/AppArmor | 第三层 | 强制访问控制 | 策略复杂、可能影响兼容性 |
| Seccomp-BPF | 第四层 | 限制系统调用面 | 只能过滤系统调用、无法检查参数内容 |
| Audit | 审计层 | 事后追溯 | 不阻止攻击、日志量大 |
9.3 容器场景下的安全配置
容器安全是以上所有机制的综合应用:
# Kubernetes Pod 安全上下文示例apiVersion: v1kind: Podspec: 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 CapCapInh: 0000000000000000CapPrm: 0000003fffffffffCapEff: 0000003fffffffffCapBnd: 0000003fffffffffCapAmb: 0000000000000000
# 解码 Capability 位图$ capsh --decode=0000003fffffffff0x0000003fffffffff=cap_chown,cap_dac_override,cap_dac_read_search,...实践 2:探索 SELinux
# 1. 查看 SELinux 状态$ sestatusSELinux status: enabledSELinux mount: /sys/fs/selinuxSELinux root directory: /etc/selinuxLoaded policy name: targetedCurrent mode: enforcingMode from config file: enforcingPolicy MLS status: enabledPolicy deny_unknown status: allowedMemory 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$ getenforcePermissive
# 5. 恢复 Enforcing 模式$ sudo setenforce 1实践 3:探索 Seccomp
# 1. 查看进程的 Seccomp 状态$ cat /proc/$$/status | grep SeccompSeccomp: 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/currentunconfined # 未被限制
# 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.c | Capabilities 系统调用实现 |
kernel/cred.c | 进程凭证管理 |
include/linux/cred.h | cred 结构体定义 |
security/selinux/ | SELinux 完整实现 |
security/selinux/avc.c | 访问向量缓存 |
security/selinux/hooks.c | SELinux LSM 钩子 |
security/selinux/ss/ | 安全服务器(策略引擎) |
security/apparmor/ | AppArmor 完整实现 |
kernel/seccomp.c | Seccomp-BPF 实现 |
include/linux/seccomp.h | Seccomp 数据结构 |
kernel/audit.c | Audit 核心实现 |
include/linux/lsm_hook_defs.h | LSM 钩子定义 |
security/security.c | LSM 框架核心 |
经典书籍与文档
| 资料 | 作者 | 说明 |
|---|---|---|
| 《SELinux by Example》 | Frank Mayer 等 | SELinux 策略编写的权威指南 |
| 《Linux Server Security》 | Michael D. Bauer | Linux 安全加固实践 |
| Linux Kernel Documentation — Security | 内核文档 | Documentation/security/ 目录 |
| SELinux User’s and Administrator’s Guide | Red Hat | RHEL 官方 SELinux 文档 |
| AppArmor Documentation | SUSE/Canonical | AppArmor 官方文档 |
| seccomp(2) man page | Michael Kerrisk | Seccomp 系统调用手册 |
| capabilities(7) man page | Michael Kerrisk | Capabilities 详细手册 |
在线资源
- Linux Kernel Security Documentation — 内核安全子系统官方文档
- SELinux Project — SELinux 用户态工具和策略
- AppArmor Wiki — AppArmor 社区文档
- libseccomp — Seccomp 高级 API 库
- Linux Audit Documentation — Audit 子系统文档
- Kernel LSM Documentation — LSM 框架开发指南
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时






