Support for querying what realm a process belongs to

This commit is contained in:
Bruce Leidl 2022-09-19 10:02:26 -04:00
parent 94a0fd210c
commit 1136cee2f7
7 changed files with 223 additions and 77 deletions

View File

@ -34,6 +34,7 @@ pub use crate::exec::{Exec,FileRange};
pub use crate::realmfs::resizer::ResizeSize; pub use crate::realmfs::resizer::ResizeSize;
pub use crate::realm::overlay::RealmOverlay; pub use crate::realm::overlay::RealmOverlay;
pub use crate::realm::realm::Realm; 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::config::{RealmConfig,OverlayType,GLOBAL_CONFIG};
pub use crate::realm::events::RealmEvent; pub use crate::realm::events::RealmEvent;
pub use crate::realm::realms::Realms; pub use crate::realm::realms::Realms;

View File

@ -1,9 +1,10 @@
use std::collections::HashSet; use std::collections::HashSet;
use std::path::{Path, PathBuf}; use std::path::Path;
use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
use posix_acl::{ACL_EXECUTE, ACL_READ, PosixACL, Qualifier}; use posix_acl::{ACL_EXECUTE, ACL_READ, PosixACL, Qualifier};
use crate::{Mountpoint, Result, Realms, RealmFS, Realm, util}; use crate::{Mountpoint, Result, Realms, RealmFS, Realm, util};
use crate::realm::pidmapper::{PidLookupResult, PidMapper};
use crate::realmfs::realmfs_set::RealmFSSet; use crate::realmfs::realmfs_set::RealmFSSet;
use super::systemd::Systemd; use super::systemd::Systemd;
@ -333,27 +334,9 @@ impl RealmManager {
self.inner().realms.by_name(name) self.inner().realms.by_name(name)
} }
pub fn realm_by_pid(&self, pid: u32) -> Option<Realm> { pub fn realm_by_pid(&self, pid: u32) -> PidLookupResult {
match Self::read_realm_name_by_pid(pid) { let mapper = PidMapper::new(self.active_realms(false));
Ok(name) => self.realm_by_name(name.as_str()), mapper.lookup_pid(pid as libc::pid_t)
Err(_) => None,
}
}
fn read_realm_name_by_pid(pid: u32) -> Result<String> {
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 rescan_realms(&self) -> Result<(Vec<Realm>,Vec<Realm>)> { pub fn rescan_realms(&self) -> Result<(Vec<Realm>,Vec<Realm>)> {

View File

@ -8,6 +8,7 @@ pub(crate) mod realm;
pub (crate) mod network; pub (crate) mod network;
pub(crate) mod create; pub(crate) mod create;
pub(crate) mod events; pub(crate) mod events;
pub(crate) mod pidmapper;
mod systemd; mod systemd;
mod launcher; mod launcher;

View File

@ -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<Realm>,
my_pid_ns_id: Option<u64>,
}
impl PidMapper {
pub fn new(active_realms: Vec<Realm>) -> 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<Process> {
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<u64> {
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<u64> {
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<u64> {
let proc = Self::read_process(pid)?;
Self::read_pid_namespace_id(&proc)
}
fn parent_process(proc: Process) -> Option<Process> {
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
}
}

View File

@ -12,6 +12,7 @@ use super::systemd::Systemd;
use crate::realmfs::Mountpoint; use crate::realmfs::Mountpoint;
use crate::{symlink, util, Result, RealmFS, CommandLine, RealmManager, OverlayType}; use crate::{symlink, util, Result, RealmFS, CommandLine, RealmManager, OverlayType};
use crate::realm::pidmapper::PidMapper;
const MAX_REALM_NAME_LEN:usize = 128; const MAX_REALM_NAME_LEN:usize = 128;
@ -37,20 +38,96 @@ impl RealmActiveState {
struct Inner { struct Inner {
config: Arc<RealmConfig>, config: Arc<RealmConfig>,
realm_name: String,
timestamp: i64, timestamp: i64,
leader_pid: Option<u32>, leader_pid: Option<u32>,
leader_pid_namespace: Option<u64>,
active: RealmActiveState, active: RealmActiveState,
} }
impl Inner { impl Inner {
fn new(config: RealmConfig) -> Inner { fn new(config: RealmConfig, name: &str) -> Inner {
Inner { Inner {
config: Arc::new(config), config: Arc::new(config),
realm_name: name.to_string(),
timestamp: 0, timestamp: 0,
leader_pid: None, leader_pid: None,
leader_pid_namespace: None,
active: RealmActiveState::Unknown, 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<u64> {
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<u32> {
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<u32> {
let output = cmd_with_output!("/usr/bin/machinectl", "show --value {} -p Leader", self.realm_name)?;
let pid = output.parse::<u32>()
.map_err(|_| format_err!("Failed to parse leader pid output from machinectl: {}", output))?;
Ok(pid)
}
} }
#[derive(Clone)] #[derive(Clone)]
@ -65,7 +142,7 @@ impl Realm {
pub(crate) fn new(name: &str) -> Realm { pub(crate) fn new(name: &str) -> Realm {
let config = RealmConfig::unloaded_realm_config(name); 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 inner = Arc::new(RwLock::new(inner));
let name = Arc::new(name.to_string()); let name = Arc::new(name.to_string());
let manager = Weak::new(); let manager = Weak::new();
@ -93,45 +170,19 @@ impl Realm {
} }
pub fn is_active(&self) -> bool { pub fn is_active(&self) -> bool {
if self.inner().active == RealmActiveState::Unknown { self.inner_mut().is_active()
self.reload_active_state();
}
self.inner().active == RealmActiveState::Active
} }
pub fn set_active(&self, is_active: bool) { pub fn set_active(&self, is_active: bool) {
let state = if is_active { self.inner_mut().set_active(is_active)
RealmActiveState::Active
} else {
RealmActiveState::Inactive
};
self.set_active_state(state);
} }
pub fn is_system(&self) -> bool { pub fn is_system(&self) -> bool {
self.config().system_realm() 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) { 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 { pub fn name(&self) -> &str {
@ -389,6 +440,17 @@ impl Realm {
self.leader_pid().map(|pid| PathBuf::from(format!("/proc/{}/root", pid))) self.leader_pid().map(|pid| PathBuf::from(format!("/proc/{}/root", pid)))
} }
pub fn pid_ns(&self) -> Option<u64> {
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<String> { pub fn pid_namespace(&self) -> Option<String> {
let pid = self.leader_pid()?; let pid = self.leader_pid()?;
let path = symlink::read(format!("/proc/{}/ns/pid", 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 /// The leader pid is the 'pid 1' of the realm container as seen from
/// outside the PID namespace. /// outside the PID namespace.
pub fn leader_pid(&self) -> Option<u32> { pub fn leader_pid(&self) -> Option<u32> {
if !self.is_active() { self.inner_mut().leader_pid()
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<u32> {
let output = cmd_with_output!("/usr/bin/machinectl", "show --value {} -p Leader", self.name())?;
let pid = output.parse::<u32>()
.map_err(|_| format_err!("Failed to parse leader pid output from machinectl: {}", output))?;
Ok(pid)
} }
/// Return `true` if `name` is a valid name for a realm. /// Return `true` if `name` is a valid name for a realm.

View File

@ -115,10 +115,10 @@ impl Systemd {
Ok(()) Ok(())
} }
pub fn is_active(realm: &Realm) -> Result<bool> { pub fn is_active(realm_name: &str) -> Result<bool> {
let ok = Command::new(SYSTEMCTL_PATH) let ok = Command::new(SYSTEMCTL_PATH)
.args(&["--quiet", "is-active"]) .args(&["--quiet", "is-active"])
.arg(format!("realm-{}", realm.name())) .arg(format!("realm-{}", realm_name))
.status() .status()
.map(|status| status.success()) .map(|status| status.success())
.map_err(context!("failed to execute {}", SYSTEMCTL_PATH))?; .map_err(context!("failed to execute {}", SYSTEMCTL_PATH))?;

View File

@ -1,7 +1,7 @@
use std::path::{Path,PathBuf}; use std::path::{Path,PathBuf};
use std::process::{Command,Stdio}; use std::process::{Command,Stdio};
use std::os::unix::ffi::OsStrExt; 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::os::unix::fs as unixfs;
use std::env; use std::env;
use std::fs::{self, File, DirEntry}; use std::fs::{self, File, DirEntry};
@ -163,6 +163,13 @@ pub fn chown(path: &Path, uid: u32, gid: u32) -> Result<()> {
Ok(()) 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` /// 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 /// A wrapper around `fs::rename()` which on failure returns an error indicating the source and