From f04550d9d5eabd49bdcc57233f9e5b9a23968bf3 Mon Sep 17 00:00:00 2001 From: Johannes Leupolz Date: Tue, 28 Oct 2025 21:16:03 +0000 Subject: [PATCH] Try to get root process id of the process that tries to create a input device. This should be more stable, because the process itself more likely vanishes that its parent process. --- vuinputd/src/main.rs | 4 +- vuinputd/src/namespace.rs | 144 +++++++++++++++++++++++++++++++------- 2 files changed, 122 insertions(+), 26 deletions(-) diff --git a/vuinputd/src/main.rs b/vuinputd/src/main.rs index e6199cb..8589a59 100644 --- a/vuinputd/src/main.rs +++ b/vuinputd/src/main.rs @@ -187,7 +187,7 @@ unsafe extern "C" fn vuinput_open( let fh = get_fresh_filehandle(); let ctx = fuse_lowlevel::fuse_req_ctx(_req); debug!("fh {}: opened by process id {} (host view)", fh, (*ctx).pid); - let namespaces = get_namespaces(Some((*ctx).pid)); + let namespaces = get_namespaces(Pid::Pid((*ctx).pid)); debug!("fh {}: namespaces {}", fh, namespaces); // namespaces net:4026531840, uts:4026531838, ipc:4026531839, pid:4026531836, pid_for_children:4026531836, user:4026531837, mnt:4026531841, cgroup:4026531835, time:4026531834, time_for_children:4026531834 (*_fi).fh = fh; @@ -645,7 +645,7 @@ fn main() -> std::io::Result<()> { VUINPUT_STATE.set(RwLock::new(HashMap::new())).unwrap(); VUINPUT_COUNTER.set(AtomicU64::new(3)).unwrap(); JOB_DISPATCHER.set(Mutex::new(Dispatcher::new())).unwrap(); - SELF_NAMESPACES.set(get_namespaces(None)).unwrap(); + SELF_NAMESPACES.set(get_namespaces(Pid::SelfPid)).unwrap(); JOB_DISPATCHER.get().unwrap().lock().unwrap().dispatch(Box::new(MonitorBackgroundLoop::new())); info!("Starting vuinputd"); diff --git a/vuinputd/src/namespace.rs b/vuinputd/src/namespace.rs index 441a51f..3c34d05 100644 --- a/vuinputd/src/namespace.rs +++ b/vuinputd/src/namespace.rs @@ -5,16 +5,34 @@ use log::debug; use nix::{ sched::{setns, CloneFlags}, - unistd::{fork, ForkResult, Pid}, + unistd::{fork, ForkResult}, }; use std::{ fs::{self, File}, os::fd::AsFd, path::{self, Path}, }; +use std::io::{self, BufRead}; +use std::path::PathBuf; + + +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] +pub enum Pid { + SelfPid, + Pid(i32), +} + +impl Pid { + pub fn path(&self) -> String { + match self { + Pid::SelfPid => "/proc/self".to_string(), + Pid::Pid(pid_no) => format!("/proc/{}",pid_no) + } + } +} + #[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] -pub struct Namespaces { - pub nspath: String, +struct NamespaceInodes { pub net: Option, pub uts: Option, pub ipc: Option, @@ -25,40 +43,53 @@ pub struct Namespaces { pub cgroup: Option, pub time: Option, pub time_for_children: Option, + +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] +pub struct Namespaces { + pub nspath: String, + pub nsroot: String, + nsinodes: NamespaceInodes, +} + +impl NamespaceInodes { + pub fn equal_mnt_and_net(&self, other: &NamespaceInodes) -> bool { + self.mnt == other.mnt && self.net == other.net + } } impl Namespaces { pub fn equal_mnt_and_net(&self, other: &Namespaces) -> bool { - self.mnt == other.mnt && self.net == other.net + self.nsinodes.equal_mnt_and_net(&other.nsinodes) } } impl std::fmt::Display for Namespaces { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { writeln!(f, "Namespaces:")?; - writeln!(f, " net: {:?}", self.net)?; - writeln!(f, " uts: {:?}", self.uts)?; - writeln!(f, " ipc: {:?}", self.ipc)?; - writeln!(f, " pid: {:?}", self.pid)?; - writeln!(f, " pid_for_children: {:?}", self.pid_for_children)?; - writeln!(f, " user: {:?}", self.user)?; - writeln!(f, " mnt: {:?}", self.mnt)?; - writeln!(f, " cgroup: {:?}", self.cgroup)?; - writeln!(f, " time: {:?}", self.time)?; - writeln!(f, " time_for_children: {:?}", self.time_for_children)?; + writeln!(f, " net: {:?}", self.nsinodes.net)?; + writeln!(f, " uts: {:?}", self.nsinodes.uts)?; + writeln!(f, " ipc: {:?}", self.nsinodes.ipc)?; + writeln!(f, " pid: {:?}", self.nsinodes.pid)?; + writeln!(f, " pid_for_children: {:?}", self.nsinodes.pid_for_children)?; + writeln!(f, " user: {:?}", self.nsinodes.user)?; + writeln!(f, " mnt: {:?}", self.nsinodes.mnt)?; + writeln!(f, " cgroup: {:?}", self.nsinodes.cgroup)?; + writeln!(f, " time: {:?}", self.nsinodes.time)?; + writeln!(f, " time_for_children: {:?}", self.nsinodes.time_for_children)?; Ok(()) } } -pub fn get_namespaces(pid: Option) -> Namespaces { +fn get_namespace_inodes(pid: Pid) -> NamespaceInodes { let pid: String = match pid { - Some(pid) => pid.to_string(), - None => "self".to_string(), + Pid::Pid(pid) => pid.to_string(), + Pid::SelfPid => "self".to_string(), }; let nspath = format!("/proc/{}/ns", pid); - let mut ns = Namespaces { - nspath: nspath.clone(), + let mut ns = NamespaceInodes { net: None, uts: None, ipc: None, @@ -96,9 +127,74 @@ pub fn get_namespaces(pid: Option) -> Namespaces { ns } +fn get_ppid(pid: Pid) -> Option { + let content = + match pid { + Pid::SelfPid => fs::read_to_string(format!("/proc/self/status")).ok()?, + Pid::Pid(pid) => fs::read_to_string(format!("/proc/{}/status", pid)).ok()? + }; + let ppid=content + .lines() + .find(|line| line.starts_with("PPid:")) + .and_then(|line| line.split_whitespace().nth(1)) + .and_then(|ppid| ppid.parse::().ok()); + match ppid { + None => None, + Some(ppid)=> Some(Pid::Pid(ppid)) + } +} + + + +pub fn get_namespaces(pid: Pid) -> Namespaces { + + match pid { + Pid::Pid(_) => + { + // go up the parent hierarchy until we find a parent with different namespaces + let mut ppid = pid; + let nsinodes = get_namespace_inodes(pid); + loop { + let candidate_ppid = get_ppid(ppid); + match candidate_ppid { + None => break, + Some(candidate_ppid) => + { + let ppid_nsinodes = get_namespace_inodes(candidate_ppid); + if nsinodes.equal_mnt_and_net(&ppid_nsinodes) { + ppid=candidate_ppid; + } else { + break; + } + } + + } + } + + let nspath = format!("{}/ns", pid.path()); + let nsroot = format!("{}/ns", ppid.path()); + Namespaces { + nspath: nspath, + nsroot: nsroot, + nsinodes: nsinodes, + } + }, + Pid::SelfPid => + { + let nsinodes = get_namespace_inodes(pid); + let nspath = format!("{}/ns", pid.path()); + Namespaces { + nspath: nspath.clone(), + nsroot: nspath, + nsinodes: nsinodes, + } + }, + } +} + /// Runs a function inside the given network and mount namespaces. /// Returns the child PID so the caller can `waitpid` on it. -pub fn run_in_net_and_mnt_namespace(ns: Namespaces, func: Box) -> nix::Result { +pub fn run_in_net_and_mnt_namespace(ns: Namespaces, func: Box) -> nix::Result { //Note: The child process is created with a single thread—the one that called fork(). match unsafe { fork()? } { ForkResult::Parent { child } => { @@ -107,13 +203,13 @@ pub fn run_in_net_and_mnt_namespace(ns: Namespaces, func: Box) -> nix: } ForkResult::Child => { // enter namespace - let path: &Path = Path::new(ns.nspath.as_str()); + let path: &Path = Path::new(ns.nsroot.as_str()); if !fs::exists(path).unwrap() { - debug!("the process whose namespaces we want to enter does not exist anymore!"); + debug!("the root process of the container whose namespaces we want to enter does not exist anymore!"); std::process::exit(0); } - let net = File::open(ns.nspath.clone() + "/net").expect("net not found"); - let mnt = File::open(ns.nspath.clone() + "/mnt").expect("mnt not found"); + let net = File::open(ns.nsroot.clone() + "/net").expect("net not found"); + let mnt = File::open(ns.nsroot.clone() + "/mnt").expect("mnt not found"); setns(net.as_fd(), CloneFlags::CLONE_NEWNET).expect("couldn't enter net"); setns(mnt.as_fd(), CloneFlags::CLONE_NEWNS).expect("couldn't enter mnt"); // execute your function