//! Real bootstrap installation and execution; no global service is changed. use super::*; use std::os::unix::fs::{MetadataExt, chown}; use std::os::unix::process::CommandExt; use std::process::Child; struct Daemon(Child); impl Drop for Daemon { fn drop(&mut self) { let _ = self.0.kill(); let _ = self.0.wait(); } } fn wait_socket(daemon: &mut Daemon, socket: &Path) { for _ in 0..200 { assert!( daemon.0.try_wait().unwrap().is_none(), "installed daemon exited before becoming ready" ); if socket.exists() { return; } std::thread::sleep(Duration::from_millis(20)); } panic!("installed daemon did not create its socket"); } /// Clients share the daemon's PID namespace so SO_PEERCRED can identify both /// peers. Separate bwrap invocations would hide the daemon PID from clients. struct IsolatedDaemon { process: Daemon, namespace_pid: u32, namespace: std::os::fd::OwnedFd, root: PathBuf, stopped: bool, } impl IsolatedDaemon { fn start(root: &Path) -> Self { let information = TemporaryDirectory::new(); let path = information.path().join("namespace.json"); let output = fs::File::create(&path).unwrap(); let mut process = Daemon( isolated_os_namespace(root) .args([ "--info-fd", "1", "--", "/fixture-proc-init", "/opt/dever/bin/deverd", "--root", "/opt/dever", ]) .stdout(Stdio::from(output)) .stderr(Stdio::inherit()) .spawn() .unwrap(), ); wait_socket(&mut process, &root.join("run/dever/deverd.sock")); assert!(fs::metadata(&path).unwrap().len() <= 4096); let info: Value = serde_json::from_slice(&fs::read(path).unwrap()).unwrap(); let namespace_pid = u32::try_from(info["child-pid"].as_u64().unwrap()).unwrap(); assert_ne!(namespace_pid, 0); let namespace = rustix::process::pidfd_open( rustix::process::Pid::from_raw(i32::try_from(namespace_pid).unwrap()).unwrap(), rustix::process::PidfdFlags::empty(), ) .unwrap(); Self { process, namespace_pid, namespace, root: root.to_owned(), stopped: false, } } fn client(&mut self) -> Command { assert!(self.process.0.try_wait().unwrap().is_none()); let target = fs::metadata(format!("/proc/{}/root", self.namespace_pid)).unwrap(); let expected = fs::metadata(&self.root).unwrap(); assert_eq!( (target.dev(), target.ino()), (expected.dev(), expected.ino()) ); let mut command = Command::new("/usr/bin/nsenter"); command.env_clear().args([ "--target", &self.namespace_pid.to_string(), "--all", "--root", "--wdns=/", "--", ]); command } fn stop(&mut self) -> Result<(), String> { if self.stopped { return Ok(()); } // Target the pinned namespace init, not a reusable numeric PID. Its // termination also kills its namespace descendants; waiting only for // the outer bwrap process does not establish daemon lock release. match rustix::process::pidfd_send_signal(&self.namespace, rustix::process::Signal::KILL) { Ok(()) | Err(rustix::io::Errno::SRCH) => {} Err(error) => return Err(format!("cannot stop owned namespace: {error}")), } let expected = fs::metadata(&self.root).map_err(|error| error.to_string())?; let lock = fs::File::open(self.root.join("opt/dever/state/deverd.lock")) .map_err(|error| error.to_string())?; let deadline = std::time::Instant::now() + Duration::from_secs(10); loop { let wrapper_exited = self .process .0 .try_wait() .map_err(|error| error.to_string())? .is_some(); let namespace_exited = match fs::metadata(format!("/proc/{}/root", self.namespace_pid)) { Ok(current) => (current.dev(), current.ino()) != (expected.dev(), expected.ino()), Err(error) if error.kind() == std::io::ErrorKind::NotFound => true, Err(error) => return Err(format!("cannot inspect owned namespace exit: {error}")), }; if wrapper_exited && namespace_exited { match fs2::FileExt::try_lock_exclusive(&lock) { Ok(()) => { self.stopped = true; return Ok(()); } Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => {} Err(error) => { return Err(format!("cannot check owned daemon lock release: {error}")); } } } if std::time::Instant::now() >= deadline { return Err("owned namespace or daemon lock remained live after 10 seconds".into()); } std::thread::sleep(Duration::from_millis(20)); } } } impl Drop for IsolatedDaemon { fn drop(&mut self) { // Cleanup must not cause a second panic while unwinding a failed // assertion. Normal stage transitions explicitly check stop(). if let Err(error) = self.stop() { eprintln!("bootstrap fixture cleanup failed: {error}"); } } } fn add_inputs(author: &Path) { let launcher = input( author, Path::new(env!("CARGO_BIN_EXE_dever-launcher")), "bootstrap/dever", ); let daemon = input( author, Path::new(env!("CARGO_BIN_EXE_deverd")), "bootstrap/deverd", ); let mut gcc = input( author, Path::new("/lib/x86_64-linux-gnu/libgcc_s.so.1"), "bootstrap/libgcc_s.so.1", ); gcc["path"] = json!("libgcc_s.so.1"); let path = author.join("config/setting.json"); let mut settings: Value = serde_json::from_slice(&fs::read(&path).unwrap()).unwrap(); settings["bootstrap"] = json!({"launcher":launcher,"daemon":daemon,"libraries":[gcc]}); fs::write(path, serde_json::to_vec(&settings).unwrap()).unwrap(); } #[test] #[ignore = "requires optimized author archives and Linux root; installs only into an owned image root"] fn signed_bootstrap_installs_and_serves_two_projects_without_host_libraries() { assert_eq!(rustix::process::geteuid().as_raw(), 0); let workspace = Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."); let author = TemporaryDirectory::new(); let key = author_inputs(author.path(), &workspace); add_inputs(author.path()); let downloaded = machine_temporary_directory(&workspace); let release = downloaded.path().join("release"); let manifest = packaging::create(author.path(), &release).unwrap(); let image = machine_temporary_directory(&workspace); ecosystems::minimal_os(image.path()); let installer = TemporaryDirectory::new(); let public = installer.path().join("release.pub"); fs::write( &public, key.public_key() .as_ref() .iter() .map(|byte| format!("{byte:02x}")) .collect::(), ) .unwrap(); write(installer.path(), "config/setting.json", serde_json::to_vec(&json!({ "release":release,"system_root":image.path(),"trusted_key":public,"activate_service":false })).unwrap()); // This author-built installer is the independently trusted first program; // no executable from an unchecked downloaded directory starts as root. command( Command::new(env!("CARGO_BIN_EXE_dever-launcher")) .arg("--dever-install") .arg(installer.path()), ); let layout = Layout::new(image.path().join("opt/dever")); assert!(!image.path().join("etc/apparmor.d/dever-process").exists()); assert!( !manifest .artifacts .iter() .any(|artifact| artifact.path.ends_with("/bin/bwrap") || artifact.path.ends_with("/bin/guard")) ); let launcher = layout.bin().join("dever"); let executable = layout.bin().join("deverd"); assert_eq!( fs::read_link(image.path().join("usr/local/bin/dever")).unwrap(), Path::new("/opt/dever/bin/dever") ); assert_eq!( fs::read_link( image .path() .join("etc/systemd/system/multi-user.target.wants/deverd.service") ) .unwrap(), Path::new("../deverd.service") ); let unit = fs::read_to_string(image.path().join("etc/systemd/system/deverd.service")).unwrap(); assert!(unit.contains("ExecStart=/opt/dever/bin/deverd --root /opt/dever")); assert!(unit.contains("User=root")); for file in [ &launcher, &executable, &layout.bin().join("lib/libgcc_s.so.1"), &layout.state().join("trusted-release-key"), &image.path().join("etc/systemd/system/deverd.service"), ] { let metadata = fs::metadata(file).unwrap(); assert_eq!(metadata.uid(), 0); assert_eq!(metadata.mode() & 0o022, 0); } let active = fs::read(layout.state().join("active-version")).unwrap(); let launcher_hash = sha256_file(&launcher).unwrap(); // Model an older, correctly signed bootstrap service template. Its exact // signed bytes establish ownership; only the incoming payload must match // the current installer's supported service template. let unit_path = image.path().join("etc/systemd/system/deverd.service"); let current_unit = fs::read_to_string(&unit_path).unwrap(); let old_unit = current_unit.replace("RestartSec=1", "RestartSec=2"); assert_ne!(old_unit, current_unit); fs::write(layout.bin().join("deverd.service"), &old_unit).unwrap(); let mut old_manifest: Value = serde_json::from_slice(&fs::read(layout.bin().join("manifest.json")).unwrap()).unwrap(); let service_artifact = old_manifest["artifacts"] .as_array_mut() .unwrap() .iter_mut() .find(|artifact| artifact["path"] == "bootstrap/deverd.service") .unwrap(); service_artifact["bytes"] = json!(old_unit.len()); service_artifact["sha256"] = json!(sha256_file(&layout.bin().join("deverd.service")).unwrap()); let encoded = serde_json::to_vec(&old_manifest).unwrap(); fs::write( layout.bin().join("manifest.sig"), key.sign(&encoded) .as_ref() .iter() .map(|byte| format!("{byte:02x}")) .collect::(), ) .unwrap(); fs::write(layout.bin().join("manifest.json"), encoded).unwrap(); fs::write(&unit_path, b"unrelated system unit").unwrap(); let rejected = Command::new(env!("CARGO_BIN_EXE_dever-launcher")) .env_clear() .arg("--dever-install") .arg(installer.path()) .output() .unwrap(); assert!(!rejected.status.success()); assert!(String::from_utf8_lossy(&rejected.stderr).contains("unrelated system service")); assert_eq!( fs::read(layout.state().join("active-version")).unwrap(), active ); fs::write(&unit_path, old_unit).unwrap(); // Retry is supported, but a later damaged package cannot damage the // published launcher or compiler selection. command( Command::new(env!("CARGO_BIN_EXE_dever-launcher")) .arg("--dever-install") .arg(installer.path()), ); assert_eq!(fs::read_to_string(&unit_path).unwrap(), current_unit); let before_bad_install = fs::read(layout.state().join("active-version")).unwrap(); fs::write( release.join("bootstrap/lib/libgcc_s.so.1"), b"changed after signing", ) .unwrap(); let failed = Command::new(env!("CARGO_BIN_EXE_dever-launcher")) .env_clear() .arg("--dever-install") .arg(installer.path()) .output() .unwrap(); assert!(!failed.status.success()); assert_eq!( fs::read(layout.state().join("active-version")).unwrap(), before_bad_install ); assert_eq!(sha256_file(&launcher).unwrap(), launcher_hash); assert!(!active.is_empty()); drop(downloaded); drop(author); drop(installer); for name in ["first", "second"] { application(&image.path().join(name), "base"); } let mut daemon = IsolatedDaemon::start(image.path()); for name in ["first", "second"] { command( daemon .client() .args(["/usr/local/bin/dever", "check", &format!("/{name}")]), ); assert_response(command(daemon.client().args([ "/usr/local/bin/dever", "run", &format!("/{name}"), "--", "sample.value.exercise", "{}", ]))); command(daemon.client().args([ "/usr/local/bin/dever", "build", &format!("/{name}"), "--output", &format!("/{name}/program"), ])); } let status = command( daemon .client() .args(["/usr/local/bin/dever", "cache", "status"]), ); assert_eq!( serde_json::from_slice::(&status.stdout).unwrap()["entries"], 1 ); daemon .stop() .expect("isolated daemon and namespace fully stopped"); drop(daemon); // Namespace-root execution above proves the complete loader closure. These // clients retain real distinct kernel UIDs, without a single-UID userns // translating file ownership to overflow IDs. fs::set_permissions(image.path(), fs::Permissions::from_mode(0o755)).unwrap(); let mut daemon = Daemon( Command::new(&executable) .env_clear() .args(["--root", layout.root().to_str().unwrap()]) .stdout(Stdio::null()) .stderr(Stdio::inherit()) .spawn() .unwrap(), ); wait_socket(&mut daemon, &layout.service_socket()); for (name, uid) in [("first", 65533), ("second", 65534)] { let project = image.path().join(name); chown(&project, Some(uid), Some(uid)).unwrap(); assert_response(command( Command::new(&launcher) .uid(uid) .gid(uid) .arg("run") .arg(&project) .args(["--", "sample.value.exercise", "{}"]), )); let denied = Command::new(&launcher) .uid(uid) .gid(uid) .env_clear() .args(["use", manifest.version.as_str()]) .output() .unwrap(); assert!(!denied.status.success()); assert_eq!( fs::read(layout.state().join("active-version")).unwrap(), before_bad_install ); } assert_eq!(cache_status(&layout).unwrap().entries, 1); drop(daemon); for name in ["first", "second"] { fs::remove_dir_all(image.path().join(name).join("module")).unwrap(); } fs::remove_dir_all(layout.root()).unwrap(); for name in ["first", "second"] { assert_response(command(isolated_os_command(image.path()).args([ &format!("/{name}/program"), "sample.value.exercise", "{}", ]))); } write(&workspace, "target/closure-linux-bootstrap-acceptance.json", serde_json::to_vec_pretty(&json!({ "format":"dever-linux-bootstrap-acceptance-v1","platform":"linux-x86_64", "bootstrap":manifest.artifacts.iter().filter(|artifact| artifact.path.starts_with("bootstrap/")).collect::>(), "checks":{"signed_first_install":true,"stable_public_entry":true,"root_owned_systemd_assets":true,"two_projects_in_minimal_os_root":true,"real_distinct_uids":true,"shared_compile_entries":1,"failed_install_preserves_old_entry":true,"source_and_machine_removed_execution":true}, "global_service_actions":"not_run","public_release":"not_published" })).unwrap()); }