第46期 | 门上多了个保安
一、门上多了个保安:系统调用也得验"符节"
门开了,就得有人看门。第43期讲过,ABI 一旦许出去就收不回来;可门开得越大,半夜翻进来的东西也越多。2005年,有人决定在门口设卡。
那年 seccomp 随 Linux 2.6.12 进内核¹——作者 Andrea Arcangeli,当时在为 CPUShare 解决一个很市井的问题:把闲置 CPU 租出去,跑别人传来的陌生字节码,凭什么信它²?于是有了"secure computing mode":往 /proc/PID/seccomp 里写个"1",进程就进了 strict mode,只剩 read、write、exit、sigreturn 四个调用——连 open 都没有,文件必须在进门之前先打开好。门焊死大半,才敢放陌生人进来干活。
strict mode 太糙,七年没人爱用。2012年,Chromium OS 的 Will Drewry 带来了 seccomp-BPF³,合进 Linux 3.5:过滤器写成 BPF 小程序,按系统调用号、甚至按参数逐条验放。Chrome 沙箱第一个吃螃蟹,OpenSSH 6.0 跟进,后来 Docker、systemd、Android 全家都站了进来;2014年 Linux 3.17 干脆给了它一个独立的系统调用 seccomp()¹。
Kerrisk 在2018年算过一笔账¹:内核提供四百上下的系统调用,每一个都是进内核的攻击面,而多数程序只用其中一小撮——剩下那些"永远不该被调用"的,就是 seccomp 要焊死的门。
照见一层平时看不见的:这道门禁的受益者是所有跑容器的人,买单的却是写过滤器的人——Docker 的默认 seccomp profile 是几百行 JSON 的白名单,维护它是一门手艺。更深的一层是前提的翻转:以前默认进程可信,seccomp 默认进程不可信。这正是 Saltzer 和 Schroeder 在1975年写下的"最小权限原则"⁴——一个原理,等了三十多年,才长成能用的工程。杨振宁说的"发展性研究",就是这三十多年:原理不缺,缺的是把原理变成能跑的东西的中间层。
美学时刻:这让我想起古代的虎符——调兵时左右两半相合才能过。seccomp 就是内核门口验符节的人:门认符,不认人。(思想化用)
二、AI 的门,不认门牌号:门禁的语义正在搬家
如果 AI 的操作系统先出现,"系统调用号"这道门可能根本不存在。agent 不背号表,它向系统陈述意图:"我要读这批向量的最近邻"。门禁验的不再是"你走的是几号门",而是"意图 × 能力 × 数据流向"的三维谈判。
这不是空想,搬家已经开始了。2021年进内核 5.13 的 Landlock⁵,作者 Mickaël Salaün 熬了五年、改了34版才合入。内核文档把话说得很直:"Landlock 管的是内核对象上的访问控制,而不是系统调用过滤——后者是 seccomp-bpf 的活。"⁵ 门禁的语义,正在从"哪扇门"走向"进门干什么"。
再往前推一格:未来的过滤器不再是 BPF 字节码,而是一段可审计、可解释的策略声明;违规不再是 SIGSYS 一枪处死,而是被引向受限仿真——像沙盘,像学徒工先在模拟机上练手。AIOS 那篇论文⁶已经把"agent 的工具调用与访问控制"画成了 OS 的一等公民,门禁就长在调度器隔壁。
范式之眼在这里眨了一下:按号过滤是线性时代的遗产——门牌号管的是"指令流进出"。可今天的算力主体是向量阵列,GPU 上一次 kernel launch 撒出去几万个线程,按"次"数门牌号毫无意义。AI 时代要管的,是数据与意图的流向,不是进出的次数。
三、亲手给进程戴上手铐
40行Python,看见"Bad system call"
先观察。手边任何一台 Linux 机器,跑:
strace -c ls /tmp
数一数,一个小小的 ls 进了几次门——第45期算过"过路费",这次亲眼数一遍。
再动手。下面这段40行 Python,用 ctypes 调 prctl,给自己装上 seccomp-BPF 白名单:只放行 write、exit、exit_group,其余一律 SIGSYS。然后试着 open("/etc/hostname"):
import ctypes, os, struct
libc = ctypes.CDLL("libc.so.6", use_errno=True)
PR_SET_NO_NEW_PRIVS, PR_SET_SECCOMP, SECCOMP_MODE_FILTER = 38, 22, 2
AUDIT_ARCH_X86_64, ALLOW, KILL = 0xC000003E, 0x7FFF0000, 0x80000000
def stmt(code, k): return struct.pack("HBBI", code, 0, 0, k)
def jump(code, k, jt, jf): return struct.pack("HBBI", code, jt, jf, k)
LD, JMP, JEQ, K, W, ABS, RET = 0x00, 0x05, 0x10, 0x00, 0x00, 0x20, 0x06
prog = b"".join([
stmt(LD|W|ABS, 4),
jump(JMP|JEQ|K, AUDIT_ARCH_X86_64, 0, 4),
stmt(LD|W|ABS, 0),
jump(JMP|JEQ|K, 1, 3, 0),
jump(JMP|JEQ|K, 60, 2, 0),
jump(JMP|JEQ|K, 231, 1, 0),
stmt(RET|K, KILL),
stmt(RET|K, ALLOW),
])
buf = ctypes.create_string_buffer(prog)
fprog = struct.pack("HQ", len(prog)//8, ctypes.addressof(buf))
assert libc.prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == 0
assert libc.prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, fprog) == 0
os.write(1, "门禁已装上:只许 write/exit。现在试着 open 一个文件……\n".encode())
os.open("/etc/hostname", os.O_RDONLY)
存成 seccomp_demo.py,跑:python3 seccomp_demo.py
你会先看到"门禁已装上",紧接着 shell 甩回一句:Bad system call。进程被处死,exit code 159。那一刻你会真切地感到:门禁不是文档里的概念,是内核里真实存在的一只手。(这段代码已在本机实测跑通。)
落点:Docker 的 --security-opt seccomp 背后那几百行 JSON,干的就是这件事,只是更精细。看懂这40行,就看懂了容器沙箱的门禁是怎么写出来的。这套手法,放到边缘设备上跑第三方插件的进程身上一样好用——先让进程"不乱跑",再谈数据不出厂。
觉 然 小 注
门认符,不认人。从"默认可信"到"默认验讫",系统调用的门禁史是一部信任简史——而 AI 时代,连"验什么"都要重写。化用《金刚经》"无我、无人"的思想:最好的门禁,是对所有进程一视同仁的不信任。(思想化用)
引 用 与 出 处
- [1] Kerrisk, M. Using seccomp to limit the kernel attack surface. NDC TechTown 2018 演讲幻灯. https://www.linuxman7.org/conf/ndctechtown2018/limiting-the-kernel-attack-surface-with-seccomp-NDC-TechTown-Kerrisk.pdf
- [2] Arcangeli, A. seccomp 初始合入记录(含 CPUShare 背景). linux-dev git log, 2005. https://git.zx2c4.com/linux-dev/log/include/linux/seccomp.h?id=ad1e4f74c072eaa2c6d77dd710db31aafecd614f&showmsg=1
- [3] Drewry, W. [PATCH v14 12/13] Documentation: prctl/seccomp_filter. linux-kernel 邮件列表, 2012. https://lkml.indiana.edu/hypermail/linux/kernel/1203.1/02224.html
- [4] Saltzer, J. H.; Schroeder, M. D. The Protection of Information in Computer Systems. Proceedings of the Fifth ACM Symposium on Operating Systems Principles, Austin, Texas, 1975.
- [5] Salaün, M. Landlock LSM: kernel documentation. The Linux Kernel documentation, 2021. https://docs.kernel.org/5.17/security/landlock.html
- [6] Mei et al. AIOS: LLM Agent Operating System. arXiv:2403.16971, 2024. https://arxiv.org/abs/2403.16971
· · ·