Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

server/sandbox/src/pids.rs

//! **The scan sidecar's process bound** (RA5-27): what stops a fork bomb in a job.
//!
//! `RLIMIT_NPROC` is no answer: it counts every process of the sidecar's uid in its user
//! namespace, which in a pod without user namespaces is node-wide (other containers that
//! run as 65532 count too), so a bound set that way starves the worker for reasons the
//! sidecar cannot see. The bound is a cgroup's `pids.max`, decided once when the sidecar
//! starts serving and named in every ping:
//!
//! | Case | When | What bounds a job |
//! |---|---|---|
//! | [`PidsBound::PerJob`] | the sidecar's cgroup v2 is delegated to it: `pids` in its `cgroup.controllers`, and the directory, `cgroup.procs` and `cgroup.subtree_control` writable; and a probe job proved it cannot leave its cgroup ([`prove_sealed`]) | the sidecar moves itself into `<own>/serve`, turns `+pids` on for its children, and runs each job in its own `<own>/job-<n>` with `pids.max` = the job's `pids`, sealed in its own cgroup namespace; after the job, `cgroup.kill` and `rmdir` |
//! | [`PidsBound::Container`] | the cgroup is read-only (a default podman or Kubernetes container), and its `pids.max` (or an ancestor's) is a number | the container's own limit, shared by every job: at most `max_jobs` times a worker's processes |
//! | [`PidsBound::Pod`] | neither, and the operator names the pod's limit (`serve --pod-pids-limit <n>`, the chart's `scanWorker.podPidsLimit`) | the kubelet's `podPidsLimit`: it sits on the POD's cgroup, above the container's cgroup namespace, so the sidecar cannot read it and takes it as stated |
//! | [`PidsBound::Unbounded`] | none of these | nothing: a RELEASE build refuses every ping and job by name (`holger-sandbox-13`); a dev build warns and runs |
//!
//! A job's processes join their cgroup between fork and exec: the parent opens the job's
//! `cgroup.procs` before the fork, and the child writes `0` (itself) to it, a plain write.
//!
//! **The seal** (RA5-27): the worker runs as the sidecar's own user, so every file of the
//! delegated cgroup is its to write. Joined, it could move itself back into `<own>/serve`
//! or write `max` to its own job's `pids.max`. So right after joining, the child unshares a
//! cgroup namespace ([`seal_in_child`]): its job's cgroup is the namespace's root, and with
//! the hierarchy mounted `nsdelegate` (systemd's default, and what a container inherits) the
//! kernel refuses a move to any cgroup outside that root and a write to the root's own
//! interface files. Without `CAP_SYS_ADMIN` the namespace is made together with a user
//! namespace that maps the worker's own uid and gid to themselves, as `unshare -r -C` does.
//! The sidecar proves the seal once, with a probe job, before it takes the per-job case
//! ([`prove_sealed`]); a seal that does not hold (no user namespaces, no `nsdelegate`) is no
//! per-job bound, and the container's limit holds instead.

use std::ffi::CStr;
use std::os::fd::{AsRawFd as _, OwnedFd, RawFd};
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Duration;

/// Where the cgroup hierarchy is mounted.
pub const CGROUP_FS: &str = "/sys/fs/cgroup";

/// The words a refusal for [`PidsBound::Unbounded`] gives: what to set.
pub const UNBOUNDED_FIX: &str = "the scan sidecar's container has no pids limit and no delegated cgroup; \
     set the container's pids limit (podman --pids-limit), or set the kubelet's podPidsLimit and name it \
     to the sidecar (serve --pod-pids-limit <n>, the chart's scanWorker.podPidsLimit), \
     or delegate the sidecar a cgroup v2 with the pids controller";

/// How the sidecar bounds its jobs' processes.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PidsBound {
    /// A delegated cgroup v2 (`own`): each job runs in its own `own/job-<n>`.
    PerJob { own: PathBuf },
    /// The container's own `pids.max`, shared by every job.
    Container { max: u64 },
    /// The pod's limit as the operator states it (`--pod-pids-limit`): out of the sidecar's sight.
    Pod { max: u64 },
    /// No bound at all.
    Unbounded,
}

impl PidsBound {
    /// The words a ping carries.
    pub fn words(&self) -> String {
        match self {
            PidsBound::PerJob { own } => format!(
                "pids: a cgroup per job under {}, each sealed in its own cgroup namespace",
                own.display()
            ),
            PidsBound::Container { max } => format!("pids: the container's limit, {max}"),
            PidsBound::Pod { max } => {
                format!("pids: the pod's limit as the operator states it, {max}")
            }
            PidsBound::Unbounded => {
                "pids: NO LIMIT (a dev build runs; a release build refuses)".into()
            }
        }
    }
}

/// What the cgroup files say: the decision's input.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Seen {
    /// The sidecar's own cgroup v2 directory, when `/proc/self/cgroup` names one.
    pub own: Option<PathBuf>,
    /// That directory is the sidecar's to write, with the pids controller.
    pub delegated: bool,
    /// The lowest numeric `pids.max` from the sidecar's cgroup up to the mount's root
    /// (cgroup v2, or v1's `pids` hierarchy).
    pub container_max: Option<u64>,
}

/// **The decision**, pure: a delegated cgroup first, then the container's limit, else none.
pub fn decide(seen: &Seen) -> PidsBound {
    match (&seen.own, seen.delegated, seen.container_max) {
        (Some(own), true, _) => PidsBound::PerJob { own: own.clone() },
        (_, _, Some(max)) => PidsBound::Container { max },
        _ => PidsBound::Unbounded,
    }
}

fn writable(p: &Path) -> bool {
    rustix::fs::access(p, rustix::fs::Access::WRITE_OK).is_ok()
}

/// The cgroup directory a `/proc/self/cgroup` line names under `fs`, if it stays under it.
fn under(fs: &Path, rel: &str) -> Option<PathBuf> {
    let rel = rel.trim().trim_start_matches('/');
    if rel.split('/').any(|s| s == "..") {
        return None;
    }
    Some(if rel.is_empty() {
        fs.to_path_buf()
    } else {
        fs.join(rel)
    })
}

/// The lowest numeric `pids.max` from `dir` up to `root`.
fn lowest_max(dir: &Path, root: &Path) -> Option<u64> {
    let mut lowest: Option<u64> = None;
    let mut at = Some(dir);
    while let Some(d) = at {
        if !d.starts_with(root) {
            break;
        }
        if let Some(n) = std::fs::read_to_string(d.join("pids.max"))
            .ok()
            .and_then(|s| s.trim().parse::<u64>().ok())
        {
            lowest = Some(lowest.map_or(n, |l| l.min(n)));
        }
        if d == root {
            break;
        }
        at = d.parent();
    }
    lowest
}

/// **Read what the cgroup files say**: `fs` is the cgroup mount (`/sys/fs/cgroup`),
/// `proc_cgroup` the text of `/proc/self/cgroup`.
pub fn look(fs: &Path, proc_cgroup: &str) -> Seen {
    let v2 = proc_cgroup
        .lines()
        .find_map(|l| l.strip_prefix("0::"))
        .and_then(|rel| under(fs, rel));
    if let Some(own) = v2 {
        let controllers =
            std::fs::read_to_string(own.join("cgroup.controllers")).unwrap_or_default();
        let delegated = own.is_dir()
            && controllers.split_whitespace().any(|c| c == "pids")
            && writable(&own)
            && writable(&own.join("cgroup.procs"))
            && writable(&own.join("cgroup.subtree_control"));
        let container_max = lowest_max(&own, fs);
        return Seen {
            own: Some(own),
            delegated,
            container_max,
        };
    }
    // cgroup v1: the `pids` hierarchy's limit only; v1 is never delegated here.
    let v1 = proc_cgroup.lines().find_map(|l| {
        let mut parts = l.splitn(3, ':');
        let (_, controllers, rel) = (parts.next()?, parts.next()?, parts.next()?);
        controllers
            .split(',')
            .any(|c| c == "pids")
            .then(|| under(&fs.join("pids"), rel))
            .flatten()
    });
    Seen {
        own: None,
        delegated: false,
        container_max: v1.and_then(|d| lowest_max(&d, &fs.join("pids"))),
    }
}

fn write(p: &Path, what: &str) -> std::io::Result<()> {
    std::fs::write(p, what)
}

/// **Decide, and set the delegated case up**: move every process of the sidecar's cgroup
/// into `<own>/serve` (a cgroup v2 with children holds no processes itself), turn the
/// pids controller on for the children, and prove with a probe job that a job cannot
/// leave its cgroup ([`prove_sealed`]). Should a step fail, the case falls back to the
/// container's limit (or none), as if the cgroup were not delegated.
pub fn establish(fs: &Path, proc_cgroup: &str) -> PidsBound {
    establish_proving(fs, proc_cgroup, prove_sealed)
}

/// [`establish`] with the seal's proof given (a test's).
pub fn establish_proving(
    fs: &Path,
    proc_cgroup: &str,
    prove: impl FnOnce(&Path) -> Result<(), String>,
) -> PidsBound {
    let seen = look(fs, proc_cgroup);
    let bound = decide(&seen);
    let PidsBound::PerJob { own } = &bound else {
        return bound;
    };
    let set_up = (|| -> std::io::Result<()> {
        let serve = own.join("serve");
        match std::fs::create_dir(&serve) {
            Ok(()) => {}
            Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {}
            Err(e) => return Err(e),
        }
        write(&serve.join("cgroup.procs"), &std::process::id().to_string())?;
        // Every other process of the cgroup follows. One that exits meanwhile is gone
        // (ESRCH); one forked meanwhile keeps the controller busy (EBUSY): move again.
        let mut last = None;
        for _ in 0..20 {
            let procs = std::fs::read_to_string(own.join("cgroup.procs")).unwrap_or_default();
            for pid in procs.split_whitespace() {
                match write(&serve.join("cgroup.procs"), pid) {
                    Err(e) if e.raw_os_error() != Some(rustix::io::Errno::SRCH.raw_os_error()) => {
                        return Err(e)
                    }
                    _ => {}
                }
            }
            match write(&own.join("cgroup.subtree_control"), "+pids") {
                Err(e) if e.raw_os_error() == Some(rustix::io::Errno::BUSY.raw_os_error()) => {
                    last = Some(e)
                }
                done => return done,
            }
            std::thread::sleep(Duration::from_millis(5));
        }
        Err(last.unwrap_or_else(|| std::io::Error::other("the cgroup stayed busy")))
    })();
    let fallback = |why: String| {
        eprintln!("holger-scan-worker: {why}; the container's limit holds instead");
        decide(&Seen {
            delegated: false,
            ..seen.clone()
        })
    };
    match set_up {
        Err(e) => fallback(format!(
            "the delegated cgroup {} could not be set up ({e})",
            own.display()
        )),
        Ok(()) => match prove(own) {
            Ok(()) => bound,
            Err(why) => fallback(format!(
                "a job in the delegated cgroup {} could leave it ({why})",
                own.display()
            )),
        },
    }
}

static NEXT_JOB: AtomicU64 = AtomicU64::new(1);

/// **One job's cgroup** (`<own>/job-<n>`, `pids.max` the job's bound). Its `cgroup.procs`
/// is open before the fork; the child writes `0` to it. Dropped: `cgroup.kill`, then removed.
#[derive(Debug)]
pub struct JobCgroup {
    dir: PathBuf,
    procs: OwnedFd,
}

impl JobCgroup {
    pub fn create(own: &Path, pids: u64) -> std::io::Result<Self> {
        let dir = own.join(format!("job-{}", NEXT_JOB.fetch_add(1, Ordering::Relaxed)));
        std::fs::create_dir(&dir)?;
        let made = (|| {
            write(&dir.join("pids.max"), &pids.to_string())?;
            let procs = rustix::fs::open(
                dir.join("cgroup.procs"),
                // CREATE only matters off a cgroupfs (a test's tempdir): there the file is the kernel's.
                rustix::fs::OFlags::WRONLY
                    | rustix::fs::OFlags::CLOEXEC
                    | rustix::fs::OFlags::CREATE,
                rustix::fs::Mode::from_raw_mode(0o644),
            )?;
            Ok::<_, std::io::Error>(procs)
        })();
        match made {
            Ok(procs) => Ok(Self { dir, procs }),
            Err(e) => {
                let _ = std::fs::remove_dir(&dir);
                Err(e)
            }
        }
    }

    pub fn dir(&self) -> &Path {
        &self.dir
    }

    /// The open `cgroup.procs`, for the child to write `0` to between fork and exec.
    pub fn procs_fd(&self) -> RawFd {
        self.procs.as_raw_fd()
    }
}

impl Drop for JobCgroup {
    fn drop(&mut self) {
        // Every process the job left behind dies with its cgroup.
        let _ = write(&self.dir.join("cgroup.kill"), "1");
        for _ in 0..100 {
            match std::fs::remove_dir(&self.dir) {
                // A cgroup with live processes is EBUSY until they are gone.
                Err(e) if e.raw_os_error() == Some(rustix::io::Errno::BUSY.raw_os_error()) => {
                    std::thread::sleep(Duration::from_millis(10))
                }
                _ => break,
            }
        }
    }
}

/// **What a job's seal writes** (RA5-27), made before the fork: the maps of a user namespace
/// that maps the worker's own uid and gid to themselves.
#[derive(Debug, Clone)]
pub struct Seal {
    uid_map: Vec<u8>,
    gid_map: Vec<u8>,
}

impl Seal {
    /// The maps for this process's effective uid and gid.
    pub fn new() -> Self {
        let (uid, gid) = (
            rustix::process::geteuid().as_raw(),
            rustix::process::getegid().as_raw(),
        );
        Self {
            uid_map: format!("{uid} {uid} 1\n").into_bytes(),
            gid_map: format!("{gid} {gid} 1\n").into_bytes(),
        }
    }
}

impl Default for Seal {
    fn default() -> Self {
        Self::new()
    }
}

/// Open, write, close: async-signal-safe, no allocation (a `CStr` path).
fn write_in_child(path: &CStr, what: &[u8]) -> std::io::Result<()> {
    let fd = rustix::fs::open(
        path,
        rustix::fs::OFlags::WRONLY | rustix::fs::OFlags::CLOEXEC,
        rustix::fs::Mode::empty(),
    )?;
    rustix::io::write(&fd, what)?;
    Ok(())
}

/// Between fork and exec, right after [`join_in_child`]: **seal the job in its cgroup**. The
/// child unshares a cgroup namespace, whose root is the cgroup it is in, its job's. Without
/// `CAP_SYS_ADMIN` that takes a user namespace along, which maps its own uid and gid to
/// themselves (`setgroups` denied first, as an unprivileged gid map needs).
///
/// # Safety
/// Only async-signal-safe system calls are made (unshare, open, write, close), with no
/// allocation: `seal`'s maps were made before the fork.
pub unsafe fn seal_in_child(seal: &Seal) -> std::io::Result<()> {
    use rustix::thread::{unshare_unsafe, UnshareFlags};
    // SAFETY: the child is single-threaded between fork and exec; nothing it shares with
    // another thread is unshared.
    match unsafe { unshare_unsafe(UnshareFlags::NEWCGROUP) } {
        Ok(()) => return Ok(()),
        Err(e) if e == rustix::io::Errno::PERM => {}
        Err(e) => return Err(e.into()),
    }
    unsafe { unshare_unsafe(UnshareFlags::NEWUSER | UnshareFlags::NEWCGROUP) }?;
    write_in_child(c"/proc/self/uid_map", &seal.uid_map)?;
    write_in_child(c"/proc/self/setgroups", b"deny")?;
    write_in_child(c"/proc/self/gid_map", &seal.gid_map)?;
    Ok(())
}

/// The probe's answers through `spawn`'s error: the escape was refused, or it was not.
const PROBE_HELD: rustix::io::Errno = rustix::io::Errno::CANCELED;
const PROBE_ESCAPED: rustix::io::Errno = rustix::io::Errno::NOTRECOVERABLE;

/// **Prove the seal** (RA5-27): a probe job joins its own cgroup under `own`, seals itself
/// ([`seal_in_child`]) and tries what a hostile worker would: write `max` to its cgroup's
/// `pids.max`, and move itself into `own/serve`. Both must be refused. The probe opens both
/// files only after the seal, as the worker would: the kernel judges a cgroup file write by
/// the namespace its opener was in, so a file opened before the seal would let it out (and
/// none such is ever left open to a job: the job's `cgroup.procs` is close-on-exec). The
/// probe never execs: it answers through the error `spawn` hands back. `Err` says why the
/// seal does not hold (no namespace could be made, or the hierarchy is not mounted
/// `nsdelegate`).
pub fn prove_sealed(own: &Path) -> Result<(), String> {
    use std::os::unix::ffi::OsStrExt as _;
    let job = JobCgroup::create(own, 8).map_err(|e| format!("the probe's cgroup: {e}"))?;
    let c_path = |p: PathBuf| {
        std::ffi::CString::new(p.as_os_str().as_bytes())
            .map_err(|e| format!("{}: {e}", p.display()))
    };
    let serve = c_path(own.join("serve").join("cgroup.procs"))?;
    let max = c_path(job.dir().join("pids.max"))?;
    let join = job.procs_fd();
    let seal = Seal::new();
    // A32-14, a process by design: the probe is a forked child that never execs.
    let mut cmd = std::process::Command::new("/proc/self/exe");
    cmd.stdin(std::process::Stdio::null())
        .stdout(std::process::Stdio::null())
        .stderr(std::process::Stdio::null());
    {
        use std::os::unix::process::CommandExt as _;
        // SAFETY: only async-signal-safe system calls between fork and exec (write, unshare,
        // open, close), with no allocation: the paths and the maps were made before the fork.
        unsafe {
            cmd.pre_exec(move || {
                join_in_child(join)?;
                seal_in_child(&seal)?;
                let escaped =
                    write_in_child(&max, b"max").is_ok() | write_in_child(&serve, b"0").is_ok();
                Err((if escaped { PROBE_ESCAPED } else { PROBE_HELD }).into())
            });
        }
    }
    // A32-14, a process by design: the probe's fork (see its command above); it answers through the error.
    let answer = match cmd.spawn() {
        Ok(mut child) => {
            let _ = child.kill();
            let _ = child.wait();
            Err("the probe ran past its seal".to_string())
        }
        Err(e) if e.raw_os_error() == Some(PROBE_HELD.raw_os_error()) => Ok(()),
        Err(e) if e.raw_os_error() == Some(PROBE_ESCAPED.raw_os_error()) => {
            Err("a sealed job could still move out of its cgroup or raise its pids.max: the cgroup hierarchy is not mounted nsdelegate".to_string())
        }
        Err(e) => Err(format!("a job's cgroup namespace could not be made: {e}")),
    };
    drop(job);
    answer
}

/// Between fork and exec: join the job's cgroup through its open `cgroup.procs`.
///
/// # Safety
/// `fd` must be open in the child; only a `write` is made (async-signal-safe, no allocation).
pub unsafe fn join_in_child(fd: RawFd) -> std::io::Result<()> {
    let fd = unsafe { std::os::fd::BorrowedFd::borrow_raw(fd) };
    rustix::io::write(fd, b"0")?;
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    /// A cgroup v2 directory in a tempdir: its controllers, its pids.max, its procs.
    fn cgroup(dir: &Path, controllers: &str, max: &str) {
        std::fs::create_dir_all(dir).unwrap();
        std::fs::write(dir.join("cgroup.controllers"), controllers).unwrap();
        std::fs::write(dir.join("cgroup.subtree_control"), "").unwrap();
        std::fs::write(
            dir.join("cgroup.procs"),
            format!("{}\n", std::process::id()),
        )
        .unwrap();
        std::fs::write(dir.join("pids.max"), max).unwrap();
    }

    #[test]
    fn the_decision_takes_a_delegated_cgroup_then_the_containers_limit_then_none() {
        let own = PathBuf::from("/sys/fs/cgroup/pod/sidecar");
        assert_eq!(
            decide(&Seen {
                own: Some(own.clone()),
                delegated: true,
                container_max: Some(2048)
            }),
            PidsBound::PerJob { own: own.clone() }
        );
        assert_eq!(
            decide(&Seen {
                own: Some(own.clone()),
                delegated: false,
                container_max: Some(2048)
            }),
            PidsBound::Container { max: 2048 }
        );
        assert_eq!(
            decide(&Seen {
                own: None,
                delegated: false,
                container_max: Some(512)
            }),
            PidsBound::Container { max: 512 }
        );
        assert_eq!(
            decide(&Seen {
                own: Some(own),
                delegated: false,
                container_max: None
            }),
            PidsBound::Unbounded
        );
        assert_eq!(decide(&Seen::default()), PidsBound::Unbounded);
    }

    #[test]
    fn a_read_only_cgroup_with_a_number_is_the_containers_limit_and_max_is_none() {
        let fs = tempfile::tempdir().unwrap();
        cgroup(fs.path(), "cpu memory pids", "2048\n");
        let seen = look(fs.path(), "0::/\n");
        assert_eq!(seen.container_max, Some(2048));
        // A tempdir is writable: the controllers decide here; without `pids` nothing is delegated.
        cgroup(fs.path(), "cpu memory", "2048\n");
        assert_eq!(
            decide(&look(fs.path(), "0::/\n")),
            PidsBound::Container { max: 2048 }
        );
        cgroup(fs.path(), "cpu memory", "max\n");
        assert_eq!(decide(&look(fs.path(), "0::/\n")), PidsBound::Unbounded);
        // An ancestor's number bounds a child that says `max`.
        cgroup(&fs.path().join("pod/c"), "cpu", "max\n");
        assert_eq!(
            look(fs.path(), "0::/pod/c\n").container_max,
            None,
            "the root here says max too"
        );
        std::fs::write(fs.path().join("pod/pids.max"), "300\n").unwrap();
        assert_eq!(look(fs.path(), "0::/pod/c\n").container_max, Some(300));
        // A path that climbs out of the mount names nothing.
        assert_eq!(look(fs.path(), "0::/../etc\n").own, None);
    }

    #[test]
    fn cgroup_v1_reads_the_pids_hierarchys_limit_and_is_never_delegated() {
        let fs = tempfile::tempdir().unwrap();
        cgroup(&fs.path().join("pids/docker/abc"), "", "100\n");
        let seen = look(fs.path(), "12:pids:/docker/abc\n11:memory:/docker/abc\n");
        assert_eq!(
            seen,
            Seen {
                own: None,
                delegated: false,
                container_max: Some(100)
            }
        );
        assert_eq!(decide(&seen), PidsBound::Container { max: 100 });
    }

    #[test]
    fn a_delegated_cgroup_moves_the_sidecar_into_serve_and_turns_pids_on() {
        let fs = tempfile::tempdir().unwrap();
        let own = fs.path().join("pod/sidecar");
        cgroup(&own, "cpu memory pids", "max\n");
        let bound = establish_proving(fs.path(), "0::/pod/sidecar\n", |_| Ok(()));
        assert_eq!(bound, PidsBound::PerJob { own: own.clone() });
        assert_eq!(
            std::fs::read_to_string(own.join("serve/cgroup.procs")).unwrap(),
            std::process::id().to_string()
        );
        assert_eq!(
            std::fs::read_to_string(own.join("cgroup.subtree_control")).unwrap(),
            "+pids"
        );
        assert!(
            bound.words().contains("a cgroup per job"),
            "{}",
            bound.words()
        );
    }

    /// ★ RA5-27: a delegated cgroup whose jobs could leave it (the seal's proof fails) is no
    /// per-job bound: the container's limit holds, or none.
    #[test]
    fn a_delegated_cgroup_a_job_could_leave_falls_back_to_the_containers_limit() {
        let fs = tempfile::tempdir().unwrap();
        let own = fs.path().join("pod/sidecar");
        cgroup(&own, "cpu memory pids", "512\n");
        let bound = establish_proving(fs.path(), "0::/pod/sidecar\n", |_| Err("not sealed".into()));
        assert_eq!(bound, PidsBound::Container { max: 512 });
        std::fs::write(own.join("pids.max"), "max\n").unwrap();
        assert_eq!(
            establish_proving(fs.path(), "0::/pod/sidecar\n", |_| Err("not sealed".into())),
            PidsBound::Unbounded
        );
        // A tempdir is no cgroup: the real proof sees the probe's writes land, or no namespace made.
        assert!(
            prove_sealed(&own).is_err(),
            "a plain directory holds no job"
        );
    }

    #[test]
    fn the_seal_maps_the_workers_own_ids() {
        let s = Seal::new();
        let uid = rustix::process::geteuid().as_raw();
        assert_eq!(
            String::from_utf8(s.uid_map).unwrap(),
            format!("{uid} {uid} 1\n")
        );
    }

    #[test]
    fn a_job_cgroup_carries_the_jobs_bound_and_is_killed_after() {
        let fs = tempfile::tempdir().unwrap();
        let job = JobCgroup::create(fs.path(), 64).expect("the job's cgroup");
        let dir = job.dir().to_path_buf();
        assert!(
            dir.file_name()
                .unwrap()
                .to_string_lossy()
                .starts_with("job-"),
            "{}",
            dir.display()
        );
        assert_eq!(std::fs::read_to_string(dir.join("pids.max")).unwrap(), "64");
        unsafe { join_in_child(job.procs_fd()) }.expect("the write");
        assert_eq!(
            std::fs::read_to_string(dir.join("cgroup.procs")).unwrap(),
            "0"
        );
        drop(job);
        assert_eq!(
            std::fs::read_to_string(dir.join("cgroup.kill")).unwrap(),
            "1",
            "the job's leftovers are killed"
        );
    }

    #[test]
    fn the_ping_words_name_the_bound() {
        assert_eq!(
            PidsBound::Container { max: 2048 }.words(),
            "pids: the container's limit, 2048"
        );
        assert!(PidsBound::Unbounded.words().contains("NO LIMIT"));
    }
}