操作系统安全不是"装个杀毒软件",而是一层套一层的纵深防御(Defense in Depth)。内核的漏洞一旦被利用,用户态的防护全部失效——所以真正可靠的边界,从内核自身、到强制访问控制、到硬件信任根、再到容器逃逸防护,每一层都必须尽责。
本文先画出操作系统攻击面全景,然后逐层深入:LSM(SELinux/AppArmor)如何把 root 的"全知全能"收窄为最小权限;内核加固(KASLR、栈保护、模块签名)如何提高利用成本;TPM 与可信启动如何让"开机即可信"成为可验证的事实;最后落到云原生场景最关心的容器逃逸防护与处置路径。
攻击面全景:操作系统都有哪些门?
防御的前提是知道门在哪。操作系统的主要攻击面包括:
| 攻击面 | 风险点 | 典型利用 |
|---|---|---|
| 内核漏洞 | 本地提权(LPE)、任意代码执行 | Dirty Pipe、Dirty COW |
| 系统调用接口 | 语义绕过、TOCTOU | 低权限调用危险 syscall |
| 驱动/固件 | 内存损坏、DMA 攻击 | IOMMU 缺失时的 DMA 攻击 |
| 用户态服务 | 越权、配置错误 | SUID 二进制提权 |
| 启动链 | 预启动 Rootkit | Bootkit 替换 Bootloader |
| 容器边界 | 逃逸到宿主 | 挂载宿主目录、capabilities 滥用 |
每类攻击面都对应专门防线。下面按"内核 → LSM → 信任根 → 容器"的顺序逐层建立防线。
LSM:Linux 安全模块框架
Linux 本身只有传统的 DAC(自主访问控制):权限由文件属主决定,root 拥有全部权力。root 一旦被攻破,系统就失守。LSM(Linux Security Module) 引入 MAC(强制访问控制):即使你是 root,也必须先通过安全策略的授权检查。
LSM 框架如何工作
LSM 在内核的关键路径(open、exec、mount、socket、ptrace…)上插入钩子函数(hook)。每个钩子调用已注册的 LSM 实现,任何一方拒绝,操作即被禁止:
sys_open() → ... → security_file_open() → SELinux 检查 → 允许/拒绝
└→ AppArmor 检查
内核通过 security_hook_heads 链表串联多个 LSM(如 integrity、capability、selinux),组合出"能力检查 + MAC 检查"的双层门禁。
SELinux:标签与策略(TE/AVC)
SELinux 用**安全标签(Label)**标记主体(进程)与客体(文件、端口、socket),策略规则形如"主体类型 A 对客体类型 B 能否执行动作 C"。它的核心是三块:
- 类型强制(TE,Type Enforcement):按
type划分主体与客体,规则定义 type 间许可。 - AVC(Access Vector Cache):缓存检查结果,避免每条路径重复查策略。
- 布尔值(Boolean):运行时开关常用策略子集。
# 查看/修改进程的 SELinux 上下文
ls -Z /etc/shadow # system_u:object_r:shadow_t:s0
ps -eZ | grep sshd # system_u:system_r:sshd_t:s0
# 常用运维命令
getenforce # Enforcing / Permissive / Disabled
sestatus
setsebool -P httpd_can_network_connect on # 临时/持久化布尔
RHEL/Fedora 系默认启用 SELinux。其策略语言强大但难写,因此生产上常见"Enforcing + 放行白名单"的折中,或用 audit2allow 从审计日志生成放行规则。
AppArmor:路径约束
AppArmor 与 SELinux 哲学不同:按路径(file path) 而非标签定义权限。它更适合直觉理解,是 Ubuntu/Debian 的默认选择。配置是一份"该程序能碰什么"的声明:
# /etc/apparmor.d/usr.sbin.nginx
abi <abi/4.0>,
include <tunables/global>
profile nginx /usr/sbin/nginx {
include <abstractions/base>
capability net_bind_service,
network inet tcp,
/etc/nginx/conf.d/ r,
/var/log/nginx/*.log w,
/usr/share/nginx/html/ r,
}
apparmor_status # 查看已加载 profile 与状态
aa-enforce /usr/sbin/nginx # 强制模式
aa-complain /usr/sbin/nginx # 仅记录不阻止(调试用)
SELinux vs AppArmor
| 维度 | SELinux | AppArmor |
|---|---|---|
| 控制模型 | 标签 + TE 策略 | 路径 + profile |
| 学习成本 | 高(策略语言复杂) | 低(直觉) |
| 精细度 | 极高(类型/角色/布尔) | 中(路径/能力) |
| 默认发行版 | RHEL/Fedora/CentOS | Ubuntu/Debian |
| 适合 | 严格合规环境 | 快速加固 |
无论哪个,核心价值相同:把进程权限收窄到它真的需要的最小集合——即最小权限原则的落地。
内核加固:把利用成本提高到"不值得"
LSM 管的是"授权",内核加固管的是"让漏洞不好利用"。内核层面常见的加固手段:
内存布局随机化与隔离
- KASLR(Kernel Address Space Layout Randomization):随机化内核代码/数据的加载地址,让攻击者无法预测符号地址。
- PTI / KAISER:隔离用户态与内核态页表,缓解 Meltdown 类侧信道。
- KPTR 隐藏:
kptr_restrict限制/proc/kallsyms暴露内核符号给普通用户。
# 查看内核保护措施
cat /proc/cmdline | tr ' ' '\n' | grep -E "kaslr|pti|nopti"
sysctl kernel.kptr_restrict # 2 表示非特权用户不可见
编译期与运行时防护
- 栈保护:
-fstack-protector-strong(CONFIG_STACKPROTECTOR_STRONG)在栈帧加金丝雀值,检测溢出。 - CFI(控制流完整性):间接调用点校验目标地址,阻止函数指针劫持(Clang CFI / ARM PAC)。
CONFIG_HARDENED_USERCOPY:校验拷贝的目标是否属于对象边界。CONFIG_RANDOMIZE_KSTACK_OFFSET:每次中断随机化内核栈偏移。
模块签名与锁定内核
CONFIG_MODULE_SIG:只加载有合法签名(内核签名密钥)的模块,阻断"加载恶意内核模块"这条提权捷径。CONFIG_SECURITY_LOCKDOWN_LSM:一旦启用 lockdown 模式,禁止加载未签名模块、禁止读写内核内存设备(/dev/mem、/dev/kmem)。
# 查看模块签名状态
cat /proc/sys/kernel/modules_disabled # 1 表示禁止加载任何模块
modinfo --signature nf_tables.ko | head # 查看模块签名
sysctl 加固基线
一组高频加固 sysctl(写入 /etc/sysctl.d/99-hardening.conf):
# 限制 dmesg 访问、隐藏内核符号
kernel.dmesg_restrict = 1
kernel.kptr_restrict = 2
# 限制 ptrace(防注入)
kernel.yama.ptrace_scope = 2
# 关闭非必要的核心转储/重定向
fs.suid_dumpable = 0
kernel.core_uses_pid = 1
# 网络层:禁 IP 转发除非需要
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
加固不是一键脚本,而是一份与业务形态相关的权衡清单——过度加固会破坏功能,需在合规与可用性之间取舍。
TPM 与可信计算:让"可信"可验证
前几层防线假设"软件没问题"。但软件可能被提前替换——Bootkit 可以在内核加载前注入恶意代码。可信计算(Trusted Computing) 用硬件信任根回答这个问题。
TPM 芯片
TPM(Trusted Platform Module) 是主板上的独立安全芯片,具备:
- 密钥生成与存储(且私钥永不离开芯片)。
- PCR(Platform Configuration Register):只支持扩展(extend)的度量寄存器,记录软件状态摘要。
- 签名与加密运算。
可信启动链(Trusted Boot)
可信启动不是"验证签名"(那是 Secure Boot),而是逐级度量:每级启动组件在把控制权交给下一级之前,先把自己(及被加载的下一级)的哈希 extend 进 TPM 的 PCR:
固件 → extend PCR[0]
Bootloader → extend PCR[4]
内核 → extend PCR[?]
initramfs → ...
PCR 有一个关键性质:PCR[n+1] = H(旧值 || 新度量)。因此PCR 的最终值只取决于整条启动链的完整历史——任何人替换了任意一级,PCR 值都会不同。系统运行时,你(或远程验证方)可以请求 TPM 签名一个 PCR 摘要(Quote),以证明这台机器是用可信组件启动的。
远程证明(Remote Attestation)
远程证明是可信计算最有价值的应用:一台服务器向另一台(验证方)证明"我的启动链是干净的"。流程:
验证方 → 随机数 Nonce
服务器:TPM 对 (PCR 摘要 || Nonce) 签名 → 返回 Quote
验证方:用服务器 TPM 的公钥验签,比对 PCR 摘要是否等于基准值
现代发行版结合 IMA(Integrity Measurement Architecture) 还能度量到每个文件,形成更细粒度的可信证明。工具如 tpm2-tools、vTPM(虚拟机)、measured boot(测量启动)都是这条技术栈的实现。
# tpm2 查看 PCR 值
tpm2_pcrread sha256:0,4,5
# 打印启动日志中 TPM 度量事件(EFI 变量)
ls /sys/kernel/security/tpm0/binary_bios_measurements
Secure Boot vs 可信启动
| 维度 | Secure Boot | Trusted Boot(TPM 度量) |
|---|---|---|
| 机制 | 签名验证(谁签的) | 哈希度量(是什么) |
| 防什么 | 未签名/篡改组件加载 | 记录整条启动链历史 |
| 谁来信任 | 固件的 db 数据库 | TPM 硬件信任根 |
| 证明 | 无(只防本地加载) | 可远程证明 |
两者互补:Secure Boot 挡住恶意 Bootloader 加载,Trusted Boot 让"整条链"可被验证。详见 https://plumephp.com/os-boot-process/ 中 Secure Boot 与启动链的讨论。
容器逃逸防护:云原生场景的最后防线
容器与宿主机共享内核,容器的安全边界完全依赖内核自身的安全机制。攻破容器的内核漏洞,等于直接攻破宿主。防护从四个维度叠加。
最小权限:Capabilities 与 rootless
容器默认去掉危险 capabilities,--privileged(等价于 CAP_SYS_ADMIN + 全设备访问)是逃逸的最快捷径,生产禁止默认开启:
# 查看进程 capabilities
capsh --print
grep CapEff /proc/self/status
# Docker 最小化
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE --user 10001:10001 nginx
seccomp:系统调用过滤
seccomp 在内核层过滤系统调用,是容器逃逸的高性价比防线。默认 docker-default profile 已禁止约 44 个高危 syscall(mount、ptrace、kexec_load…)。
# 用 strace 收集应用实际用到的 syscall,生成白名单
strace -f -e trace=network -o /tmp/syscalls.log ./app
# 据此生成 seccomp profile 后
docker run --security-opt seccomp=./profile.json nginx
seccomp 的过滤由 seccomp(2) 与 BPF 过滤器(SECCOMP_RET_ERRNO / SECCOMP_RET_TRAP)实现——底层正是 https://plumephp.com/os-ebpf-observability/ 讨论的 BPF 指令集,机制一脉相承。
LSM 与只读根文件系统
- 容器内启用 AppArmor/SELinux profile(Docker 默认
docker-defaultAppArmor)。 --read-only让容器根文件系统只读,攻击者无法持久化写入。- 禁用
--mount type=bind对宿主敏感路径(/、/etc、/proc)的写挂载。
逃逸事件处置路径
当检测到逃逸迹象(异常 mount、unshare、可疑进程——可用 https://plumephp.com/os-ebpf-observability/ 的 execsnoop/mount 追踪),处置三板斧:
# 1) 立即冻结容器
docker pause <ctr> # 或 kubectl rollout 隔离
# 2) 捕获证据(进程、网络、文件改动)
docker inspect <ctr> > evidence.json
ps aux | grep <ctr_pid>
ss -tnp | grep <ctr_pid>
# 3) 隔离宿主、下线重建,审计宿主内核漏洞与内核版本
uname -r
# 升级内核 / 启用 lockdown / 更新 seccomp 策略
# 纵深防御基线组合
docker run \
--user 10001:10001 \
--cap-drop=ALL \
--cap-add=NET_BIND_SERVICE \
--security-opt seccomp=default.json \
--security-opt apparmor=docker-default \
--read-only \
--tmpfs /tmp \
-p 8080:80 nginx
结语
操作系统安全是一场"拉高攻击成本"的持久战。从 LSM 把 root 收窄为最小权限,到 KASLR/CFI/模块签名让漏洞难以利用,再到 TPM 让"可信"变成可以远程验证的事实,最后到 seccomp/Capabilities 守住容器边界——每一层都假设"上一层已被攻破",每一层都让攻击者多付出一份代价。
对工程师而言,正确的姿态不是追求"绝对安全",而是建立可度量的纵深:知道自己有哪些攻击面、每一层防线在哪、加固到什么程度、如何验证与审计。这套体系配合 eBPF 的实时观测(https://plumephp.com/os-ebpf-observability/)与容器的资源隔离(https://plumephp.com/os-virtualization-containers/),才能构成真正完整的 OS 安全视图。
延伸阅读
- Linux Kernel Documentation:
Documentation/admin-guide/LSM/与security/lsm_hooks.h - SELinux 项目文档:
github.com/SELinuxProject/selinux - TPM2 规范与
tpm2-software/tpm2-tools - Docker/OCI Runtime
specs(seccomp、capabilities、Linux namespace 配置) - NSA/CIS Linux Benchmark(加固基线:
CIS Benchmarks)
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。