mirror of
https://github.com/openai/codex.git
synced 2026-11-06 20:20:34 +00:00
fix: overhaul how we spawn commands under seccomp/landlock on Linux
This commit is contained in:
@@ -7,10 +7,6 @@ edition = "2024"
|
||||
name = "codex"
|
||||
path = "src/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "codex-linux-sandbox"
|
||||
path = "src/linux-sandbox/main.rs"
|
||||
|
||||
[lib]
|
||||
name = "codex_cli"
|
||||
path = "src/lib.rs"
|
||||
@@ -24,6 +20,7 @@ clap = { version = "4", features = ["derive"] }
|
||||
codex-core = { path = "../core" }
|
||||
codex-common = { path = "../common", features = ["cli"] }
|
||||
codex-exec = { path = "../exec" }
|
||||
codex-linux-sandbox = { path = "../linux-sandbox" }
|
||||
codex-mcp-server = { path = "../mcp-server" }
|
||||
codex-tui = { path = "../tui" }
|
||||
serde_json = "1"
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use codex_common::SandboxPermissionOption;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config::ConfigOverrides;
|
||||
use codex_core::exec::StdioPolicy;
|
||||
use codex_core::exec::spawn_command_under_linux_sandbox;
|
||||
use codex_core::exec::spawn_command_under_seatbelt;
|
||||
use codex_core::exec_env::create_env;
|
||||
use codex_core::protocol::SandboxPolicy;
|
||||
|
||||
use crate::LandlockCommand;
|
||||
use crate::SeatbeltCommand;
|
||||
use crate::exit_status::handle_exit_status;
|
||||
|
||||
pub async fn run_command_under_seatbelt(command: SeatbeltCommand) -> anyhow::Result<()> {
|
||||
let SeatbeltCommand {
|
||||
full_auto,
|
||||
sandbox,
|
||||
command,
|
||||
} = command;
|
||||
run_command_under_sandbox(full_auto, sandbox, command, None, SandboxType::Seatbelt).await
|
||||
}
|
||||
|
||||
pub async fn run_command_under_landlock(command: LandlockCommand) -> anyhow::Result<()> {
|
||||
let LandlockCommand {
|
||||
full_auto,
|
||||
sandbox,
|
||||
command,
|
||||
} = command;
|
||||
run_command_under_sandbox(full_auto, sandbox, command, None, SandboxType::Landlock).await
|
||||
}
|
||||
|
||||
enum SandboxType {
|
||||
Seatbelt,
|
||||
Landlock,
|
||||
}
|
||||
|
||||
async fn run_command_under_sandbox(
|
||||
full_auto: bool,
|
||||
sandbox: SandboxPermissionOption,
|
||||
command: Vec<String>,
|
||||
codex_linux_sandbox_exe: Option<PathBuf>,
|
||||
sandbox_type: SandboxType,
|
||||
) -> anyhow::Result<()> {
|
||||
let sandbox_policy = create_sandbox_policy(full_auto, sandbox);
|
||||
let cwd = std::env::current_dir()?;
|
||||
let config = Config::load_with_overrides(ConfigOverrides {
|
||||
sandbox_policy: Some(sandbox_policy),
|
||||
codex_linux_sandbox_exe,
|
||||
..Default::default()
|
||||
})?;
|
||||
let stdio_policy = StdioPolicy::Inherit;
|
||||
let env = create_env(&config.shell_environment_policy);
|
||||
|
||||
let mut child = match sandbox_type {
|
||||
SandboxType::Seatbelt => {
|
||||
spawn_command_under_seatbelt(command, &config.sandbox_policy, cwd, stdio_policy, env)
|
||||
.await?
|
||||
}
|
||||
SandboxType::Landlock => {
|
||||
#[expect(clippy::expect_used)]
|
||||
let codex_linux_sandbox_exe = config
|
||||
.codex_linux_sandbox_exe
|
||||
.expect("codex-linux-sandbox executable not found");
|
||||
spawn_command_under_linux_sandbox(
|
||||
codex_linux_sandbox_exe,
|
||||
command,
|
||||
&config.sandbox_policy,
|
||||
cwd,
|
||||
stdio_policy,
|
||||
env,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
};
|
||||
let status = child.wait().await?;
|
||||
|
||||
handle_exit_status(status);
|
||||
}
|
||||
|
||||
pub fn create_sandbox_policy(full_auto: bool, sandbox: SandboxPermissionOption) -> SandboxPolicy {
|
||||
if full_auto {
|
||||
SandboxPolicy::new_full_auto_policy()
|
||||
} else {
|
||||
match sandbox.permissions.map(Into::into) {
|
||||
Some(sandbox_policy) => sandbox_policy,
|
||||
None => SandboxPolicy::new_read_only_policy(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
//! `debug landlock` implementation for the Codex CLI.
|
||||
//!
|
||||
//! On Linux the command is executed inside a Landlock + seccomp sandbox by
|
||||
//! calling the low-level `exec_linux` helper from `codex_core::linux`.
|
||||
|
||||
use codex_core::config::Config;
|
||||
use codex_core::exec::StdioPolicy;
|
||||
use codex_core::exec::spawn_child_sync;
|
||||
use codex_core::exec_linux::apply_sandbox_policy_to_current_thread;
|
||||
use std::process::ExitStatus;
|
||||
|
||||
use crate::exit_status::handle_exit_status;
|
||||
|
||||
/// Execute `command` in a Linux sandbox (Landlock + seccomp) the way Codex
|
||||
/// would.
|
||||
pub fn run_landlock(command: Vec<String>, config: &Config) -> anyhow::Result<()> {
|
||||
if command.is_empty() {
|
||||
anyhow::bail!("command args are empty");
|
||||
}
|
||||
|
||||
// Spawn a new thread and apply the sandbox policies there.
|
||||
let env = codex_core::exec_env::create_env(&config.shell_environment_policy);
|
||||
let sandbox_policy = config.sandbox_policy.clone();
|
||||
let handle = std::thread::spawn(move || -> anyhow::Result<ExitStatus> {
|
||||
let cwd = std::env::current_dir()?;
|
||||
|
||||
apply_sandbox_policy_to_current_thread(&sandbox_policy, &cwd)?;
|
||||
let mut child = spawn_child_sync(command, cwd, &sandbox_policy, StdioPolicy::Inherit, env)?;
|
||||
let status = child.wait()?;
|
||||
Ok(status)
|
||||
});
|
||||
let status = handle
|
||||
.join()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to join thread: {e:?}"))??;
|
||||
|
||||
handle_exit_status(status);
|
||||
}
|
||||
+1
-15
@@ -1,12 +1,9 @@
|
||||
pub mod debug_sandbox;
|
||||
mod exit_status;
|
||||
#[cfg(unix)]
|
||||
pub mod landlock;
|
||||
pub mod proto;
|
||||
pub mod seatbelt;
|
||||
|
||||
use clap::Parser;
|
||||
use codex_common::SandboxPermissionOption;
|
||||
use codex_core::protocol::SandboxPolicy;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct SeatbeltCommand {
|
||||
@@ -35,14 +32,3 @@ pub struct LandlockCommand {
|
||||
#[arg(trailing_var_arg = true)]
|
||||
pub command: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn create_sandbox_policy(full_auto: bool, sandbox: SandboxPermissionOption) -> SandboxPolicy {
|
||||
if full_auto {
|
||||
SandboxPolicy::new_full_auto_policy()
|
||||
} else {
|
||||
match sandbox.permissions.map(Into::into) {
|
||||
Some(sandbox_policy) => sandbox_policy,
|
||||
None => SandboxPolicy::new_read_only_policy(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
fn main() -> anyhow::Result<()> {
|
||||
eprintln!("codex-linux-sandbox is not supported on this platform.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn main() -> anyhow::Result<()> {
|
||||
use clap::Parser;
|
||||
use codex_cli::LandlockCommand;
|
||||
use codex_cli::create_sandbox_policy;
|
||||
use codex_cli::landlock;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config::ConfigOverrides;
|
||||
|
||||
let LandlockCommand {
|
||||
full_auto,
|
||||
sandbox,
|
||||
command,
|
||||
} = LandlockCommand::parse();
|
||||
let sandbox_policy = create_sandbox_policy(full_auto, sandbox);
|
||||
let config = Config::load_with_overrides(ConfigOverrides {
|
||||
sandbox_policy: Some(sandbox_policy),
|
||||
..Default::default()
|
||||
})?;
|
||||
landlock::run_landlock(command, &config)?;
|
||||
Ok(())
|
||||
}
|
||||
+12
-40
@@ -1,15 +1,10 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::Parser;
|
||||
use codex_cli::LandlockCommand;
|
||||
use codex_cli::SeatbeltCommand;
|
||||
use codex_cli::create_sandbox_policy;
|
||||
use codex_cli::proto;
|
||||
use codex_cli::seatbelt;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config::ConfigOverrides;
|
||||
use codex_exec::Cli as ExecCli;
|
||||
use codex_tui::Cli as TuiCli;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::proto::ProtoCli;
|
||||
|
||||
@@ -66,14 +61,14 @@ enum DebugCommand {
|
||||
#[derive(Debug, Parser)]
|
||||
struct ReplProto {}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let codex_linux_sandbox_exe: Option<PathBuf> = if cfg!(target_os = "linux") {
|
||||
std::env::current_exe().ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
fn main() -> anyhow::Result<()> {
|
||||
codex_linux_sandbox::run_with_sandbox(|codex_linux_sandbox_exe| async move {
|
||||
cli_main(codex_linux_sandbox_exe).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
async fn cli_main(codex_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()> {
|
||||
let cli = MultitoolCli::parse();
|
||||
|
||||
match cli.subcommand {
|
||||
@@ -90,34 +85,11 @@ async fn main() -> anyhow::Result<()> {
|
||||
proto::run_main(proto_cli).await?;
|
||||
}
|
||||
Some(Subcommand::Debug(debug_args)) => match debug_args.cmd {
|
||||
DebugCommand::Seatbelt(SeatbeltCommand {
|
||||
command,
|
||||
sandbox,
|
||||
full_auto,
|
||||
}) => {
|
||||
let sandbox_policy = create_sandbox_policy(full_auto, sandbox);
|
||||
let config = Config::load_with_overrides(ConfigOverrides {
|
||||
sandbox_policy: Some(sandbox_policy),
|
||||
..Default::default()
|
||||
})?;
|
||||
seatbelt::run_seatbelt(command, &config).await?;
|
||||
DebugCommand::Seatbelt(seatbelt_command) => {
|
||||
codex_cli::debug_sandbox::run_command_under_seatbelt(seatbelt_command).await?;
|
||||
}
|
||||
#[cfg(unix)]
|
||||
DebugCommand::Landlock(LandlockCommand {
|
||||
command,
|
||||
sandbox,
|
||||
full_auto,
|
||||
}) => {
|
||||
let sandbox_policy = create_sandbox_policy(full_auto, sandbox);
|
||||
let config = Config::load_with_overrides(ConfigOverrides {
|
||||
sandbox_policy: Some(sandbox_policy),
|
||||
..Default::default()
|
||||
})?;
|
||||
codex_cli::landlock::run_landlock(command, &config)?;
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
DebugCommand::Landlock(_) => {
|
||||
anyhow::bail!("Landlock is only supported on Linux.");
|
||||
DebugCommand::Landlock(landlock_command) => {
|
||||
codex_cli::debug_sandbox::run_command_under_landlock(landlock_command).await?;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
use codex_core::config::Config;
|
||||
use codex_core::exec::StdioPolicy;
|
||||
use codex_core::exec::spawn_command_under_seatbelt;
|
||||
use codex_core::exec_env::create_env;
|
||||
|
||||
use crate::exit_status::handle_exit_status;
|
||||
|
||||
pub async fn run_seatbelt(command: Vec<String>, config: &Config) -> anyhow::Result<()> {
|
||||
let cwd = std::env::current_dir()?;
|
||||
let env = create_env(&config.shell_environment_policy);
|
||||
let mut child = spawn_command_under_seatbelt(
|
||||
command,
|
||||
&config.sandbox_policy,
|
||||
cwd,
|
||||
StdioPolicy::Inherit,
|
||||
env,
|
||||
)
|
||||
.await?;
|
||||
let status = child.wait().await?;
|
||||
handle_exit_status(status);
|
||||
}
|
||||
Reference in New Issue
Block a user