#![cfg(windows)]
use iron_shell::env::Env;
use iron_shell::value::Value;
use std::collections::BTreeMap;
use std::process::Command;
use std::sync::Mutex;
static SERIAL: Mutex<()> = Mutex::new(());
fn disposable() -> bool {
std::env::var("AE_DISPOSABLE_HOST").as_deref() == Ok("1")
}
fn s(text: &str) -> Value {
Value::Str(text.to_string())
}
fn argv(items: &[&str]) -> Value {
Value::Array(items.iter().map(|item| s(item)).collect())
}
fn options(timeout_ms: i64) -> Value {
Value::Record(BTreeMap::from([(
"timeout_ms".to_string(),
Value::Int(timeout_ms),
)]))
}
fn fields(value: Value) -> BTreeMap<String, Value> {
match value {
Value::Record(map) => map,
other => panic!("expected a Record, got {other:?}"),
}
}
fn text(map: &BTreeMap<String, Value>, key: &str) -> String {
match map.get(key) {
Some(Value::Str(s)) => s.clone(),
other => panic!("{key}: expected a String, got {other:?}"),
}
}
fn require_elevated(env: &mut Env) {
let elevated = iron_shell::builtins::call("sudo_check", vec![], env).unwrap();
assert_eq!(elevated, Value::Bool(true), "the runner must be elevated");
}
fn ping_count() -> usize {
let out = Command::new("tasklist")
.args(["/FI", "IMAGENAME eq PING.EXE", "/FO", "CSV", "/NH"])
.output()
.expect("tasklist runs on the runner");
String::from_utf8_lossy(&out.stdout)
.lines()
.filter(|line| line.to_ascii_lowercase().contains("ping.exe"))
.count()
}
#[test]
#[ignore = "runs commands: disposable Windows runner only (AE_DISPOSABLE_HOST=1)"]
fn elevated_command_runs_in_a_high_integrity_token() {
if !disposable() {
return;
}
let _lock = SERIAL.lock().unwrap_or_else(|e| e.into_inner());
let mut env = Env::default();
require_elevated(&mut env);
let run = fields(
iron_shell::builtins::call(
"sudo_exec",
vec![argv(&[r"C:\Windows\System32\whoami.exe", "/groups"])],
&mut env,
)
.unwrap(),
);
assert_eq!(run.get("exit_code"), Some(&Value::Int(0)));
assert_eq!(run.get("timed_out"), Some(&Value::Bool(false)));
assert!(
text(&run, "stdout").contains("S-1-16-12288"),
"expected the High mandatory level in {:?}",
text(&run, "stdout")
);
}
#[test]
#[ignore = "runs commands: disposable Windows runner only (AE_DISPOSABLE_HOST=1)"]
fn nonzero_exit_is_reported_in_exit_code() {
if !disposable() {
return;
}
let _lock = SERIAL.lock().unwrap_or_else(|e| e.into_inner());
let mut env = Env::default();
require_elevated(&mut env);
let run = fields(
iron_shell::builtins::call(
"sudo_exec",
vec![argv(&[r"C:\Windows\System32\cmd.exe", "/c", "exit 3"])],
&mut env,
)
.unwrap(),
);
assert_eq!(run.get("exit_code"), Some(&Value::Int(3)));
assert_eq!(run.get("timed_out"), Some(&Value::Bool(false)));
}
#[test]
#[ignore = "runs commands: disposable Windows runner only (AE_DISPOSABLE_HOST=1)"]
fn missing_file_argument_is_a_nonzero_exit_with_stderr() {
if !disposable() {
return;
}
let _lock = SERIAL.lock().unwrap_or_else(|e| e.into_inner());
let mut env = Env::default();
require_elevated(&mut env);
let run = fields(
iron_shell::builtins::call(
"sudo_exec",
vec![argv(&[
r"C:\Windows\System32\where.exe",
"no-such-ae-thing",
])],
&mut env,
)
.unwrap(),
);
assert!(
matches!(run.get("exit_code"), Some(Value::Int(code)) if *code != 0),
"{run:?}"
);
assert!(!text(&run, "stderr").is_empty(), "{run:?}");
assert_eq!(run.get("timed_out"), Some(&Value::Bool(false)));
}
#[test]
#[ignore = "runs commands: disposable Windows runner only (AE_DISPOSABLE_HOST=1)"]
fn timeout_kills_the_command_and_its_tree() {
if !disposable() {
return;
}
let _lock = SERIAL.lock().unwrap_or_else(|e| e.into_inner());
let mut env = Env::default();
require_elevated(&mut env);
let before = ping_count();
let started = std::time::Instant::now();
let run = fields(
iron_shell::builtins::call(
"sudo_exec",
vec![
argv(&[r"C:\Windows\System32\ping.exe", "-n", "30", "127.0.0.1"]),
options(500),
],
&mut env,
)
.unwrap(),
);
let elapsed = started.elapsed();
assert_eq!(run.get("timed_out"), Some(&Value::Bool(true)));
assert_eq!(run.get("exit_code"), Some(&Value::Null));
assert!(elapsed < std::time::Duration::from_secs(5), "{elapsed:?}");
assert!(
ping_count() <= before,
"a ping.exe started by the call is still running"
);
}