You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
BUG: /tmp workspace root can be rebound by a confined Landlock child and widen the next workspace-write grant
#278
direct outside write before rebind:
denied
workspace root entry swap:
success
next SandboxPolicyService.resolve():
workspaceRoot changed to outside
second real Landlock-confined write:
success
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
先说结论:当一个 session 的 workspace 本身位于系统
/tmp下时,当前 Linux Landlock fallback 的workspace-write可以被同一个受限 child 确定性地重新绑定到 workspace 外目录。受限 child 自己直接写 workspace 外文件会被 Landlock 拒绝;但是因为
workspace-write同时允许修改整个 host/tmp,child 可以把自己的/tmp/.../workspace根目录项重命名,再在原路径创建一个指向外部目录的 symlink。下一次 Harness 解析 sandbox policy 时,会重新realpath()这个未变化的SessionHeader.cwd字符串,并把 symlink target 当成新的workspaceRoot。之后新的 sandboxed bash/fs 调用会把这个外部目录作为正式 workspace grant。实际测试中,第二个真实 Landlock-confined invocation 随后成功在原本明确不可写的外部目录中写入文件。
这形成的是一个确定性的 workspace identity rebind:
这不需要 race,也不需要额外未受限 host process。
当前动态复现基于最新
master:测试只使用独立临时目录和 harmless marker 文件,没有修改真实用户配置或业务数据。
1.
workspace-write的 policy root 每次调用都会重新解析 session cwd当前:
中的
resolve():resolveWorkspaceRoot()最终使用 native filesystem canonicalization,所以这里不是保存 session 创建时已经确定的 workspace directory identity,而是:SessionHeader.cwd字符串本身虽然不可变,但该 pathname 后面的 filesystem identity 并不是不可变的。2. 文档把这个 cwd 描述成 session 的 workspace identity
当前 sandbox-policy README 写道:
也就是说,从 policy contract 看,session 创建时记录下来的 cwd 是这个 session 后续 capability calls 使用的 workspace boundary。
但当前实现实际绑定的是:
而不是:
这两个语义在 pathname 可以被攻击者重绑定时并不等价。
3. Landlock
workspace-write同时授予整个 host/tmp当前:
Landlock profile 在
workspace-write下构造:因此一个位于:
的 workspace 不只是
project本身可写。受限 child 也被允许修改:
中的目录项,因为整个
/tmp都是 Landlock read-write root。仓库自己的 Landlock e2e 也明确把 host
/tmpwritable 作为当前 backend contract 测试。4.
/tmpworkspace 是产品允许的 workspace 位置Host 的 workspace adoption 接口接受用户选择的已有目录,并 canonicalize 后作为 Workspace path。
当前产品没有禁止 workspace 位于
/tmp;仓库自己的多处 sandbox/workspace tests 也会使用tmpdir()/mkdtemp()创建合法 workspace。因此这不是通过伪造非法 SessionHeader 才能进入的状态。
触发条件只是:
5. Negative control:同一个 Landlock child 直接写 outside 会失败
测试创建:
首先用当前仓库 native Landlock launcher 和正常
workspace-writepolicy 启动 child,让它直接执行:结果:
outside 文件没有被创建。
所以最初的 policy 确实在阻止 workspace 外 mutation。
这排除了:
这种解释。
6. 同一个受限 child 可以替换自己的
/tmpworkspace 根目录项随后仍然用当前
workspace-writeLandlock profile 启动 child。它只做
/tmp内的 namespace 操作:结果:
这些操作全部发生在当前 Landlock policy 已经明确允许写的 host
/tmp内。child 并没有直接修改 outside 目录。
即使这个 child 的当前 working directory 原本就是
project,rename 后进程仍持有旧目录 identity;它仍然可以通过父目录 pathname 在/tmp内完成目录项替换。7. SessionHeader.cwd 没有变化,但下一次 policy root 变成 outside
session 中记录的 cwd 始终是同一个字符串:
替换前第一次:
得到:
目录项替换以后,再调用完全相同的:
得到:
当前动态测试打印的证据结构为:
所以:
但:
这正是 rebind 发生的位置。
8. Positive control:第二个真实 Landlock invocation 获得 outside write grant
为了确认这不只是 policy object 中显示了错误 path,我把
after.workspaceRoot直接送入下一次当前仓库 Landlock profile。也就是说第二次执行仍然不是 unconfined bash,而是:
只是这时:
随后让第二个受限 child 写:
结果:
完整动态结果:
因此不是“Host 自己无视 sandbox 写了文件”,而是 Harness 的 policy resolver 主动给下一次 sandbox execution 生成了错误的新 grant root。
9. 完整证据链
当前真实 runner 测试可以压缩为:
这个结果是确定性的,不依赖 TOCTOU race timing。
10. 为什么这不是
/tmp本来可写所以没有安全影响workspace-write允许/tmp是为了 temporary-file compatibility。这里的 child 确实只利用了它本来有权修改的
/tmpnamespace。但最终获得的能力不是“继续修改
/tmp”。实际结果是:
被下一次 policy 解析成:
然后正式进入 kernel sandbox 的 allow-list。
所以
/tmpgrant 只是攻击者用来重绑定 workspace pathname 的手段;真正跨越的边界是 workspace identity。11. 为什么这和 post-check fs pathname race 是不同问题
这个问题不要求在 fs containment check 和 syscall 之间抢 race。
在这里:
第二次调用从一开始就收到了错误的 outside
workspaceRoot。因此即使 filesystem mutation 改成完全 fd-bound、消除了 post-check pathname race,这个 workspace-root rebind 仍然存在。
同样,它不仅影响
tool-fs:任何消费SandboxPolicyService.resolve({session})的 enforcement family 都可能拿到新的 root。12. Backend 范围
当前动态复现确认的是:
这是 shipped Linux backend chain 的正常 fallback,当 bubblewrap 不可用时会使用 Landlock。
不应把这份结果直接泛化成所有 backend:
workspace-write使用独立 tmpfs/tmp,语义不同,本报告没有证明相同 root-swap 可以跨出其 mount namespace;/tmp/ user temp area,而 policy resolver 逻辑共享,所以存在相同的静态 identity-rebind concern,但本文没有把 macOS 写成已动态验证;/tmp触发链的同一前提。13. 修复建议
核心修复应当是:
Workspace adoption / session creation 已经有 canonical directory identity。
可以考虑:
关键 invariant 应当类似:
而不是:
如果产品希望允许 workspace 在 host 上被用户外部移动/替换并自动跟随,也应该把“是否仍然是原 Workspace”的认证与这种便利行为分开;安全 allow-list 不应该静默跟随一个受 sandbox child 自己可修改的 directory entry。
总结
当前最新
master上,Linux Landlock fallback 的真实动态证据为:因此,
SessionHeader.cwd的字符串不可变并不足以构成稳定 workspace security identity。建议固定 session/workspace 的 canonical security root,并在 pathname identity 后续漂移时 fail closed,而不是让下一次 capability call 自动扩大 sandbox allow-list。
All reactions