//! Ordinary operator entry: authenticated configuration, finite helper scope, //! and the same per-task kernel gate exercised by independent-VM acceptance. use crate::{ artifacts::ArtifactRoot, launcher::{blocked::InitializationFailure, run}, outcome::{ErrorCode, ExecutionError, ExecutionStage}, runtime::{RuntimeConfig, ValidatedRuntimeConfig}, spec::{ExecutionSpec, ValidatedSpec}, }; use rustix::{ fd::{AsRawFd, OwnedFd}, fs::{Mode, OFlags}, }; use serde::Serialize; use std::{ fs::File, io::{self, Read, Write}, path::Path, }; #[derive(Debug, Serialize)] pub struct RunFailure { pub error: &'static str, pub failure: Option, pub cleanup_failure: Option, } fn refused(code: &'static str, e: &io::Error) -> RunFailure { RunFailure { error: code, failure: Some(crate::launcher::blocked::failure( if code == "output_publication_failed" { ExecutionStage::ArtifactExport } else { ExecutionStage::Preparation }, if code == "output_publication_failed" { ErrorCode::ArtifactExportFailed } else { ErrorCode::CapabilityUnavailable }, Some(e), )), cleanup_failure: None, } } fn invalid(code: &'static str) -> io::Error { io::Error::new(io::ErrorKind::PermissionDenied, code) } impl From for RunFailure { fn from(e: InitializationFailure) -> Self { Self { error: "sandbox_initialization_failed", failure: Some(e.failure), cleanup_failure: e.cleanup_failure, } } } fn read_pinned(fd: OwnedFd, limit: u64) -> io::Result> { let st = rustix::fs::fstat(&fd)?; if rustix::fs::FileType::from_raw_mode(st.st_mode) != rustix::fs::FileType::RegularFile || st.st_nlink != 1 || st.st_size < 0 || st.st_size as u64 > limit { return Err(invalid("invalid bounded regular file")); } let read = rustix::fs::open( format!("/proc/self/fd/{}", fd.as_raw_fd()), OFlags::RDONLY | OFlags::CLOEXEC | OFlags::NONBLOCK | OFlags::NOCTTY, Mode::empty(), )?; let mut bytes = Vec::new(); File::from(read).take(limit + 1).read_to_end(&mut bytes)?; if bytes.len() as u64 > limit { return Err(invalid("file exceeds bound")); } Ok(bytes) } pub fn read_bounded_file(path: &Path, limit: u64) -> io::Result> { if limit == 0 || limit > 262_144 { return Err(invalid("bounded JSON ceiling")); } let fd = rustix::fs::open( path, OFlags::PATH | OFlags::NOFOLLOW | OFlags::CLOEXEC, Mode::empty(), )?; read_pinned(fd, limit) } pub fn read_spec(path: &Path) -> io::Result { serde_json::from_slice(&read_bounded_file(path, crate::spec::MAX_SPEC_BYTES)?) .map_err(|_| invalid("invalid execution JSON")) } pub fn load_config(path: &Path) -> io::Result { let parent = crate::isolation::root::open_directory( path.parent().ok_or_else(|| invalid("config parent"))?, )?; let name = path.file_name().ok_or_else(|| invalid("config name"))?; let fd = rustix::fs::openat( &parent, name, OFlags::PATH | OFlags::NOFOLLOW | OFlags::CLOEXEC, Mode::empty(), )?; let st = rustix::fs::fstat(&fd)?; if st.st_uid != 0 || st.st_gid != 0 || st.st_mode & 0o7022 != 0 { return Err(invalid("untrusted configuration owner/mode")); } let config: RuntimeConfig = serde_json::from_slice(&read_pinned(fd, crate::runtime::MAX_CONFIG_BYTES)?) .map_err(|_| invalid("invalid runtime JSON"))?; config .validate() .map_err(|_| invalid("invalid runtime policy")) } /// These checks do not certify namespace visibility. Each task must still /// pass every actual namespace/root/identity/seccomp/exec acknowledgement. pub(crate) fn authorize(config: &ValidatedRuntimeConfig) -> io::Result<()> { crate::launcher::entry::ensure_single_threaded()?; if !rustix::process::getuid().is_root() || !rustix::process::geteuid().is_root() || !rustix::process::getgid().is_root() || !rustix::process::getegid().is_root() { return Err(invalid( "dedicated root helper required; setuid invocation refused", )); } let o = crate::probe::observe(); if o.os != "linux" || o.architecture != "x86_64" || o.kernel_release .as_deref() .and_then(crate::runtime::kernel_supported) != Some(true) || o.current_process_seccomp_mode != Some(0) { return Err(invalid("unsupported helper platform/kernel/filter")); } let c = config.config(); for path in [&c.state_root, &c.tasks_root, &c.image_root] { crate::isolation::root::open_directory(Path::new(path))?; } // The operator owns the image. An untrusted request cannot change roots, // image revision, delegation, identity pool or helper reservations. let image = crate::isolation::root::open_directory(Path::new(&c.image_root))?; let manifest = rustix::fs::openat( &image, "manifest.json", OFlags::PATH | OFlags::NOFOLLOW | OFlags::CLOEXEC, Mode::empty(), )?; let st = rustix::fs::fstat(&manifest)?; if st.st_uid != 0 || st.st_gid != 0 || st.st_mode & 0o7022 != 0 { return Err(invalid("image manifest ownership")); } let bytes = read_pinned(manifest, crate::image::MAX_CATALOG_BYTES)?; if let Some(identity) = &c.runtime_identity { crate::image::verify(&image, identity, &bytes)?; } else { let m: serde_json::Value = serde_json::from_slice(&bytes).map_err(|_| invalid("image manifest JSON"))?; if m != serde_json::json!({"image_kind":"linux_gnu_cxx_x86_64_v1","architecture":"x86_64","revision":1}) { return Err(invalid("unsupported image manifest")); } } let own = std::fs::read_to_string("/proc/self/cgroup")?; let path = own .lines() .find_map(|s| s.strip_prefix("0::")) .filter(|s| s.starts_with('/') && !s.split('/').any(|p| p == ".." || p == ".")) .ok_or_else(|| invalid("unified helper cgroup"))?; let full = format!("/sys/fs/cgroup{path}"); if full == c.delegated_cgroup_root || full.starts_with(&(c.delegated_cgroup_root.clone() + "/")) { return Err(invalid("helper must be outside task delegation")); } let scope = crate::isolation::root::open_directory(Path::new(&full))?; if rustix::fs::fstatfs(&scope)?.f_type as u64 != 0x6367_7270 { return Err(invalid("helper cgroup v2")); } let read = |name| crate::cgroup::read(&scope, name); let number = |name| { read(name)? .trim() .parse::() .map_err(|_| invalid("finite helper cgroup required")) }; let b = &c.aggregate; if number("memory.max")? > b.supervisor_memory_bytes || number("memory.swap.max")? != 0 || number("pids.max")? > u64::from(b.supervisor_processes) { return Err(invalid("helper resource reservation missing")); } let cpu = read("cpu.max")?; let parts: Vec<_> = cpu.split_whitespace().collect(); if parts.len() != 2 { return Err(invalid("helper CPU budget")); } let q = parts[0] .parse::() .map_err(|_| invalid("finite helper CPU quota"))?; let p = parts[1] .parse::() .map_err(|_| invalid("helper CPU period"))?; if q == 0 || p == 0 || u128::from(q) * u128::from(b.cpu_period_us) > u128::from(b.supervisor_cpu_quota_us) * u128::from(p) { return Err(invalid("helper CPU reservation exceeded")); } Ok(()) } /// Factory admission proves every advertised image under the same finite /// supervisor scope. Per-request authorization still revalidates its image. pub(crate) fn authorize_registry(config: &ValidatedRuntimeConfig) -> io::Result<()> { authorize(config)?; for registered in config.registered_configs() { authorize(®istered)?; } Ok(()) } pub(crate) fn execute( config: &ValidatedRuntimeConfig, request: ExecutionSpec, artifacts: &ArtifactRoot, mode: run::MonitorMode<'_>, ) -> Result<(run::Completed, ValidatedSpec), RunFailure> { let selected = config .for_request(&request) .map_err(|_| refused("invalid_execution_spec", &invalid("request policy")))?; let config = &selected; authorize(config).map_err(|e| refused("runtime_not_ready", &e))?; let spec = request .validate(&config.spec_policy()) .map_err(|_| refused("invalid_execution_spec", &invalid("request policy")))?; let result = run::execute( config, &spec, &std::env::current_exe().map_err(|e| refused("helper_binary_unavailable", &e))?, artifacts, mode, )?; result .report .classify(&spec) .map_err(|_| refused("invalid_execution_report", &invalid("report")))?; Ok((result, spec)) } /// Operator-only file publication. Request IDs never choose host paths. pub fn run_files( config_path: &Path, spec_path: &Path, artifacts_path: &Path, output_path: &Path, ) -> Result { let config = load_config(config_path).map_err(|e| refused("untrusted_runtime_config", &e))?; authorize(&config).map_err(|e| refused("runtime_not_ready", &e))?; let request = read_spec(spec_path).map_err(|e| refused("invalid_execution_spec", &e))?; let fd = crate::isolation::root::open_directory(artifacts_path) .map_err(|e| refused("invalid_artifact_publication", &e))?; let artifacts = ArtifactRoot::from_directory(fd) .map_err(|e| refused("invalid_artifact_publication", &e))?; let output = crate::isolation::root::open_directory(output_path) .map_err(|e| refused("invalid_output_publication", &e))?; let (completed, spec) = execute(&config, request, &artifacts, run::MonitorMode::Normal)?; let outcome = completed .report .classify(&spec) .map_err(|_| refused("invalid_execution_report", &invalid("outcome")))?; // Snapshot files are immutable; publish only after complete cleanup. if completed.report.cleanup != crate::outcome::CleanupState::Complete { return Err(refused( "cleanup_incomplete", &invalid("publication withheld"), )); } let total = completed .outputs .iter() .try_fold( completed.stdout.bytes() + completed.stderr.bytes(), |n, f| n.checked_add(f.file.bytes()), ) .ok_or_else(|| refused("output_publication_failed", &invalid("publication budget")))?; if total > config.config().aggregate.supervisor_workdir_bytes { return Err(refused( "output_publication_failed", &invalid("publication budget"), )); } let name = spec.spec().run_id.as_str(); let temporary = format!(".{name}.pending"); rustix::fs::mkdirat(&output, temporary.as_str(), Mode::from_raw_mode(0o700)) .map_err(|e| refused("output_publication_failed", &e.into()))?; let dest = rustix::fs::openat( &output, temporary.as_str(), OFlags::RDONLY | OFlags::DIRECTORY | OFlags::NOFOLLOW | OFlags::CLOEXEC, Mode::empty(), ) .map_err(|e| refused("output_publication_failed", &e.into()))?; let mut installed = false; let publication = (|| { for (n, file) in [("stdout", &completed.stdout), ("stderr", &completed.stderr)] { publish(&dest, n, file)?; } for (i, file) in completed.outputs.iter().enumerate() { publish(&dest, &format!("artifact-{i}"), &file.file)?; } rustix::fs::fsync(&dest)?; rustix::fs::renameat_with( &output, temporary.as_str(), &output, name, rustix::fs::RenameFlags::NOREPLACE, )?; installed = true; rustix::fs::fsync(&output)?; Ok::<_, io::Error>(()) })(); if let Err(e) = publication { if installed { return Err(refused("output_publication_failed", &e)); } // Only fixed names we just created, beneath our exclusive held inode. // Never recurse or delete a pre-existing destination publication. for n in ["stdout".to_owned(), "stderr".to_owned()] .into_iter() .chain((0..completed.outputs.len()).map(|i| format!("artifact-{i}"))) { let _ = rustix::fs::unlinkat(&dest, n.as_str(), rustix::fs::AtFlags::empty()); } let _ = rustix::fs::unlinkat(&output, temporary.as_str(), rustix::fs::AtFlags::REMOVEDIR); return Err(refused("output_publication_failed", &e)); } Ok( serde_json::json!({"report":completed.report,"outcome":outcome,"publication":name,"proof":completed.proof}), ) } fn publish(parent: &OwnedFd, name: &str, file: &crate::artifacts::SnapshotFile) -> io::Result<()> { let fd = rustix::fs::openat( parent, name, OFlags::WRONLY | OFlags::CREATE | OFlags::EXCL | OFlags::NOFOLLOW | OFlags::CLOEXEC, Mode::from_raw_mode(0o600), )?; let mut out = File::from(fd); let copied = io::copy(&mut file.reader()?, &mut out)?; if copied != file.bytes() { return Err(invalid("snapshot publication bytes")); } out.flush()?; out.sync_all() }