mind-body: mind-control takes --root <dir>; the window probe runs in a tempdir
Soul s3: the window probe wrote a window-probe-<pid> 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 <noreply@anthropic.com>
This commit is contained in:
parent
44c5410bbf
commit
a50b25c08e
3 changed files with 44 additions and 11 deletions
|
|
@ -3,7 +3,7 @@
|
|||
//! root-owned and the filesystem is the authority.
|
||||
//!
|
||||
//! ```text
|
||||
//! mind-control --instance <slug> show
|
||||
//! mind-control [--root <dir>] --instance <slug> show
|
||||
//! mind-control --instance <slug> brake hold
|
||||
//! mind-control --instance <slug> brake release
|
||||
//! mind-control --instance <slug> 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 <slug> show | brake hold | brake release | dial set --heat H --base-cooldown-s S --base-run-usd U";
|
||||
"usage: mind-control [--root <dir>] --instance <slug> 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<String>, user: Option<String>) -> String
|
|||
.unwrap_or_else(|| "unknown".to_string())
|
||||
}
|
||||
|
||||
/// Runs one command against `<root>/<instance>/control.cc`. The root, the
|
||||
/// operator's name, the clock and the output are parameters so a test drives
|
||||
/// Runs one command against `<root>/<instance>/control.cc`. `root` is the
|
||||
/// default; a leading `--root <dir>` 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<Utc>, 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}"),
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue