diff --git a/crates/nibrunnerd/src/adapters/vm/manager.rs b/crates/nibrunnerd/src/adapters/vm/manager.rs index 5101e770..5661ddfc 100644 --- a/crates/nibrunnerd/src/adapters/vm/manager.rs +++ b/crates/nibrunnerd/src/adapters/vm/manager.rs @@ -48,7 +48,10 @@ pub struct VmManager { impl VmManager { pub fn working_dir_for(&self, app_id: &AppId) -> PathBuf { - self.vm_dir.join(app_id.as_str()) + self.processes + .read_record(app_id) + .and_then(|record| record.jail_root) + .unwrap_or_else(|| self.vm_dir.join(app_id.as_str())) } fn api(&self, app_id: &AppId) -> FirecrackerApi { @@ -386,6 +389,21 @@ impl Vmm for VmManager { self.logs.detach(app_id).await; return Err(VmError::Host(error.to_string())); } + if self + .processes + .read_record(app_id) + .is_some_and(|record| record.jail_root.is_some()) + { + use std::os::unix::fs::MetadataExt; + let owner = std::fs::metadata(&working_dir).map_err(|error| VmError::Host(error.to_string()))?; + for socket in [ + tenant_log_socket_path(&working_dir), + cron_registration_socket_path(&working_dir), + ] { + crate::unix_socket::own(&socket, owner.uid(), owner.gid()) + .map_err(|error| VmError::Host(error.to_string()))?; + } + } Ok(()) } @@ -776,6 +794,76 @@ mod tests { ); } + #[tokio::test] + async fn a_running_legacy_vmm_keeps_its_existing_channels_until_it_is_redeployed() { + let fixture = fixture(); + fixture.state.put_record(instance_record(|_| {})).await; + fixture + .manager + .processes + .write_record(&crate::adapters::vm::process::VmRecord { + app_id: app_id(), + pid: std::process::id() as i32, + host_boot_id: fixture.manager.processes.boot_id().into(), + started_at_ms: 0, + exit_code: None, + signal: None, + stop_requested: false, + jail_root: None, + jail_uid: None, + }) + .unwrap(); + let legacy_directory = fixture.manager.vm_dir.join(app_id().as_str()); + assert_eq!(fixture.manager.working_dir_for(&app_id()), legacy_directory); + fixture.manager.readopt(&app_id()).await.unwrap(); + assert!( + tokio::net::UnixStream::connect(tenant_log_socket_path(&legacy_directory)) + .await + .is_ok() + ); + assert!( + tokio::net::UnixStream::connect(cron_registration_socket_path(&legacy_directory)) + .await + .is_ok() + ); + } + + #[tokio::test] + async fn a_recorded_jail_is_readopted_using_its_existing_identity() { + use std::os::unix::fs::MetadataExt; + let fixture = fixture(); + let short_directory = tempfile::tempdir_in("/tmp").unwrap(); + let root = short_directory.path().join("root"); + make_directory(&root, VM_DIR_MODE).unwrap(); + let identity = std::fs::metadata(&root).unwrap(); + fixture.state.put_record(instance_record(|_| {})).await; + fixture + .manager + .processes + .write_record(&crate::adapters::vm::process::VmRecord { + app_id: app_id(), + pid: std::process::id() as i32, + host_boot_id: fixture.manager.processes.boot_id().into(), + started_at_ms: 0, + exit_code: None, + signal: None, + stop_requested: false, + jail_root: Some(root.clone()), + jail_uid: None, + }) + .unwrap(); + fixture.manager.readopt(&app_id()).await.unwrap(); + assert_eq!(fixture.manager.working_dir_for(&app_id()), root); + for socket in [ + tenant_log_socket_path(&root), + cron_registration_socket_path(&root), + ] { + let owner = std::fs::metadata(&socket).unwrap(); + assert_eq!((owner.uid(), owner.gid()), (identity.uid(), identity.gid())); + assert!(tokio::net::UnixStream::connect(socket).await.is_ok()); + } + } + #[tokio::test] async fn readopting_a_microvm_this_host_holds_no_record_of_is_a_no_op() { let fixture = fixture(); diff --git a/crates/nibrunnerd/src/adapters/vm/process.rs b/crates/nibrunnerd/src/adapters/vm/process.rs index 5a11cc15..b646f337 100644 --- a/crates/nibrunnerd/src/adapters/vm/process.rs +++ b/crates/nibrunnerd/src/adapters/vm/process.rs @@ -65,6 +65,10 @@ pub struct VmRecord { pub signal: Option, #[serde(default)] pub stop_requested: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub jail_root: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub jail_uid: Option, } impl VmRecord { @@ -237,6 +241,8 @@ impl VmProcesses { exit_code: None, signal: None, stop_requested: false, + jail_root: None, + jail_uid: None, }; self.write_record(&record)?; @@ -335,6 +341,8 @@ mod tests { exit_code: None, signal: None, stop_requested: false, + jail_root: None, + jail_uid: None, }) .unwrap(); let status = processes.status(&app_id()); @@ -357,6 +365,8 @@ mod tests { exit_code: None, signal: None, stop_requested: false, + jail_root: None, + jail_uid: None, }) .unwrap(); let status = processes.status(&app_id()); @@ -377,6 +387,8 @@ mod tests { exit_code: Some(1), signal: None, stop_requested: false, + jail_root: None, + jail_uid: None, }; processes.write_record(&record).unwrap(); let crashed = processes.status(&app_id()); @@ -386,6 +398,8 @@ mod tests { processes .write_record(&VmRecord { stop_requested: true, + jail_root: None, + jail_uid: None, ..record.clone() }) .unwrap(); @@ -461,6 +475,8 @@ mod tests { exit_code: Some(0), signal: None, stop_requested: true, + jail_root: None, + jail_uid: None, }) .unwrap(); std::fs::write(processes.console_path(&app_id()), b"[nibrun] gone\n").unwrap(); @@ -640,6 +656,8 @@ mod tests { exit_code: None, signal: None, stop_requested: false, + jail_root: None, + jail_uid: None, }) .unwrap(); processes.stop(&app_id()).await; diff --git a/crates/nibrunnerd/src/unix_socket.rs b/crates/nibrunnerd/src/unix_socket.rs index ae90874f..edb8769e 100644 --- a/crates/nibrunnerd/src/unix_socket.rs +++ b/crates/nibrunnerd/src/unix_socket.rs @@ -57,6 +57,19 @@ pub(crate) fn bind(path: &Path) -> std::io::Result { Ok(listener) } +#[allow( + unsafe_code, + reason = "a VMM may replace a socket with a symlink; ownership must never follow it" +)] +pub(crate) fn own(path: &Path, uid: u32, gid: u32) -> std::io::Result<()> { + use std::os::unix::ffi::OsStrExt; + let name = std::ffi::CString::new(path.as_os_str().as_bytes()).map_err(std::io::Error::other)?; + if unsafe { libc::lchown(name.as_ptr(), uid, gid) } < 0 { + return Err(std::io::Error::last_os_error()); + } + Ok(()) +} + pub(crate) async fn connect(path: &Path) -> std::io::Result { use std::os::unix::fs::MetadataExt;