给 Go 程序加个沙箱:go-landlock
写服务端程序的时候,总担心一个问题:要是代码有漏洞,被利用了怎么办?
比如一个文件上传功能,本来只想让用户上传到 /tmp/uploads,结果路径穿越漏洞,文件写到了 /etc/passwd。
传统的解决方案是容器化、chroot、seccomp,但这些都挺重的。而且 chroot 需要 root 权限,seccomp 配置起来复杂。
Linux 5.13 引入了 Landlock,一个轻量级的沙箱机制。go-landlock 就是它的 Go 封装。
Landlock 是什么
Landlock 是 Linux 内核的一个安全模块,允许非特权进程给自己加限制。
注意几个点:
- 不需要 root —— 普通用户就能用
- 进程自己限制自己 —— 不是外部强制,是程序主动收紧权限
- 只减不增 —— 权限只能越来越少,没法撤销后重新获取
这跟传统的 sudo、capabilities 完全是反着来的思路:不是给程序更多权限,而是主动砍掉不需要的权限。
怎么用
最简单的例子:
err := landlock.V9.BestEffort().RestrictPaths(
landlock.RODirs("/usr", "/bin"),
landlock.RWDirs("/tmp"),
)
这行代码执行后,当前进程只能:
- 读
/usr和/bin目录 - 读写
/tmp目录
其他路径?访问不了。
举个例子,你的程序是一个 Web 服务,启动后加载配置、初始化完毕,就可以调用这段代码把权限收紧。之后就算代码有漏洞,想读 /etc/shadow?没门。
实际场景
文件处理服务
只允许访问上传目录和临时目录,其他地方碰不了。
CLI 工具
比如一个图片压缩工具,只需要读输入文件、写输出文件,没必要访问整个文件系统。
Web 服务
启动时加载完配置、证书、静态资源,然后锁定权限。日志目录、数据目录开放读写,其他目录关闭。
版本号 V9 是什么意思
Landlock 内核 API 有版本演进。V9 表示使用 Landlock ABI 版本 1 到最新版本的特性,会自动选择内核支持的最高版本。
BestEffort() 的意思是:如果内核不支持某个特性,尽量降级而不是报错。
生产环境可以用 Strict(),严格模式,不支持就失败。
能限制什么
目前 Landlock 支持:
- 文件系统访问 —— 读、写、执行、删除、创建目录等
- 部分网络操作 —— TCP/UDP 端口绑定、连接等
- 部分 IPC 操作 —— Unix socket 等
不是所有系统调用都能限制,但文件系统这块已经覆盖了大部分攻击面。
跟容器比
容器(Docker、containerd)是隔离整个环境,go-landlock 是在进程内部限制权限。
两者不冲突:
- 容器解决的是环境隔离问题
- Landlock 解决的是最小权限问题
你可以把 go-landlock 作为容器的补充,双重保险。
总结
go-landlock 让给程序加沙箱变得简单,一行代码就能限制文件访问。
适合场景:
- 长期运行的服务
- 处理用户输入的程序
- 需要最小权限原则的 CLI 工具
前提是服务器内核版本够新(Linux 5.13+)。
GitHub:https://github.com/landlock-lsm/go-landlock(⭐ 331,MIT)
原文来自微信公众号。