Codex Handbook
linux-sandbox/src/linux_run_main_tests.rs 641 lines
#[cfg(test)]use super::*;#[cfg(test)]use crate::linux_run_main::install_bwrap_signal_forwarders;#[cfg(test)]use crate::linux_run_main::wait_for_bwrap_child;#[cfg(test)]use codex_protocol::models::PermissionProfile;#[cfg(test)]use codex_protocol::protocol::FileSystemSandboxPolicy;#[cfg(test)]use codex_protocol::protocol::NetworkSandboxPolicy;#[cfg(test)]use codex_utils_absolute_path::AbsolutePathBuf;#[cfg(test)]use pretty_assertions::assert_eq;fn read_only_permission_profile() -> PermissionProfile {    PermissionProfile::read_only()}fn read_only_file_system_policy() -> FileSystemSandboxPolicy {    read_only_permission_profile().file_system_sandbox_policy()}#[test]fn detects_proc_mount_invalid_argument_failure() {    let stderr = "bwrap: Can't mount proc on /newroot/proc: Invalid argument";    assert!(is_proc_mount_failure(stderr));}#[test]fn detects_proc_mount_operation_not_permitted_failure() {    let stderr = "bwrap: Can't mount proc on /newroot/proc: Operation not permitted";    assert!(is_proc_mount_failure(stderr));}#[test]fn detects_proc_mount_permission_denied_failure() {    let stderr = "bwrap: Can't mount proc on /newroot/proc: Permission denied";    assert!(is_proc_mount_failure(stderr));}#[test]fn ignores_non_proc_mount_errors() {    let stderr = "bwrap: Can't bind mount /dev/null: Operation not permitted";    assert!(!is_proc_mount_failure(stderr));}#[test]fn inserts_bwrap_argv0_before_command_separator() {    let file_system_sandbox_policy = read_only_file_system_policy();    let mut argv = build_bwrap_argv(        vec!["/bin/true".to_string()],        &file_system_sandbox_policy,        Path::new("/"),        Path::new("/"),        BwrapOptions {            mount_proc: true,            network_mode: BwrapNetworkMode::FullAccess,            ..Default::default()        },    )    .expect("build bwrap argv")    .args;    apply_inner_command_argv0_for_launcher(        &mut argv,        /*supports_argv0*/ true,        "/tmp/codex-arg0-session/codex-linux-sandbox".to_string(),    );    assert_eq!(        argv,        vec![            "bwrap".to_string(),            "--new-session".to_string(),            "--die-with-parent".to_string(),            "--ro-bind".to_string(),            "/".to_string(),            "/".to_string(),            "--dev".to_string(),            "/dev".to_string(),            "--unshare-user".to_string(),            "--unshare-pid".to_string(),            "--proc".to_string(),            "/proc".to_string(),            "--argv0".to_string(),            "codex-linux-sandbox".to_string(),            "--".to_string(),            "/bin/true".to_string(),        ]    );}#[test]fn rewrites_inner_command_path_when_bwrap_lacks_argv0() {    let file_system_sandbox_policy = read_only_file_system_policy();    let mut argv = build_bwrap_argv(        vec!["/bin/true".to_string()],        &file_system_sandbox_policy,        Path::new("/"),        Path::new("/"),        BwrapOptions {            mount_proc: true,            network_mode: BwrapNetworkMode::FullAccess,            ..Default::default()        },    )    .expect("build bwrap argv")    .args;    apply_inner_command_argv0_for_launcher(        &mut argv,        /*supports_argv0*/ false,        "/tmp/codex-arg0-session/codex-linux-sandbox".to_string(),    );    assert!(!argv.iter().any(|arg| arg == "--argv0"));    assert!(        argv.windows(2)            .any(|window| { window == ["--", "/tmp/codex-arg0-session/codex-linux-sandbox"] })    );}#[test]fn rewrites_bwrap_helper_command_not_nested_user_command_when_current_exe_appears_later() {    let nested_current_exe = std::env::current_exe()        .expect("current exe")        .to_string_lossy()        .into_owned();    let mut argv = vec![        "bwrap".to_string(),        "--".to_string(),        "/tmp/helper-symlink".to_string(),        "--sandbox-policy-cwd".to_string(),        "/tmp/cwd".to_string(),        "--".to_string(),        nested_current_exe.clone(),        "--codex-run-as-apply-patch".to_string(),        "patch".to_string(),    ];    apply_inner_command_argv0_for_launcher(        &mut argv,        /*supports_argv0*/ false,        "/tmp/argv0-fallback-helper".to_string(),    );    assert_eq!(        argv,        vec![            "bwrap".to_string(),            "--".to_string(),            "/tmp/argv0-fallback-helper".to_string(),            "--sandbox-policy-cwd".to_string(),            "/tmp/cwd".to_string(),            "--".to_string(),            nested_current_exe,            "--codex-run-as-apply-patch".to_string(),            "patch".to_string(),        ]    );}#[test]fn inserts_unshare_net_when_network_isolation_requested() {    let file_system_sandbox_policy = read_only_file_system_policy();    let argv = build_bwrap_argv(        vec!["/bin/true".to_string()],        &file_system_sandbox_policy,        Path::new("/"),        Path::new("/"),        BwrapOptions {            mount_proc: true,            network_mode: BwrapNetworkMode::Isolated,            ..Default::default()        },    )    .expect("build bwrap argv")    .args;    assert!(argv.contains(&"--unshare-net".to_string()));}#[test]fn inserts_unshare_net_when_proxy_only_network_mode_requested() {    let file_system_sandbox_policy = read_only_file_system_policy();    let argv = build_bwrap_argv(        vec!["/bin/true".to_string()],        &file_system_sandbox_policy,        Path::new("/"),        Path::new("/"),        BwrapOptions {            mount_proc: true,            network_mode: BwrapNetworkMode::ProxyOnly,            ..Default::default()        },    )    .expect("build bwrap argv")    .args;    assert!(argv.contains(&"--unshare-net".to_string()));}#[test]fn proxy_only_mode_takes_precedence_over_full_network_policy() {    let mode = bwrap_network_mode(        NetworkSandboxPolicy::Enabled,        /*allow_network_for_proxy*/ true,    );    assert_eq!(mode, BwrapNetworkMode::ProxyOnly);}#[test]fn split_only_filesystem_policy_requires_direct_runtime_enforcement() {    let temp_dir = tempfile::TempDir::new().expect("tempdir");    let docs = temp_dir.path().join("docs");    std::fs::create_dir_all(&docs).expect("create docs");    let docs = AbsolutePathBuf::from_absolute_path(&docs).expect("absolute docs");    let policy = FileSystemSandboxPolicy::restricted(vec![        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Special {                value: codex_protocol::permissions::FileSystemSpecialPath::project_roots(                    /*subpath*/ None,                ),            },            access: codex_protocol::permissions::FileSystemAccessMode::Write,        },        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Path { path: docs },            access: codex_protocol::permissions::FileSystemAccessMode::Read,        },    ]);    assert!(        policy.needs_direct_runtime_enforcement(NetworkSandboxPolicy::Restricted, temp_dir.path(),)    );}#[test]fn root_write_read_only_carveout_requires_direct_runtime_enforcement() {    let temp_dir = tempfile::TempDir::new().expect("tempdir");    let docs = temp_dir.path().join("docs");    std::fs::create_dir_all(&docs).expect("create docs");    let docs = AbsolutePathBuf::from_absolute_path(&docs).expect("absolute docs");    let policy = FileSystemSandboxPolicy::restricted(vec![        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Special {                value: codex_protocol::permissions::FileSystemSpecialPath::Root,            },            access: codex_protocol::permissions::FileSystemAccessMode::Write,        },        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Path { path: docs },            access: codex_protocol::permissions::FileSystemAccessMode::Read,        },    ]);    assert!(        policy.needs_direct_runtime_enforcement(NetworkSandboxPolicy::Restricted, temp_dir.path(),)    );}#[test]fn managed_proxy_preflight_argv_is_wrapped_for_full_access_policy() {    let mode = bwrap_network_mode(        NetworkSandboxPolicy::Enabled,        /*allow_network_for_proxy*/ true,    );    let argv = build_preflight_bwrap_argv(        Path::new("/"),        Path::new("/"),        &FileSystemSandboxPolicy::unrestricted(),        mode,    )    .expect("build preflight argv")    .args;    assert!(argv.iter().any(|arg| arg == "--"));}#[test]fn cleanup_synthetic_mount_targets_removes_only_empty_mount_targets() {    let temp_dir = tempfile::TempDir::new().expect("tempdir");    let empty_file = temp_dir.path().join(".git");    let empty_dir = temp_dir.path().join(".agents");    let non_empty_file = temp_dir.path().join("non-empty");    let missing_file = temp_dir.path().join(".missing");    std::fs::write(&empty_file, "").expect("write empty file");    std::fs::create_dir(&empty_dir).expect("create empty dir");    std::fs::write(&non_empty_file, "keep").expect("write nonempty file");    let registrations = register_synthetic_mount_targets(&[        crate::bwrap::SyntheticMountTarget::missing(&empty_file),        crate::bwrap::SyntheticMountTarget::missing_empty_directory(&empty_dir),        crate::bwrap::SyntheticMountTarget::missing(&non_empty_file),        crate::bwrap::SyntheticMountTarget::missing(&missing_file),    ]);    cleanup_synthetic_mount_targets(&registrations);    assert!(!empty_file.exists());    assert!(!empty_dir.exists());    assert_eq!(        std::fs::read_to_string(&non_empty_file).expect("read nonempty file"),        "keep"    );    assert!(!missing_file.exists());}#[test]fn synthetic_mount_registry_root_is_unique_to_effective_user() {    let effective_uid = unsafe { libc::geteuid() };    assert_eq!(        synthetic_mount_registry_root(),        std::env::temp_dir().join(format!(            "codex-bwrap-synthetic-mount-targets-{effective_uid}"        ))    );}#[test]fn cleanup_synthetic_mount_targets_waits_for_other_active_registrations() {    let temp_dir = tempfile::TempDir::new().expect("tempdir");    let empty_file = temp_dir.path().join(".git");    std::fs::write(&empty_file, "").expect("write empty file");    let target = crate::bwrap::SyntheticMountTarget::missing(&empty_file);    let registrations = register_synthetic_mount_targets(std::slice::from_ref(&target));    let active_marker = registrations[0].marker_dir.join("1");    std::fs::write(&active_marker, "").expect("write active marker");    cleanup_synthetic_mount_targets(&registrations);    assert!(empty_file.exists());    std::fs::remove_file(active_marker).expect("remove active marker");    let registrations = register_synthetic_mount_targets(std::slice::from_ref(&target));    cleanup_synthetic_mount_targets(&registrations);    assert!(!empty_file.exists());}#[test]fn cleanup_synthetic_mount_targets_removes_transient_file_after_concurrent_owner_exits() {    let temp_dir = tempfile::TempDir::new().expect("tempdir");    let empty_file = temp_dir.path().join(".git");    let first_target = crate::bwrap::SyntheticMountTarget::missing(&empty_file);    let first_registrations = register_synthetic_mount_targets(&[first_target]);    std::fs::write(&empty_file, "").expect("write transient empty file");    let active_marker = first_registrations[0].marker_dir.join("1");    std::fs::write(&active_marker, SYNTHETIC_MOUNT_MARKER_SYNTHETIC).expect("write active marker");    let metadata = std::fs::symlink_metadata(&empty_file).expect("stat empty file");    let second_target =        crate::bwrap::SyntheticMountTarget::existing_empty_file(&empty_file, &metadata);    let second_registrations = register_synthetic_mount_targets(&[second_target]);    cleanup_synthetic_mount_targets(&first_registrations);    assert!(empty_file.exists());    std::fs::remove_file(active_marker).expect("remove active marker");    cleanup_synthetic_mount_targets(&second_registrations);    assert!(!empty_file.exists());}#[test]fn cleanup_synthetic_mount_targets_preserves_real_pre_existing_empty_file() {    let temp_dir = tempfile::TempDir::new().expect("tempdir");    let empty_file = temp_dir.path().join(".git");    std::fs::write(&empty_file, "").expect("write pre-existing empty file");    let metadata = std::fs::symlink_metadata(&empty_file).expect("stat empty file");    let first_target =        crate::bwrap::SyntheticMountTarget::existing_empty_file(&empty_file, &metadata);    let second_target =        crate::bwrap::SyntheticMountTarget::existing_empty_file(&empty_file, &metadata);    let first_registrations = register_synthetic_mount_targets(&[first_target]);    let second_registrations = register_synthetic_mount_targets(&[second_target]);    cleanup_synthetic_mount_targets(&first_registrations);    cleanup_synthetic_mount_targets(&second_registrations);    assert!(empty_file.exists());}#[test]fn cleanup_protected_create_targets_removes_created_path_and_reports_violation() {    let temp_dir = tempfile::TempDir::new().expect("tempdir");    let dot_git = temp_dir.path().join(".git");    let target = crate::bwrap::ProtectedCreateTarget::missing(&dot_git);    let registrations = register_protected_create_targets(&[target]);    std::fs::create_dir(&dot_git).expect("create protected path");    let violation = cleanup_protected_create_targets(&registrations);    assert!(violation);    assert!(!dot_git.exists());}#[test]fn cleanup_protected_create_targets_waits_for_other_active_registrations() {    let temp_dir = tempfile::TempDir::new().expect("tempdir");    let dot_git = temp_dir.path().join(".git");    let target = crate::bwrap::ProtectedCreateTarget::missing(&dot_git);    let registrations = register_protected_create_targets(std::slice::from_ref(&target));    let active_marker = registrations[0].marker_dir.join("1");    std::fs::write(&active_marker, PROTECTED_CREATE_MARKER).expect("write active marker");    std::fs::write(&dot_git, "").expect("create protected path");    let violation = cleanup_protected_create_targets(&registrations);    assert!(violation);    assert!(dot_git.exists());    std::fs::remove_file(active_marker).expect("remove active marker");    let registrations = register_protected_create_targets(std::slice::from_ref(&target));    let violation = cleanup_protected_create_targets(&registrations);    assert!(violation);    assert!(!dot_git.exists());}#[test]fn bwrap_signal_forwarder_terminates_child_and_keeps_parent_alive() {    let supervisor_pid = unsafe { libc::fork() };    assert!(supervisor_pid >= 0, "failed to fork supervisor");    if supervisor_pid == 0 {        run_bwrap_signal_forwarder_test_supervisor();    }    let status = wait_for_bwrap_child(supervisor_pid);    assert!(libc::WIFEXITED(status), "supervisor status: {status}");    assert_eq!(libc::WEXITSTATUS(status), 0);}#[cfg(test)]fn run_bwrap_signal_forwarder_test_supervisor() -> ! {    let child_pid = unsafe { libc::fork() };    if child_pid < 0 {        unsafe {            libc::_exit(2);        }    }    if child_pid == 0 {        loop {            unsafe {                libc::pause();            }        }    }    install_bwrap_signal_forwarders(child_pid);    unsafe {        libc::raise(libc::SIGTERM);    }    let status = wait_for_bwrap_child(child_pid);    let child_terminated_by_sigterm =        libc::WIFSIGNALED(status) && libc::WTERMSIG(status) == libc::SIGTERM;    unsafe {        libc::_exit(if child_terminated_by_sigterm { 0 } else { 1 });    }}#[test]fn managed_proxy_inner_command_includes_route_spec() {    let permission_profile = read_only_permission_profile();    let args = build_inner_seccomp_command(InnerSeccompCommandArgs {        sandbox_policy_cwd: Path::new("/tmp"),        command_cwd: Some(Path::new("/tmp/link")),        permission_profile: &permission_profile,        allow_network_for_proxy: true,        proxy_route_spec: Some("{\"routes\":[]}".to_string()),        command: vec!["/bin/true".to_string()],    });    assert!(args.iter().any(|arg| arg == "--proxy-route-spec"));    assert!(args.iter().any(|arg| arg == "{\"routes\":[]}"));}#[test]fn inner_command_includes_permission_profile_flag() {    let permission_profile = read_only_permission_profile();    let args = build_inner_seccomp_command(InnerSeccompCommandArgs {        sandbox_policy_cwd: Path::new("/tmp"),        command_cwd: Some(Path::new("/tmp/link")),        permission_profile: &permission_profile,        allow_network_for_proxy: false,        proxy_route_spec: None,        command: vec!["/bin/true".to_string()],    });    assert!(args.iter().any(|arg| arg == "--permission-profile"));    assert!(        args.windows(2)            .any(|window| { window == ["--command-cwd", "/tmp/link"] })    );}#[test]fn non_managed_inner_command_omits_route_spec() {    let permission_profile = read_only_permission_profile();    let args = build_inner_seccomp_command(InnerSeccompCommandArgs {        sandbox_policy_cwd: Path::new("/tmp"),        command_cwd: Some(Path::new("/tmp/link")),        permission_profile: &permission_profile,        allow_network_for_proxy: false,        proxy_route_spec: None,        command: vec!["/bin/true".to_string()],    });    assert!(!args.iter().any(|arg| arg == "--proxy-route-spec"));}#[test]fn managed_proxy_inner_command_requires_route_spec() {    let result = std::panic::catch_unwind(|| {        let permission_profile = read_only_permission_profile();        build_inner_seccomp_command(InnerSeccompCommandArgs {            sandbox_policy_cwd: Path::new("/tmp"),            command_cwd: Some(Path::new("/tmp/link")),            permission_profile: &permission_profile,            allow_network_for_proxy: true,            proxy_route_spec: None,            command: vec!["/bin/true".to_string()],        })    });    assert!(result.is_err());}#[test]fn resolve_permission_profile_derives_runtime_policies() {    let permission_profile = read_only_permission_profile();    let resolved = resolve_permission_profile(Some(permission_profile.clone()))        .expect("profile should resolve");    assert_eq!(resolved.permission_profile, permission_profile);    assert_eq!(        resolved.file_system_sandbox_policy,        read_only_file_system_policy()    );    assert_eq!(        resolved.network_sandbox_policy,        NetworkSandboxPolicy::Restricted    );}#[test]fn resolve_permission_profile_preserves_direct_runtime_profile() {    let temp_dir = tempfile::TempDir::new().expect("tempdir");    let docs = temp_dir.path().join("docs");    std::fs::create_dir_all(&docs).expect("create docs");    let docs = AbsolutePathBuf::from_absolute_path(&docs).expect("absolute docs");    let file_system_sandbox_policy = FileSystemSandboxPolicy::restricted(vec![        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Special {                value: codex_protocol::permissions::FileSystemSpecialPath::Root,            },            access: codex_protocol::permissions::FileSystemAccessMode::Read,        },        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Path { path: docs },            access: codex_protocol::permissions::FileSystemAccessMode::Write,        },    ]);    let permission_profile = PermissionProfile::from_runtime_permissions(        &file_system_sandbox_policy,        NetworkSandboxPolicy::Restricted,    );    let resolved = resolve_permission_profile(Some(permission_profile.clone()))        .expect("profile should resolve");    assert_eq!(resolved.permission_profile, permission_profile);    assert_eq!(        resolved.file_system_sandbox_policy,        file_system_sandbox_policy    );    assert_eq!(        resolved.network_sandbox_policy,        NetworkSandboxPolicy::Restricted    );}#[test]fn resolve_permission_profile_rejects_missing_configuration() {    let err = resolve_permission_profile(/*permission_profile*/ None)        .expect_err("missing profile should fail");    assert_eq!(err, ResolvePermissionProfileError::MissingConfiguration);}#[test]fn apply_seccomp_then_exec_with_legacy_landlock_panics() {    let result = std::panic::catch_unwind(|| {        ensure_inner_stage_mode_is_valid(            /*apply_seccomp_then_exec*/ true, /*use_legacy_landlock*/ true,        )    });    assert!(result.is_err());}#[test]fn legacy_landlock_rejects_split_only_filesystem_policies() {    let temp_dir = tempfile::TempDir::new().expect("tempdir");    let docs = temp_dir.path().join("docs");    std::fs::create_dir_all(&docs).expect("create docs");    let docs = AbsolutePathBuf::from_absolute_path(&docs).expect("absolute docs");    let policy = FileSystemSandboxPolicy::restricted(vec![        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Special {                value: codex_protocol::permissions::FileSystemSpecialPath::Root,            },            access: codex_protocol::permissions::FileSystemAccessMode::Read,        },        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Path { path: docs },            access: codex_protocol::permissions::FileSystemAccessMode::Write,        },    ]);    let result = std::panic::catch_unwind(|| {        ensure_legacy_landlock_mode_supports_policy(            /*use_legacy_landlock*/ true,            &policy,            NetworkSandboxPolicy::Restricted,            temp_dir.path(),        );    });    assert!(result.is_err());}#[test]fn valid_inner_stage_modes_do_not_panic() {    ensure_inner_stage_mode_is_valid(        /*apply_seccomp_then_exec*/ false, /*use_legacy_landlock*/ false,    );    ensure_inner_stage_mode_is_valid(        /*apply_seccomp_then_exec*/ false, /*use_legacy_landlock*/ true,    );    ensure_inner_stage_mode_is_valid(        /*apply_seccomp_then_exec*/ true, /*use_legacy_landlock*/ false,    );}