diff --git a/crates/nibrunnerd/tests/guest/isolation.rs b/crates/nibrunnerd/tests/guest/isolation.rs index 92c88b9b..0af9d69e 100644 --- a/crates/nibrunnerd/tests/guest/isolation.rs +++ b/crates/nibrunnerd/tests/guest/isolation.rs @@ -4,9 +4,11 @@ use std::net::{Ipv4Addr, SocketAddr, TcpListener}; +use nibrunnerd::adapters::vm::process::VmProcesses; use nibrunnerd::test_support::egress::EgressEndpoint; use nibrunnerd::test_support::machine::{RunningHost, Tenant}; use protocol::InstanceState; +use std::os::unix::fs::MetadataExt; /// The tenant gives up after two seconds, so anything under this came back because a rule said /// no rather than because nothing was there. @@ -149,3 +151,97 @@ async fn an_address_the_configuration_denies_cannot_be_reached() { closed.stop().await; } + +const JAILER_IDLE_TIMEOUT_MS: u64 = 60_000; + +#[tokio::test(flavor = "multi_thread")] +async fn jailed_vmm_processes_have_distinct_non_root_identities_and_resume_their_memory_and_volume() { + let Some(host) = crate::host().await else { + return; + }; + let one = host.tenant(1).on_request(JAILER_IDLE_TIMEOUT_MS); + let another = host.tenant(2); + host.deploy(&[one.clone(), another.clone()]).await; + host.until_state(&one.app_id, InstanceState::Running).await; + host.until_state(&another.app_id, InstanceState::Running).await; + let processes = VmProcesses::new(host.host.config.runtime_dir.clone()); + let first = processes.read_record(&one.app_id).expect("the first VMM record"); + let second = processes + .read_record(&another.app_id) + .expect("the second VMM record"); + for record in [&first, &second] { + let root = std::fs::metadata(record.jail_root.as_ref().expect("the recorded jail root")) + .expect("the jail root's owner"); + assert_eq!(record.jail_uid, Some(root.uid())); + let status = std::fs::read_to_string(format!("/proc/{}/status", record.pid)) + .expect("the VMM's process status"); + for (label, identity) in [("Uid:", root.uid()), ("Gid:", root.gid())] { + assert_ne!(identity, 0); + let line = status + .lines() + .find(|line| line.starts_with(label)) + .expect("the process identity line"); + assert!( + line.split_whitespace() + .skip(1) + .all(|value| value.parse::() == Ok(identity)), + "{line}" + ); + } + } + let root = first.jail_root.as_ref().expect("the recorded jail root"); + let process_root = std::path::PathBuf::from(format!("/proc/{}/root", first.pid)); + let actual = std::fs::metadata(&process_root).expect("the VMM root"); + let recorded = std::fs::metadata(root).expect("the recorded root inode"); + assert_eq!((actual.dev(), actual.ino()), (recorded.dev(), recorded.ino())); + assert!( + std::fs::metadata(process_root.join("assets/kernel")) + .unwrap() + .len() + > 0 + ); + assert_ne!(first.jail_root, second.jail_root); + let other_root = second.jail_root.as_ref().expect("the second jail"); + assert_ne!( + std::fs::metadata(root).unwrap().uid(), + std::fs::metadata(other_root).unwrap().uid() + ); + assert_ne!( + std::fs::metadata(root).unwrap().gid(), + std::fs::metadata(other_root).unwrap().gid() + ); + assert!(!process_root.join("etc/nibrunner/config.toml").exists()); + assert_eq!( + host.get(&one, "/remember") + .await + .expect("memory before sleep") + .body, + "1" + ); + assert_eq!( + host.get(&one, "/write?path=kept&body=jailed") + .await + .expect("a volume write") + .status, + 200 + ); + host.let_sleep(&one).await; + assert_eq!( + host.get(&one, "/remember").await.expect("memory after wake").body, + "2" + ); + assert_eq!( + host.get(&one, "/read?path=kept") + .await + .expect("the volume after wake") + .body, + "jailed" + ); + host.host + .vms + .readopt(&one.app_id) + .await + .expect("reattach jailed host channels"); + assert_eq!(host.get(&one, "/").await.expect("the readopted app").status, 200); + host.stop().await; +}