
字节笔记本
2026年10月6日 · 约 6 分钟读完
landlock-run 契约精读:白名单锁住子进程
Agent 要替用户执行命令,第一个要回答的问题是:怎么保证这条命令只碰它该碰的文件?在 Linux 上,DeepSeek Harness 给出的底层答案之一是 landlock-run:一个基于内核 Landlock 机制的「先限制自身、再执行」启动器。整个启动器只有约 300 行 C11,与 musl 静态链接成单个二进制,glibc 和 musl 发行版都能直接运行;它先把 Landlock 规则集安装在自己身上,再 exec 被包装的命令,规则集跨 execve 继承,命令以及它派生的每一个进程都在约束之下运行,而发起调用的进程自身不受任何限制。它以失败闭合为原则:内核无法强制执行时,命令根本不会运行。本文依据仓库中的 CLI 契约文档,把这个启动器对外承诺的行为完整过一遍。
契约即兼容面
landlock-run 是一份被钉死的契约:二进制与所有消费方之间跨仓库的兼容面。消费方不手工拼命令行,而是通过 npm 入口包 @deepseek-ai/node-addon-landlock-run 的三个函数与它对话:launcherPath() 解析当前平台二进制的路径,probe() 做功能性探测,grantArgs() 把授权配置翻译成命令行参数。平台二进制则按 linux-x64 与 linux-arm64 两个 npm 包分发,npm 的 os 与 cpu 字段保证安装器只拉取匹配的一份。契约里钉死的任何一条行为,改动都要求整个包家族一起升版本,并在发布说明里记录,因为下游全靠这些行为做归因与降级决策。
调用语法:两个旗标加一个分隔符
landlock-run [--ro <path>]... [--rw <path>]... -- <argv>...
landlock-run --probe--ro 与 --rw 负责授权,-- 之后是被包装命令的 argv,经 execvp 启动,环境变量原样传递。语义要点有五条。第一,规则集是允许清单,未授予的一切访问都被拒绝,没有「默认放行再加排除」的写法。第二,--ro 授予路径之下的读取与执行,--rw 授予路径之下的全部文件访问,具体以运行内核协商出的 Landlock ABI 能管辖的访问为准。第三,对非目录的授权只保留文件兼容的访问位,--rw /dev/null 正是靠这一条工作的。第四,-- 是强制分隔符,--probe 与授权参数、被包装命令互斥。第五,没有其他旗标,也不读取任何环境变量:哪个二进制以什么方式限制进程,永远不会被周围环境悄悄改写。

退出码 125:只属于启动器
启动器层的一切失败都返回 125,也就是入口包里的约定常量 LAUNCHER_FAILURE_EXIT:用法错误、内核无法强制执行 Landlock、授权根目录打不开、exec 失败,共同点是此时被包装的命令根本没有运行。exec 成功之后,子进程的退出状态原样透传,哪怕子进程自己碰巧也退出 125。因此消费方的归因规则必须是合取的:退出码 125 与 stderr 中一行以 landlock-run: 为前缀的致命诊断同时成立,才能判定为启动器失败;只看到一个 125,什么也说明不了。--probe 是这条规则的特例:内核可以强制执行(完整或部分)时返回 0,否则返回 125。
报告行:探测结果怎么读
探测成功时,启动器在标准输出上精确打印一行:landlock: fully enforced,或者 landlock: partially enforced (older ABI)。入口包的 probe() 把这两行分别映射为 full 与 partial,非零退出则映射为 unusable。在只有部分 ABI 的内核上,受限运行会先用 stderr 打出一行通知 landlock-run: partial enforcement (older Landlock ABI),然后照常执行命令,内核支持的范围内仍然受限。所有致命错误遵守同一格式:一行以 landlock-run: 为前缀的 stderr,随后以 125 退出。

限制语义:一次安装,代代受限
启动器先设置 no_new_privs,再把规则集安装在自己身上,最后 exec 目标命令。Landlock 规则集跨 execve 继承,限制不依赖任何守护进程或监控者,命令的每一个后代进程生来就在围栏里。规则集管辖的范围是当前内核协商出的 Landlock ABI 所覆盖的文件系统访问,这一版构建最高到 ABI 5;比运行中 ABI 更新的访问类型不受管辖,这段差距就是 full 与 partial 的分界线。
接入者该记住的三件事
对想集成这个启动器的人来说,契约里有三条最实用。其一,机制与策略分离:哪些路径可读、哪些可写是消费方的政策决定,启动器只负责把授权变成内核强制执行。其二,可用性以探测为准:probe() 是功能性的,会在一个短命子进程里真实构建并强制执行一个最大规则集,因为版本检查会漏掉那些带着相关系统调用却拒绝强制执行的内核,探测结果才是权威信号。其三,失败闭合:任何一层无法给出受约束的执行,命令就不会运行,不存在静默的无隔离透传。三条合在一起,跑一条不可信命令就变成了可以精确推理的操作:授权清单是边界,探测结果是前提,125 加致命行是故障信号,其余一切退出状态都属于被包装的命令本身。



