系统调用是安全边界
拦截系统调用可以限制不受信任的进程,不需要修改应用。
MIT 6.1810 · 第 7 课
如何限制不受信任的进程的行为?本课以 Janus 项目为例,探讨系统调用拦截(interposition)的技术、陷阱与实践。
浏览器(Netscape)运行 ghostscript 查看 PostScript 文件。ghostscript 允许 PS 文件读写文件、生成进程等。攻击者可以构造恶意 PS 文件,诱导用户打开——ghostscript 的权限就是攻击者的权限。
类似问题:PDF 中的 JavaScript、Office 宏、WASM 模块……
| 原因 | 说明 |
|---|---|
| 方便的边界 | 系统调用是用户/内核的天然边界 |
| 保护重要资源 | 其他进程、文件、网络——必须经过系统调用 |
| 不关心进程内部状态 | 允许进程损坏自己的内存(它已经有这个权限) |
| 不需要修改应用 | 不需要重写每个应用程序使其安全 |
在 libc 层拦截(如替换 printf、write)的问题:
| 机制 | 平台 | 特点 |
|---|---|---|
| ptrace | UNIX 通用 | 基础但有许多限制,性能差 |
| Seccomp/BPF | Linux | 高性能,用 BPF 程序过滤系统调用 |
| AppArmor | Linux | 与内核状态深度集成 |
| Firejail | Linux | 用户可配置的安全配置 |
| Seatbelt | macOS | Apple 的沙箱机制 |
# 创建测试文件
$ echo foo > ~/foo.txt
$ echo bar > ~/bar.txt
$ echo qux > ~/qux.txt
# example.profile:只允许访问 foo.txt
$ cat example.profile
blacklist ~/bar.txt
blacklist ~/qux.txt
# 进入沙箱
$ firejail --profile=example.profile sh
$ ls
$ cat ~/foo.txt # OK
$ cat ~/bar.txt # Permission denied
$ echo hello > ~/foo.txt # OK
Janus 的设计模式:内核 stub + 用户态 helper 进程。
| 部分 | 职责 |
|---|---|
| 内核模块 | 拦截系统调用,区分敏感/非敏感。敏感调用转发给 Janus 进程。 |
| Janus 进程 | 策略引擎,决定允许/拒绝。 |
这种设计的优势:
系统调用的参数可能是指针,但指针的好坏取决于调用者的上下文:
0x1234 是好是坏?取决于调用者地址空间的内容x 是好是坏?取决于当前工作目录a/b/c 是好是坏?取决于 a、a/b、a/b/c 是什么策略引擎可能需要维护内核状态的副本:
a/b/c)但细微的 corner case 会导致状态发散——策略引擎的状态与内核真实状态不一致。
| 问题 | 说明 |
|---|---|
| Core dump | 崩溃时内核写 core 文件,没有 open 系统调用可拦截 |
| fd 传递 | 通过 Unix socket 传递 fd 绕过 open 检查 |
| 辅助进程 | 其他用户态进程可能绕过策略 |
检查和使用之间状态发生变化——经典的竞态条件:
| 场景 | 攻击方式 |
|---|---|
| 符号链接 | 检查文件 A → 通过 → 攻击者把 A 换成指向 /etc/shadow 的链接 |
| 目录遍历 | 检查 /tmp/foo → 父目录被 rename → 实际访问的是 /etc/shadow/foo |
| 工作目录 | 检查路径 → chdir → 实际访问的是另一个目录 |
| 内存参数 | 检查缓冲区 → 用户修改 → 实际使用的是恶意数据 |
| fd | 检查 fd → 关闭并重新打开另一个文件 → 实际使用的是新文件 |
禁止符号链接相关调用的复杂性:
symlink()rename(symlink)O_NOFOLLOW)现代 API:openat2 的 RESOLVE_NO_SYMLINKS 标志。
阻止系统调用可能改变程序行为:
setuid() → 程序无法降低自身权限close() → 程序无法关闭 fd| 方法 | 说明 |
|---|---|
| 内核内拦截 | 不用 ptrace,在内核内直接拦截——避免状态不一致 |
| 先拷贝用户内存 | 在检查前把用户内存拷贝到内核——避免 TOCTTOU |
| 集成到内核代码路径 | 不是"先检查再操作",而是在操作中检查——避免竞态 |
| 修改应用 | 展开路径遍历为可检查的步骤(对某些应用可行) |
系统调用拦截在实践中被广泛使用:
Q1:为什么系统调用是拦截的理想边界?
Q2:TOCTTOU 攻击的本质是什么?
Q3:Janus 的设计模式是什么?
Q4:以下哪项不是系统调用拦截的典型挑战?
Q5:Linux 中用于替代 ptrace 的高性能沙箱机制是?
拦截系统调用可以限制不受信任的进程,不需要修改应用。
上下文缺失、状态复制、宽接口、TOCTTOU——每个都是安全陷阱。
Janus 模式:内核拦截 + 用户态策略引擎——兼顾性能与灵活性。
Chrome 渲染器、Docker 容器、macOS App Sandbox——底层都是系统调用拦截。