From a50b25c08e880a06eb7a1ab6806535e4e95f4be2 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 9 Oct 2026 16:53:27 +0200 Subject: [PATCH] mind-body: mind-control takes --root ; the window probe runs in a tempdir Soul s3: the window probe wrote a window-probe- instance under the real /etc/gamecult/minds. A leading --root argv flag (argv, because sudo -E carries the environment) replaces the default root; the probe points the real binary at a tempdir, whose drop removes whatever a failed assert leaves. No test names /etc/gamecult any more; the path-derivation test uses CONTROL_ROOT. Co-Authored-By: Claude Opus 5.5 --- crates/mind-body/src/cli.rs | 13 +++++++++---- crates/mind-body/tests/cli.rs | 24 ++++++++++++++++++++++++ crates/mind-body/tests/control.rs | 18 +++++++++++------- 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/crates/mind-body/src/cli.rs b/crates/mind-body/src/cli.rs index e7aa841..491b64b 100644 --- a/crates/mind-body/src/cli.rs +++ b/crates/mind-body/src/cli.rs @@ -3,7 +3,7 @@ //! root-owned and the filesystem is the authority. //! //! ```text -//! mind-control --instance show +//! mind-control [--root ] --instance show //! mind-control --instance brake hold //! mind-control --instance brake release //! mind-control --instance dial set --heat H --base-cooldown-s S --base-run-usd U @@ -21,7 +21,7 @@ use rust_decimal::Decimal; use crate::control::{BurnRate, ControlWriter, FileSource, control_path, load_state, read_effective}; const USAGE: &str = - "usage: mind-control --instance show | brake hold | brake release | dial set --heat H --base-cooldown-s S --base-run-usd U"; + "usage: mind-control [--root ] --instance show | brake hold | brake release | dial set --heat H --base-cooldown-s S --base-run-usd U"; /// Who made the change: `SUDO_USER`, else `USER`, else `unknown`. An empty /// variable counts as unset. @@ -33,10 +33,15 @@ pub fn operator_name(sudo_user: Option, user: Option) -> String .unwrap_or_else(|| "unknown".to_string()) } -/// Runs one command against `//control.cc`. The root, the -/// operator's name, the clock and the output are parameters so a test drives +/// Runs one command against `//control.cc`. `root` is the +/// default; a leading `--root ` argument replaces it (argv, not environment, +/// because `sudo -E` carries the environment). The operator's name, the clock and the output are parameters so a test drives /// the whole command without touching `/etc`. pub fn run(args: &[String], root: &Path, set_by: &str, now: DateTime, out: &mut impl Write) -> Result<()> { + let (root, args) = match args { + [flag, dir, rest @ ..] if flag == "--root" => (Path::new(dir), rest), + _ => (root, args), + }; let (instance, command) = match args { [flag, instance, command @ ..] if flag == "--instance" => (Slug(instance.clone()), command), _ => bail!("{USAGE}"), diff --git a/crates/mind-body/tests/cli.rs b/crates/mind-body/tests/cli.rs index 1c01625..ff6e8ea 100644 --- a/crates/mind-body/tests/cli.rs +++ b/crates/mind-body/tests/cli.rs @@ -110,3 +110,27 @@ fn the_binary_exits_nonzero_with_the_usage_when_refused() { assert!(String::from_utf8_lossy(&output.stderr).contains("usage: mind-control")); assert!(output.stdout.is_empty()); } + +#[test] +fn a_leading_root_flag_replaces_the_default_root() { + let default = tempfile::tempdir().unwrap(); + let chosen = tempfile::tempdir().unwrap(); + let chosen_arg = chosen.path().to_str().unwrap(); + let (result, _) = go(default.path(), &["--root", chosen_arg, "--instance", "eureka", "brake", "release"], "op", 0); + result.unwrap(); + assert!(chosen.path().join("eureka").join("control.cc").exists(), "the write went to the chosen root"); + assert_eq!(std::fs::read_dir(default.path()).unwrap().count(), 0, "the default root was not touched"); + // Without the flag the default applies. + go(default.path(), &["--instance", "eureka", "brake", "release"], "op", 1).0.unwrap(); + assert!(default.path().join("eureka").join("control.cc").exists()); +} + +#[test] +fn a_root_flag_without_a_directory_or_an_instance_is_refused_and_writes_nothing() { + let default = tempfile::tempdir().unwrap(); + for args in [&["--root"][..], &["--root", "/nonexistent-root-for-test"][..]] { + assert!(go(default.path(), args, "op", 0).0.is_err(), "{args:?}"); + } + assert_eq!(std::fs::read_dir(default.path()).unwrap().count(), 0); + assert!(!std::path::Path::new("/nonexistent-root-for-test").exists()); +} diff --git a/crates/mind-body/tests/control.rs b/crates/mind-body/tests/control.rs index 1d9131e..10505d4 100644 --- a/crates/mind-body/tests/control.rs +++ b/crates/mind-body/tests/control.rs @@ -1,6 +1,7 @@ //! Behavioural tests for the brake, the dial and the one reader. Every test //! works in a temporary directory through the same file door the units use; -//! nothing touches `/etc` except the window probe, which writes a uniquely named +//! nothing touches `/etc`, including the window probe, which points the real +//! binary at a tempdir with `--root`. //! instance there as root and removes it. //! //! The permission boundary is the filesystem's, but the writer refuses to write @@ -290,7 +291,7 @@ fn reading_writes_nothing_and_takes_no_lock_file() { #[test] fn the_control_path_is_derived_from_a_valid_instance_only() { use eureka_pipeline::Slug; - let root = Path::new("/etc/gamecult/minds"); + let root = Path::new(mind_body::control::CONTROL_ROOT); assert_eq!(control_path(root, &Slug("eureka".into())).unwrap(), root.join("eureka").join("control.cc")); for bad in ["", "../etc", "a/b", "A b", "eureka/.."] { assert!(control_path(root, &Slug(bad.into())).is_err(), "{bad:?}"); @@ -545,13 +546,17 @@ mod unsafe_installs { #[test] fn no_non_root_open_for_write_succeeds_while_the_binary_writes_under_umask_000() { if !running_as_root() || Command::new("setpriv").arg("--version").output().is_err() { - eprintln!("SKIPPED: needs root and setpriv; it writes under /etc/gamecult/minds in the verify container"); + eprintln!("SKIPPED: needs root and setpriv"); return; } let instance = format!("window-probe-{}", std::process::id()); - let live = Path::new(mind_body::control::CONTROL_ROOT).join(&instance); + // A root-owned 0755 tempdir is the control root; its drop removes whatever + // the probe left, even after a failed assert. let work = tempfile::tempdir().unwrap(); chmod(work.path(), 0o755); + let minds = work.path().join("minds"); + std::fs::create_dir(&minds).unwrap(); + chmod(&minds, 0o755); let attacker_bin = work.path().join("attacker"); std::fs::copy(std::env::current_exe().unwrap(), &attacker_bin).unwrap(); chmod(&attacker_bin, 0o755); @@ -561,7 +566,7 @@ mod unsafe_installs { .arg(&attacker_bin) .args(["--exact", "modes::mode_child", "--nocapture"]) .env("MIND_BODY_MODE_CHILD", "attacker") - .env("MIND_BODY_MODE_ROOT", live.parent().unwrap()) + .env("MIND_BODY_MODE_ROOT", &minds) .env("MIND_BODY_STOP", &stop) .env("MIND_BODY_INSTANCE", &instance) .spawn() @@ -570,14 +575,13 @@ mod unsafe_installs { for round in 0..100 { let verb = if round % 2 == 0 { "release" } else { "hold" }; let status = Command::new("sh") - .args(["-c", "umask 000; exec \"$@\"", "sh", bin, "--instance", &instance, "brake", verb]) + .args(["-c", "umask 000; exec \"$@\"", "sh", bin, "--root", minds.to_str().unwrap(), "--instance", &instance, "brake", verb]) .status() .unwrap(); assert!(status.success()); } std::fs::write(&stop, b"").unwrap(); let attacker_status = attacker.wait().unwrap(); - std::fs::remove_dir_all(&live).unwrap(); assert_eq!(attacker_status.code(), Some(0), "a non-root process opened the live store for write"); } }