From 1136cee2f70e936c307be3ca2e21f7742df45ea4 Mon Sep 17 00:00:00 2001 From: Bruce Leidl Date: Mon, 19 Sep 2022 10:02:26 -0400 Subject: [PATCH] Support for querying what realm a process belongs to --- libcitadel/src/lib.rs | 1 + libcitadel/src/realm/manager.rs | 27 +----- libcitadel/src/realm/mod.rs | 1 + libcitadel/src/realm/pidmapper.rs | 112 +++++++++++++++++++++++ libcitadel/src/realm/realm.rs | 146 +++++++++++++++++++----------- libcitadel/src/realm/systemd.rs | 4 +- libcitadel/src/util.rs | 9 +- 7 files changed, 223 insertions(+), 77 deletions(-) create mode 100644 libcitadel/src/realm/pidmapper.rs diff --git a/libcitadel/src/lib.rs b/libcitadel/src/lib.rs index 57ae60c..2c90f2f 100644 --- a/libcitadel/src/lib.rs +++ b/libcitadel/src/lib.rs @@ -34,6 +34,7 @@ pub use crate::exec::{Exec,FileRange}; pub use crate::realmfs::resizer::ResizeSize; pub use crate::realm::overlay::RealmOverlay; pub use crate::realm::realm::Realm; +pub use crate::realm::pidmapper::PidLookupResult; pub use crate::realm::config::{RealmConfig,OverlayType,GLOBAL_CONFIG}; pub use crate::realm::events::RealmEvent; pub use crate::realm::realms::Realms; diff --git a/libcitadel/src/realm/manager.rs b/libcitadel/src/realm/manager.rs index 81f3b94..cabc1bd 100644 --- a/libcitadel/src/realm/manager.rs +++ b/libcitadel/src/realm/manager.rs @@ -1,9 +1,10 @@ use std::collections::HashSet; -use std::path::{Path, PathBuf}; +use std::path::Path; use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; use posix_acl::{ACL_EXECUTE, ACL_READ, PosixACL, Qualifier}; use crate::{Mountpoint, Result, Realms, RealmFS, Realm, util}; +use crate::realm::pidmapper::{PidLookupResult, PidMapper}; use crate::realmfs::realmfs_set::RealmFSSet; use super::systemd::Systemd; @@ -333,27 +334,9 @@ impl RealmManager { self.inner().realms.by_name(name) } - pub fn realm_by_pid(&self, pid: u32) -> Option { - match Self::read_realm_name_by_pid(pid) { - Ok(name) => self.realm_by_name(name.as_str()), - Err(_) => None, - } - } - - fn read_realm_name_by_pid(pid: u32) -> Result { - let run = PathBuf::from(format!("/proc/{}/root/run", pid)); - let realm_name = run.join("realm-name"); - - // ensure that /proc/pid/root/run and /proc/pid/root/run/realm-name - // are not symlinks - let run_meta = run.symlink_metadata() - .map_err(context!("failed reading symlink metadata {:?}", run))?; - let name_meta = realm_name.symlink_metadata() - .map_err(context!("failed reading symlink metadata {:?}", realm_name))?; - if !run_meta.file_type().is_dir() || !name_meta.file_type().is_file() { - bail!("invalid path"); - } - util::read_to_string(&realm_name) + pub fn realm_by_pid(&self, pid: u32) -> PidLookupResult { + let mapper = PidMapper::new(self.active_realms(false)); + mapper.lookup_pid(pid as libc::pid_t) } pub fn rescan_realms(&self) -> Result<(Vec,Vec)> { diff --git a/libcitadel/src/realm/mod.rs b/libcitadel/src/realm/mod.rs index 891c712..d70f9a2 100644 --- a/libcitadel/src/realm/mod.rs +++ b/libcitadel/src/realm/mod.rs @@ -8,6 +8,7 @@ pub(crate) mod realm; pub (crate) mod network; pub(crate) mod create; pub(crate) mod events; +pub(crate) mod pidmapper; mod systemd; mod launcher; diff --git a/libcitadel/src/realm/pidmapper.rs b/libcitadel/src/realm/pidmapper.rs new file mode 100644 index 0000000..e50471a --- /dev/null +++ b/libcitadel/src/realm/pidmapper.rs @@ -0,0 +1,112 @@ +use std::ffi::OsStr; +use procfs::process::Process; +use crate::Realm; + +pub enum PidLookupResult { + Unknown, + Realm(Realm), + Citadel, +} + +pub struct PidMapper { + active_realms: Vec, + my_pid_ns_id: Option, +} + +impl PidMapper { + pub fn new(active_realms: Vec) -> Self { + let my_pid_ns_id = Self::self_pid_namespace_id(); + PidMapper { active_realms, my_pid_ns_id } + } + + fn read_process(pid: libc::pid_t) -> Option { + match Process::new(pid) { + Ok(proc) => Some(proc), + Err(err) => { + warn!("Failed to read process [{}]: {}", pid, err); + None + } + } + } + + pub fn self_pid_namespace_id() -> Option { + let myself = match Process::myself() { + Ok(proc) => proc, + Err(err) => { + warn!("Failed to read process information for self pid: {}", err); + return None; + } + }; + + Self::read_pid_namespace_id(&myself) + } + + fn read_pid_namespace_id(proc: &Process) -> Option { + let namespaces = match proc.namespaces() { + Ok(namespaces) => namespaces, + Err(err) => { + warn!("Failed to read namespaces for process {}: {}", proc.pid, err); + return None; + } + }; + + for ns in namespaces { + if ns.ns_type.as_os_str() == OsStr::new("pid") { + return Some(ns.identifier); + } + } + warn!("No pid namespace found for process {}", proc.pid); + None + } + + pub fn lookup_pid_namespace(pid: libc::pid_t) -> Option { + let proc = Self::read_process(pid)?; + Self::read_pid_namespace_id(&proc) + } + + fn parent_process(proc: Process) -> Option { + let ppid = proc.stat.ppid as libc::pid_t; + if ppid == 1 { + return None; + } + Self::read_process(ppid) + } + + pub fn lookup_pid(&self, pid: libc::pid_t) -> PidLookupResult { + const MAX_PARENT_SEARCH: i32 = 8; + let mut n = 0; + + let mut proc = match Self::read_process(pid) { + Some(proc) => proc, + None => return PidLookupResult::Unknown, + }; + + while n < MAX_PARENT_SEARCH { + + let pid_ns_id = match Self::read_pid_namespace_id(&proc) { + None => return PidLookupResult::Unknown, + Some(ns_id) => ns_id, + }; + + if Some(pid_ns_id) == self.my_pid_ns_id { + return PidLookupResult::Citadel; + } + + if let Some(realm) = self.active_realms.iter() + .find(|r| r.has_pid_ns(pid_ns_id)) + .cloned() + { + return PidLookupResult::Realm(realm) + } + + proc = match Self::parent_process(proc) { + Some(proc) => proc, + None => return PidLookupResult::Unknown, + }; + + n += 1; + } + PidLookupResult::Unknown + } + +} diff --git a/libcitadel/src/realm/realm.rs b/libcitadel/src/realm/realm.rs index d41797d..7dbcbb4 100644 --- a/libcitadel/src/realm/realm.rs +++ b/libcitadel/src/realm/realm.rs @@ -12,6 +12,7 @@ use super::systemd::Systemd; use crate::realmfs::Mountpoint; use crate::{symlink, util, Result, RealmFS, CommandLine, RealmManager, OverlayType}; +use crate::realm::pidmapper::PidMapper; const MAX_REALM_NAME_LEN:usize = 128; @@ -37,20 +38,96 @@ impl RealmActiveState { struct Inner { config: Arc, + realm_name: String, timestamp: i64, leader_pid: Option, + leader_pid_namespace: Option, active: RealmActiveState, } impl Inner { - fn new(config: RealmConfig) -> Inner { + fn new(config: RealmConfig, name: &str) -> Inner { Inner { config: Arc::new(config), + realm_name: name.to_string(), timestamp: 0, leader_pid: None, + leader_pid_namespace: None, active: RealmActiveState::Unknown, } } + + fn reload_active_state(&mut self) { + match Systemd::is_active(&self.realm_name) { + Ok(active) => self.set_active(active), + Err(err) => { + warn!("Failed to run systemctl to determine realm is-active state: {}", err); + self.set_active_state(RealmActiveState::Failed) + }, + } + } + fn is_active(&mut self) -> bool { + if self.active == RealmActiveState::Unknown { + self.reload_active_state(); + } + self.active == RealmActiveState::Active + } + + fn set_active(&mut self, is_active: bool) { + let state = if is_active { + RealmActiveState::Active + } else { + RealmActiveState::Inactive + }; + self.set_active_state(state); + } + + fn set_active_state(&mut self, state: RealmActiveState) { + if state != RealmActiveState::Active { + self.leader_pid = None; + self.leader_pid_namespace = None; + } + self.active = state; + } + + fn pid_ns(&mut self) -> Option { + if !self.is_active() { + return None; + } + if let Some(pid_ns) = self.leader_pid_namespace { + return Some(pid_ns); + } + + let leader_pid = self.leader_pid()? as libc::pid_t; + self.leader_pid_namespace = PidMapper::lookup_pid_namespace(leader_pid); + self.leader_pid_namespace + } + + /// Query for 'leader pid' of realm nspawn instance with machinectl. + /// The leader pid is the 'pid 1' of the realm container as seen from + /// outside the PID namespace. + fn leader_pid(&mut self) -> Option { + if !self.is_active() { + return None; + } + if let Some(pid) = self.leader_pid { + return Some(pid); + } + + match self.query_leader_pid() { + Ok(pid) => self.leader_pid = Some(pid), + Err(e) => warn!("error retrieving leader pid for realm: {}", e) + } + self.leader_pid + } + + fn query_leader_pid(&self) -> Result { + let output = cmd_with_output!("/usr/bin/machinectl", "show --value {} -p Leader", self.realm_name)?; + let pid = output.parse::() + .map_err(|_| format_err!("Failed to parse leader pid output from machinectl: {}", output))?; + Ok(pid) + } + } #[derive(Clone)] @@ -65,7 +142,7 @@ impl Realm { pub(crate) fn new(name: &str) -> Realm { let config = RealmConfig::unloaded_realm_config(name); - let inner = Inner::new(config); + let inner = Inner::new(config, name); let inner = Arc::new(RwLock::new(inner)); let name = Arc::new(name.to_string()); let manager = Weak::new(); @@ -93,45 +170,19 @@ impl Realm { } pub fn is_active(&self) -> bool { - if self.inner().active == RealmActiveState::Unknown { - self.reload_active_state(); - } - self.inner().active == RealmActiveState::Active + self.inner_mut().is_active() } pub fn set_active(&self, is_active: bool) { - let state = if is_active { - RealmActiveState::Active - } else { - RealmActiveState::Inactive - }; - self.set_active_state(state); + self.inner_mut().set_active(is_active) } pub fn is_system(&self) -> bool { self.config().system_realm() } - fn set_active_state(&self, state: RealmActiveState) { - let mut inner = self.inner_mut(); - if state != RealmActiveState::Active { - inner.leader_pid = None; - } - inner.active = state; - } - - fn reload_active_state(&self) { - match Systemd::is_active(self) { - Ok(active) => self.set_active(active), - Err(err) => { - warn!("Failed to run systemctl to determine realm is-active state: {}", err); - self.set_active_state(RealmActiveState::Failed) - }, - } - } - pub fn set_active_from_systemctl(&self, output: &str) { - self.set_active_state(RealmActiveState::from_sysctl_output(output)); + self.inner_mut().set_active_state(RealmActiveState::from_sysctl_output(output)); } pub fn name(&self) -> &str { @@ -389,6 +440,17 @@ impl Realm { self.leader_pid().map(|pid| PathBuf::from(format!("/proc/{}/root", pid))) } + pub fn pid_ns(&self) -> Option { + self.inner_mut().pid_ns() + } + + pub fn has_pid_ns(&self, ns_id: u64) -> bool { + self.inner_mut() + .pid_ns() + .map(|id| id == ns_id) + .unwrap_or(false) + } + pub fn pid_namespace(&self) -> Option { let pid = self.leader_pid()?; let path = symlink::read(format!("/proc/{}/ns/pid", pid))?; @@ -399,27 +461,7 @@ impl Realm { /// The leader pid is the 'pid 1' of the realm container as seen from /// outside the PID namespace. pub fn leader_pid(&self) -> Option { - if !self.is_active() { - return None; - } - - let mut lock = self.inner_mut(); - - if let Some(pid) = lock.leader_pid { - return Some(pid); - } - match self.query_leader_pid() { - Ok(pid) => lock.leader_pid = Some(pid), - Err(e) => warn!("error retrieving leader pid for realm: {}", e) - } - lock.leader_pid - } - - fn query_leader_pid(&self) -> Result { - let output = cmd_with_output!("/usr/bin/machinectl", "show --value {} -p Leader", self.name())?; - let pid = output.parse::() - .map_err(|_| format_err!("Failed to parse leader pid output from machinectl: {}", output))?; - Ok(pid) + self.inner_mut().leader_pid() } /// Return `true` if `name` is a valid name for a realm. diff --git a/libcitadel/src/realm/systemd.rs b/libcitadel/src/realm/systemd.rs index 11837de..d479748 100644 --- a/libcitadel/src/realm/systemd.rs +++ b/libcitadel/src/realm/systemd.rs @@ -115,10 +115,10 @@ impl Systemd { Ok(()) } - pub fn is_active(realm: &Realm) -> Result { + pub fn is_active(realm_name: &str) -> Result { let ok = Command::new(SYSTEMCTL_PATH) .args(&["--quiet", "is-active"]) - .arg(format!("realm-{}", realm.name())) + .arg(format!("realm-{}", realm_name)) .status() .map(|status| status.success()) .map_err(context!("failed to execute {}", SYSTEMCTL_PATH))?; diff --git a/libcitadel/src/util.rs b/libcitadel/src/util.rs index 733d12c..0a3e4df 100644 --- a/libcitadel/src/util.rs +++ b/libcitadel/src/util.rs @@ -1,7 +1,7 @@ use std::path::{Path,PathBuf}; use std::process::{Command,Stdio}; use std::os::unix::ffi::OsStrExt; -use std::os::unix::fs::MetadataExt; +use std::os::unix::fs::{MetadataExt, PermissionsExt}; use std::os::unix::fs as unixfs; use std::env; use std::fs::{self, File, DirEntry}; @@ -163,6 +163,13 @@ pub fn chown(path: &Path, uid: u32, gid: u32) -> Result<()> { Ok(()) } +pub fn chmod(path: &Path, mode: u32) -> Result<()> { + let meta = path.metadata() + .map_err(context!("Failed to read metadata from path {:?}", path))?; + meta.permissions().set_mode(mode); + Ok(()) +} + /// Rename or move file at `from` to file path `to` /// /// A wrapper around `fs::rename()` which on failure returns an error indicating the source and