MIT 6.1810 · 第 7 课

系统调用拦截

如何限制不受信任的进程的行为?本课以 Janus 项目为例,探讨系统调用拦截(interposition)的技术、陷阱与实践。

授课:rtm (Robert Morris) 日期:2025-02-11 难度:中等

问题:如何限制不受信任的代码?

场景

浏览器(Netscape)运行 ghostscript 查看 PostScript 文件。ghostscript 允许 PS 文件读写文件、生成进程等。攻击者可以构造恶意 PS 文件,诱导用户打开——ghostscript 的权限就是攻击者的权限。

类似问题:PDF 中的 JavaScript、Office 宏、WASM 模块……

目标:最小化损害

为什么选择系统调用作为拦截点?

原因说明
方便的边界系统调用是用户/内核的天然边界
保护重要资源其他进程、文件、网络——必须经过系统调用
不关心进程内部状态允许进程损坏自己的内存(它已经有这个权限)
不需要修改应用不需要重写每个应用程序使其安全

为什么不直接在库(libc)层拦截?

在 libc 层拦截(如替换 printf、write)的问题:

现代沙箱机制

机制平台特点
ptraceUNIX 通用基础但有许多限制,性能差
Seccomp/BPFLinux高性能,用 BPF 程序过滤系统调用
AppArmorLinux与内核状态深度集成
FirejailLinux用户可配置的安全配置
SeatbeltmacOSApple 的沙箱机制

Demo:Firejail

# 创建测试文件
$ 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 设计:内核模块 + 用户态策略

Janus 的设计模式:内核 stub + 用户态 helper 进程。

部分职责
内核模块拦截系统调用,区分敏感/非敏感。敏感调用转发给 Janus 进程。
Janus 进程策略引擎,决定允许/拒绝。

这种设计的优势:

为什么系统调用拦截如此困难?

挑战 1:系统调用没有足够的上下文信息

系统调用的参数可能是指针,但指针的好坏取决于调用者的上下文:

挑战 2:复制内核代码/状态

策略引擎可能需要维护内核状态的副本:

但细微的 corner case 会导致状态发散——策略引擎的状态与内核真实状态不一致。

挑战 3:宽接口与意外交互

问题说明
Core dump崩溃时内核写 core 文件,没有 open 系统调用可拦截
fd 传递通过 Unix socket 传递 fd 绕过 open 检查
辅助进程其他用户态进程可能绕过策略

挑战 4:TOCTTOU(Time of Check to Time of Use)

检查和使用之间状态发生变化——经典的竞态条件:

场景攻击方式
符号链接检查文件 A → 通过 → 攻击者把 A 换成指向 /etc/shadow 的链接
目录遍历检查 /tmp/foo → 父目录被 rename → 实际访问的是 /etc/shadow/foo
工作目录检查路径 → chdir → 实际访问的是另一个目录
内存参数检查缓冲区 → 用户修改 → 实际使用的是恶意数据
fd检查 fd → 关闭并重新打开另一个文件 → 实际使用的是新文件

挑战 5:强制子集 API

禁止符号链接相关调用的复杂性:

现代 API:openat2 的 RESOLVE_NO_SYMLINKS 标志。

挑战 6:阻止重要系统调用

阻止系统调用可能改变程序行为:

如何避免这些陷阱?

方法说明
内核内拦截不用 ptrace,在内核内直接拦截——避免状态不一致
先拷贝用户内存在检查前把用户内存拷贝到内核——避免 TOCTTOU
集成到内核代码路径不是"先检查再操作",而是在操作中检查——避免竞态
修改应用展开路径遍历为可检查的步骤(对某些应用可行)

实际应用

系统调用拦截在实践中被广泛使用:


知识检查

Q1:为什么系统调用是拦截的理想边界?

Q2:TOCTTOU 攻击的本质是什么?

Q3:Janus 的设计模式是什么?

Q4:以下哪项不是系统调用拦截的典型挑战?

Q5:Linux 中用于替代 ptrace 的高性能沙箱机制是?


本课要点

系统调用是安全边界

拦截系统调用可以限制不受信任的进程,不需要修改应用。

拦截困难重重

上下文缺失、状态复制、宽接口、TOCTTOU——每个都是安全陷阱。

内核 + 用户态混合

Janus 模式:内核拦截 + 用户态策略引擎——兼顾性能与灵活性。

现代沙箱广泛使用

Chrome 渲染器、Docker 容器、macOS App Sandbox——底层都是系统调用拦截。

延伸阅读