Firejail 是我日常对付各种浏览器、PDF 阅读器和聊天客户端时最顺手的武器而 Landlock 则是这几年内核里少见的、真正能让普通进程自己给自己上锁的文件系统沙箱机制。接触越深我越觉得 Firejail 把 Landlock 接入进来这一步属于典型的“112”一个负责把进程关进笼子另一个负责把笼子里的每一扇门都焊死。这篇文章想写透一件具体的事——Firejail 的 Landlock 支持究竟是怎么工作的为什么要这么设计实际配置时有哪些参数和坑以及最后怎么验证它真的在起作用。我从编译 Firejail 到调通沙箱策略前后折腾了大概一个下午中间踩了不少文档里不会写的坑整理出来应该能帮你省掉不少尝试时间。1. 先拆清楚Firejail 和 Landlock 到底在解决什么问题1.1 Firejail 这套沙箱是怎么工作的Firejail 本质上是一个通过 SUID root 方式启动的沙箱封装程序。它启动目标程序的过程第一次看的人会觉得夸张把进程丢进一个全新的 mount namespace 和 PID namespace挂载一堆只读目录和 bind mount塞入 seccomp 过滤器最后降权成普通用户再 exec 目标程序。这一整套下来程序以为自己在跑实际它能看到的东西、能调用的系统调用都已经和宿主环境完全不是一回事了。我刚开始用 Firejail 的时候经常被“为什么我在沙箱里看到的主目录内容跟外面不一样”这类问题绕晕。其实这就是 mount namespace 的作用Firejail 先把真实的主目录改挂成只读再把 /tmp、/dev/shm 等必要目录重新 bind 成可写的新副本。程序在里面做任何修改都只影响临时副本宿主目录纹丝不动。这正是它最核心的价值不需要改程序本身也不需要写复杂策略一条命令就能把不信任的程序限制在独立空间里。但 mount namespace 解决的是“看得到看不到”的问题它并不能替代真正的权限控制。如果程序里有一个恶意模块或者浏览器被某个漏洞利用只要攻击者还能通过某种方式拿到宿主文件系统的句柄namespace 这层隔离就可能被击穿。这时候 Landlock 的价值就凸显出来了它不管你看到的是什么路径形态而是在 VFS 权限检查那一层直接判断“这个文件你能不能碰”。1.2 Landlock 跟传统命名空间、seccomp 的区别很多刚接触 Linux 安全机制的人会把 Landlock 和 seccomp、AppArmor 混在一起实际上它们处于完全不同层面。seccomp 过滤的是系统调用本身比如你可以禁止进程调用mount、ptrace这样敏感的系统调用AppArmor 和 SELinux 则是系统级的强制访问控制需要管理员预先加载策略对整个系统的进程统一施加规则。而 Landlock 走的是另一条路它是给“无特权的进程自己限制自己”设计的。我给一个很生活化的类比。seccomp 相当于限制一个人“不许碰任何危险工具”AppArmor 相当于公司规定“每个员工只能在自己工位抽屉里拿东西”而 Landlock 更像是一个人自己戴上手铐把双手的活动范围约束在一张提前画好的桌子上。Landlock 的关键特性是它不需要 root 权限也不需要系统管理员预先定义全局策略进程启动后自己调用内核接口创建规则把后续自己和所有子进程的文件访问严格限制在规则允许的路径集合内。这里有个细节值得强调Landlock 是在 LSMLinux Security Module框架下实现的它会跟普通文件权限、能力位等叠加生效。即使进程有 root 权限只要它主动应用了 Landlock 规则集也没办法绕过这条路径白名单。这是 Firejail 特别看重它的原因即使沙箱内的进程找到了提权漏洞它仍被困在 Landlock 划出的文件访问边界里。1.3 什么时候你才真正需要 Landlock 这一层我不止一次被人问已经有了 Firejail 的 namespace 和 seccomp为什么还要专门开 Landlock答案是纵深防御。Firejail 默认的 mount namespace 会把宿主根目录以只读方式挂载到某个隐藏路径用来维持一些程序的兼容性。假如攻击者利用内核漏洞或某个误配置拿到了这块隐藏空间的访问权单靠 namespace 是拦不住的。Landlock 进程级规则会在 VFS 层直接终结这类访问即使路径能看到权限检查也过不去。另一个更现实的场景是共享机器。我曾在实验室的公共工作站上跑可疑的统计软件同机还有别人放的 SSH 私钥。为了避免一个 PDF 阅读器的渲染漏洞就把整个用户目录拖下水我给所有图形程序都包了一层 Firejail 并强制开启 Landlock。这样即便软件被攻破攻击者在文件系统层面的活动范围也被压到最小。可以说在没有统一 AppArmor/SELinux 策略的桌面环境里Landlock 是性价比最高的“个人版 MAC”。2. Landlock 原理拆开看规则集、命令与访问权2.1 一次只能加严不能放松的规则模型Landlock 的规则模型有一个反直觉的设计一旦进程调用接口把自己限制住这个限制就不可逆后续只能继续叠加更严格的规则永远不能放松。这与安全上的“最小心态”完全一致——限制一旦生效任何逻辑漏洞都无法让进程重新获得被放弃的权限。具体来说Landlock 有三个核心接口landlock_create_ruleset用来创建一个规则集landlock_add_rule往规则集里添加一条具体规则landlock_restrict_self把规则集正式应用到当前进程。规则由两个要素组成路径句柄和访问权限位。路径句柄标识一个文件或目录访问权限位则定义了允许在那个对象上执行的操作比如读文件、写文件、创建目录、删除文件、截断文件等。值得注意的一个概念是handled_access_fs位掩码。进程在创建规则集时需要声明“哪些权限类别由这套规则接管”。没有声明接管的权限类别会继续走系统原有的权限检查逻辑只有声明过的类别才受 Landlock 白名单约束。所以 Firejail 接入时要做一件非常重要的事把所有需要管制的文件访问权限位全部放进handled_access_fs才能真正做到“白名单之外一律拒绝”否则就会留下规则没有覆盖到的权限通道。这也是我后来排查时第一个怀疑的方向非常多自定义脚本都会在这里栽坑。2.2 关键系统调用与 ABI 版本Landlock 对内核版本有硬性要求而且不同版本的 ABI 能用的权限位和特性差别很大。ABI 版本本质上是内核向用户空间暴露的能力等级在高版本内核上运行的程序如果检测到当前 ABI 不支持某个能力就必须优雅降级而不是强行要求。ABI 版本对应内核版本主要新增能力1Linux 5.13基础的读、写、执行、目录遍历、文件创建/删除等文件系统访问权2Linux 5.19新增对文件截断TRUNCATE操作的控制3Linux 6.2新增 REFER 权限可限制 rename 和 link 跨目录操作4约 Linux 6.10新增网络层能力可限制 TCP bind/connect 目标端口这里面 ABI v3 的 REFER 权限尤其值得注意。以前没有它时一个进程无法单独控制“是否允许把 A 目录里的文件重命名到 B 目录”而这是很多沙箱逃逸攻击喜欢利用的点。Firejail 在较新版本里会尽量使用当前内核支持的最高 ABI但它的核心策略始终是文件路径白名单所以 ABI v4 的网络控制目前还没被完整接进它的命令行体系实际使用时不要指望用 Firejail 参数去直接限制端口。由于 glibc 至今没有为 Landlock 提供统一的原生封装Firejail 在实现时是直接通过syscall()调用的同时配合prctl(PR_SET_NO_NEW_PRIVS)来确保规则应用后进程无法通过 exec 提权。这也是整套机制安全性的根基no_new_privs 状态继承给所有子进程Landlock 规则集同样依赖这个状态保证限制不会在 exec 后失效。2.3 Firejail 是怎么把 Landlock 接进来的Firejail 的源码里专门有 landlock 相关模块负责在沙箱启动流程的特定阶段创建规则集。整个过程大致是先完成 mount namespace、文件系统重挂载、seccomp 初始化然后在降权之前以 root 身份调用 Landlock 接口建立白名单白名单路径由三部分拼成编译时的默认系统路径集合、当前沙箱的绑定挂载清单、以及用户通过命令行或配置文件额外指定的可读/可写路径。这个设计顺序是有讲究的。Landlock 规则集一旦应用到进程后续路径访问都会受它约束所以必须在程序真正启动前完成。但也不能太早否则 Firejail 自己后续的初始化操作可能被白名单挡住。源码里专门处理了这类边界情况确保规则集覆盖足够广的同时不会锁死沙箱自身的运行。我印象中 Firejail 是 0.9.72 开始引入实验性 Landlock 支持到 0.9.74 左右基本稳定如果你手上的版本低于 0.9.72很多参数都不存在建议直接升级。值得一提的细节是 Firejail 对“路径句柄”的处理。Landlock 规则基于路径句柄和 inode 识别文件对象Firejail 在解析配置文件里松散路径时会做路径规范化和去重。用户写一个/home/user/../user/Downloads这种带.的路径它会先归一化成/home/user/Downloads再纳入白名单避免规则预期跟实际生效不一致。这类细节不写在官方文档里但在排查问题时非常关键。3. 实际操作从编译到配置把 Landlock 用起来3.1 确认内核与发行版支持动手之前先确认三件事缺一不可。第一查看内核版本是否支持 ABI v1 以上uname -r输出 5.13 以上一般没问题第二确认内核编译时开启了 Landlock 支持最简单的判断是查看/proc/sys/kernel/landlock存在这个文件且能读出 ABI 版本号说明内核已启用第三查看当前系统 LSM 列表里有没有 Landlock命令是cat /sys/kernel/security/lsm。我在几台不同发行版机器上测过情况差异很大Ubuntu 22.04 自带内核默认开启Debian 12 也是默认支持但某些企业级发行版的自带内核虽然版本号够高却没有打开 Landlock 配置读/proc/sys/kernel/landlock会直接报“文件不存在”。如果你遇到这种内核要么换发行版的开源内核要么自己编译内核时把CONFIG_SECURITY_LANDLOCKy加进去没有别的捷径。还需要说明一点/proc/sys/kernel/landlock读出来的数字就是当前内核支持的 ABI 版本。例如输出3就表示支持 ABI v1 到 v3 的能力。Firejail 运行时自己会做检测遇到不支持的权限位就自动回退所以即使内核 ABI 只有 1Firejail 也能跑只是策略覆盖会少一些。3.2 编译 Firejail 时的 landlock 开关我建议直接从源码编译最新版 Firejail因为很多发行版仓库里的 Firejail 版本偏老要么没带 Landlock 支持要么带了一些早期不稳定的 bug。编译前需要准备这些依赖构建工具链、libcap-dev、pkg-config以及内核 UAPI 头文件linux-libc-dev。在 Debian/Ubuntu 系上命令大致是sudo apt install build-essential libcap-dev pkg-config linux-libc-dev然后从官方仓库克隆或下载源码包我的习惯是放到/opt/src下方便统一管理版本。解压后先运行./autogen.sh生成 configure 脚本再执行./configure --prefix/usr --enable-landlock make -j$(nproc) sudo make install最容易被忽略的是--enable-landlock这个开关。不少人从官方包管理器装 Firejail等到想用--landlock参数时发现没有任何反应十有八九就是发行版编译时没开这个特性。另外如果你编译的机器本身内核比较老/usr/include/linux/landlock.h可能都没生成这时候需要从新版内核源码里手动复制include/uapi/linux/landlock.h到系统的头文件目录再重新编译。编译完之后怎么确认支持已经打进去了直接看二进制里的帮助信息最直观firejail --help | grep -i landlock如果有--landlock和--landlock-writable相关选项出现说明编译时支持已经启用。这一步务必在写进自动化脚本之前做否则后面排查会浪费很多时间。3.3 命令行与配置文件的关键参数Firejail 的 Landlock 参数设计得比较简洁核心就两个--landlock开启 Landlock 白名单限制--landlock-writable路径额外把某个目录放进可写白名单。结合起来一个典型用法是这样firejail --landlock --landlock-writable$HOME/Downloads firefox这条命令的意思是Firefox 运行在 Firejail 沙箱里同时启用 Landlock只允许它读取系统库文件、沙箱重挂载的目录和主目录的只读部分而$HOME/Downloads被显式声明为可写目录。如果省略--landlock-writableFirejail 默认只授予极少的可写入口程序可能会在写配置时失败。这里要特别强调一个容易误会的点--landlock并不是对所有文件都开“读白名单”它更精确地说是在沙箱已有文件系统视图的基础上再叠加一层路径访问规则。比如/usr/lib通常会被 Firejail 自动识别为必要读取路径并纳入白名单但某些自定义安装路径、/opt下的软件目录如果没被自动识别程序就可能读取失败。遇到这类情况不要急着怀疑 Landlock而是先用调试模式看规则集到底包含了哪些路径。除了命令行还可以把配置写进全局配置或 profile 文件里。全局配置在/etc/firejail/firejail.config里面有landlock开关设置成yes后对所有沙箱默认生效。针对单个程序则是在~/.config/firejail/程序名.local这类 profile 里追加参数。例如我在 Firefox 的 local profile 里写的是landlock yes writable $HOME/Downloads writable $HOME/.cache/mozillaprofile 文件的好处是可以跟随程序长期维护不需要每次手动敲参数。我个人的习惯是先用命令行试跑确认路径没问题后固化到 profile 里这样后续升级内核或迁移机器都不需要重新踩坑。3.4 验证 Landlock 是否真正生效配置完了不能拍脑袋说“应该生效了”必须做验证。Firejail 提供了调试输出功能命令是firejail --debug --landlock 程序名它会打印大量沙箱初始化日志其中就包括 Landlock 规则集的注册情况。在日志里搜landlock关键字能看到“landlock: 已注册N条路径规则”之类的信息这比任何外部猜测都可靠。方法二是在沙箱内做实际访问测试。进入一个 Landlock 限制的 Bash 沙箱尝试读取一个本不应该被访问的宿主路径正常情况下会得到Permission denied。需要注意这里测试的目标最好选一个不在默认白名单里的路径比如某个未被绑定的宿主目录。如果测试返回成功说明规则根本没有限制住需要回看前三步配置。方法三看内核日志。Landlock 在触发拒绝访问时会在审计日志里留下记录。通过sudo dmesg | grep -i landlock或者sudo journalctl -k | grep -i landlock能看到形如“Landlock 拒绝进程访问路径”的审计条目。这个方法适合在沙箱里跑一段自动化测试脚本触发足够多次越权访问后统一回看日志判断是否有规则没有覆盖到。我在实际项目里会组合使用三种方法调试日志确认规则集正常注册沙箱内访问测试确认行为符合预期再抽查 dmesg 确认内核侧也记录了拦截事件。三个都通过我才会放心地把这套策略部署到生产环境。4. 常见问题排查与避坑实录4.1 Landlock 不可用或版本不认识最典型的现象是执行firejail --landlock 程序后程序正常启动但日志里根本没有 Landlock 相关输出或者提示Landlock is not supported。这通常有三个原因内核版本太低或没编译 Landlock 支持、Firejail 编译时没开--enable-landlock、以及没有安装匹配的 Linux UAPI 头文件导致运行时检测失败。排查顺序建议是先看/proc/sys/kernel/landlock是否存在不存在就是内核问题再看firejail --help里有没有--landlock没有就是编译问题最后看调试日志输出的内核 ABI 版本值如果版本值显示 0 或者其他怪异数字重点检查头文件与运行内核是否匹配。我在一台老旧的 Ubuntu 20.04 内核上就踩过这种坑编译时用了较新的头文件运行时内核 ABI 只有 1Firejail 自己做了降级整体功能倒是没崩只是比预期少了一些权限位控制。4.2 启动白名单不合适导致程序读写文件失败Landlock 模式下最常遇到的运行时报错是程序一启动就崩溃或者某个功能无法初始化看调用栈能发现是在读取某个配置文件时被拒绝。麻烦的地方在于很多 GUI 程序写配置的位置并不按常规来尤其像 Electron 应用会往~/.config甚至~/.local/share里写大量缓存如果这些路径不在白名单里就会静默失败。我的处理思路是分两步走。第一步先在宽松模式下观察程序都访问了哪些路径可以用firejail --landlock配合strace -f -e openat,open跟踪文件访问把所有EACCES的路径收集起来第二步把这些路径按“真正必要”和“可有可无”分类只把必要的加进白名单而不是图省事直接--landlock-writable$HOME那等于把 Landlock 这一层直接废掉。另外要提醒一点Firejail 的--landlock-writable如果同时指定多个路径路径之间要用冒号分隔我在早期版本试过用逗号分隔结果只有第一个路径生效程序写第二个目录时照样报错。后来看源码注释才知道解析函数用的是冒号分隔。这个坑虽然小但非常隐蔽。4.3 与 AppArmor、其他安全工具组合时的注意点Firejail 本身也支持通过--apparmor加载额外的 AppArmor profile很多人以为 Landlock 和 AppArmor 二选一就行实际两者可以共存而且叠加后效果更好。AppArmor 是系统级 MACLandlock 是进程自限制一个管环境、一个管进程自身各司其职。但在个别发行版上AppArmor 会默认限制/usr/bin/firejail本身导致即使我配置正确Firejail 一启动就被系统拦截。这时需要单独给 Firejail 放行规则或检查发行版是否把它的 AppArmor profile 切换到了 enforce 模式。这类问题通常表现为Firejail 启动后没有任何错误信息但目标程序没有被沙箱化ps一看进程还是宿主环境路径。还有一个容易犯的错是同时启用--apparmor和--landlock时把 AppArmor 的可写目录声明和 Landlock 的可写路径声明写得不一致。两者判定标准不同AppArmor 按 profile 规则放行Landlock 按路径白名单放行只要有一边漏掉程序在实际运行时依然会失败。我的建议是写一个统一的可写路径清单然后在两个配置里保持一致维护成本会低很多。4.4 一个快速排查表常见问题判断方法解决建议参数没生效firejail --help无 landlock 选项用--enable-landlock重新编译内核不支持/proc/sys/kernel/landlock不存在升级内核或自己开启编译选项程序启动崩溃dmesg 中有 EACCES用 strace 找出缺失白名单路径可写目录不生效指定路径未被写成功确认冒号分隔、路径已归一化与 AppArmor 冲突沙箱进程没有生效检查系统 AppArmor profile 状态ABI 版本不符调试日志显示降级信息清理老内核头文件后重新编译这张表基本覆盖了我实际部署 Firejail Landlock 时碰到过的绝大多数问题。每次排查我会先把表格条件挨个过一遍定位速度会快很多。5. 最后聊聊我的使用心得前前后后折腾了这几年我最深的感触是 Landlock 这套文件系统沙箱刻意强调“进程自限制、无特权运行”的设计思路放在真实安全对抗环境里其实非常实用。它不像 AppArmor 那样需要管理员系统级部署也不像 SELinux 那样有复杂到吓人的策略语言普通用户用一条firejail --landlock firefox就能获得比较可靠的文件访问边界。对于桌面 Linux 爱好者、共享主机使用者或者只是想在跑不信任脚本时多一层保险的人这套组合的门槛已经低到不用专门看内核文档了。在实际使用中我强烈建议循序渐进地收紧策略。先在宽白名单下运行一段时间用调试日志和审计记录观察程序到底需要哪些路径之后再逐步裁剪。直接照搬网上的最小白名单配置碰到需要写缓存的软件大概率会让日常使用变得痛苦。根据我个人的经验火力全开的 Landlock 用在浏览器、办公软件、PDF 阅读器上收益最明显共用工作站的场景下收益会更大。如果后续 Landlock 的 ABI v4 网络端口控制能被 Firejail 完整暴露成命令行参数那这套沙箱的可玩性还能再上一个台阶届时无特权进程的网络访问也能被精确画线想想还挺期待。在那之前先把文件系统这层锁用好已经能挡住相当大一部分现实中的安全风险了。
阅读完成 · 觉得有帮助?