Linux Landlock策略
在 rust-v0.150.0 中,Linux 默认文件系统 sandbox 是 Bubblewrap;Landlock 仍存在,但属于显式启用的 legacy fallback。这个区别决定了文章的阅读顺序:先看 codex-linux-sandbox 如何解析 PermissionProfile、选择 outer/inner stage,再看 legacy Landlock 何时安装规则。若把 Landlock 当默认主路径,就会误读 split filesystem、unreadable glob、protected metadata 和 nested mount 的实际 owner。
Linux helper 还把 filesystem、network 和 process hardening 分成不同层。Bubblewrap 创建 user/PID/IPC namespace 和 filesystem mount;inner stage 在 filesystem view 建立之后设置 no_new_privs、安装网络 Seccomp,再 execvp 用户命令。legacy Landlock 只表达“全盘可读、少量根可写”的旧模型;复杂 read-only carveout 会在模式检查阶段拒绝,而不是悄悄丢失限制。
本文承接跨平台Sandbox抽象和macOS沙箱执行流程。前文解释 sandbox intent 与 enforcement owner,本文追踪 Linux helper 的 argv、Bubblewrap outer/inner、legacy Landlock ABI、network Seccomp、WSL/bwrap 前置检查和失败边界;不把当前实现描述成所有 Linux 内核版本的统一能力。
1. Helper入口
1.1 CLI输入
LandlockCommand 的命令行参数已经体现了当前 owner:--permission-profile 传入 canonical profile,--use-legacy-landlock 是 opt-in,--apply-seccomp-then-exec 表示已经在 Bubblewrap namespace 中的 inner stage,--allow-network-for-proxy 表示 managed proxy routing。
源码位置:codex-rs/linux-sandbox/src/linux_run_main.rs :: LandlockCommand
/// Canonical runtime permissions for the command.
#[arg(
long = "permission-profile",
hide = true,
value_parser = parse_permission_profile
)]
pub permission_profile: Option<PermissionProfile>,
/// Opt-in: use the legacy Landlock Linux sandbox fallback.
#[arg(long = "use-legacy-landlock", hide = true, default_value_t = false)]
pub use_legacy_landlock: bool,
/// Internal: apply seccomp and `no_new_privs` in the already-sandboxed
/// process, then exec the user command.
#[arg(long = "apply-seccomp-then-exec", hide = true, default_value_t = false)]
pub apply_seccomp_then_exec: bool,
/// Internal compatibility flag for managed proxy routing.
#[arg(long = "allow-network-for-proxy", hide = true, default_value_t = false)]
pub allow_network_for_proxy: bool,
/// Full command args to run under the Linux sandbox helper.
#[arg(trailing_var_arg = true)]
pub command: Vec<String>,helper 同时接收 --sandbox-policy-cwd 和可选 --command-cwd。二者可以不同:policy cwd 用于解释 project_roots 和相对 glob,command cwd 用于保持用户看到的逻辑工作目录。
1.2 入口顺序
run_main 的顺序不是实现细节,而是权限不变量:先解析 profile 和模式,再根据 legacy flag 选择路径;默认路径先 Bubblewrap,进入 inner stage 后才设置 Seccomp;legacy 路径最后才安装 Landlock 并执行。
源码位置:codex-rs/linux-sandbox/src/linux_run_main.rs :: run_main
/// The sequence is:
/// 1. When needed, wrap the command with bubblewrap to construct the
/// filesystem view.
/// 2. Apply in-process restrictions (no_new_privs + seccomp).
/// 3. `execvp` into the final command.
pub fn run_main() -> ! {
let LandlockCommand {
sandbox_policy_cwd,
command_cwd,
permission_profile,
use_legacy_landlock,
apply_seccomp_then_exec,
allow_network_for_proxy,
proxy_route_spec,
verify_fd_mounts,
no_proc,
command,
} = LandlockCommand::parse();
if command.is_empty() {
panic!("No command specified to execute.");
}
ensure_inner_stage_mode_is_valid(apply_seccomp_then_exec, use_legacy_landlock);
let EffectivePermissions {
permission_profile,
mut file_system_sandbox_policy,
network_sandbox_policy,
} = resolve_permission_profile(permission_profile).unwrap_or_else(|err| panic!("{err}"));缺少 profile 会返回 MissingConfiguration,空 command 会 panic;这两种错误发生在任何 filesystem 或 network 限制安装之前。
2. 默认Bubblewrap
2.1 Full write快路径
若 filesystem policy 有 full disk write 且没有 proxy-only network,helper 不需要 Bubblewrap 文件视图,只应用必要的 in-process network/process restriction 后直接 exec_or_panic。这不是危险 full access 的普遍 shortcut:开启 managed proxy 或存在受限 carveout 时仍会进入 Bubblewrap。
源码位置:codex-rs/linux-sandbox/src/linux_run_main.rs :: run_main
if file_system_sandbox_policy.has_full_disk_write_access() && !allow_network_for_proxy {
if let Err(e) = apply_permission_profile_to_current_thread(
&permission_profile,
&sandbox_policy_cwd,
/*apply_landlock_fs*/ false,
allow_network_for_proxy,
/*proxy_routed_network*/ false,
) {
panic!("error applying Linux sandbox restrictions: {e:?}");
}
exec_or_panic(command);
}apply_landlock_fs=false 表明这个分支没有安装 legacy filesystem rules。网络 Seccomp 是否安装仍由 profile 和 proxy flag 独立决定。
2.2 Outer与inner
默认非 legacy 路径先构造 inner command,再调用 run_bwrap_with_proc_fallback。如果 managed proxy active,会先准备 route spec 和 socket directory,并把 socket directory 增加为 readable root;Bubblewrap 建立 namespace 后重新进入 helper,inner stage 才应用 Seccomp。
源码位置:codex-rs/linux-sandbox/src/linux_run_main.rs :: run_main
if !use_legacy_landlock {
// Outer stage: bubblewrap first, then re-enter this binary in the
// sandboxed environment to apply seccomp. This path never falls back
// to legacy Landlock on failure.
let proxy_route_spec = if allow_network_for_proxy {
let (proxy_route_spec, socket_dir) = prepare_host_proxy_route_spec()
.unwrap_or_else(|err| panic!("failed to prepare host proxy routing bridge: {err}"));
file_system_sandbox_policy = file_system_sandbox_policy
.with_additional_readable_roots(&sandbox_policy_cwd, std::slice::from_ref(&socket_dir));
Some(proxy_route_spec)
} else {
None
};
let inner = build_inner_seccomp_command(InnerSeccompCommandArgs {
sandbox_policy_cwd: &sandbox_policy_cwd,
command_cwd: command_cwd.as_deref(),
permission_profile: &permission_profile,
allow_network_for_proxy,
proxy_route_spec,
command,
});
run_bwrap_with_proc_fallback(
&sandbox_policy_cwd,
command_cwd.as_deref(),
&file_system_sandbox_policy,
network_sandbox_policy,
inner,
!no_proc,
allow_network_for_proxy,
);
}Bubblewrap path失败不会自动切换到 Landlock;legacy 是显式模式,不是运行时 fallback。
3. Bubblewrap文件
3.1 参数构造
create_bwrap_flags 先调用 create_filesystem_args,再加入 --new-session、--die-with-parent、user/PID/IPC namespace、可选 network namespace、fresh /proc、canonical --chdir 和 --cap-drop ALL。filesystem mount 生成在 namespace 参数之前,保证 inner command 看到的是 policy view。
源码位置:codex-rs/linux-sandbox/src/bwrap.rs :: create_bwrap_flags
fn create_bwrap_flags(
command: Vec<String>,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
sandbox_policy_cwd: &Path,
command_cwd: &Path,
options: BwrapOptions,
) -> Result<BwrapArgs> {
let BwrapArgs {
args: filesystem_args,
preserved_files,
synthetic_mount_targets,
protected_create_targets,
} = create_filesystem_args(
file_system_sandbox_policy,
sandbox_policy_cwd,
options
.glob_scan_max_depth
.or(file_system_sandbox_policy.glob_scan_max_depth),
)?;
let normalized_command_cwd = normalize_command_cwd_for_bwrap(command_cwd);
let mut args = Vec::new();
args.push("--new-session".to_string());
args.push("--die-with-parent".to_string());
args.extend(filesystem_args);
args.push("--unshare-user".to_string());
args.push("--unshare-pid".to_string());
args.push("--unshare-ipc".to_string());
if options.network_mode.should_unshare_network() {
args.push("--unshare-net".to_string());
}
if options.mount_proc {
args.push("--proc".to_string());
args.push("/proc".to_string());
}
if normalized_command_cwd.as_path() != command_cwd {
args.push("--chdir".to_string());
args.push(path_to_string(normalized_command_cwd.as_path()));
}
args.push("--cap-drop".to_string());
args.push("ALL".to_string());
args.push("--".to_string());
args.extend(command);
Ok(BwrapArgs {
args,
preserved_files,
synthetic_mount_targets,
protected_create_targets,
})
}0.150.0 显式加入 --unshare-ipc 和 --cap-drop ALL;这使 Bubblewrap outer stage 的 process boundary 不再只是 mount namespace。
3.2 Filesystem分支
full disk write 且无 unreadable glob 时,filesystem args 可以走 full-filesystem path;此路径仍加入 --dev /dev、可选 /dev/shm、user/PID/IPC namespace、--cap-drop ALL。restricted 或有 glob 时,helper 从 read-only view 开始,再按路径 specificity 叠加 writable/read-only/deny mount。
源码位置:codex-rs/linux-sandbox/src/bwrap.rs :: build_bwrap_argv, create_bwrap_flags_full_filesystem
if file_system_sandbox_policy.has_full_disk_write_access() && unreadable_globs.is_empty() {
return if options.network_mode == BwrapNetworkMode::FullAccess {
Ok(BwrapArgs {
args: command,
preserved_files: Vec::new(),
synthetic_mount_targets: Vec::new(),
protected_create_targets: Vec::new(),
})
} else {
Ok(create_bwrap_flags_full_filesystem(command, options))
};
}
create_bwrap_flags(
command,
file_system_sandbox_policy,
sandbox_policy_cwd,
command_cwd,
options,
)只要有 unreadable glob,即使整体是 full disk write,也必须具体展开并 mask 匹配路径;“全盘可写”不再意味着跳过所有文件视图构造。
3.3 Writable root
在 writable bind 下,Bubblewrap 会重新应用 protected metadata 的 read-only mounts,并记录 protected_create_targets。0.150.0 还维护 synthetic mount registry,保证多个并发 sandbox 对同一保护路径的临时 mount 不互相清理。
源码位置:codex-rs/linux-sandbox/src/bwrap.rs :: create_filesystem_args
let mount_root = symlink_target.as_deref().unwrap_or(root);
bwrap_args.args.push("--bind".to_string());
bwrap_args.args.push(path_to_string(mount_root));
bwrap_args.args.push(path_to_string(mount_root));
let protected_metadata_names = writable_root.protected_metadata_names.clone();
append_metadata_path_masks_for_writable_root(
&mut read_only_subpaths,
root,
&protected_metadata_names,
);
append_protected_create_targets_for_writable_root(
&mut bwrap_args,
&protected_metadata_names,
root,
symlink_target.as_deref(),
&read_only_subpaths,
);--ro-bind 的顺序按 path depth 排序,较窄的 writable child 可以重新打开较宽 read-only parent 的一部分;较窄的 denied subpath 仍在最后获胜。
4. Legacy Landlock
4.1 模式互斥
inner stage 已经处于 Bubblewrap filesystem view 中,只需要 Seccomp/no_new_privs;legacy Landlock 则在没有 Bubblewrap outer stage 时安装 filesystem rules。两者同时设置会在 ensure_inner_stage_mode_is_valid 阶段拒绝。
源码位置:codex-rs/linux-sandbox/src/linux_run_main.rs :: ensure_inner_stage_mode_is_valid
fn ensure_inner_stage_mode_is_valid(apply_seccomp_then_exec: bool, use_legacy_landlock: bool) {
if apply_seccomp_then_exec && use_legacy_landlock {
panic!("--apply-seccomp-then-exec is incompatible with --use-legacy-landlock");
}
}4.2 兼容性检查
legacy mode 使用 FileSystemSandboxPolicy::needs_direct_runtime_enforcement 判断当前 policy 是否超出旧模型。split-only read/write、Root Write + read-only carveout 或其它需要直接 runtime enforcement 的 profile 会被拒绝。
源码位置:codex-rs/linux-sandbox/src/linux_run_main.rs :: ensure_legacy_landlock_mode_supports_policy
fn ensure_legacy_landlock_mode_supports_policy(
use_legacy_landlock: bool,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
sandbox_policy_cwd: &Path,
) {
if use_legacy_landlock
&& file_system_sandbox_policy
.needs_direct_runtime_enforcement(network_sandbox_policy, sandbox_policy_cwd)
{
panic!(
"permission profiles requiring direct runtime enforcement are incompatible with --use-legacy-landlock"
);
}
}这是保留约束而非能力缺失:默认 Bubblewrap 仍可表达 split filesystem;legacy 模式选择失败 closed,避免把 read-only carveout 丢掉后继续运行。
4.3 ABI V5 ruleset
legacy Landlock 在当前 thread 创建 ABI V5 ruleset,默认允许整个 filesystem 的 read access,只对 /dev/null 和 writable roots 加 read-write access。CompatLevel::BestEffort 允许内核缺少部分 ABI 能力时继续创建 ruleset,但 restrict_self 返回 NotEnforced 仍转为 SandboxErr::LandlockRestrict。
源码位置:codex-rs/linux-sandbox/src/landlock.rs :: install_filesystem_landlock_rules_on_current_thread
fn install_filesystem_landlock_rules_on_current_thread(
writable_roots: Vec<AbsolutePathBuf>,
) -> Result<()> {
let abi = ABI::V5;
let access_rw = AccessFs::from_all(abi);
let access_ro = AccessFs::from_read(abi);
let mut ruleset = Ruleset::default()
.set_compatibility(CompatLevel::BestEffort)
.handle_access(access_rw)?
.create()?
.add_rules(landlock::path_beneath_rules(&["/"], access_ro))?
.add_rules(landlock::path_beneath_rules(&["/dev/null"], access_rw))?
.set_no_new_privs(true);
if !writable_roots.is_empty() {
ruleset = ruleset.add_rules(landlock::path_beneath_rules(&writable_roots, access_rw))?;
}
let status = ruleset.restrict_self()?;
if status.ruleset == landlock::RulesetStatus::NotEnforced {
return Err(CodexErr::Sandbox(SandboxErr::LandlockRestrict));
}
Ok(())
}4.4 当前thread
Landlock 和 Seccomp 的安装函数都在 helper 的当前 thread 执行;主 CLI 通过 Bubblewrap 或 self-invocation 将限制继承给 sandbox child。这样 parent session 不会被不可逆地锁死,但 child 一旦安装规则也不能主动放宽。
5. Network Seccomp
5.1 选择模式
network_seccomp_mode 用 network policy、allow_network_for_proxy 和 proxy route 状态选择 None、Restricted 或 ProxyRouted。full network 且没有 managed proxy 时跳过 network Seccomp;restricted network 总是安装;managed proxy 即使 profile 是 Enabled 也安装。
源码位置:codex-rs/linux-sandbox/src/landlock.rs :: should_install_network_seccomp, network_seccomp_mode
fn should_install_network_seccomp(
network_sandbox_policy: NetworkSandboxPolicy,
allow_network_for_proxy: bool,
) -> bool {
// Managed-network sessions should remain fail-closed even for policies that
// would normally grant full network access.
!network_sandbox_policy.is_enabled() || allow_network_for_proxy
}
fn network_seccomp_mode(
network_sandbox_policy: NetworkSandboxPolicy,
allow_network_for_proxy: bool,
proxy_routed_network: bool,
) -> Option<NetworkSeccompMode> {
if !should_install_network_seccomp(network_sandbox_policy, allow_network_for_proxy) {
None
} else if proxy_routed_network {
Some(NetworkSeccompMode::ProxyRouted)
} else {
Some(NetworkSeccompMode::Restricted)
}
}5.2 Seccomp两种模式
Restricted mode 无条件拒绝 connect、accept、bind、listen 等网络 syscall,并通过 socket 参数规则只允许 AF_UNIX;socketpair 同样只允许 AF_UNIX。ProxyRouted mode 允许 AF_INET/AF_INET6 socket 到 Bubblewrap 隔离 namespace 的 TCP bridge,但只允许 AF_UNIX socketpair 做进程内 IPC。
源码位置:codex-rs/linux-sandbox/src/landlock.rs :: install_network_seccomp_filter_on_current_thread
match mode {
NetworkSeccompMode::Restricted => {
deny_syscall(&mut rules, libc::SYS_connect);
deny_syscall(&mut rules, libc::SYS_accept);
deny_syscall(&mut rules, libc::SYS_accept4);
deny_syscall(&mut rules, libc::SYS_bind);
deny_syscall(&mut rules, libc::SYS_listen);
deny_syscall(&mut rules, libc::SYS_sendto);
deny_syscall(&mut rules, libc::SYS_sendmmsg);
deny_syscall(&mut rules, libc::SYS_recvmmsg);
deny_syscall(&mut rules, libc::SYS_getsockopt);
deny_syscall(&mut rules, libc::SYS_setsockopt);
let unix_only_rule = SeccompRule::new(vec![SeccompCondition::new(
0,
SeccompCmpArgLen::Dword,
SeccompCmpOp::Ne,
libc::AF_UNIX as u64,
)?])?;
rules.insert(libc::SYS_socket, vec![unix_only_rule.clone()]);
rules.insert(libc::SYS_socketpair, vec![unix_only_rule]);
}
NetworkSeccompMode::ProxyRouted => {
let deny_non_ip_socket = SeccompRule::new(vec![
SeccompCondition::new(
0,
SeccompCmpArgLen::Dword,
SeccompCmpOp::Ne,
libc::AF_INET as u64,
)?,
SeccompCondition::new(
0,
SeccompCmpArgLen::Dword,
SeccompCmpOp::Ne,
libc::AF_INET6 as u64,
)?,
])?;
let deny_non_unix_socketpair = SeccompRule::new(vec![SeccompCondition::new(
0,
SeccompCmpArgLen::Dword,
SeccompCmpOp::Ne,
libc::AF_UNIX as u64,
)?])?;
rules.insert(libc::SYS_socket, vec![deny_non_ip_socket]);
rules.insert(libc::SYS_socketpair, vec![deny_non_unix_socketpair]);
}
}所有 filter 的 default action 是 Allow,命中 rule 返回 EPERM。因此未列出的 syscall 仍可能执行;这段代码描述的是 process hardening 的选定 syscall 集合,不是完整 syscall deny list。
6. Helper与bwrap
6.1 bwrap选择
Linux helper 优先使用当前工作目录之外 PATH 中找到的 system bwrap;找不到时使用 bundled bwrap,并在 host startup 阶段产生 warning。WSL1 无法创建所需 user namespace,Bubblewrap 路径在 helper 前置检查中拒绝;WSL2 走普通 Linux 路径。
源码位置:codex-rs/linux-sandbox/src/bwrap.rs :: find_system_bwrap_in_search_paths, system_bwrap_warning_for_path
fn system_bwrap_warning_for_path(system_bwrap_path: Option<&Path>) -> Option<String> {
if is_wsl1() {
return Some(WSL1_BWRAP_WARNING.to_string());
}
let Some(system_bwrap_path) = system_bwrap_path else {
return Some(MISSING_BWRAP_WARNING.to_string());
};
if !system_bwrap_has_user_namespace_access(system_bwrap_path, SYSTEM_BWRAP_PROBE_TIMEOUT) {
return Some(USER_NAMESPACE_WARNING.to_string());
}
None
}find_system_bwrap_in_search_paths 会过滤当前 cwd 下的同名 executable,避免项目目录中恶意 bwrap 优先于系统路径;bundled bwrap 还会在 exec 前校验 digest。
6.2 inner command
inner command 把 PermissionProfile JSON、policy cwd、可选 command cwd、proxy route spec 和原始 command 重新传给 helper。-- 分隔 helper 参数与用户 command,避免用户 executable 以 - 开头时被 helper 当成自己的 flag。
源码位置:codex-rs/linux-sandbox/src/linux_run_main.rs :: build_inner_seccomp_command;codex-rs/sandboxing/src/landlock.rs :: create_linux_sandbox_command_args_for_permission_profile
let mut linux_cmd: Vec<String> = vec![
"--sandbox-policy-cwd".to_string(),
sandbox_policy_cwd,
"--command-cwd".to_string(),
command_cwd,
"--permission-profile".to_string(),
permission_profile_json,
];
if use_legacy_landlock && !allow_network_for_proxy {
linux_cmd.push("--use-legacy-landlock".to_string());
}
if allow_network_for_proxy {
linux_cmd.push("--allow-network-for-proxy".to_string());
}
linux_cmd.push("--".to_string());
linux_cmd.extend(command);
linux_cmd当 proxy-only mode 开启时,代码故意不加入 --use-legacy-landlock;proxy network 需要 Bubblewrap 隔离 network namespace 和 route bridge,不能与 legacy Landlock filesystem shortcut 混用。
7. 测试边界
本篇涉及三组测试:
codex-sandboxinglandlock tests:4 项覆盖 helper argv 中的 legacy flag、PermissionProfile JSON、proxy flag 优先级和allow_network_for_proxy。codex-linux-sandboxlandlock tests:6 项覆盖 network Seccomp mode、managed proxy、full network skip、模式互斥、split-only policy 和 profile resolution。codex-linux-sandboxLinux integration landlock suite:这些测试由 Linux target 条件编译,并依赖 user namespace、Bubblewrap binary 和codex-linux-sandboxhelper;它们负责验证 read/write、network、glob、symlink 和 protected metadata 的实际执行边界。
源码位置:
codex-rs/sandboxing/src/landlock_tests.rs :: legacy_landlock_flag_is_included_when_requestedcodex-rs/sandboxing/src/landlock_tests.rs :: proxy_flag_takes_precedence_over_legacy_landlockcodex-rs/linux-sandbox/src/landlock.rs :: managed_network_enforces_seccomp_even_for_full_network_policycodex-rs/linux-sandbox/src/linux_run_main_tests.rs :: apply_seccomp_then_exec_with_legacy_landlock_panicscodex-rs/linux-sandbox/src/linux_run_main_tests.rs :: legacy_landlock_rejects_split_only_filesystem_policiescodex-rs/linux-sandbox/src/linux_run_main_tests.rs :: managed_proxy_inner_command_includes_route_spec
cargo test -p codex-sandboxing --lib landlock::tests:: -- --nocapture --test-threads=1
cargo test -p codex-linux-sandbox --lib landlock::tests:: -- --nocapture --test-threads=1
cargo test -p codex-linux-sandbox --lib linux_run_main::tests:: -- --nocapture --test-threads=1输入 NetworkSandboxPolicy::Enabled + allow_network_for_proxy=true 的断言是 Some(ProxyRouted);输入 Enabled + false 的断言是 None。输入 split-only filesystem policy 的断言是 legacy compatibility check 失败。它们证明分支条件和参数形状;Linux integration 需要具备 user namespace 与 Bubblewrap 的 Linux target 才能运行,不能外推到不支持 user namespace 的容器,也不能证明 Bubblewrap 之外的 container runtime 会提供同样 mount 语义。
8. 继续阅读
建议按 SandboxManager::transform 生成 helper argv,再进入 linux_run_main::run_main;默认路径阅读 run_bwrap_with_proc_fallback → create_bwrap_flags → inner apply_permission_profile_to_current_thread,legacy 路径阅读 ensure_legacy_landlock_mode_supports_policy → install_filesystem_landlock_rules_on_current_thread。网络则独立沿 network_seccomp_mode → install_network_seccomp_filter_on_current_thread。
下一篇Linux Seccomp与Namespace将继续展开 Bubblewrap namespace、Seccomp syscall 规则和进程树清理,而不是重复 Landlock 的 filesystem policy。
