Linux Seccomp与Namespace
Linux sandbox 不是一次 syscall 就完成的。Bubblewrap outer stage 先建立 filesystem、user、PID、IPC 和可选 network namespace;inner helper 在新的 filesystem view 内验证 descriptor-backed mounts 与 capabilities,再安装 no_new_privs 和 Seccomp,最后 fork/exec 用户命令。父 helper 留在 namespace 中负责信号转发、child 回收和退出状态映射。
网络限制也分两层。Isolated 与 ProxyOnly 都通过 Bubblewrap --unshare-net 脱离 host network namespace;Restricted Seccomp 随后只保留 AF_UNIX IPC,ProxyRouted Seccomp 则允许 AF_INET/AF_INET6 去访问隔离 namespace 内的本地 bridge,同时禁止可连接 host Unix socket 的 socket(AF_UNIX)。因此“允许 IP socket”并不等于恢复 host network。
本文承接Linux Landlock策略和跨平台Sandbox抽象。前文说明 Bubblewrap 是默认 filesystem owner,本篇深入 Namespace、inner verification、Seccomp 和进程生命周期;不重复 bind/ro-bind 的完整 mount 算法,那部分由下一篇负责。
1. Network mode
1.1 三种模式
BwrapNetworkMode 只描述 Bubblewrap namespace 层,不直接描述 Seccomp。FullAccess 保留 host network namespace;Isolated 新建 network namespace且没有 bridge;ProxyOnly 同样新建 namespace,但 inner helper 会建立 proxy route。
源码位置:codex-rs/linux-sandbox/src/bwrap.rs :: BwrapNetworkMode, BwrapNetworkMode::should_unshare_network
/// Network policy modes for bubblewrap.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(crate) enum BwrapNetworkMode {
/// Keep access to the host network namespace.
#[default]
FullAccess,
/// Remove access to the host network namespace.
Isolated,
/// Intended proxy-only mode.
///
/// Bubblewrap enforces this by unsharing the network namespace. The
/// proxy-routing bridge is established by the helper process after startup.
ProxyOnly,
}
impl BwrapNetworkMode {
fn should_unshare_network(self) -> bool {
!matches!(self, Self::FullAccess)
}
}1.2 Policy映射
bwrap_network_mode 让 managed proxy 优先于 NetworkSandboxPolicy::Enabled。这保证即使 profile 原本允许网络,只要本次 execution 要求 managed routing,Bubblewrap 仍必须隔离 host network。
源码位置:codex-rs/linux-sandbox/src/linux_run_main.rs :: bwrap_network_mode
fn bwrap_network_mode(
network_sandbox_policy: NetworkSandboxPolicy,
allow_network_for_proxy: bool,
) -> BwrapNetworkMode {
if allow_network_for_proxy {
BwrapNetworkMode::ProxyOnly
} else if network_sandbox_policy.is_enabled() {
BwrapNetworkMode::FullAccess
} else {
BwrapNetworkMode::Isolated
}
}测试 proxy_only_mode_takes_precedence_over_full_network_policy 输入 Enabled + proxy flag,断言结果仍是 ProxyOnly,而不是 FullAccess。
2. Namespace argv
2.1 基础隔离
无论 restricted filesystem 还是需要 network-only wrapper 的 full filesystem,Bubblewrap 都显式创建 user、PID 与 IPC namespace,并丢弃全部 capabilities。这样即使 caller 在容器内是 uid 0,也不依赖 ambient CAP_SYS_ADMIN 建立其余 namespace。
源码位置:codex-rs/linux-sandbox/src/bwrap.rs :: create_bwrap_flags
let mut args = Vec::new();
args.push("--new-session".to_string());
args.push("--die-with-parent".to_string());
args.extend(filesystem_args);
// Request a user namespace explicitly rather than relying on bubblewrap's
// auto-enable behavior, which is skipped when the caller runs as uid 0.
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);--new-session 建立新的 session,--die-with-parent 让 Bubblewrap 随 launcher 消亡;PID namespace 与 fresh /proc 必须配套理解,否则 sandbox 内的 process view 会与 host 泄漏。
2.2 Full filesystem
当 filesystem 是 full disk write,但 network mode 是 Isolated 或 ProxyOnly 时,helper 仍需要 Bubblewrap。它 bind 整个 /,重新提供 minimal /dev 与 /dev/shm,再加入 namespace 和 capability flags。
源码位置:codex-rs/linux-sandbox/src/bwrap.rs :: create_bwrap_flags_full_filesystem
let mut args = vec![
"--new-session".to_string(),
"--die-with-parent".to_string(),
"--bind".to_string(),
"/".to_string(),
"/".to_string(),
// Preserve nodev on the root bind while exposing only standard devices.
"--dev".to_string(),
"/dev".to_string(),
// Restore shared memory without exposing other host devices.
"--bind-try".to_string(),
"/dev/shm".to_string(),
"/dev/shm".to_string(),
"--unshare-user".to_string(),
"--unshare-pid".to_string(),
"--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());
}
args.push("--cap-drop".to_string());
args.push("ALL".to_string());这说明是否需要 Bubblewrap 由 filesystem 和 network 两类约束共同决定,不能只看 writable roots。
3. Outer与inner
3.1 Inner command
outer stage 不直接执行用户 command,而是构造一次 helper re-entry。inner argv 携带 PermissionProfile JSON、policy cwd、可选 command cwd、--apply-seccomp-then-exec 和可选 proxy route spec,最后才接原始 command。
源码位置:codex-rs/linux-sandbox/src/linux_run_main.rs :: InnerSeccompCommandArgs, build_inner_seccomp_command
struct InnerSeccompCommandArgs<'a> {
sandbox_policy_cwd: &'a Path,
command_cwd: Option<&'a Path>,
permission_profile: &'a PermissionProfile,
allow_network_for_proxy: bool,
proxy_route_spec: Option<String>,
command: Vec<String>,
}
fn build_inner_seccomp_command(args: InnerSeccompCommandArgs<'_>) -> Vec<String> {
let permission_profile_json = serde_json::to_string(args.permission_profile)
.unwrap_or_else(|err| panic!("failed to serialize permission profile: {err}"));
let mut inner = vec![
std::env::current_exe()
.unwrap_or_else(|err| panic!("failed to resolve current executable path: {err}"))
.to_string_lossy()
.to_string(),
"--sandbox-policy-cwd".to_string(),
args.sandbox_policy_cwd.to_string_lossy().to_string(),
];
if let Some(command_cwd) = args.command_cwd {
inner.push("--command-cwd".to_string());
inner.push(command_cwd.to_string_lossy().to_string());
}
inner.extend([
"--permission-profile".to_string(),
permission_profile_json,
"--apply-seccomp-then-exec".to_string(),
]);managed proxy 模式必须同时包含 --allow-network-for-proxy 与 --proxy-route-spec。缺少 spec 会在 outer stage 构造 inner argv 时直接 panic,不能让 command 在隔离 namespace 中无路由地启动。
3.2 proc fallback
run_bwrap_with_proc_fallback 先 preflight /proc mount。某些容器允许 user/PID namespace,却拒绝 --proc /proc;此时 helper静默把 mount_proc 改为 false,再构造正式 argv。它不会取消其他 namespace 或 filesystem 限制。
源码位置:codex-rs/linux-sandbox/src/linux_run_main.rs :: run_bwrap_with_proc_fallback
let network_mode = bwrap_network_mode(network_sandbox_policy, allow_network_for_proxy);
let mut mount_proc = mount_proc;
let command_cwd = command_cwd.unwrap_or(sandbox_policy_cwd);
if mount_proc
&& !preflight_proc_mount_support(network_mode)
.unwrap_or_else(|err| exit_with_bwrap_build_error(err))
{
// Keep the retry silent so sandbox-internal diagnostics do not leak into the
// child process stderr stream.
mount_proc = false;
}
let options = BwrapOptions {
mount_proc,
network_mode,
..Default::default()
};
let mut bwrap_args = build_bwrap_argv(
inner,
file_system_sandbox_policy,
sandbox_policy_cwd,
command_cwd,
options,
)
.unwrap_or_else(|err| exit_with_bwrap_build_error(err));
apply_inner_command_argv0(&mut bwrap_args.args);
run_or_exec_bwrap(bwrap_args);这个 fallback只改变 /proc 可见性。若 Bubblewrap 本身不可用、user namespace 被禁用或 mount policy 构造失败,仍走明确错误路径。
4. Inner验证
4.1 FD mount认证
Bubblewrap 对某些 fallback mount 传递打开的 file descriptor。inner stage 在用户代码运行前检查 marker:descriptor 必须大于标准流、不能重复、destination 必须绝对,descriptor 与 destination 的 device/inode 必须一致。无论成功失败,descriptor 都设置 FD_CLOEXEC 并关闭,避免可写 host descriptor 泄露给 bridge 或用户进程。
源码位置:codex-rs/linux-sandbox/src/fd_mount.rs :: verify_fd_mounts
let descriptor_metadata = file.metadata()?;
let destination_metadata = fs::symlink_metadata(destination)?;
if (descriptor_metadata.dev(), descriptor_metadata.ino())
!= (destination_metadata.dev(), destination_metadata.ino())
{
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
format!(
"descriptor-backed mount does not match its destination: {}",
destination.display()
),
));
}
// Closing immediately prevents a writable host directory descriptor
// from reaching bridge workers or the sandboxed command.
drop(file);测试覆盖 matching inode、swapped destination、symlink destination、标准流、relative destination 和 duplicate descriptor。
4.2 Capability归零
Bubblewrap argv 已含 --cap-drop ALL,但 inner stage 不盲信 outer launcher。它通过 capget 读取两个 capability set,要求 effective 和 permitted 均为零;否则拒绝执行用户 command。
源码位置:codex-rs/linux-sandbox/src/linux_run_main.rs :: run_main inner stage
let mut capability_header = [LINUX_CAPABILITY_VERSION_3, 0];
let mut capability_sets = [[0_u32; 3]; 2];
let result = unsafe {
libc::syscall(
libc::SYS_capget,
capability_header.as_mut_ptr(),
capability_sets.as_mut_ptr(),
)
};
if result < 0 {
panic!(
"failed to verify Linux sandbox capabilities: {}",
std::io::Error::last_os_error()
);
}
if capability_sets
.into_iter()
.any(|[effective, permitted, _]| effective != 0 || permitted != 0)
{
panic!("Linux sandbox retained effective or permitted capabilities");
}这是一道发布屏障:只有 outer namespace、mount 和 capability 状态符合预期,inner 才继续安装 Seccomp。
5. Seccomp filter
5.1 no_new_privs
Seccomp filter 安装前调用 PR_SET_NO_NEW_PRIVS。代码故意不在 Bubblewrap outer stage过早设置,因为一些 system bwrap 部署依赖 setuid;先建立 namespace,再在 inner stage不可逆收紧。
源码位置:codex-rs/linux-sandbox/src/landlock.rs :: apply_permission_profile_to_current_thread, set_no_new_privs
if network_seccomp_mode.is_some()
|| (apply_landlock_fs && !file_system_sandbox_policy.has_full_disk_write_access())
{
set_no_new_privs()?;
}
if let Some(mode) = network_seccomp_mode {
install_network_seccomp_filter_on_current_thread(mode)?;
}5.2 通用进程限制
两种 network mode 都先拒绝 ptrace、process_vm_readv/writev 和 io_uring_setup/enter/register。这些规则防止进程检查其他地址空间或绕过同步 syscall 集合使用 io_uring。
源码位置:codex-rs/linux-sandbox/src/landlock.rs :: install_network_seccomp_filter_on_current_thread
let mut rules: BTreeMap<i64, Vec<SeccompRule>> = BTreeMap::new();
deny_syscall(&mut rules, libc::SYS_ptrace);
deny_syscall(&mut rules, libc::SYS_process_vm_readv);
deny_syscall(&mut rules, libc::SYS_process_vm_writev);
deny_syscall(&mut rules, libc::SYS_io_uring_setup);
deny_syscall(&mut rules, libc::SYS_io_uring_enter);
deny_syscall(&mut rules, libc::SYS_io_uring_register);5.3 Restricted模式
Restricted mode 无条件拒绝连接、监听和多种 socket 操作。socket 与 socketpair 使用参数条件:domain 不等于 AF_UNIX 时命中 deny rule,因此只保留本地 IPC。
源码位置:codex-rs/linux-sandbox/src/landlock.rs :: 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_getpeername);
deny_syscall(&mut rules, libc::SYS_getsockname);
deny_syscall(&mut rules, libc::SYS_shutdown);
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]);recvfrom 没有被无条件拒绝,源码注释说明部分工具依赖 socketpair 与 child process 管理。Seccomp 规则是经过兼容性取舍的 allow-by-default filter,不是“禁止一切未列 syscall”。
5.4 ProxyRouted模式
ProxyRouted mode 的 namespace 已与 host 隔离,所以允许 AF_INET/AF_INET6 仅能访问 namespace 内配置的 loopback bridge。普通 socket(AF_UNIX) 被拒绝,避免连接 host-mounted Unix socket;socketpair(AF_UNIX) 保留,因为它不能连接既有外部 endpoint。
源码位置:codex-rs/linux-sandbox/src/landlock.rs :: 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,命中 action 是 Errno(EPERM)。因此被拒绝 syscall 通常表现为权限错误,而不是 SIGSYS;SIGSYS 仍可能来自其它 filter 或平台路径。
6. Proxy bridge
6.1 Host计划
outer stage从 proxy env 中只接受 parseable loopback endpoint,移除 attribution token,再为每个 distinct endpoint 创建 Unix socket 和 host bridge。无法得到 route 时返回 InvalidInput,不进入 inner command。
源码位置:codex-rs/linux-sandbox/src/proxy_routing.rs :: prepare_host_proxy_route_spec
let (attribution_token, plan) = extract_attribution_token_and_plan(std::env::vars().collect());
unsafe {
std::env::remove_var(PROXY_ATTRIBUTION_TOKEN_ENV_KEY);
}
if plan.routes.is_empty() {
let message = if plan.has_proxy_config {
"managed proxy mode requires parseable loopback proxy endpoints"
} else {
"managed proxy mode requires proxy environment variables"
};
return Err(io::Error::new(io::ErrorKind::InvalidInput, message));
}
let socket_dir = create_proxy_socket_dir()?;
let readable_socket_dir = AbsolutePathBuf::relative_to_current_dir(&socket_dir)?;socket directory 会作为额外 readable root进入 Bubblewrap view,host bridge 将 Unix socket 流量转发到原 loopback proxy。
6.2 Netns激活
inner stage读取 route spec,在新 network namespace 中为每个 UDS 建立 localhost TCP listener,并重写 HTTP/SOCKS/WebSocket proxy env 指向新端口。loopback interface 不可用时会尝试标记 lo 为 UP 并设置 127.0.0.1。
源码位置:codex-rs/linux-sandbox/src/proxy_routing.rs :: activate_proxy_routes_in_netns
let spec: ProxyRouteSpec = serde_json::from_str(serialized_spec).map_err(io::Error::other)?;
if spec.routes.is_empty() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"proxy routing spec contained no routes",
));
}
let mut local_port_by_uds_path: BTreeMap<PathBuf, u16> = BTreeMap::new();
for route in &spec.routes {
if local_port_by_uds_path.contains_key(&route.uds_path) {
continue;
}
let local_port = spawn_local_bridge(route.uds_path.as_path())?;
local_port_by_uds_path.insert(route.uds_path.clone(), local_port);
}
for route in spec.routes {
let local_port = local_port_by_uds_path
.get(&route.uds_path)
.ok_or_else(|| io::Error::other("missing local bridge port"))?;
let original_value = std::env::var(&route.env_key)?;
let rewritten = rewrite_proxy_env_value(&original_value, *local_port)
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "invalid proxy URL"))?;
unsafe {
std::env::set_var(route.env_key, rewritten);
}
}此时 ProxyRouted Seccomp 才安装。顺序不能反转:如果先禁止 AF_UNIX socket 或 network setup,bridge 无法激活。
7. Child生命周期
7.1 inner child
完成 mount、capability、proxy 和 Seccomp 后,inner helper fork 用户 command。child 恢复默认 signal handler 并 execvp;parent 安装 forwarder、循环 waitpid(-1),只在目标 command 退出时恢复 handler并按原 status 退出。
源码位置:codex-rs/linux-sandbox/src/linux_run_main.rs :: run_main inner stage
let signal_mask = ForwardedSignalMask::block();
let command_pid = unsafe { libc::fork() };
if command_pid < 0 {
let err = std::io::Error::last_os_error();
panic!("failed to fork sandboxed command: {err}");
}
if command_pid == 0 {
reset_forwarded_signal_handlers_to_default();
signal_mask.restore();
exec_or_panic(command);
}
let signal_forwarders = install_bwrap_signal_forwarders(command_pid);
signal_mask.restore();
loop {
let mut status = 0;
let reaped_pid = unsafe { libc::waitpid(-1, &mut status, 0) };
if reaped_pid == command_pid {
let exit_signal_mask = ForwardedSignalMask::block();
signal_forwarders.restore();
exit_signal_mask.restore();
exit_with_wait_status(status);
}
if reaped_pid >= 0 {
continue;
}
let err = std::io::Error::last_os_error();
if err.raw_os_error() != Some(libc::EINTR) {
panic!("failed to reap sandboxed child: {err}");
}
}使用 waitpid(-1) 是因为 command 可能产生其它可回收 child;只有主 command status 决定 helper 终态。
7.2 Signal转发
outer helper 把需要转发的 signal 同时发给 process group -pid 和 child pid,覆盖已经建组和尚未建组的竞态。安装 handler 前到达的信号保存在 atomic 中,handler 安装后 replay。
源码位置:codex-rs/linux-sandbox/src/linux_run_main.rs :: install_bwrap_signal_forwarders, forward_signal_to_bwrap_child, send_signal_to_bwrap_child
fn install_bwrap_signal_forwarders(pid: libc::pid_t) -> ForwardedSignalHandlers {
BWRAP_CHILD_PID.store(pid, Ordering::SeqCst);
let mut previous = Vec::with_capacity(FORWARDED_SIGNALS.len());
for signal in FORWARDED_SIGNALS {
let mut action: libc::sigaction = unsafe { std::mem::zeroed() };
let mut previous_action: libc::sigaction = unsafe { std::mem::zeroed() };
action.sa_sigaction = forward_signal_to_bwrap_child as *const () as libc::sighandler_t;
unsafe {
libc::sigemptyset(&mut action.sa_mask);
if libc::sigaction(*signal, &action, &mut previous_action) < 0 {
panic!("failed to install bubblewrap signal forwarder");
}
}
previous.push((*signal, previous_action));
}
replay_pending_forwarded_signal(pid);
ForwardedSignalHandlers { previous }
}
fn send_signal_to_bwrap_child(pid: libc::pid_t, signal: libc::c_int) {
unsafe {
libc::kill(-pid, signal);
libc::kill(pid, signal);
}
}bwrap_signal_forwarder_terminates_child_and_keeps_parent_alive 用 supervisor/child 两层 fork验证:supervisor 收到 SIGTERM 后仍能等待 child,child 由 SIGTERM 终止。
7.3 Synthetic清理
若 filesystem policy创建 synthetic mount或 protected-create target,outer helper不能直接 exec_bwrap。它 fork Bubblewrap child、建立 process group和 parent-death signal,启动 protected target monitor,等待 child后清理 mount registry,并将新建 protected path视为 policy violation。
源码位置:codex-rs/linux-sandbox/src/linux_run_main.rs :: run_bwrap_in_child_with_synthetic_mount_cleanup
let setup_signal_mask = ForwardedSignalMask::block();
let synthetic_mount_registrations = register_synthetic_mount_targets(&synthetic_mount_targets);
let protected_create_registrations =
register_protected_create_targets(&protected_create_targets);
let exec_start_pipe = create_exec_start_pipe(!protected_create_targets.is_empty());
let parent_pid = unsafe { libc::getpid() };
let pid = unsafe { libc::fork() };
if pid == 0 {
reset_forwarded_signal_handlers_to_default();
setup_signal_mask.restore();
let setpgid_res = unsafe { libc::setpgid(0, 0) };
if setpgid_res < 0 {
panic!("failed to place bubblewrap child in its own process group");
}
terminate_with_parent(parent_pid);
wait_for_parent_exec_start(exec_start_pipe[0], exec_start_pipe[1]);
exec_bwrap(args, preserved_files);
}
let protected_create_monitor = ProtectedCreateMonitor::start(&protected_create_targets);
let signal_forwarders = install_bwrap_signal_forwarders(pid);
release_child_exec_start(exec_start_pipe[1]);
setup_signal_mask.restore();
let status = wait_for_bwrap_child(pid);cleanup owner必须留在 sandbox 外层,用户 command不能删除 marker或伪造 registration。
8. 测试边界
本篇直接涉及的 Linux helper 源码中有 53 项 unit test,覆盖 namespace argv、argv0 compatibility、network mode、proc preflight、FD mount、proxy route、capability/mode输入、signal forwarding和synthetic cleanup;这些测试由 target_os = "linux" 条件编译。当前非 Linux 主机可核对源码并编译非 Linux stub,但不能运行 Linux namespace/Seccomp 断言。
当前可执行的近场测试仍包括 codex-sandboxing 的 4 项 helper argv测试,确认 PermissionProfile、legacy/proxy flag 和 command separator 进入 Linux helper命令。Linux target上应执行:
cargo test -p codex-linux-sandbox --lib -- --nocapture --test-threads=1
cargo test -p codex-linux-sandbox --test all -- --nocapture --test-threads=1
cargo test -p codex-sandboxing --lib landlock::tests:: -- --nocapture --test-threads=1关键输入与断言包括:
BwrapNetworkMode::Isolated和ProxyOnly的 argv 都包含--unshare-net;FullAccess 不包含。- normal argv包含
--unshare-user、--unshare-pid、--unshare-ipc和--cap-drop ALL。 - matching descriptor mount验证成功后 FD 已关闭;mismatched inode、symlink destination和duplicate FD均失败并关闭 FD。
- managed proxy inner command必须包含 route spec;没有 spec时构造失败。
- signal forwarding test要求 child 被 SIGTERM终止,而 supervisor保持存活并成功回收。
这些结果证明构造、状态检查和 Linux fixture 行为;边界在于 namespace创建仍依赖 host kernel、user namespace配置、Bubblewrap版本和container能力。Seccomp filter只对 x86_64/aarch64生成,其他架构当前走 unimplemented!,不能宣称全架构支持。
9. 继续阅读
读者可以从 bwrap_network_mode 进入 create_bwrap_flags,再跟随 inner argv 到 run_main 的 apply_seccomp_then_exec 分支;随后分别阅读 verify_fd_mounts、capability检查、activate_proxy_routes_in_netns、install_network_seccomp_filter_on_current_thread 和 signal/waitpid 收尾。这样能够区分 namespace、route bridge、Seccomp 和process supervisor各自保证什么。
下一篇Bubblewrap命令构造将展开 filesystem mount顺序、read/write/deny重叠、glob mask、symlink和synthetic mount的具体 argv算法。
