mirror of
https://github.com/joleuger/vuinputd.git
synced 2026-07-17 16:36:03 +00:00
- Add parameter device_owner that can be used when uids are mapped inside the container to ensure that the folders get created with the uid 0 of the container
- internal refactorings - target-namespace is now target-pid (which a shorter syntax)
This commit is contained in:
parent
8e0a00e817
commit
b75806cc09
7 changed files with 271 additions and 47 deletions
|
|
@ -29,7 +29,7 @@ cargo build -p vuinputd-tests
|
|||
podman build --dns 1.1.1.1 -t vuinputd-tests -f vuinputd-tests/podman/Containerfile .
|
||||
```
|
||||
|
||||
Run with `cargo test -p vuinputd-tests --features "requires-privileges requires-uinput requires-podman -- --test-threads=1"`.
|
||||
Run with `cargo test -p vuinputd-tests --features "requires-privileges requires-uinput requires-podman" -- --test-threads=1`.
|
||||
|
||||
## Performance tests
|
||||
|
||||
|
|
|
|||
|
|
@ -107,6 +107,18 @@ impl PodmanBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn userns(mut self, mode: &str) -> Self {
|
||||
self.args.push("--userns".into());
|
||||
self.args.push(mode.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn uidmap(mut self, uidmap: &str) -> Self {
|
||||
self.args.push("--uidmap".into());
|
||||
self.args.push(uidmap.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable bidirectional IPC using a Unix seqpacket socketpair.
|
||||
pub fn with_ipc(mut self) -> io::Result<(Self, SandboxIpc)> {
|
||||
let (parent, child) = nix::sys::socket::socketpair(
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ impl Drop for VuinputdGuard {
|
|||
// Wait a bit
|
||||
for _ in 0..10 {
|
||||
if let Ok(Some(_)) = self.child.try_wait() {
|
||||
println!("vuinputd for tests shutdown gracefully");
|
||||
return;
|
||||
}
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
|
|
@ -79,5 +80,6 @@ impl Drop for VuinputdGuard {
|
|||
// Still alive → SIGKILL
|
||||
let _ = signal::kill(pid, Signal::SIGKILL);
|
||||
let _ = self.child.wait();
|
||||
println!("vuinputd for tests killed");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ pub struct GlobalConfig {
|
|||
pub policy: DevicePolicy,
|
||||
pub placement: Placement,
|
||||
pub vudevname: String,
|
||||
pub device_owner: DeviceOwner,
|
||||
}
|
||||
|
||||
// The actual static variable. It starts empty and is set once in main().
|
||||
|
|
@ -41,16 +42,40 @@ pub enum Placement {
|
|||
None,
|
||||
}
|
||||
|
||||
/// Device owner of the created devices
|
||||
#[derive(Debug, Clone, ValueEnum, Default, PartialEq, Eq)]
|
||||
pub enum DeviceOwner {
|
||||
#[default]
|
||||
/// Automatically derive useful settings (how might change in the future)
|
||||
Auto,
|
||||
/// Use the uid and gid of vuinputd
|
||||
Vuinputd,
|
||||
/// Same as dev folder in container
|
||||
ContainerDevFolder,
|
||||
}
|
||||
|
||||
impl DeviceOwner {
|
||||
pub fn to_string_rep(&self) -> String {
|
||||
match self {
|
||||
DeviceOwner::Auto => "auto".to_string(),
|
||||
DeviceOwner::Vuinputd => "vuinputd".to_string(),
|
||||
DeviceOwner::ContainerDevFolder => "container-dev-folder".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initialize_global_config(
|
||||
device_policy: &DevicePolicy,
|
||||
placement: &Placement,
|
||||
devname: &Option<String>,
|
||||
device_owner: &DeviceOwner,
|
||||
) {
|
||||
if CONFIG
|
||||
.set(GlobalConfig {
|
||||
policy: device_policy.clone(),
|
||||
placement: placement.clone(),
|
||||
vudevname: devname.clone().unwrap_or("vuinput".to_string()),
|
||||
device_owner: device_owner.clone(),
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
|
|
@ -70,3 +95,7 @@ pub fn get_placement<'a>() -> &'a Placement {
|
|||
pub fn get_vudevname<'a>() -> &'a String {
|
||||
&CONFIG.get().unwrap().vudevname
|
||||
}
|
||||
|
||||
pub fn get_device_owner<'a>() -> &'a DeviceOwner {
|
||||
&CONFIG.get().unwrap().device_owner
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ use crate::cuse_device::evdev_write_watcher::{
|
|||
use crate::cuse_device::state::{initialize_dedup_last_error, initialize_vuinput_state};
|
||||
use crate::cuse_device::vuinput_make_cuse_ops;
|
||||
use crate::cuse_device::vuinput_open::VUINPUT_COUNTER;
|
||||
use crate::global_config::{DevicePolicy, Placement};
|
||||
use crate::global_config::{DeviceOwner, DevicePolicy, Placement};
|
||||
use crate::input_realizer::host_fs;
|
||||
use crate::jobs::monitor_udev_job::MonitorBackgroundLoop;
|
||||
|
||||
|
|
@ -80,13 +80,13 @@ struct Args {
|
|||
#[arg(long = "action-base64", value_name = "BASE64")]
|
||||
pub action_base64: Option<String>,
|
||||
|
||||
/// Path to the target process's /proc/<pid>/ns directory used as namespace source.
|
||||
/// Process id that is used as the namespace source (e.g. 1234 is used to read the namespaces from /proc/1234/ns).
|
||||
#[arg(
|
||||
long = "target-namespace",
|
||||
value_name = "NS_PATH",
|
||||
help = "Path to /proc/<pid>/ns used as the namespace source (e.g. /proc/1234/ns or /proc/self/ns)"
|
||||
long = "target-pid",
|
||||
value_name = "PID",
|
||||
help = "Process id that is used as the namespace source (e.g. 1234 is used to read the namespaces from /proc/1234/ns)."
|
||||
)]
|
||||
pub target_namespace: Option<String>,
|
||||
pub target_pid: Option<String>,
|
||||
|
||||
#[arg(
|
||||
long = "vt-guard",
|
||||
|
|
@ -104,6 +104,10 @@ struct Args {
|
|||
/// Placement of device nodes and udev data
|
||||
#[arg(long, value_enum, default_value_t)]
|
||||
pub placement: Placement,
|
||||
|
||||
/// Owner of the created devices
|
||||
#[arg(long = "device-owner", value_enum, default_value_t)]
|
||||
pub device_owner: DeviceOwner,
|
||||
}
|
||||
|
||||
fn validate_args(args: &Args) -> Result<(), String> {
|
||||
|
|
@ -116,18 +120,18 @@ fn validate_args(args: &Args) -> Result<(), String> {
|
|||
}
|
||||
};
|
||||
|
||||
// action might only occur with target-namespace
|
||||
// action might only occur with target-pid
|
||||
match (
|
||||
&args.major,
|
||||
&args.minor,
|
||||
&args.devname,
|
||||
action,
|
||||
&args.target_namespace,
|
||||
&args.target_pid,
|
||||
) {
|
||||
(None, None, None, Some(_), _) => {}
|
||||
(_, _, _, None, None) => {}
|
||||
_ => {
|
||||
return Err("--action or --action-base64 must not be used in combination with any other argument other than target-namespace".into());
|
||||
return Err("--action or --action-base64 must not be used in combination with any other argument other than target-pid".into());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -181,8 +185,8 @@ fn main() -> std::io::Result<()> {
|
|||
};
|
||||
|
||||
if action.is_some() {
|
||||
if let Some(target_namespace) = args.target_namespace {
|
||||
process_tools::run_in_net_and_mnt_namespace(target_namespace.as_str()).unwrap();
|
||||
if let Some(target_pid) = args.target_pid {
|
||||
process_tools::run_in_net_and_mnt_namespace(target_pid.as_str(),&args.device_owner).unwrap();
|
||||
}
|
||||
let error_code = actions::handle_action::handle_cli_action(action.unwrap());
|
||||
std::process::exit(error_code);
|
||||
|
|
@ -196,7 +200,12 @@ fn main() -> std::io::Result<()> {
|
|||
check_permissions().expect("failed to read the capabilities of the vuinputd process");
|
||||
vt_tools::check_vt_status();
|
||||
|
||||
global_config::initialize_global_config(&args.device_policy, &args.placement, &args.devname);
|
||||
global_config::initialize_global_config(
|
||||
&args.device_policy,
|
||||
&args.placement,
|
||||
&args.devname,
|
||||
&args.device_owner,
|
||||
);
|
||||
initialize_evdev_write_watcher().expect(
|
||||
"failed to initialize the watcher that watches for writes on the created evdev devices",
|
||||
);
|
||||
|
|
@ -208,7 +217,7 @@ fn main() -> std::io::Result<()> {
|
|||
.set(Mutex::new(Dispatcher::new()))
|
||||
.expect("failed to initialize the job dispatcher");
|
||||
SELF_NAMESPACES
|
||||
.set(get_namespace(Pid::SelfPid))
|
||||
.set(get_self_namespace())
|
||||
.expect("failed to retrieve the namespaces of the vuinputd process");
|
||||
initialize_dedup_last_error();
|
||||
JOB_DISPATCHER
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use std::{
|
|||
io::Read,
|
||||
os::{
|
||||
fd::{AsRawFd, FromRawFd, OwnedFd, RawFd},
|
||||
unix::process::CommandExt,
|
||||
unix::{fs::MetadataExt, process::CommandExt},
|
||||
},
|
||||
path::Path,
|
||||
process::Command,
|
||||
|
|
@ -21,23 +21,34 @@ use std::{
|
|||
use anyhow::anyhow;
|
||||
use std::io;
|
||||
|
||||
use crate::actions::action::Action;
|
||||
use crate::{
|
||||
actions::action::Action,
|
||||
global_config::{get_device_owner, DeviceOwner},
|
||||
};
|
||||
|
||||
pub mod ns_fscreds;
|
||||
|
||||
pub static SELF_NAMESPACES: OnceLock<Namespaces> = OnceLock::new();
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||
pub enum Pid {
|
||||
SelfPid,
|
||||
Pid(u32),
|
||||
}
|
||||
|
||||
impl Pid {
|
||||
pub fn path(&self) -> String {
|
||||
match self {
|
||||
Pid::SelfPid => "/proc/self".to_string(),
|
||||
Pid::Pid(pid_no) => format!("/proc/{}", pid_no),
|
||||
}
|
||||
}
|
||||
pub fn to_string_rep(&self) -> String {
|
||||
let Pid::Pid(val) = self;
|
||||
val.to_string()
|
||||
}
|
||||
}
|
||||
enum PidOrSelf {
|
||||
Pid(u32),
|
||||
SelfPid,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
||||
|
|
@ -80,15 +91,13 @@ pub fn is_compat_process(pid: Pid) -> Option<bool> {
|
|||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
Pid::SelfPid => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Rename to capture all relevant process information
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct RequestingProcess {
|
||||
pub nspath: String,
|
||||
pub nsroot: String,
|
||||
pub pid_requestor: Pid,
|
||||
pub pid_requestor_root: Pid,
|
||||
pub namespaces: Namespaces,
|
||||
pub is_compat: bool,
|
||||
}
|
||||
|
|
@ -134,10 +143,19 @@ impl std::fmt::Display for RequestingProcess {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_self_namespace() -> Namespaces {
|
||||
get_namespace_of_pid_or_self(PidOrSelf::SelfPid)
|
||||
}
|
||||
|
||||
pub fn get_namespace(pid: Pid) -> Namespaces {
|
||||
let pid: String = match pid {
|
||||
Pid::Pid(pid) => pid.to_string(),
|
||||
Pid::SelfPid => "self".to_string(),
|
||||
let Pid::Pid(pid) = pid;
|
||||
get_namespace_of_pid_or_self(PidOrSelf::Pid(pid))
|
||||
}
|
||||
|
||||
fn get_namespace_of_pid_or_self(pid_or_self: PidOrSelf) -> Namespaces {
|
||||
let pid: String = match pid_or_self {
|
||||
PidOrSelf::Pid(pid) => pid.to_string(),
|
||||
PidOrSelf::SelfPid => "self".to_string(),
|
||||
};
|
||||
let nspath = format!("/proc/{}/ns", pid);
|
||||
|
||||
|
|
@ -181,7 +199,6 @@ pub fn get_namespace(pid: Pid) -> Namespaces {
|
|||
|
||||
fn get_ppid(pid: Pid) -> Option<Pid> {
|
||||
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
|
||||
|
|
@ -239,29 +256,26 @@ pub fn get_requesting_process(pid: Pid) -> RequestingProcess {
|
|||
pid.path()
|
||||
);
|
||||
|
||||
let nspath = format!("{}/ns", pid.path());
|
||||
let nsroot = format!("{}/ns", ppid.path());
|
||||
RequestingProcess {
|
||||
nspath: nspath,
|
||||
nsroot: nsroot,
|
||||
pid_requestor: pid,
|
||||
pid_requestor_root: ppid,
|
||||
namespaces: nsinodes,
|
||||
is_compat: is_compat,
|
||||
}
|
||||
}
|
||||
Pid::SelfPid => {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_debug_string(action: &str, ns: &RequestingProcess) {
|
||||
let action_base64 = (BASE64_STANDARD.encode(action));
|
||||
let action_base64 = BASE64_STANDARD.encode(action);
|
||||
let mut debugstring = String::new();
|
||||
debugstring.push_str("In case you need to debug the system calls, call `strace vuinputd");
|
||||
debugstring.push_str(" --target-namespace ");
|
||||
debugstring.push_str(ns.nsroot.as_str());
|
||||
debugstring.push_str(" --target-pid ");
|
||||
debugstring.push_str(&ns.pid_requestor_root.to_string_rep());
|
||||
debugstring.push_str(" --action-base64 ");
|
||||
debugstring.push_str(action_base64.as_str());
|
||||
debugstring.push_str(" --device-owner ");
|
||||
debugstring.push_str(get_device_owner().to_string_rep().as_str());
|
||||
debugstring.push_str("`");
|
||||
debug!("{}", debugstring);
|
||||
}
|
||||
|
|
@ -272,13 +286,17 @@ pub fn start_action(action: Action, ns: &RequestingProcess) -> anyhow::Result<u3
|
|||
let action_json = serde_json::to_string(&action).unwrap();
|
||||
print_debug_string(&action_json, &ns);
|
||||
|
||||
let device_owner = get_device_owner().to_string_rep();
|
||||
|
||||
let child = unsafe {
|
||||
Command::new("/proc/self/exe")
|
||||
.args([
|
||||
"--action",
|
||||
action_json.as_str(),
|
||||
"--target-namespace",
|
||||
ns.nsroot.as_str(),
|
||||
"--target-pid",
|
||||
ns.pid_requestor_root.to_string_rep().as_str(),
|
||||
"--device-owner",
|
||||
device_owner.as_str(),
|
||||
])
|
||||
.pre_exec(|| {
|
||||
// Last resort, if the parent just is killed.
|
||||
|
|
@ -292,24 +310,40 @@ pub fn start_action(action: Action, ns: &RequestingProcess) -> anyhow::Result<u3
|
|||
Result::Ok(child.id())
|
||||
}
|
||||
|
||||
pub fn run_in_net_and_mnt_namespace(target_namespace: &str) -> anyhow::Result<()> {
|
||||
pub fn run_in_net_and_mnt_namespace(target_pid: &str, device_owner: &DeviceOwner) -> anyhow::Result<()> {
|
||||
debug!(
|
||||
"Entering namespaces of process {}. We assume this is the root process of the container.",
|
||||
target_namespace
|
||||
target_pid
|
||||
);
|
||||
|
||||
let path: &Path = Path::new(target_namespace);
|
||||
let fs_uid_gid = if *device_owner == DeviceOwner::ContainerDevFolder {
|
||||
let pid:u32 = target_pid.trim().parse()?;
|
||||
let pid = Pid::Pid(pid);
|
||||
let fs_uid=ns_fscreds::get_uid_in_container(pid, 0)?;
|
||||
let fs_gid=ns_fscreds::get_gid_in_container(pid, 0)?;
|
||||
Some((fs_uid,fs_gid))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let nspath = format!("/proc/{}/ns", target_pid);
|
||||
let path: &Path = Path::new(&nspath);
|
||||
if !fs::exists(path).unwrap() {
|
||||
return Err(anyhow!("the root process of the container whose namespaces we want to enter does not exist anymore"));
|
||||
}
|
||||
let net = File::open(target_namespace.to_string() + "/net")?;
|
||||
let mnt = File::open(target_namespace.to_string() + "/mnt")?;
|
||||
let net = File::open(nspath.to_string() + "/net")?;
|
||||
let mnt = File::open(nspath.to_string() + "/mnt")?;
|
||||
|
||||
unsafe {
|
||||
// enter namespaces
|
||||
libc::setns(net.as_raw_fd(), libc::CLONE_NEWNET);
|
||||
libc::setns(mnt.as_raw_fd(), libc::CLONE_NEWNS);
|
||||
};
|
||||
|
||||
if let Some((fs_uid,fs_gid)) = fs_uid_gid {
|
||||
ns_fscreds::acquire_uid_and_gid(fs_uid, fs_gid)?;
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -338,9 +372,6 @@ pub async fn await_process(pid: Pid) -> io::Result<i32> {
|
|||
Ok(si.si_status())
|
||||
}
|
||||
}
|
||||
Pid::SelfPid => {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
141
vuinputd/src/process_tools/ns_fscreds.rs
Normal file
141
vuinputd/src/process_tools/ns_fscreds.rs
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Author: Johannes Leupolz <dev@leupolz.eu>
|
||||
|
||||
use std::fs;
|
||||
use std::io::{self, BufRead};
|
||||
use std::path::Path;
|
||||
|
||||
use crate::process_tools::Pid;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
struct IdMapEntry {
|
||||
pub inside_start: u64,
|
||||
pub outside_start: u64,
|
||||
pub length: u64,
|
||||
}
|
||||
|
||||
fn parse_id_map(pid: u32, map_type: &str) -> io::Result<Vec<IdMapEntry>> {
|
||||
let path = format!("/proc/{}/{}", pid, map_type);
|
||||
let file = fs::File::open(&path)?;
|
||||
let reader = io::BufReader::new(file);
|
||||
|
||||
Ok(reader
|
||||
.lines()
|
||||
.filter_map(|line| {
|
||||
let line = line.ok()?;
|
||||
let mut parts = line.split_whitespace();
|
||||
let inside_start = parts.next()?.parse().ok()?;
|
||||
let outside_start = parts.next()?.parse().ok()?;
|
||||
let length = parts.next()?.parse().ok()?;
|
||||
Some(IdMapEntry {
|
||||
inside_start,
|
||||
outside_start,
|
||||
length,
|
||||
})
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn to_host_id(entries: &[IdMapEntry], inside_id: u64) -> Option<u64> {
|
||||
entries.iter().find_map(|e| {
|
||||
if inside_id >= e.inside_start && inside_id < e.inside_start + e.length {
|
||||
Some(e.outside_start + (inside_id - e.inside_start))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the host UID that corresponds to `ns_uid` (e.g. 0) inside the container.
|
||||
pub fn get_uid_in_container(pid: Pid, ns_uid: u64) -> anyhow::Result<u32> {
|
||||
let Pid::Pid(pid) = pid;
|
||||
let entries = parse_id_map(pid, "uid_map")?;
|
||||
to_host_id(&entries, ns_uid)
|
||||
.map(|id| id as u32)
|
||||
.ok_or_else(|| anyhow::anyhow!("uid {} is not mapped in /proc/{}/uid_map", ns_uid, pid))
|
||||
}
|
||||
|
||||
/// Returns the host GID that corresponds to `ns_gid` (e.g. 0) inside the container.
|
||||
pub fn get_gid_in_container(pid: Pid, ns_gid: u64) -> anyhow::Result<u32> {
|
||||
let Pid::Pid(pid) = pid;
|
||||
let entries = parse_id_map(pid, "gid_map")?;
|
||||
to_host_id(&entries, ns_gid)
|
||||
.map(|id| id as u32)
|
||||
.ok_or_else(|| anyhow::anyhow!("gid {} is not mapped in /proc/{}/gid_map", ns_gid, pid))
|
||||
}
|
||||
|
||||
/// Switch filesystem UID/GID to the given host IDs.
|
||||
/// GID must be set before UID — dropping UID=0 removes the ability to change GID.
|
||||
pub fn acquire_uid_and_gid(target_uid: u32, target_gid: u32) -> anyhow::Result<()> {
|
||||
unsafe {
|
||||
libc::setfsgid(target_gid as libc::gid_t);
|
||||
libc::setfsuid(target_uid as libc::uid_t);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Switch filesystem UID/GID to match whatever owner the given path has on the host.
|
||||
pub fn acquire_uid_and_gid_of_path(path: &str) -> anyhow::Result<()> {
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
let metadata = fs::metadata(Path::new(path))?;
|
||||
let target_uid = metadata.uid();
|
||||
let target_gid = metadata.gid();
|
||||
acquire_uid_and_gid(target_uid, target_gid)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn parse_str(s: &str) -> Vec<IdMapEntry> {
|
||||
s.lines()
|
||||
.filter_map(|line| {
|
||||
let mut parts = line.split_whitespace();
|
||||
let inside_start = parts.next()?.parse().ok()?;
|
||||
let outside_start = parts.next()?.parse().ok()?;
|
||||
let length = parts.next()?.parse().ok()?;
|
||||
Some(IdMapEntry {
|
||||
inside_start,
|
||||
outside_start,
|
||||
length,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uid0_in_rootless_container_maps_to_host_uid() {
|
||||
// Typical rootless setup: container root (0) → host uid 100000
|
||||
let map = parse_str("0 100000 65536");
|
||||
assert_eq!(to_host_id(&map, 0), Some(100000));
|
||||
assert_eq!(to_host_id(&map, 1), Some(100001));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uid_outside_range_returns_none() {
|
||||
let map = parse_str("0 100000 65536");
|
||||
assert_eq!(to_host_id(&map, 65536), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn identity_map_returns_same_id() {
|
||||
// Process not in a user namespace: 0 0 4294967295
|
||||
let map = parse_str("0 0 4294967295");
|
||||
assert_eq!(to_host_id(&map, 0), Some(0));
|
||||
assert_eq!(to_host_id(&map, 1000), Some(1000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proc_self_uid_is_parseable() {
|
||||
let uid = unsafe { libc::getuid() } as u64;
|
||||
let entries =
|
||||
parse_id_map(std::process::id(), "uid_map").expect("failed to read /proc/self/uid_map");
|
||||
assert!(
|
||||
to_host_id(&entries, uid).is_some(),
|
||||
"current uid {} not found in uid_map",
|
||||
uid
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue