Landlock:Linux 深度防御
其设计理念类似于OpenBSD的unveil()(以及程度较轻的pledge()):程序可与内核达成契约,声明“我仅需访问这些文件或资源——若遭入侵,请拒绝我访问其他一切内容”。
Landlock 是什么?
Landlock是Linux的一项API,允许应用程序明确声明其被允许访问的资源。其设计理念类似于OpenBSD的unveil()(以及程度较轻的pledge()):程序可与内核达成契约,声明“我仅需访问这些文件或资源——若遭入侵,请拒绝我访问其他一切内容”。
它为应用程序提供了一种简单易用的深度防御方案。相较于传统Linux安全机制,Landlock更易理解且集成便捷。
本文旨在提供通俗易懂的入门指南,期待能说服您尝试Landlock。
工作原理
Landlock是自Linux 5.13起支持的Linux安全模块(LSM)。不同于SELinux或AppArmor等强制访问控制框架,Landlock采用 临时性 限制:策略在运行时创建,仅对当前线程及其后续子进程生效,进程退出后即消失。
无需为文件添加标签或扩展属性,应用程序可动态创建策略。
Landlock策略包含两部分:
- 受控访问——需限制的操作类别(如文件系统读写)。
- 访问许可——明确允许执行这些操作的对象清单。
例如,可创建处理所有文件系统读写及网络绑定的策略,并授予:
- 仅读取访问
/home/user - 读写访问
/tmp - 绑定端口
2222的权限
应用程序随后调用 landlock_restrict_self() 进入受限域。自此时起,该线程的子线程和子进程将永久受限。限制不可撤销。
策略支持分层(最多16层)。子层可进一步 缩减 访问权限,但无法恢复父层已撤销的权限。例如子线程可添加策略层将自身限制为仅读取/home/user,但一旦某层取消该授权,便无法重新获得绑定端口2222的权限。
Landlock采用无特权设计——任何应用程序均可实现自我沙箱化。其采用ABI版本控制机制,即使在缺乏新功能的旧版内核上,程序也能实现尽力而为的沙箱化。
该方案同时支持堆叠式LSM,可与selinux或apparmor组合构建补充层。
应用场景
当应用程序所需文件或目录集可预测时,Landlock 便能大显身手。例如,Web 服务器可限制自身仅访问 /var/www/html 和 /tmp 目录。
与 SELinux 或 AppArmor 不同,Landlock 策略无需管理员介入或全局配置。开发者可直接将策略嵌入应用程序代码,使沙箱化成为开发流程的自然组成部分。
由于 Landlock 使用时 无需特权 ,将其添加到大多数程序中非常简单。
目前已提供 Rust、Go 和 Haskell 等语言的绑定库,多个项目还提供了类似 unveil 的用户友好型封装工具。
遗憾的是官方 C 库尚未发布,但已有若干第三方实现可供尝试。
以下是一个简短的 Rust 示例:
use landlock::{
ABI, Access, AccessFs, Ruleset, RulesetAttr, RulesetCreatedAttr, RulesetStatus, RulesetError,
path_beneath_rules,
};
fn restrict_thread() -> Result<(), RulesetError> {
let abi = ABI::V1;
let status = Ruleset::default()
.handle_access(AccessFs::from_all(abi))?
.create()?
// 对 /usr、/etc 和 /dev 的只读访问权限
.add_rules(path_beneath_rules(&[“/usr”, “/etc”, “/dev”], AccessFs::from_read(abi)))?
// 对 /home 和 /tmp 进行读写访问
.add_rules(path_beneath_rules(&[“/home”, “/tmp”], AccessFs::from_all(abi)))?
.restrict_self()?;
match status.ruleset {
RulesetStatus::FullyEnforced => println!(“完全沙盒化。”),
RulesetStatus::PartiallyEnforced => println!(“部分沙盒化。”),
RulesetStatus::NotEnforced => println!(“未沙盒化!请更新内核。”),
}
Ok(())
}
Linux 沙箱现状:为何至关重要
随着 Linux 应用范围扩大,针对桌面用户的恶意软件数量也在增长。虽然 Linux 历史上享有相对安全,但这主要归因于其较小的市场份额和比 Windows 更高的技术门槛——而非 Linux 本身更安全。
Linux 并非安全万灵药。例如,在多数主流发行版中:
- 用户可下载并执行未经信任的二进制文件,且不会触发任何警告。
- 来自互联网的Shell脚本可被直接管道传输并盲目执行。
- 大量用户启用了无密码sudo功能,使其能随时获取root权限。
- 非特权应用通常可:
- 读取
~/.ssh、~/.bashrc、浏览器Cookie及$HOME目录内任何内容 - 修改环境变量和
$PATH路径 - 创建systemd用户服务
- (在X11环境下)记录键盘输入并读取输入设备
- 绑定任意网络端口
- 读取
多种工具试图提升Linux安全性,但各自存在显著缺陷:
容器化(Docker、Podman)
- 设计初衷是服务隔离,而非桌面应用。
- 用户目录访问管理笨拙。
- 许多用户通过
--privileged或--network host破坏隔离机制。
Flatpak / Snap
- 适用于图形化应用(Flatpak尤佳)。
- 常需授予过宽权限。
- 不太适合命令行工具。
Firejail
- 需为每个应用创建配置文件。
- 每次运行必须显式调用,或需依赖封装脚本。
从开发者视角:
seccomp
- 强大的系统调用过滤功能。
- 配置过程繁琐且易出错。
- 黑名单机制脆弱;新系统调用可能破坏现有规则。
- 参数过滤难度高且存在TOCTOU(触发时与触发前)安全隐患。
SELinux
- 功能极其强大,但理解难度高。
- 需全局策略配置且涉及管理员操作。
- 因复杂性导致多数用户禁用。
- 大多数发行版默认未启用(在安卓系统中应用广泛)
AppArmor
- 比SELinux易用,但仍需管理员定义配置文件
- 仅全局生效,缺乏进程级命名空间隔离
- 多数发行版禁用该功能,但在桌面系统中应用更普遍
Landlock
- 无特权模式
- 以应用为核心
- 易于集成
- 默认拒绝访问
- 自5.13版本起广泛支持
- 具备前向/后向兼容机制
Landlock虽非完美,却填补了关键空白:提供简单、自包含的无特权沙箱工具。
Landlock的潜在应用场景:
长期运行的系统守护进程若以提升权限运行,可受益于 Landlock 的限制机制。
处理二进制格式的桌面应用(如 PDF 阅读器、图像查看器、网页浏览器和文字处理器)可被限制仅访问其初始打开的文件。
FTP 和 HTTP 服务器可被绑定至所需文件。即使 nginx 以 root 身份运行,攻击者即使获得完整反向 shell,也无法访问策略范围外的文件。
若监督机制提案获采纳,我们将为Linux桌面引入类安卓权限体系。Flatpak在此领域表现尚可,但试想若桌面每个进程访问敏感文件或资源前都需明确请求(至少一次)会如何。
结合易用的图形界面、更新管理机制及权限授予保存系统,我们有望在桌面端打造更安全可靠的Linux用户体验。
Landlock 当前开发进展
多项潜力功能正在积极开发中:
- 监督模式
允许用户空间的“监督者”交互式地允许或拒绝访问——类似于Android风格的权限提示。 - 套接字限制 对进程可使用的套接字类型或端口进行精细控制。
- LANDLOCK_RESTRICT_SELF_TSYNC 确保限制规则传播至进程内所有线程。
- LANDLOCK_ADD_RULE_QUIET 允许对特定对象抑制审计消息。
- LANDLOCK_ADD_RULE_NO_INHERIT * (注:此为本人提交的补丁系列)* 防止规则意外继承父目录权限,实现更精细的文件系统控制。
快速概览
Landlock 是为 Linux 设计的简单、无特权、默认拒绝访问的沙箱机制。
它易于理解、易于集成,并具有显著提升桌面及应用程序安全性的潜力。
欢迎在您的应用中尝试使用。
本文文字及图片出自 Landlock-ing Linux
我使用Landlock检测并阻止了不需要的遥测数据。编写了C代码来限制网络通信:仅允许单一端口接收连接,禁止任何出站连接,且拒绝其他端口的连接请求。
dmesg日志会显示被拦截的连接(我认为这可能是审计功能)。我以示例文件sandboxer.c为基础进行开发(https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux…),但仅配置为禁用文件限制功能,仅保留网络访问权限,确保仅允许该白名单入站端口。我喜欢它是因为它作为无特权用户模式程序就能直接运行,无需任何配置。这只是个微型C程序,在容器内运行时也无需设置防火墙。除了需要编译小段C代码外,几乎无需额外操作。我最初接触Landlock是想寻找bubblewrap的替代方案,因为始终没能找到在bwrap中实现同等功能的便捷方法。
“Landlock: 非特权访问控制”中的“非特权”特性,正是它在这个使用场景中的核心卖点。
不过我认为它对主动攻击型程序并不有效。
能否分享你的源代码?
我刚上传到这里:https://github.com/Noeda/landlock-network-sandboxer-tool
希望大家明白这绝非高质量代码 🙂 它类似我另一条评论中沙箱示例的简化版。例如完全无需涉及文件系统操作。
建议参考其他相关评论获取更完善的工具方案。不过作为示例或许仍有参考价值。
感谢!
我最近用“bwrap”给下载的itch.io游戏搞沙盒测试,发现要实现最小权限运行并不简单。目前“Microlandia”始终无法运行,但其他Unity游戏在“bwrap”下运行正常。期待更多Landlock工具问世,让这项任务变得更轻松。
– https://github.com/containers/bubblewrap
– https://codeberg.org/dannyfritz/dotfiles/src/commit/38343008…
– https://explodi.itch.io/microlandia
我正在尝试使用 bwrap 将 npm 项目操作与系统其他部分隔离。
效果似乎相当不错?不过我这个周末才刚开始尝试bwrap。真希望能够直接告诉bwrap“将程序放入预先准备好的网络命名空间”,因为访问不安全的本地开发服务器也可能存在风险。
我曾设想为每个项目创建工具箱+自定义用户——这样就能“简单”实现隔离环境,但确实会导致大量冗余。我认为这确实是个天真的解决方案。
Bwrap似乎是更好的选择。
若愿意编写配置文件,https://wiki.archlinux.org/title/Systemd-nspawn 中的 systemd-nspawn 恰能实现自定义用户+多重沙箱的组合方案。
bwrap看似简单得多,但若需要更精细的控制(比如基于Ubuntu运行环境——许多游戏都是针对该环境编译的),systemd-nspawn能提供强大功能。
安卓就是这么做的。每个应用都属于不同用户。
哦真的吗?这倒出乎意料
Landlock在容器运行时中的现状如何?简单搜索发现CRI似乎正试图定义自定义Landlock接口。
这必然会落后于内核支持,但更关键的是,我预见不到多少容器镜像打包者、Helm配方维护者及其他YAML处理者会参与维护Landlock沙箱策略。
应用程序直接使用Landlock沙箱化解析器等敏感组件是合理的。但若CRI默认阻断系统调用,基础设施人员绝不会为每个应用维护专属沙箱策略——应用只会收到ENOSYS异常而无法沙箱化。
或许我完全误解了核心思路,但实在不明白为何需要在中间添加自定义层,而非让容器运行时直接放行安全系统调用?
> 快速检索发现CRI似乎正试图定义自己的定制版Landlock接口。
您指的是[0, 1]吗?
> 但如果CRI默认只是拦截系统调用
是这样吗?你从哪里得出这个结论的?
> 我可能完全没理解这个思路,但实在不明白为什么需要在中间添加自定义层,而不是让容器运行时直接放行安全系统调用?
因为后者需要信任应用程序会执行正确的锁定操作?
[0]: https://github.com/opencontainers/runc/issues/2859
[1]: https://github.com/opencontainers/runtime-spec/issues/1110
我认为这并非真正为容器运行时设计的方案。或许能勉强实现某种“方枘圆凿”的效果,但核心使用场景截然不同。
若容器内的应用程序需要添加更严格的规则,应允许其操作。但它不应能干扰容器管理器已施加的现有规则。这才是理想状态。
此处无需额外操作。Landlock机制已确保无法撤销已生效的规则。应用程序可进一步自我限制,但无法解除自身限制。
只需确保容器管理器不阻塞landlock系统调用即可
对此我感到困惑:
> 遗憾的是官方C库尚未存在,但已有若干第三方库可供尝试。
> Landlock作为Linux安全模块(LSM)自Linux 5.13起可用
为何开发者接触Linux内核时,C API不再是首要接口?
内核的首要接口是系统调用接口(即uapi)。libc及其他C库(如liburing或libcap)都基于此构建。许多系统调用经年累月仍未在libc中实现封装。
但许多系统调用确实存在官方库——多数情况下是libc的封装层,尤其io_uring已知提供C库,多数应用程序理应使用该库而非直接调用原始系统调用。
io_uring本身不就是一组系统调用吗?
https://github.com/axboe/liburing
“此为 io_uring 库 liburing。该库提供辅助函数用于初始化与销毁 io_uring 实例,并为无需(或不愿)处理完整内核实现的应用程序提供简化接口。”
是的,它本身就是。但实现相当复杂,并非所有应用都需要其全部功能。
https://lwn.net/Articles/810414/
感谢澄清!我的疑问在于:为何没有先推出C语言API,反而先有Rust、Haskell和Go版本——这对我来说颇为意外。
我理解文章的意思是:目前没有 官方 的C库,但确实存在非官方实现。以下引文重点标注:
此外,C程序调用Landlock API似乎并非完全没有支持。即使没有第三方库,你也不必直接用魔法数字调用syscall():
https://docs.kernel.org/userspace-api/landlock.html
https://github.com/torvalds/linux/blob/6bda50f4/include/uapi…
https://github.com/torvalds/linux/blob/6bda50f4/include/linu…
我不明白你的意思。这件事也没有所谓的“官方”Rust、Haskell和Go API。所有可用的库似乎都只是第三方提供的。还有几个C库,但没有一个得到Linux内核团队的官方认可。
Go语言因无需依赖libc即可直接调用内核而闻名。Rust和Haskell的社区对安全性能极为重视,因此更早采用了这类方案。
至于C语言,目前非官方支持似乎已足够。
那你用什么调用系统调用?汇编语言?
这其实很微妙,它指的是 标准 C库(libc.{a,so,dll等})。即工具链提供的语言支持库。
这意味着glibc、musl或你偏好的C库可能尚未实现此功能,但由于系统调用定义明确,你可以使用 任意 C库(例如通过_syscallN宏创建自定义头文件)。
因为C并非内核系统调用的主要接口语言。系统调用本身才是核心接口,且具有语言无关性——这正是Linux的强大优势之一:其稳定的系统调用API无需依赖系统库。
> 从什么时候起,C API不再是开发者接触Linux内核的首要接口了?
难道是因为内核开发者不写用户空间软件?
缺乏C API不应阻碍任何C开发者使用它——至少希望如此。封装库相对简单(例如https://codeberg.org/git-bruh/landbox),且Rust与Go都能提供C FFI接口,供开发者选择链接更“官方”的库。
即使找不到现成的库,Linux内核也提供了使用系统调用的简明示例: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/lin…
虽然不存在“liblandlock”之类的库(尽管完全可能存在),但Rust、Go和Haskell之所以能为该系统调用提供易用API,纯粹是因为有人费心实现了封装库并发布到常用包管理器。各类发行版添加新库的流程同样适用于推送landlock库/头文件,从而在C语言中调用它。
C语言已有API支持,例如
man landlock_add_rule。https://github.com/torvalds/linux/blob/master/include/uapi/l…
若不愿使用 syscall() 函数,可添加简易单行封装器。
https://man7.org/linux/man-pages/man7/landlock.7.html 核心功能基本已实现。
我使用这个方案且运行良好:https://github.com/marty1885/landlock-unveil
医疗设备开发者在此:这正是高度监管行业所需的工作。我们使用内部版本的类似API来管理关键线程/进程。继续保持!
有趣的是他们新增了系统调用,而非像SELinux和AppArmor那样通过/sys处理配置。我猜这必然源于无特权原则。
系统管理员能否通过seccomp禁用Landlock系统调用?虽然看不出禁用的必要性,但好奇其分层机制。
问题可能出在:若系统早期已为某个进程设置了系统调用白名单,而Landlock作为新机制,其系统调用编号不会被包含在内。当程序已更新为使用Landlock,而seccomp策略仍基于旧版时,程序是否会因此收到SIGSYS异常而终止?
程序如何在不实际调用系统调用的情况下判断Landlock是否存在?
其他LSM也在逐步转向系统调用。虽然我原则上认同(甚至滥用过)“万物皆文件”的理念,但多数安全机制确实应通过专用系统调用实现。基于文件系统的API存在太多潜在风险。此外,若采用文件系统API,将无法利用Landlock基于目录描述符限制文件系统访问。
关于seccomp的疑问取决于规则设计。规范编写的过滤器在此情况下会返回-ENOSYS,程序端将视作系统调用不被支持。
可通过seccomp限制Landlock系统调用。
但我认为此举并无特殊价值。
即使在陆锁模式下允许某些操作,传统DAC及其他限制依然有效——因为这是可叠加的LSM。它只能限制现有访问权限,无法新增访问权限。
遗憾的是并非尽善尽美。详见讨论https://github.com/landlock-lsm/linux/issues/28及https://lore.kernel.org/all/CAG48ez1O0VTwEiRd3KqexoF78WR+cmP…
即便示例代码构建的“沙箱”也存在争议,它触及了上述讨论中的核心问题。假设我们允许应用程序对用户主目录进行读写操作,但需排除
/.ssh等特定位置。此时若尝试添加规则禁止访问/.ssh,却面临安全对象必须在策略建立时就存在的限制(规则通过文件描述符引用目录)。因此,若未创建.ssh目录,则无法通过规则禁止访问。当你启动沙箱应用时,自以为设置了严密的沙箱环境,但当~/.ssh目录在某个时刻被创建后,未经信任的应用便能读取你的SSH密钥。合理担忧。或许可通过补丁解决…
可能的解决方案是将文件系统中尚未存在的路径存储在Landlock的红黑树中。
临时方案是预先创建.ssh目录
若试图阻止程序访问互联网,请注意Landlock仅限制TCP套接字。程序仍可自由建立UDP或原始套接字。
这似乎是个重大疏漏…设计初衷是什么?
Landlock目前尚不完善,仍处于早期开发阶段。
该方案要求开发者预先定义“默认拒绝”策略,但仅实现了TCP套接字绑定控制,其他功能尚未覆盖。因此虽然能“默认拒绝”TCP绑定,内核中其他套接字路径却不受Landlock保护。该方案极力将功能承诺作为Landlock API的核心组成部分,旨在使应用程序能在支持不同Landlock规范版本的多内核环境中运行。但这意味着随着API发展,旧版Landlock必须比新版更宽松,否则程序将无法跨内核版本运行。
这样,一个在内核6.30上限制严格的程序也能在限制较少的内核6.1上运行。程序保持相同的行为方式(绝不破坏用户空间)。实现这一目标的唯一方法是让开发者明确告知需要限制哪些部分,而尚未实现的功能则无法被限制。
他们计划将该机制扩展至所有套接字类型。相关说明可见链接文章https://github.com/landlock-lsm/linux/issues/6
若当前需要完全禁用网络功能,可通过unshare创建全新网络命名空间,或启用seccomp严格模式
新增系统调用功能时总需审慎评估,因为一旦添加便无法撤回。所有依赖landlock的下游库都仰仗内核API的稳定性。
目前有两个并行推进的补丁系列:一个针对UDP协议,另一个涉及通用套接字控制。
相关讨论可参阅linux-security-module邮件列表。
本质上UDP因其无连接特性更难实现钩子机制,因此bind和connect操作的实现逻辑与传统套接字存在差异。
https://lore.kernel.org/all/20241214184540.3835222-1-matthie…
https://lore.kernel.org/linux-security-module/20251118134639…
防火墙可禁用这些功能,iptables能根据所有者uid匹配出站套接字。虽然这和landlock机制不同,但依然实用。
据我所知,原始套接字本就需要提升权限。
使用原始套接字确实需要root权限,或至少具备CAP_NET_RAW能力。不过UDP似乎相当糟糕。
这漏洞可不小!
哎呀!太糟糕了… :/ 知道就好… 真是奇怪的限制。
只有不懂原理才会觉得“奇怪”… 添加UDP/原始套接字支持难度极高,若为此延迟开发,项目整体将错失关键发展窗口期。
想看酷炫实例?看看Nomad(灵活工作负载编排器)的exec2任务驱动程序:https://github.com/hashicorp/nomad-driver-exec2
该方案支持隔离运行非/半可信工作负载。无需容器化即可将应用接入功能完善的调度器,同时保持良好的隔离性,这在集成应用时相当实用。
当HashiCorp将Nomad从开源许可证转为BSL许可后,我便弃用了该工具。但不得不说,我确实怀念它的简洁性。
随着我逐渐摆脱Mac的束缚,准备购置Linux工作站作为个人与工作计算的永久平台,我正缓慢地对Linux安全产生兴趣。因此我对这些内容还很陌生。
我的问题是:
– 这对防范恶意软件有何帮助?我想构建这样的环境:任何程序若试图读取~/.ssh目录内的内容都会被自动拒绝。绝不能让恶意构建脚本窃取我的敏感数据!
– 这款软件似乎很适合用来编写应用程序启动器,是这样吗?如果属实,这个想法虽好但操作起来太过繁琐。
或许我理解有误。我强烈倾向于采用隐形默认拒绝机制——即系统主动阻断绝大多数访问请求,而非用户主动启用。恶意行为者绝不会乖乖受限于Landlock,他们会在眨眼间就试图窃取一切。
感觉关键信息缺失了。有人能帮忙吗?
此处的威胁模型并非恶意软件,而是合法应用程序中的代码执行漏洞。若您正在开发应用程序,可利用此API主动拒绝自身不必要的权限,如此即便攻击者在您的应用中发现代码执行漏洞,也无法借此控制用户设备。
该技术不适用于对未设计为沙箱化方式运行的程序进行沙箱化处理。此类场景需采用本文列举的其他技术方案。
我希望构建这样的环境:任何程序尝试读取~/.ssh目录内文件时均被自动拒绝
这需要采用apparmor[0]或selinux[1]这类MAC安全模型。它们能根据进程环境数据(如可执行路径或安全上下文)拒绝文件系统访问。但这些方案需要外部枚举访问规则,而landlock的核心在于应用程序主动限制自身——或限制其子进程:例如让npm将包安装后脚本的执行范围限制在npm缓存/构建树内就是极佳方案。
该软件似乎为我们开发应用程序启动器提供了绝佳契机
如同OpenBSD的承诺[2],此API主要面向应用程序开发者而非启动器。但OpenBSD基础系统由同一团队整体维护,而Linux则是各类发行版拼凑而成,使用不同软件构建完整系统。这意味着landlock要达到OpenBSD中pledge的覆盖范围尚需时日。在此期间,在Linux上使用封装器/启动器仍是最佳方案。
[0] https://en.opensuse.org/SDB:AppArmor_geeks#Anatomy_of_a_prof…
[1] https://manpages.opensuse.org/Tumbleweed/selinux-policy-doc/…
[2] https://unix.stackexchange.com/a/411157
非常感谢,这非常有用。
我所说的启动器,更像是例如 我自制的迷你Go语言程序——它通过Landlock的DSL列出规则,然后启动Firefox之类的应用。
不过这可能没必要,听说Flatpak已有庞大的规则数据库。以后再研究吧。
Mac和iOS有个几乎完全相同的功能叫沙盒机制。守护进程或应用启动时(通常在main函数内),首要任务就是启用沙箱并声明白名单资源,其余资源一律拒绝访问。
该机制仅能防范恶意输入导致进程执行非预期操作,无法抵御恶意程序(如恶意软件)的攻击——防范恶意软件需要其他安全机制。
Linux系统已具备SELinux和AppArmor机制。
SELinux和AppArmor通常由管理员配置,需root权限操作且采用人机交互界面。程序主动向内核申请应用AppArmor配置文件的情况实属罕见,且这些机制也不支持逐步剥夺权限的设计。
在Windows和MacOS中,程序可自由通过编程方式实现沙箱化且无需权限。Linux则是个例外——几乎所有程序化降权限的方式都要求先具备root权限,或至少由管理员预先配置系统以实现该功能。
这两种方案都难以正确实现,导致桌面用户普遍禁用它们。用户体验至关重要。
此言不实。Fedora默认启用SELinux,我从未因此遇到问题。
> – 这如何防范恶意软件?我需要构建这样的环境:任何程序试图读取~/.ssh目录内文件时自动被拒绝。我绝不允许恶意构建脚本窃取所有敏感数据!
你的包管理器可指定策略,仅允许构建脚本进行特定访问。或者使用封装程序。
> – 这款软件似乎很适合开发应用启动器,是这样吗?如果可行,这个想法不错,但操作似乎过于手动。
有可能。本质上它适合任何了解程序功能的人使用。
说“官方C库尚未存在,但可尝试现有第三方库”有点讽刺——毕竟它明明就在标准库里…
https://man7.org/linux/man-pages/man7/landlock.7.html
不过我可能忽略了某些用户真正需要的功能…
你希望库在这里做什么?抽象处理以简化操作?(现有API已相对简单)
这是个严肃的问题,并非针对任何人…只是想了解大家对封装系统调用或标准库功能的库有何期待。
实际上,提供方法而非文档化系统调用会是个好开端。libc 不仅处理了大量系统调用的需求和副作用,还帮你管理了各个系统调用的编号。
我有点惊讶 glibc 至今仍未提供标准接口,不过这可能与非 Linux 兼容性有关?
glibc 近年来对新增系统调用封装一直持谨慎态度。最近情况稍有好转(过去几年他们补上了积压的约五年系统调用),但进展缓慢也在意料之中。
值得庆幸的是,近几年Linux已实现系统调用编号统一(几乎涵盖所有架构),追踪起来比从前轻松多了。
那么通过命令行工具运行我的软件也能实现吗?
没错。已有工具利用landlock实现了这种功能。
https://github.com/Zouuup/landrun
只需应用策略并执行 fork() exec() 操作即可。firejail 同样支持此功能。
Firejail 需要 SUID 权限,而 LandLock 无需。
此外,您完全可以使用任意编程语言编写自定义 LandLock 策略,将任意程序封装其中,无需从 Github 下载工具。以下是 Go 语言的示例:
这个示例不就是“从GitHub下载内容”(外部Go依赖项)加上额外步骤吗?(需要编写并编译Go应用程序)
所以本质上是在编写程序启动器?这种情况下,你希望在桌面创建快捷方式的是这个程序而非Firefox本身,是这样吗?
没错,参考 sydbox:https://gitlab.exherbo.org/sydbox/sydbox
作为新手想问:既然每个任务都运行在虚拟机或容器里,为什么还需要这个?再次声明我是新手,请多包涵
这是个合理的问题。答案在于并非所有任务都运行在虚拟机或容器中:许多操作(尤其在开发者机器上)直接在主机用户环境中执行,此时它们会接触到大量非必需的全局状态(如开发者凭证、浏览器状态等)。
但需注意:即便在容器(本身并非沙箱)或虚拟机内部,仍存在同心圆式的信任层级与权限分级。例如当从互联网安装任意依赖项时,你可能需要基础防护机制来阻止这些依赖项在构建阶段泄露你的机密信息。
在桌面/笔记本电脑上,多数任务可能并不运行于虚拟机或容器中。虽然部分应用会使用Flatpak、snaps等技术,但当前主流Linux发行版的默认状态仍是“完全不启用沙箱机制”。
尽管Linux在桌面操作系统市场份额微乎其微,却在技术达人群体中略占优势——这群用户通常拥有可观的可支配收入。因此尽管实际使用率不高,该平台对恶意软件作者和分发者的吸引力却在持续攀升。
这是合法应用为系统增添额外防护层的有效手段,可抵御恶意输入或受损依赖项的威胁,且操作极为简便(详见https://github.com/landlock-lsm/go-landlock)。作为应用开发者,为应用添加Landlock功能极其简单。
另一优势在于它能更精细地控制应用生命周期中的资源。例如应用初始化时需凭证获取数据,后续则无需凭证。Landlock允许应用自行撤销对这些凭证的访问权限。
容器环境最令人困扰之处在于无法实现更深层的自我沙箱化。命名空间、挂载点、chroot等常规方法均与容器运行机制存在兼容性问题。因此,若需突破容器提供的安全边界,Landlock正是强大的解决方案。
此外,虽然像容器化这样的“全过程”沙箱机制在某些条件下非常有效,但拥有更精细的访问控制以及能够 随时间推移 降低权限的能力才是真正强大的。
试想我在程序中需要打开某个文件。文件路径由环境变量
CONFIG_PATH提供。此时程序必须拥有 完整的文件系统读取权限 才能支持任意配置文件路径的读取,尽管它实际只需读取 单个文件 。相反,我可设计程序仅读取该文件一次后永不重复访问,或配置为仅需读取该单一文件而无需其他文件等。这种逐步缩减权限的能力非常酷炫——容器无法实现,它们只能获得初始分配的权限。
cgroups和命名空间都是分层结构,因此完全可以细分沙箱环境。前提是你具备扎实的C语言编程能力,且能解读晦涩的内核文档。理论上可在Docker中运行Docker,但需特权root容器支持,连该功能的创建者都建议直接绑定挂载Docker套接字。
我总觉得Plan9系统三十年前就可能解决了这些问题。
> 由于cgroups和命名空间均采用分层结构,沙箱确实可以进行细分。
此说法正确,可在现有命名空间内创建新命名空间,但命名空间操作属于特权操作。
据我所知,Docker 中的 Docker 确实已采用套接字绑定挂载,但这存在明显的权限提升漏洞——由于 Docker 以 root 身份运行,只要能访问套接字,就能执行
docker run --privileged --user root image_name -it bash命令,从而以主机 root 用户身份获取 shell。解决方案是允许非特权用户主动放弃特权,这正是MacOS和Windows的运作方式。Windows通过完整性级别、令牌等机制实现,用户无需特权即可主动降级权限;MacOS则采用seatbelt机制。
Linux曾试图通过非特权用户命名空间实现类似功能,但因三十年来“root权限→内核特权提升不构成安全隐患”的认知导致该方案不可行。
Docker-in-Docker与绑定挂载套接字机制不同:前者在容器内运行新的Docker守护进程,后者仅与主机套接字通信。无论如何,https://jpetazzo.github.io/2015/09/03/do-not-use-docker-in-d… 这篇官方文档给出了最权威的说明。如今似乎甚至无需特权容器就能实现,但作者仍列举了若干潜在陷阱。
Landlock作为无特权限制的起点尚可,但确实远不及pledge()和unveil()优雅。
感谢指正,我误记为仅需–privileged参数。鉴于无特权用户命名空间不可行,该参数要求预计仍将保留。
容器绝非安全封装层,它们只是懒人规避依赖地狱的便捷工具。
虚拟机可以作为安全封装层,但若将整个$HOME目录暴露给虚拟机,那么从数据安全角度看,实际防护效果微乎其微。
这让应用程序开发者能自主强化安全性,无需终端用户额外操作(如部署到虚拟机)。
事实恰恰相反。容器化系统最初是作为操作系统内置的安全特性开发的。而如今被称为“容器”的用例——即“Linux软件包管理是自找麻烦的地狱,不如用胶带把squashfs贴在新安全隔离系统上,称之为部署基础单元”——是后来才出现的,属于相当笨拙且浪费的解决之道,本质上是逃避软件包管理地狱的困境。这对许多人确实有价值!但它绝对是塞进套接字层(安全隔离层)这个圆孔的方楔——就像setns、chroot等工具那样。
容器在安全性上基本可强化至虚拟机级别(但通常默认不具备),而虚拟机能提供、容器无法实现的核心优势在于独立内核实例。在虚拟机环境中,必须破坏两个内核才能完全掌控整台机器。
而在容器中,若容器软件未提供此功能(这很可能),只需破坏一个内核即可。
我一直认为容器是懒惰开发者解决“我不知道,反正在我机器上能用”问题的方案:直接打包整台机器发货。
事实并非如此,这里存在一段引人入胜的历史。
支撑容器化技术(命名空间、chroot、cgroups及其在BSD/Solaris系统中的前身)的诞生,正是为了实现安全隔离与资源管控。
提出现代“容器”概念的人们发现了一个巧妙的解决方案:将这些安全导向的工具与盒装文件系统及打包系统结合,使得人们能够打包完整的操作系统用户空间,并在多个环境中以高度确定性的方式运行它们。命名空间/cgroups/chroot的安全隔离特性恰好也提供了更强的确定性。
我并非在批评这种设计;容器确实是解决普遍问题的巧妙方案,我每天都在使用。
但必须指出,容器之所以能如此普及,恰恰暴露了软件工程领域 本可避免 的自我诱发问题。遗憾的是,这个根源在于人性与激励机制,因此容器可能是我们能获得的最佳解决方案——问题在于,它们其实并不完美。
我曾在其他文章中深入剖析过这些根本问题,直接引用原文链接比在此重复阐述更清晰:https://news.ycombinator.com/item?id=44069483
Drew deVault对此的阐述远比我更全面透彻:https://drewdevault.com/2021/09/27/Let-distros-do-their-job….
> 它为应用程序提供了简单易用的深度防御方案。
深度防御。将贵重物品锁进保险箱,再将保险箱锁进上锁的房屋。既然房屋已上锁,为何还要用保险箱?因为若有人闯入房屋,你需要额外防护“以防万一”。同理,万一我写了糟糕代码导致服务器被攻破,仍需将贵重物品锁进保险箱,让窃贼无法盗取昂贵的银器(生产环境凭证)。
难道没有现成的SELinux或AppArmor方案实现这个功能吗?
有,但基本上没人用这些东西。像红帽这样的厂商默认启用部分功能,但当用户遇到软件运行问题时,技术支持首先建议的就是关闭这些安全机制。
这意味着现实中这些机制处于启用状态并保障安全性的概率很低,但并非为零。
而Landlock、pledge/unveil等技术由软件开发者自行编写配置,默认开启且无法关闭(至少难以关闭)。
配置这些需要root权限。这类策略通常由管理员驱动,而非开发者驱动。Landlock属于无特权模式,意味着程序无需root权限即可自行配置策略。
这具有重大意义,因为Linux上大多数降权限的方式都要求程序本身已具备高权限(即root权限)。
两者都存在缺陷。
Landlock并非容器的替代方案。它可作为额外的安全层,在容器内外使用。
甚至可与chroot结合构建容器运行时。它更像是进程限制的构建模块。
类似于使用白名单模式的seccomp(实现相对简单),并具备对象级访问权限控制。
我期待看到 Landlock 与受限容器的对比分析。
需注意容器采用虚拟化机制。你进入新的“命名空间”后,虽不必然受限于该空间内部,但整个命名空间本质上是你的专属操作域。因此PID命名空间仅允许你观察该空间内的其他进程。
这与Landlock这类资源导向方案截然不同。Landlock或许能实现“对特定进程执行特定操作”的控制,但无法达到“从根本上仅能观察特定进程”的语义效果。两者可形成良好互补。
同样地,容器提供虚拟化文件系统。容器内的写入操作被允许,但该写入与宿主机隔离。而Landlock则会直接允许或拒绝该写入操作。
二者结合效果极佳。
将Landlock与容器进行比较并非真正意义上的同类对比。容器通过组合使用chroot、seccomp和用户命名空间等Linux安全机制实现目标,而Landlock只是开发者可用的另一种构建模块。
趣闻:由于Landlock属于非特权工具,甚至可在容器内部使用它,或者用来构建非特权容器运行时 🙂
seccomp用于限制内核系统调用。但由于UNIX系统遵循“万物皆文件”原则,仅凭
open、openat、read和write这几个操作就能实现诸多善恶兼具的功能。当然,但你也可以限制这些操作。我编写的seccomp白名单库仅在所有文件描述符为特定操作打开后才进行自我封装,且API并未直接暴露调用接口。一旦封装完成,应用程序只能执行当前明确允许的操作。
我对Linux系统编程毫无经验,可能遗漏了关键点——但应用程序在运行时自我限制有何意义?若程序遭到某种破坏,难道不会直接解除限制吗?
LWN关于unveil()的文章解释得很清楚——限制会永久应用于进程及其子进程直至终止:https://lwn.net/Articles/767137/
内核强制规定:策略一旦添加就无法移除。
因此限制在程序生命周期内是永久性的。即便是root也无法撤销。
由于运行时无法重新启用特权,攻击者必须修改自身代码并重启程序;若禁止运行进程访问自身代码,则无法实施任何能在代码重启后持续生效的修改。
正如文章所述,你无法授予额外权限,只能进一步限制。
作为网页开发者阅读此文,不禁联想到Demo的权限系统。
Deno作为JS运行环境,常在我的指令下执行未经本人编写且未经验证的代码。运行时,我可通过 –allow-read=$PWD 调用 Deno,确保所有不可信 JS 代码无法读取当前目录外的文件。
若 Deno 自身遭入侵则无效,但其攻击面远小于所有 NPM 包。
这只是此类机制实际应用的典型案例。
> 若应用程序遭到某种方式的破坏,难道不会直接解除自身限制吗?
该API仅支持设置限制,不支持解除限制。由于限制通常在程序启动时生效,攻击者获得远程执行权限前限制已然存在,其可操作空间将受到严格约束…
内核保证受限进程将持续保持受限状态。唯一解除限制的方式是同时攻破Linux内核。这意味着攻击者必须攻破两个目标才能掌控机器,而非仅需攻破一个。
对于底层软件被视为非恶意的沙箱环境(如浏览器沙箱),此类限制可在程序执行初期即生效。若程序在限制生效后才接受任何不可信输入,即便存在漏洞,仍能有效抵御权限提升攻击。
codex-cli是开源Rust程序的典范案例,它利用Landlock执行大型语言模型编写的代码指令(参见[1])。其设计理念是:用户信任代理程序(codex-cli),但对远程语言模型要求codex-cli执行的指令信任度大幅降低。
[1] https://developers.openai.com/codex/security/
关键在于它无法做到。
感谢分享。
此前我对此并不知晓,因多重原因正寻求简易的进程隔离方案。我正在构建一个编码代理https://github.com/brainless/nocodo,该代理以无头模式运行于Linux实例。生成的代码可即时用于演示。
我刚接触隔离技术,不考虑基于容器的方法。从安全角度需要隔离,但知识储备不足。这个方案对我来说是个绝佳的起点。
这种方案愚不可及。
这就像指望罪犯在作案后自己戴上手铐。
用户空间沙箱的运作原理并非如此。其核心假设是权限从可信父进程流向不可信子进程,因此由可信父进程负责设置访问控制。
并非如此。这更像是系安全带:汽车本不该发生碰撞,但万一意外发生,请别让乘客从挡风玻璃飞出去。
等着看作者发现FreeBSD的Capsicum吧。我认为它比其他主流操作系统提供的多数API都更胜一筹。
已收藏。
[已删除]
这是陈述句还是疑问句?
哎呀被发帖表单自动添加了。本以为会显示为副标题而非评论哈哈
LandLock是面向软件开发者的轻量级LSM。开发者将其集成到源代码中,用于限制程序的读写权限。以下是Go语言的简易示例:
我忍不住想问:这个注释和代码示例是你自己写的,还是让AI生成的?如果是AI生成的,为什么不说明?如果是前者,为什么采用这种奇怪的格式,而不是直接链接到官方示例[https://github.com/landlock-lsm/go-landlock/blob/main/exampl…]?(https://github.com/landlock-lsm/go-landlock/blob/main/examples/go-landlock-convert/main.go)?
没错。目前Landlock在应用程序代码内部的应用最为出色。
它作为不可信应用程序封装器的实用性也在不断提升。
我不理解为何有人要用自定义代码封装不可信应用,而非直接利用Systemd的exec功能实现相同效果——后者无需二进制封装器。相比Systemd方案,你认为它有哪些优势?
Systemd的执行能力固然出色,但无法让应用开发者 动态 限制资源访问权限。例如你无法将文本编辑器的访问范围限定在启动时指定的编辑文件,只能硬编码目录范围。