Linux 进程沙箱一直是个尴尬的话题。seccomp-bpf 能限制系统调用,但得自己写 BPF 程序,端口号变了还得改过滤器。容器太笨重,bubblewrap 依赖 setuid 辅助。Firejail 倒是好用,但它的 setuid 二进制本身就是攻击面。Landlock 是内核直接给的答案——一个不需要 root、不需要 setuid、不需要写 BPF 的沙箱 API,5.13 就合进去了,只是没多少人知道怎么用。
背景#
Landlock 是 Mickaël Salaün 在 2021 年合入主线的一个 Linux Security Module(LSM)。它的核心设计目标很明确:允许任意进程(包括非特权进程)自愿限制自己的系统资源访问。注意"自愿"这个词——Landlock 不是强制性的 MAC 策略,它是一种自陷机制(self-sandboxing)。
传统沙箱方案的问题:
| 方案 | 需要 root/setuid | 优势 | 硬伤 |
|---|---|---|---|
| 容器 (Docker/Podman) | 需要(daemon root) | 完整的命名空间隔离 | 启动慢,镜像大,资源重 |
| seccomp-bpf | 不需要(prctl) | 精细控制系统调用 | BPF 过滤器维护成本高 |
| bubblewrap | 需要 setuid | 轻量命名空间 | setuid 二进制是攻击面 |
| Firejail | 需要 setuid | 预置 1000+ profile | 同样依赖 setuid |
| Landlock | 不需要 | 零特权沙箱,纯系统调用 API | 仅限文件/网络,不隔离进程视图 |
Landlock 的定位不是替代 seccomp 或容器,而是填补它们之间的空白——让你能在普通进程里对自己加限制,不需要任何外部工具。你写好规则集,调用三个系统调用,然后就锁住了。
核心原理#
Landlock 的 API 出奇地简单,只有三个系统调用:
| |
锁住之后不可逆——没有"解除"的接口。这是设计上的故意选择:一旦你限制了自己,同一个进程无法放宽限制,只能被新线程继承。
规则类型(截至 ABI v6)#
Landlock 的 ABI 版本在演进,每个版本追加新的能力:
| ABI | Linux 内核 | 新能力 |
|---|---|---|
| 1 | 5.13 | 基本文件系统限制(读写执行) |
| 2 | 5.19 | LANDLOCK_ACCESS_FS_REFER(跨目录 rename/link) |
| 3 | 6.2 | LANDLOCK_ACCESS_FS_TRUNCATE(截断文件) |
| 4 | 6.7 | 网络限制(TCP bind/connect) |
| 5 | 6.10 | LANDLOCK_ACCESS_FS_IOCTL_DEV(ioctl 限制) |
| 6 | 6.12 | IPC 作用域(abstract UNIX socket + 信号隔离) |
当前最新是 ABI v6(Linux 6.12+,2024 年底)。每个版本向后兼容——你在老内核上降级能力就行。
分层模型#
Landlock 有个关键设计:多层策略(layers)。每次调用 landlock_restrict_self 追加一层,最多 16 层。访问检查通过所有层的规则才放行——跟 SELinux 的 intersection 逻辑一样,不能的权限一层否决就能挡住。
| |
锁住之前必须调用 prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)。这个操作会防止进程通过 setuid、setcap 等方式获得新特权——Landlock 要求你不能一边限制自己一边偷偷升级。
代码实战#
下面是一个完整的 C 程序,演示如何用 Landlock 把进程关在一个"只读 /usr + 读写 /tmp + 能连 GitHub HTTPS"的沙箱里。
| |
编译运行:
| |
行为对比(同样调 open("/etc/shadow", O_RDONLY)):
| 操作 | 无沙箱 | 有沙箱 |
|---|---|---|
读 /etc/shadow | 普通用户→拒绝(DAC) | 拒绝(Landlock 层先拦住) |
读 /usr/bin/gcc | 允许 | 允许(规则 A) |
写 /tmp/test.txt | 允许 | 允许(规则 B) |
| 连 example.com:80 | 允许 | 拒绝(没声明 80 端口) |
| 连 github.com:443 | 允许 | 允许(网络规则) |
注意文件 /etc/shadow 即使 DAC 也不允许普通用户读,但 Landlock 的价值在于:即使 DAC 意外允许了(比如文件权限被改为 644),Landlock 也锁住了。这是纵深防御。
关于 ABI 兼容#
写产品级代码时一定要做 ABI 降级。上面的示例用了简单的 switch,更健壮的做法是:
| |
不这样做的话,在旧内核上 landlock_create_ruleset 会返回 EINVAL 或 ENOSYS。
生态现状#
已经有几个项目在生产环境用 Landlock:
| 项目 | 用途 | 备注 |
|---|---|---|
| landrun | 通用 CLI 沙箱工具 | Go 实现,2025 年火过一波 |
| nono | 无特权能力限制引擎 | 生产级使用 |
| systemd | service 的 RestrictFileSystems= | 从 v255 开始实验支持 |
内核 samples/landlock/sandboxer.c | 官方参考实现 | 功能完整的沙箱管理器 |
| go-landlock | Go 语言的 Landlock 绑定 | 官方维护 |
| Firejail (待合并) | 已有 issue #5269 提议加入 | 还没合入 |
Landrun 是最值得关注的新工具。它的用法简单到离谱:
| |
核心逻辑就是封装了上面那段 C 代码。跟 Firejail 的区别是:landrun 不需要 setuid,不需要安装 daemon,不修改 /proc/sys 配置。就是一个普通的 Go 二进制,任何用户都能跑。
今日可执行动作#
检查内核是否支持 Landlock:运行
dmesg | grep landlock或journalctl -kb -g landlock。如果没输出,检查/boot/config-*中CONFIG_SECURITY_LANDLOCK=y。Debian/Ubuntu 的通用内核默认启用。Arch Linux 也是。跑官方的 sandboxer 示例:在 Linux 内核源码树里执行以下操作:
1 2 3 4git clone --depth=1 https://github.com/torvalds/linux.git /tmp/linux make -C /tmp/linux samples/landlock/sandboxer LL_FS_RO="/usr:/lib:/etc/ssl" LL_FS_RW="/tmp" \ LL_TCP_CONNECT="443:80" ./samples/landlock/sandboxer bash试试在沙箱里
rm /etc/hostname——对比外面和里面的效果。试用 landrun:
go install github.com/Zouuup/landrun@latest,然后:1 2 3 4# 沙箱里跑 curl —— 只允许连 443 端口 landrun --ro /usr:/etc --net-connect 443 -- curl https://github.com # 试试连 80 端口,应该被拒绝 landrun --ro /usr:/etc --net-connect 443 -- curl http://example.com
参考#
- Landlock 官网和文档: https://landlock.io/
- landlock(7) 手册页: https://man7.org/linux/man-pages/man7/landlock.7.html
- 内核用户空间 API 文档: https://docs.kernel.org/userspace-api/landlock.html
- 内核 LSM 文档: https://www.kernel.org/doc/html/v5.13/security/landlock.html
- 官方示例 sandboxer.c: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/samples/landlock/sandboxer.c
- landrun GitHub: https://github.com/Zouuup/landrun
- LWN 文章 “Landlock LSM: unprivileged sandboxing”: https://lwn.net/Articles/840419/

