diff --git a/src/main.rs b/src/main.rs index 8fbe02a..8e1fa5d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -78,6 +78,8 @@ fn main() -> ExitCode { "role-agent-exec" => { runtime::role_agent_exec(&args).map_err(|message| ("role-agent-exec", message)) } + "authority-supervisor-exec" => supervisor::authority_supervisor_exec(&args) + .map_err(|message| ("authority-supervisor-exec", message)), "snapshot" => snapshot::run(&args) .map(|_| ExitCode::SUCCESS) .map_err(|message| ("snapshot", message)), diff --git a/src/role_sandbox.rs b/src/role_sandbox.rs index c0f7665..86c3756 100644 --- a/src/role_sandbox.rs +++ b/src/role_sandbox.rs @@ -29,8 +29,10 @@ pub fn gate_setuid_invocation(command: &str) -> Result<(), String> { fn privileged_command_allowed(command: &str, real_uid: u32) -> bool { command == "role-agent-exec" && real_uid == crate::config::ORCHESTRATOR_UID - || matches!(command, "container-bootstrap" | "launch") - && real_uid == crate::config::CONTROL_UID + || matches!( + command, + "authority-supervisor-exec" | "container-bootstrap" | "launch" + ) && real_uid == crate::config::CONTROL_UID } #[cfg(not(unix))] @@ -243,7 +245,7 @@ fn drop_identity(uid: u32, gid: u32, supplementary_gids: &[u32]) -> Result<(), S groups.extend(supplementary_gids.iter().map(|value| *value as libc::gid_t)); groups.sort_unstable(); groups.dedup(); - if unsafe { libc::setgroups(1, groups.as_ptr()) } != 0 { + if unsafe { libc::setgroups(groups.len() as _, groups.as_ptr()) } != 0 { return Err(format!( "set role supplementary groups: {}", std::io::Error::last_os_error() @@ -264,6 +266,17 @@ fn drop_identity(uid: u32, gid: u32, supplementary_gids: &[u32]) -> Result<(), S Ok(()) } +pub(crate) fn exec_as_identity( + uid: u32, + gid: u32, + supplementary_gids: &[u32], + command: &str, + args: &[String], +) -> Result { + drop_identity(uid, gid, supplementary_gids)?; + exec(command, args) +} + #[cfg(not(unix))] fn drop_identity(_uid: u32, _gid: u32, _supplementary_gids: &[u32]) -> Result<(), String> { Err("role-exec uid isolation requires Unix".into()) @@ -469,6 +482,14 @@ mod tests { CONTROL_UID )); assert!(privileged_command_allowed("launch", CONTROL_UID)); + assert!(privileged_command_allowed( + "authority-supervisor-exec", + CONTROL_UID + )); + assert!(!privileged_command_allowed( + "authority-supervisor-exec", + ORCHESTRATOR_UID + )); assert!(!privileged_command_allowed("launch", ORCHESTRATOR_UID)); for command in ["role-exec", "subagent", "snapshot", "workflow"] { assert!(!privileged_command_allowed(command, ORCHESTRATOR_UID)); diff --git a/src/supervisor.rs b/src/supervisor.rs index 8df2bd0..81ad7b0 100644 --- a/src/supervisor.rs +++ b/src/supervisor.rs @@ -62,6 +62,36 @@ pub fn run(args: &[String]) -> Result { } } +#[cfg(target_os = "linux")] +pub fn authority_supervisor_exec(args: &[String]) -> Result { + if !args.is_empty() { + return Err("authority-supervisor-exec does not accept arguments".into()); + } + if env::var("MULTIAGENT_UID_SANDBOX").as_deref() != Ok("1") { + return Err("authority-supervisor-exec requires MULTIAGENT_UID_SANDBOX=1".into()); + } + let real_uid = unsafe { libc::getuid() }; + if real_uid != 0 && real_uid != config::CONTROL_UID { + return Err("authority-supervisor-exec is reserved for root or the control UID".into()); + } + + let executable = env::current_exe() + .map_err(|error| format!("resolve authority supervisor executable: {error}"))?; + let command = executable.display().to_string(); + crate::role_sandbox::exec_as_identity( + config::SUPERVISOR_UID, + config::ROLE_GID, + &[config::SUPERVISOR_CREDENTIAL_GID], + &command, + &["supervisor".into(), "serve".into()], + ) +} + +#[cfg(not(target_os = "linux"))] +pub fn authority_supervisor_exec(_args: &[String]) -> Result { + Err("authority-supervisor-exec requires Linux".into()) +} + fn bootstrap_test() -> Result { if env::var("MULTIAGENT_TEST_MODE").as_deref() != Ok("1") { return Err("supervisor bootstrap-test requires MULTIAGENT_TEST_MODE=1".into()); @@ -909,17 +939,7 @@ pub fn start(state: &Path, executable: &Path) -> Result { command.env("HOME", &home).env("CODEX_HOME", &home); } let child = command - .arg("role-exec") - .arg("--uid") - .arg(config::SUPERVISOR_UID.to_string()) - .arg("--gid") - .arg(config::ROLE_GID.to_string()) - .arg("--supplementary-gid") - .arg(config::SUPERVISOR_CREDENTIAL_GID.to_string()) - .arg("--") - .arg(executable) - .arg("supervisor") - .arg("serve") + .arg("authority-supervisor-exec") .stdin(Stdio::null()) .stdout(Stdio::from(log_stdout)) .stderr(Stdio::from(log))