use std::ffi::{CStr, CString, c_char, c_int};
use std::fs;
use std::io::{Read, Write};
use std::net::Shutdown;
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::{FileTypeExt, MetadataExt};
use std::os::unix::net::UnixStream;
use std::os::unix::process::CommandExt;
use std::process::{Child, Command, Stdio};
use std::thread::JoinHandle;
use std::time::{Duration, Instant};
use static_assertions::assert_not_impl_any;
use super::*;
const FIXTURE_ENV: &[u8] = b"NATIVE_IPC_TEST_EXACT_BROKER_LAUNCHER\0";
const VALID_EXEC: &[u8] = b"valid-exec";
const POST_EXEC: &[u8] = b"post-exec";
const TARGET_STOP: &[u8] = b"target-stop";
const POST_EXEC_STOP: &[u8] = b"post-exec-stop";
const TARGET_DELAY: &[u8] = b"target-delay";
const POST_EXEC_DELAY: &[u8] = b"post-exec-delay";
const TARGET_SIGKILL: &[u8] = b"target-sigkill";
const POST_EXEC_SIGKILL: &[u8] = b"post-exec-sigkill";
const FAKE_TRAP: &[u8] = b"fake-trap";
const UNEXPECTED_STOP: &[u8] = b"unexpected-stop";
const UNTRACED_STOP: &[u8] = b"untraced-stop";
const SUBSTITUTE_EXEC: &[u8] = b"substitute-exec";
const EXACT_TEST: &str =
"backend::macos::supervisor::broker_entry::broker_launcher::tests::fixture_target";
const DEPLOYER_LAUNCHER_PATH: &CStr =
c"/example/NativeIPC.app/Contents/Helpers/native-ipc-launcher";
const PRODUCTION_BROKER_FIXTURE_SUFFIX: &[u8] = b".native-ipc-production-broker-fixture";
const PT_TRACE_ME: c_int = 0;
const ENOENT: c_int = 2;
const ECHILD: c_int = 10;
const WNOHANG: c_int = 1;
const F_DUPFD: c_int = 0;
const F_GETFD: c_int = 1;
const F_SETFD: c_int = 2;
const O_ACCMODE: c_int = 3;
const FD_CLOEXEC: c_int = 1;
const MACH_PORT_TYPE_SEND: u32 = 1 << 16;
const MACH_PORT_TYPE_RECEIVE: u32 = 1 << 17;
const MACH_PORT_TYPE_DEAD_NAME: u32 = 1 << 20;
unsafe extern "C" {
fn _exit(status: c_int) -> !;
fn getdtablesize() -> c_int;
fn getenv(name: *const c_char) -> *mut c_char;
fn getegid() -> u32;
fn getgid() -> u32;
fn geteuid() -> u32;
fn getuid() -> u32;
fn mach_port_type(task: u32, name: u32, port_type: *mut u32) -> c_int;
fn pipe(descriptors: *mut c_int) -> c_int;
fn raise(signal: c_int) -> c_int;
fn setenv(name: *const c_char, value: *const c_char, overwrite: c_int) -> c_int;
fn task_get_special_port(task: u32, which: c_int, port: *mut u32) -> c_int;
}
assert_not_impl_any!(SpawnedLauncher: Clone, Copy, Send, Sync);
assert_not_impl_any!(InitialStopObserved: Clone, Copy, Send, Sync);
assert_not_impl_any!(AwaitingExecTrap: Clone, Copy, Send, Sync);
assert_not_impl_any!(ExecTrapHeld: Clone, Copy, Send, Sync);
assert_not_impl_any!(SignatureVerifiedExecTrap: Clone, Copy, Send, Sync);
struct FakeSignatureWorkerAuthority;
unsafe impl super::super::super::auth_adapter::ExactAuthWorkerAuthority
for FakeSignatureWorkerAuthority
{
type Failure = ();
fn try_reap_after_result(
&mut self,
) -> Result<Option<super::super::super::auth_adapter::ReapedAuthWorker>, Self::Failure> {
Ok(Some(
super::super::super::auth_adapter::ReapedAuthWorker::from_test_clean_exit(),
))
}
fn try_terminate_and_reap(
&mut self,
) -> Result<Option<super::super::super::auth_adapter::ReapedAuthWorker>, Self::Failure> {
Ok(Some(
super::super::super::auth_adapter::ReapedAuthWorker::from_test_clean_exit(),
))
}
fn emergency_terminate_and_reap(
&mut self,
) -> super::super::super::auth_adapter::ReapedAuthWorker {
super::super::super::auth_adapter::ReapedAuthWorker::from_test_clean_exit()
}
}
fn signature_job_id(byte: u8) -> super::super::super::auth_adapter::FreshAuthJobId {
unsafe {
super::super::super::auth_adapter::FreshAuthJobId::from_fresh_random([byte; 32]).unwrap()
}
}
fn signature_worker_pool(
code_identity: [u8; 32],
expected_audit_identity: [u8; 32],
) -> (
super::super::super::auth_adapter::AuthWorkerPool<FakeSignatureWorkerAuthority>,
JoinHandle<()>,
) {
use super::super::super::auth_adapter::{
AuthWorkerEndpoint, AuthWorkerJob, AuthWorkerPool, ExactAuthWorker,
FreshAuthWorkerGeneration,
};
let (request_reader, request_writer) = test_pipe();
let (result_reader, result_writer) = test_pipe();
super::set_nonblocking(request_writer.as_raw_fd(), true).unwrap();
super::set_nonblocking(result_reader.as_raw_fd(), true).unwrap();
assert_eq!(
unsafe { fcntl(request_writer.as_raw_fd(), F_SETNOSIGPIPE, 1) },
0
);
let endpoint =
unsafe { AuthWorkerEndpoint::from_private_parent_pipe_ends(request_writer, result_reader) };
let worker =
unsafe { ExactAuthWorker::from_test_unreaped_direct_child(FakeSignatureWorkerAuthority) };
let generation = unsafe { FreshAuthWorkerGeneration::from_unique_service_value(1).unwrap() };
let pool =
AuthWorkerPool::from_test_precreated_workers(vec![(generation, worker, endpoint)]).unwrap();
let worker_thread = std::thread::spawn(move || {
let mut request = Vec::new();
std::fs::File::from(request_reader)
.read_to_end(&mut request)
.unwrap();
let job = AuthWorkerJob::decode_pipe_frame(&request).unwrap();
assert_eq!(job.audit_identity(), expected_audit_identity);
let result = job.encode_test_result(code_identity);
std::fs::File::from(result_writer)
.write_all(&result)
.unwrap();
});
(pool, worker_thread)
}
#[test]
fn installed_launcher_vectors_and_same_user_identity_are_fixed() {
let image =
unsafe { InstalledLauncherImage::from_verified_installation(DEPLOYER_LAUNCHER_PATH) }
.unwrap();
let argv = image.argv();
let environment = image.environment();
let argv = argv[..argv.len() - 1]
.iter()
.map(|value| unsafe { CStr::from_ptr(value.cast_const()) }.to_bytes())
.collect::<Vec<_>>();
let environment = environment[..environment.len() - 1]
.iter()
.map(|value| unsafe { CStr::from_ptr(value.cast_const()) }.to_bytes())
.collect::<Vec<_>>();
assert_eq!(
argv,
[
DEPLOYER_LAUNCHER_PATH.to_bytes(),
INSTALLED_LAUNCHER_MODE.as_bytes(),
INSTALLED_LAUNCHER_DEATH_ARGUMENT.as_bytes(),
INSTALLED_LAUNCHER_PLAN_ARGUMENT.as_bytes(),
]
);
assert_eq!(
environment,
[&b"PATH=/usr/bin:/bin"[..], &b"LANG=C"[..], &b"LC_ALL=C"[..],]
);
let identity = image.fixed_identity();
unsafe {
assert_eq!(identity.real_uid, getuid());
assert_eq!(identity.effective_uid, geteuid());
assert_eq!(identity.real_gid, getgid());
assert_eq!(identity.effective_gid, getegid());
}
assert_eq!(identity.executable, DEPLOYER_LAUNCHER_PATH.to_bytes());
assert!(matches!(
unsafe { InstalledLauncherImage::from_verified_installation(c"relative-launcher") },
Err(LauncherSpawnFailure::InvalidFixedImage)
));
}
#[test]
fn launcher_identity_never_expects_or_requires_root() {
let running_as_root = unsafe { geteuid() == 0 };
assert!(
!running_as_root,
"the unprivileged supervisor's tests must not run as root",
);
let image =
unsafe { InstalledLauncherImage::from_verified_installation(DEPLOYER_LAUNCHER_PATH) }
.unwrap();
let identity = image.fixed_identity();
assert_ne!(identity.real_uid, 0);
assert_ne!(identity.effective_uid, 0);
}
struct SpawnBoundary {
active: Option<ActiveBrokerProcess>,
gate_writer: Option<OwnedFd>,
_trace_peer: UnixStream,
}
impl SpawnBoundary {
fn new(deadline: Instant) -> Self {
let mut descriptors = [-1; 2];
assert_eq!(unsafe { pipe(descriptors.as_mut_ptr()) }, 0);
let gate_reader = unsafe { OwnedFd::from_raw_fd(descriptors[0]) };
let gate_writer = unsafe { OwnedFd::from_raw_fd(descriptors[1]) };
let expected_executable = std::env::current_exe()
.unwrap()
.as_os_str()
.as_bytes()
.to_vec();
let plan = super::super::super::auth_adapter::broker_plan::ExactParentBrokerLaunchPlan::for_launcher_test(
deadline,
unsafe { geteuid() },
unsafe { getegid() },
expected_executable,
);
let (trace, trace_peer) = UnixStream::pair().unwrap();
trace.set_nonblocking(true).unwrap();
Self {
active: Some(ActiveBrokerProcess {
gate: ActiveBrokerGate {
reader: gate_reader,
},
plan,
trace,
}),
gate_writer: Some(gate_writer),
_trace_peer: trace_peer,
}
}
fn take_active(&mut self) -> ActiveBrokerProcess {
self.active.take().unwrap()
}
fn close_gate(&mut self) {
drop(self.gate_writer.take());
}
fn poison_gate(&mut self) {
let writer = self.gate_writer.as_ref().unwrap().try_clone().unwrap();
std::fs::File::from(writer).write_all(&[1]).unwrap();
}
}
fn test_pipe() -> (OwnedFd, OwnedFd) {
let mut descriptors = [-1; 2];
assert_eq!(unsafe { pipe(descriptors.as_mut_ptr()) }, 0);
let reader = unsafe { OwnedFd::from_raw_fd(descriptors[0]) };
let writer = unsafe { OwnedFd::from_raw_fd(descriptors[1]) };
(reader, writer)
}
fn uninstalled_fixed_image() -> InstalledLauncherImage {
unsafe { InstalledLauncherImage::from_verified_installation(DEPLOYER_LAUNCHER_PATH) }.unwrap()
}
#[test]
fn uninstalled_fixed_launcher_image_fails_only_at_the_exact_spawn() {
assert!(
!std::path::Path::new(std::ffi::OsStr::from_bytes(
DEPLOYER_LAUNCHER_PATH.to_bytes()
))
.exists(),
"this boundary test is only meaningful while the fixed image is absent",
);
let mut boundary = SpawnBoundary::new(Instant::now() + Duration::from_secs(5));
let (active, failure) = spawn_fixed_launcher(
boundary.take_active(),
&uninstalled_fixed_image(),
&mut DedicatedChildWaitDomain::for_spawn_test(),
)
.err()
.expect("an absent fixed launcher image cannot spawn")
.into_parts();
assert_eq!(failure, LauncherSpawnFailure::Spawn(ENOENT));
drop(active);
boundary.close_gate();
}
#[test]
fn production_spawn_installs_exact_launcher_fd_topology_and_characterizes_bootstrap() {
let executable = std::env::current_exe().unwrap();
let installed_path = CString::new(executable.as_os_str().as_bytes()).unwrap();
let image =
unsafe { InstalledLauncherImage::from_verified_installation(&installed_path) }.unwrap();
let mut boundary = SpawnBoundary::new(Instant::now() + Duration::from_secs(5));
let sentinel = fs::File::open("/dev/null").unwrap();
let inherited_sentinel = unsafe { fcntl(sentinel.as_raw_fd(), F_DUPFD, 100) };
assert!(inherited_sentinel >= 100);
assert_eq!(unsafe { fcntl(inherited_sentinel, F_SETFD, 0) }, 0);
let inherited_sentinel = unsafe { OwnedFd::from_raw_fd(inherited_sentinel) };
let spawned = match spawn_fixed_launcher(
boundary.take_active(),
&image,
&mut DedicatedChildWaitDomain::for_spawn_test(),
) {
Ok(spawned) => spawned,
Err(error) => {
let (_active, failure) = error.into_parts();
panic!("production-shaped launcher fixture failed to spawn: {failure:?}")
}
};
let initial = spawned.wait_initial_stop().unwrap();
drop(initial);
drop(inherited_sentinel);
boundary.close_gate();
}
#[test]
fn service_death_preempts_the_fixed_launcher_spawn() {
let mut boundary = SpawnBoundary::new(Instant::now() + Duration::from_secs(5));
boundary.close_gate();
let (_active, failure) = spawn_fixed_launcher(
boundary.take_active(),
&uninstalled_fixed_image(),
&mut DedicatedChildWaitDomain::for_spawn_test(),
)
.err()
.expect("service death must preempt the spawn")
.into_parts();
assert_eq!(failure, LauncherSpawnFailure::ServiceGone);
}
#[test]
fn expired_deadline_preempts_the_fixed_launcher_spawn() {
let mut boundary = SpawnBoundary::new(Instant::now() + Duration::from_millis(1));
std::thread::sleep(Duration::from_millis(2));
let (_active, failure) = spawn_fixed_launcher(
boundary.take_active(),
&uninstalled_fixed_image(),
&mut DedicatedChildWaitDomain::for_spawn_test(),
)
.err()
.expect("an expired deadline must preempt the spawn")
.into_parts();
assert_eq!(failure, LauncherSpawnFailure::DeadlineExpired);
boundary.close_gate();
}
#[test]
fn a_gate_byte_preempts_the_fixed_launcher_spawn() {
let mut boundary = SpawnBoundary::new(Instant::now() + Duration::from_secs(5));
boundary.poison_gate();
let (_active, failure) = spawn_fixed_launcher(
boundary.take_active(),
&uninstalled_fixed_image(),
&mut DedicatedChildWaitDomain::for_spawn_test(),
)
.err()
.expect("a noncanonical gate byte must preempt the spawn")
.into_parts();
assert_eq!(failure, LauncherSpawnFailure::InvalidGate);
boundary.close_gate();
}
#[used]
#[unsafe(link_section = "__DATA,__mod_init_func")]
static EXACT_LAUNCHER_HOOK: extern "C" fn() = exact_launcher_hook;
extern "C" fn exact_launcher_hook() {
let mut arguments = std::env::args_os();
let argument0 = arguments.next();
let launcher_mode = arguments
.next()
.is_some_and(|argument| argument.as_bytes() == INSTALLED_LAUNCHER_MODE.as_bytes());
if launcher_mode && production_spawn_has_canonical_environment() {
if let Some(argument0) = argument0
&& argument0
.as_bytes()
.ends_with(PRODUCTION_BROKER_FIXTURE_SUFFIX)
{
let installed = CString::new(argument0.as_bytes()).unwrap_or_else(|_| {
unsafe { _exit(105) }
});
unsafe { super::super::super::launcher_entry::run_fixed_launcher_process(&installed) }
}
production_spawn_containment_hook();
}
let mode = unsafe { getenv(FIXTURE_ENV.as_ptr().cast()) };
if mode.is_null() {
return;
}
let mode = unsafe { CStr::from_ptr(mode) }.to_bytes();
if mode == POST_EXEC
|| mode == POST_EXEC_STOP
|| mode == POST_EXEC_DELAY
|| mode == POST_EXEC_SIGKILL
{
return;
}
if mode != VALID_EXEC
&& mode != FAKE_TRAP
&& mode != UNEXPECTED_STOP
&& mode != UNTRACED_STOP
&& mode != SUBSTITUTE_EXEC
&& mode != TARGET_STOP
&& mode != TARGET_DELAY
&& mode != TARGET_SIGKILL
{
unsafe { _exit(91) }
}
if mode == UNTRACED_STOP {
if unsafe { raise(SIGSTOP) } != 0 {
unsafe { _exit(100) }
}
unsafe { _exit(101) }
}
if unsafe { ptrace(PT_TRACE_ME, 0, std::ptr::null_mut(), 0) } != 0
|| unsafe { raise(SIGSTOP) } != 0
{
unsafe { _exit(92) }
}
if mode == FAKE_TRAP {
if unsafe { raise(SIGTRAP) } != 0 {
unsafe { _exit(93) }
}
unsafe { _exit(94) }
}
if mode == UNEXPECTED_STOP {
if unsafe { raise(SIGSTOP) } != 0 {
unsafe { _exit(98) }
}
unsafe { _exit(99) }
}
let substitute_exec = mode == SUBSTITUTE_EXEC;
let post_exec = if mode == TARGET_STOP {
c"post-exec-stop"
} else if mode == TARGET_DELAY {
c"post-exec-delay"
} else if mode == TARGET_SIGKILL {
c"post-exec-sigkill"
} else {
c"post-exec"
};
if unsafe { setenv(FIXTURE_ENV.as_ptr().cast(), post_exec.as_ptr(), 1) } != 0 {
unsafe { _exit(95) }
}
let executable = if substitute_exec {
std::path::PathBuf::from("/usr/bin/true")
} else {
match std::env::current_exe() {
Ok(executable) => executable,
Err(_) => unsafe { _exit(96) },
}
};
let error = Command::new(executable)
.arg("--exact")
.arg(EXACT_TEST)
.arg("--ignored")
.arg("--nocapture")
.exec();
let _ = error;
unsafe { _exit(97) }
}
fn production_spawn_containment_hook() -> ! {
if !production_spawn_has_exact_arguments()
|| !production_spawn_has_canonical_environment()
|| !production_spawn_has_null_stdio()
|| !production_spawn_has_exact_descriptors()
|| !production_spawn_has_bounded_bootstrap_right()
{
unsafe { _exit(102) }
}
if unsafe { ptrace(PT_TRACE_ME, 0, std::ptr::null_mut(), 0) } != 0
|| unsafe { raise(SIGSTOP) } != 0
{
unsafe { _exit(103) }
}
unsafe { _exit(104) }
}
fn production_spawn_has_canonical_environment() -> bool {
let mut environment = std::env::vars_os()
.map(|(key, value)| (key.as_bytes().to_vec(), value.as_bytes().to_vec()))
.collect::<Vec<_>>();
environment.sort_unstable();
environment
== [
(b"LANG".to_vec(), b"C".to_vec()),
(b"LC_ALL".to_vec(), b"C".to_vec()),
(b"PATH".to_vec(), b"/usr/bin:/bin".to_vec()),
]
}
fn production_spawn_has_exact_arguments() -> bool {
let arguments = std::env::args_os()
.map(|argument| argument.as_bytes().to_vec())
.collect::<Vec<_>>();
arguments.len() == 4
&& arguments[0].first() == Some(&b'/')
&& arguments[1] == INSTALLED_LAUNCHER_MODE.as_bytes()
&& arguments[2] == INSTALLED_LAUNCHER_DEATH_ARGUMENT.as_bytes()
&& arguments[3] == INSTALLED_LAUNCHER_PLAN_ARGUMENT.as_bytes()
}
fn production_spawn_has_null_stdio() -> bool {
let Ok(null) = fs::metadata("/dev/null") else {
return false;
};
(0..=2).all(|descriptor| {
let Ok(metadata) = fs::metadata(format!("/dev/fd/{descriptor}")) else {
return false;
};
metadata.file_type().is_char_device()
&& metadata.dev() == null.dev()
&& metadata.ino() == null.ino()
&& metadata.rdev() == null.rdev()
})
}
fn production_spawn_has_exact_descriptors() -> bool {
for descriptor in [LAUNCHER_DEATH_FD, LAUNCHER_PLAN_FD] {
let descriptor_flags = unsafe { fcntl(descriptor, F_GETFD) };
let status_flags = unsafe { fcntl(descriptor, F_GETFL) };
let Ok(metadata) = fs::metadata(format!("/dev/fd/{descriptor}")) else {
return false;
};
if descriptor_flags < 0
|| descriptor_flags & FD_CLOEXEC != 0
|| status_flags < 0
|| status_flags & O_ACCMODE != 0
|| !metadata.file_type().is_fifo()
{
return false;
}
}
let descriptor_limit = unsafe { getdtablesize() };
descriptor_limit > LAUNCHER_PLAN_FD
&& (0..descriptor_limit).all(|descriptor| {
let is_open = unsafe { fcntl(descriptor, F_GETFD) } >= 0;
is_open == (0..=LAUNCHER_PLAN_FD).contains(&descriptor)
})
}
fn production_spawn_has_bounded_bootstrap_right() -> bool {
let task = crate::backend::macos::current_task();
let mut bootstrap = 0;
if unsafe { task_get_special_port(task, TASK_BOOTSTRAP_PORT, &raw mut bootstrap) } != 0
|| bootstrap == 0
{
return false;
}
if bootstrap == MACH_PORT_DEAD {
return true;
}
let mut port_type = 0;
let result = unsafe { mach_port_type(task, bootstrap, &raw mut port_type) };
crate::backend::macos::deallocate_port(task, bootstrap);
result == 0
&& port_type & MACH_PORT_TYPE_RECEIVE == 0
&& port_type & (MACH_PORT_TYPE_SEND | MACH_PORT_TYPE_DEAD_NAME) != 0
}
struct Fixture {
child: Child,
gate: Option<ActiveBrokerGate>,
gate_writer: Option<OwnedFd>,
trace_peer: Option<UnixStream>,
launcher_readers: Option<(OwnedFd, OwnedFd)>,
plan_frame: Vec<u8>,
}
impl Fixture {
fn spawn(mode: &str) -> Self {
let mut descriptors = [-1; 2];
assert_eq!(unsafe { pipe(descriptors.as_mut_ptr()) }, 0);
let gate_reader = unsafe { OwnedFd::from_raw_fd(descriptors[0]) };
let gate_writer = unsafe { OwnedFd::from_raw_fd(descriptors[1]) };
assert_eq!(
unsafe {
super::super::fcntl(
gate_reader.as_raw_fd(),
super::super::F_SETFD,
super::super::FD_CLOEXEC,
)
},
0
);
assert_eq!(
unsafe {
super::super::fcntl(
gate_writer.as_raw_fd(),
super::super::F_SETFD,
super::super::FD_CLOEXEC,
)
},
0
);
let child = Command::new(std::env::current_exe().unwrap())
.arg("--exact")
.arg(EXACT_TEST)
.arg("--ignored")
.arg("--nocapture")
.env(
std::str::from_utf8(&FIXTURE_ENV[..FIXTURE_ENV.len() - 1]).unwrap(),
mode,
)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.unwrap();
Self {
child,
gate: Some(ActiveBrokerGate {
reader: gate_reader,
}),
gate_writer: Some(gate_writer),
trace_peer: None,
launcher_readers: None,
plan_frame: Vec::new(),
}
}
fn take_plan_reader(&mut self) -> OwnedFd {
let (death_reader, plan_reader) = self.launcher_readers.take().unwrap();
self.launcher_readers = Some((death_reader, test_pipe().0));
plan_reader
}
fn spawned_launcher(&mut self, deadline: Instant) -> SpawnedLauncher {
let expected_executable = std::env::current_exe()
.unwrap()
.as_os_str()
.as_bytes()
.to_vec();
self.spawned_launcher_with_identity(deadline, expected_executable, None)
}
fn spawned_launcher_with_identity(
&mut self,
deadline: Instant,
expected_launcher_executable: Vec<u8>,
expected_effective_uid: Option<u32>,
) -> SpawnedLauncher {
let pid = c_int::try_from(self.child.id()).unwrap();
let gate = self.gate.take().unwrap();
let expected_executable = std::env::current_exe()
.unwrap()
.as_os_str()
.as_bytes()
.to_vec();
let plan = super::super::super::auth_adapter::broker_plan::ExactParentBrokerLaunchPlan::for_launcher_test(
deadline,
unsafe { geteuid() },
unsafe { getegid() },
expected_executable,
);
let (trace, trace_peer) = UnixStream::pair().unwrap();
trace.set_nonblocking(true).unwrap();
self.trace_peer = Some(trace_peer);
let active = ActiveBrokerProcess { gate, plan, trace };
let expected_launcher = FixedLauncherIdentity::for_test(
unsafe { getuid() },
expected_effective_uid.unwrap_or_else(|| unsafe { geteuid() }),
unsafe { getgid() },
unsafe { getegid() },
expected_launcher_executable,
);
let (death_reader, death_writer) = test_pipe();
let (plan_reader, plan_writer) = test_pipe();
self.launcher_readers = Some((death_reader, plan_reader));
let channels =
RetainedLauncherChannels::for_test(plan_writer, death_writer, self.plan_frame.clone());
unsafe { SpawnedLauncher::from_positive_spawn(pid, active, expected_launcher, channels) }
.unwrap()
}
fn deadline(&self) -> Instant {
Instant::now() + Duration::from_secs(5)
}
fn close_gate(&mut self) {
drop(self.gate_writer.take());
}
fn held_exec(&mut self) -> (c_int, SignatureVerifiedExecTrap) {
self.held_exec_until(self.deadline())
}
fn held_exec_until(&mut self, deadline: Instant) -> (c_int, SignatureVerifiedExecTrap) {
let (pid, held) = self.held_exec_unverified_until(deadline);
(pid, unsafe { held.assume_signature_verified_for_test() })
}
fn held_exec_unverified(&mut self) -> (c_int, ExecTrapHeld) {
self.held_exec_unverified_until(self.deadline())
}
fn held_exec_unverified_until(&mut self, deadline: Instant) -> (c_int, ExecTrapHeld) {
let pid = c_int::try_from(self.child.id()).unwrap();
let initial = self.spawned_launcher(deadline).wait_initial_stop().unwrap();
let running = initial.prove_trace_and_continue_to_exec().unwrap();
(pid, running.wait_exec_trap().unwrap())
}
fn drain_report(&mut self) -> UnixStream {
let mut service = self.trace_peer.take().unwrap();
let mut report =
[0_u8; super::super::super::auth_adapter::broker_report::BROKER_TRACE_REPORT_BYTES];
service.read_exact(&mut report).unwrap();
assert_eq!(&report[..8], b"NIPCBTR1");
let mut extension = [0_u8; 1];
assert_eq!(service.read(&mut extension).unwrap(), 0);
service
}
}
#[test]
fn real_exec_changes_audit_pid_version_at_exact_trap() {
let mut fixture = Fixture::spawn("valid-exec");
let pid = c_int::try_from(fixture.child.id()).unwrap();
let deadline = fixture.deadline();
let initial = fixture
.spawned_launcher(deadline)
.wait_initial_stop()
.unwrap();
let running = initial.prove_trace_and_continue_to_exec().unwrap();
let held = running.wait_exec_trap().unwrap();
assert_eq!(held.exact_pid_for_test(), pid);
drop(held);
fixture.close_gate();
}
#[test]
fn exec_trap_signature_gate_accepts_the_installed_target_identity() {
let mut fixture = Fixture::spawn("valid-exec");
let (pid, held) = fixture.held_exec_unverified();
let expected_audit_identity = held._after_exec.audit_identity();
let (mut pool, worker) = signature_worker_pool([4; 32], expected_audit_identity);
let verified = held
.verify_signature(&mut pool, signature_job_id(0x71))
.unwrap();
assert_eq!(verified.exact_pid_for_test(), pid);
worker.join().unwrap();
drop(verified);
assert_no_reapable_status(pid);
fixture.close_gate();
}
#[test]
fn exec_trap_signature_gate_rejects_zero_or_substituted_identity_and_exact_cleans() {
for (job_byte, code_identity) in [(0x72, [0; 32]), (0x73, [9; 32])] {
let mut fixture = Fixture::spawn("valid-exec");
let (pid, held) = fixture.held_exec_unverified();
let expected_audit_identity = held._after_exec.audit_identity();
let (mut pool, worker) = signature_worker_pool(code_identity, expected_audit_identity);
assert!(matches!(
held.verify_signature(&mut pool, signature_job_id(job_byte)),
Err(LauncherSignatureError::Auth(
super::super::super::auth_adapter::AuthAdapterError::AuthenticationRejected
))
));
worker.join().unwrap();
assert_no_reapable_status(pid);
fixture.close_gate();
}
}
#[test]
fn same_image_sigtrap_cannot_counterfeit_exec_transition() {
let mut fixture = Fixture::spawn("fake-trap");
let deadline = fixture.deadline();
let initial = fixture
.spawned_launcher(deadline)
.wait_initial_stop()
.unwrap();
let running = initial.prove_trace_and_continue_to_exec().unwrap();
let result = running.wait_exec_trap();
assert!(matches!(result, Err(LauncherWaitError::IdentityTransition)));
fixture.close_gate();
}
#[test]
fn unexpected_second_stop_is_rejected_and_exact_cleaned() {
let mut fixture = Fixture::spawn("unexpected-stop");
let deadline = fixture.deadline();
let initial = fixture
.spawned_launcher(deadline)
.wait_initial_stop()
.unwrap();
let running = initial.prove_trace_and_continue_to_exec().unwrap();
let result = running.wait_exec_trap();
assert!(matches!(result, Err(LauncherWaitError::UnexpectedStatus)));
fixture.close_gate();
}
#[test]
fn service_death_preempts_initial_stop_authority() {
let mut fixture = Fixture::spawn("valid-exec");
fixture.close_gate();
let deadline = fixture.deadline();
assert!(matches!(
fixture.spawned_launcher(deadline).wait_initial_stop(),
Err(LauncherWaitError::ServiceGone)
));
}
#[test]
fn expired_deadline_preempts_initial_stop_authority() {
let mut fixture = Fixture::spawn("valid-exec");
let launcher = fixture.spawned_launcher(Instant::now() + Duration::from_millis(1));
std::thread::sleep(Duration::from_millis(2));
assert!(matches!(
launcher.wait_initial_stop(),
Err(LauncherWaitError::DeadlineExpired)
));
fixture.close_gate();
}
#[test]
fn untraced_initial_sigstop_cannot_mint_ptrace_authority() {
let mut fixture = Fixture::spawn("untraced-stop");
let deadline = fixture.deadline();
let initial = fixture
.spawned_launcher(deadline)
.wait_initial_stop()
.unwrap();
assert!(matches!(
initial.prove_trace_and_continue_to_exec(),
Err(LauncherWaitError::Native(_))
));
fixture.close_gate();
}
#[test]
fn wrong_initial_launcher_image_never_reaches_ptrace_continue() {
let mut fixture = Fixture::spawn("valid-exec");
let deadline = fixture.deadline();
assert!(matches!(
fixture
.spawned_launcher_with_identity(deadline, b"/usr/bin/false".to_vec(), None)
.wait_initial_stop(),
Err(LauncherWaitError::IdentityTransition)
));
fixture.close_gate();
}
#[test]
fn wrong_initial_launcher_credentials_never_reach_ptrace_continue() {
let mut fixture = Fixture::spawn("valid-exec");
let deadline = fixture.deadline();
let expected_executable = std::env::current_exe()
.unwrap()
.as_os_str()
.as_bytes()
.to_vec();
let wrong_uid = unsafe { geteuid() }.wrapping_add(1);
assert!(matches!(
fixture
.spawned_launcher_with_identity(deadline, expected_executable, Some(wrong_uid))
.wait_initial_stop(),
Err(LauncherWaitError::IdentityTransition)
));
fixture.close_gate();
}
#[test]
fn different_exec_image_cannot_mint_bound_identity() {
let mut fixture = Fixture::spawn("substitute-exec");
let deadline = fixture.deadline();
let initial = fixture
.spawned_launcher(deadline)
.wait_initial_stop()
.unwrap();
let running = initial.prove_trace_and_continue_to_exec().unwrap();
assert!(matches!(
running.wait_exec_trap(),
Err(LauncherWaitError::IdentityTransition)
));
fixture.close_gate();
}
#[test]
fn held_exec_reports_waits_for_ready_and_resumes_exactly_once() {
let mut fixture = Fixture::spawn("valid-exec");
let (pid, held) = fixture.held_exec();
let reported = held.report_trace_stops().unwrap().unwrap();
let mut service = fixture.drain_report();
service
.write_all(&super::super::super::auth_adapter::broker_report::BROKER_RESUME_BYTE)
.unwrap();
service.shutdown(Shutdown::Write).unwrap();
let committed = reported.wait_for_ready_commit().unwrap().unwrap();
let resumed = committed.resume_target().unwrap();
assert_eq!(resumed.exact_pid_for_test(), pid);
drop(resumed);
fixture.close_gate();
}
#[test]
fn service_death_before_report_exactly_cleans_held_target() {
let mut fixture = Fixture::spawn("valid-exec");
let (_, held) = fixture.held_exec();
fixture.close_gate();
held.wait_for_gate_eof_for_test();
match held.report_trace_stops() {
Ok(Err(BrokerGateExit::ServiceGone)) => {}
Ok(Err(exit)) => panic!("unexpected gate exit: {exit:?}"),
Ok(Ok(_)) => panic!("service death minted reported authority"),
Err(error) => panic!("service death returned protocol error: {error:?}"),
}
}
#[test]
fn malformed_or_extended_resume_exactly_cleans_reported_target() {
for resume in [[9_u8].as_slice(), [1_u8, 2].as_slice()] {
let mut fixture = Fixture::spawn("valid-exec");
let (_, held) = fixture.held_exec();
let reported = held.report_trace_stops().unwrap().unwrap();
let mut service = fixture.drain_report();
service.write_all(resume).unwrap();
service.shutdown(Shutdown::Write).unwrap();
assert!(matches!(
reported.wait_for_ready_commit(),
Err(BrokerEntryError::Plan(SupervisorWireError::Malformed))
));
fixture.close_gate();
}
}
#[test]
fn service_death_after_commit_preempts_delayed_resume() {
let mut fixture = Fixture::spawn("valid-exec");
let (_, held) = fixture.held_exec();
let reported = held.report_trace_stops().unwrap().unwrap();
let mut service = fixture.drain_report();
service
.write_all(&super::super::super::auth_adapter::broker_report::BROKER_RESUME_BYTE)
.unwrap();
service.shutdown(Shutdown::Write).unwrap();
let committed = reported.wait_for_ready_commit().unwrap().unwrap();
fixture.close_gate();
committed.wait_for_gate_eof_for_test();
assert!(matches!(
committed.resume_target(),
Err(LauncherWaitError::ServiceGone)
));
}
#[test]
fn ready_resume_commit_has_no_broker_side_deadline_veto() {
let mut fixture = Fixture::spawn("valid-exec");
let deadline = Instant::now() + Duration::from_millis(250);
let (_, held) = fixture.held_exec_until(deadline);
let reported = held.report_trace_stops().unwrap().unwrap();
let mut service = fixture.drain_report();
std::thread::sleep(Duration::from_millis(300));
service
.write_all(&super::super::super::auth_adapter::broker_report::BROKER_RESUME_BYTE)
.unwrap();
service.shutdown(Shutdown::Write).unwrap();
let committed = reported.wait_for_ready_commit().unwrap().unwrap();
let resumed = committed.resume_target().unwrap();
drop(resumed);
fixture.close_gate();
}
#[test]
fn deliver_plan_writes_the_exact_frame_on_fd4_then_closes_for_eof() {
let mut fixture = Fixture::spawn("valid-exec");
let frame: Vec<u8> = (0..131_072_u32).map(|index| (index % 251) as u8).collect();
fixture.plan_frame = frame.clone();
let deadline = fixture.deadline();
let launcher = fixture.spawned_launcher(deadline);
let reader = fixture.take_plan_reader();
let expected_len = frame.len();
let drain = std::thread::spawn(move || {
let mut received = Vec::new();
let mut file = std::fs::File::from(reader);
file.read_to_end(&mut received).unwrap();
received
});
let initial = launcher.wait_initial_stop().unwrap();
let mut awaiting = initial.prove_trace_and_continue_to_exec().unwrap();
awaiting.deliver_plan().unwrap();
let received = drain.join().unwrap();
assert_eq!(received.len(), expected_len, "delivered length must match");
assert_eq!(received, frame, "delivered bytes must match exactly");
drop(awaiting);
fixture.close_gate();
}
#[test]
fn deliver_plan_is_preempted_by_service_death() {
let mut fixture = Fixture::spawn("valid-exec");
fixture.plan_frame = vec![0x5a; 64];
let deadline = fixture.deadline();
let launcher = fixture.spawned_launcher(deadline);
let _reader = fixture.take_plan_reader();
let initial = launcher.wait_initial_stop().unwrap();
let mut awaiting = initial.prove_trace_and_continue_to_exec().unwrap();
fixture.close_gate();
assert!(matches!(
awaiting.deliver_plan(),
Err(LauncherWaitError::ServiceGone),
));
}
#[test]
fn dropping_an_exact_launcher_drains_it_leaving_no_zombie() {
let mut fixture = Fixture::spawn("valid-exec");
let deadline = fixture.deadline();
let (pid, held) = fixture.held_exec_until(deadline);
drop(held);
assert_no_reapable_status(pid);
fixture.close_gate();
}
fn assert_no_reapable_status(pid: c_int) {
let mut status = 0;
let result = unsafe { waitpid(pid, &raw mut status, WNOHANG | WUNTRACED) };
let error = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
assert!(
result < 0 && error == ECHILD,
"exact child {pid} still had a reapable status: waitpid returned \
{result} (status 0x{status:04x}, errno {error}), so it is a zombie",
);
}
#[test]
fn resumed_target_natural_exit_is_exactly_reaped() {
let mut fixture = Fixture::spawn("valid-exec");
let (_, held) = fixture.held_exec();
let reported = held.report_trace_stops().unwrap().unwrap();
let mut service = fixture.drain_report();
service
.write_all(&super::super::super::auth_adapter::broker_report::BROKER_RESUME_BYTE)
.unwrap();
service.shutdown(Shutdown::Write).unwrap();
let resumed = reported
.wait_for_ready_commit()
.unwrap()
.unwrap()
.resume_target()
.unwrap();
assert_eq!(resumed.wait_for_exit(), Ok(ExactTargetExit::Exited(101)));
fixture.close_gate();
}
#[test]
fn post_ready_sigkill_is_a_terminal_traced_stop_and_exactly_cleaned() {
let mut fixture = Fixture::spawn("target-sigkill");
let (_, held) = fixture.held_exec();
let reported = held.report_trace_stops().unwrap().unwrap();
let mut service = fixture.drain_report();
service
.write_all(&super::super::super::auth_adapter::broker_report::BROKER_RESUME_BYTE)
.unwrap();
service.shutdown(Shutdown::Write).unwrap();
let resumed = reported
.wait_for_ready_commit()
.unwrap()
.unwrap()
.resume_target()
.unwrap();
assert_eq!(
resumed.wait_for_exit(),
Err(LauncherWaitError::UnexpectedStatus)
);
fixture.close_gate();
}
#[test]
fn terminal_status_decoder_distinguishes_exit_and_signal() {
assert_eq!(
exact_target_exit(23 << 8),
Some(ExactTargetExit::Exited(23))
);
assert_eq!(
exact_target_exit(SIGKILL),
Some(ExactTargetExit::Signaled(SIGKILL))
);
assert_eq!(exact_target_exit((SIGSTOP << 8) | 0x7f), None);
}
#[test]
fn service_death_after_terminal_wait_wins_classification() {
let mut fixture = Fixture::spawn("valid-exec");
let (_, held) = fixture.held_exec();
let reported = held.report_trace_stops().unwrap().unwrap();
let mut service = fixture.drain_report();
service
.write_all(&super::super::super::auth_adapter::broker_report::BROKER_RESUME_BYTE)
.unwrap();
service.shutdown(Shutdown::Write).unwrap();
let resumed = reported
.wait_for_ready_commit()
.unwrap()
.unwrap()
.resume_target()
.unwrap();
let gate_writer = fixture.gate_writer.take().unwrap();
assert_eq!(
resumed.wait_for_exit_with_post_wait_for_test(move |gate| {
drop(gate_writer);
wait_for_gate_eof(gate);
}),
Err(LauncherWaitError::ServiceGone)
);
}
#[test]
fn service_death_after_stop_wait_wins_and_exactly_cleans() {
let mut fixture = Fixture::spawn("target-stop");
let (_, held) = fixture.held_exec();
let reported = held.report_trace_stops().unwrap().unwrap();
let mut service = fixture.drain_report();
service
.write_all(&super::super::super::auth_adapter::broker_report::BROKER_RESUME_BYTE)
.unwrap();
service.shutdown(Shutdown::Write).unwrap();
let resumed = reported
.wait_for_ready_commit()
.unwrap()
.unwrap()
.resume_target()
.unwrap();
let gate_writer = fixture.gate_writer.take().unwrap();
assert_eq!(
resumed.wait_for_exit_with_post_wait_for_test(move |gate| {
drop(gate_writer);
wait_for_gate_eof(gate);
}),
Err(LauncherWaitError::ServiceGone)
);
}
#[test]
fn service_death_while_target_runs_preempts_exit_and_exactly_cleans() {
let mut fixture = Fixture::spawn("valid-exec");
let (_, held) = fixture.held_exec();
let reported = held.report_trace_stops().unwrap().unwrap();
let mut service = fixture.drain_report();
service
.write_all(&super::super::super::auth_adapter::broker_report::BROKER_RESUME_BYTE)
.unwrap();
service.shutdown(Shutdown::Write).unwrap();
let resumed = reported
.wait_for_ready_commit()
.unwrap()
.unwrap()
.resume_target()
.unwrap();
fixture.close_gate();
resumed.wait_for_gate_eof_for_test();
assert_eq!(resumed.wait_for_exit(), Err(LauncherWaitError::ServiceGone));
}
#[test]
fn service_death_after_waiting_begins_still_wins_and_exactly_cleans() {
let mut fixture = Fixture::spawn("target-delay");
let (_, held) = fixture.held_exec();
let reported = held.report_trace_stops().unwrap().unwrap();
let mut service = fixture.drain_report();
service
.write_all(&super::super::super::auth_adapter::broker_report::BROKER_RESUME_BYTE)
.unwrap();
service.shutdown(Shutdown::Write).unwrap();
let resumed = reported
.wait_for_ready_commit()
.unwrap()
.unwrap()
.resume_target()
.unwrap();
let gate_writer = fixture.gate_writer.take().unwrap();
let closer = std::thread::spawn(move || {
std::thread::sleep(Duration::from_millis(20));
drop(gate_writer);
});
assert_eq!(resumed.wait_for_exit(), Err(LauncherWaitError::ServiceGone));
closer.join().unwrap();
}
#[test]
fn post_ready_target_stop_is_terminal_and_exactly_cleaned() {
let mut fixture = Fixture::spawn("target-stop");
let (_, held) = fixture.held_exec();
let reported = held.report_trace_stops().unwrap().unwrap();
let mut service = fixture.drain_report();
service
.write_all(&super::super::super::auth_adapter::broker_report::BROKER_RESUME_BYTE)
.unwrap();
service.shutdown(Shutdown::Write).unwrap();
let resumed = reported
.wait_for_ready_commit()
.unwrap()
.unwrap()
.resume_target()
.unwrap();
assert_eq!(
resumed.wait_for_exit(),
Err(LauncherWaitError::UnexpectedStatus)
);
fixture.close_gate();
}
#[test]
#[ignore = "exec target for the exact broker-launcher native fixture"]
fn fixture_target() {
let mode = unsafe { getenv(FIXTURE_ENV.as_ptr().cast()) };
if !mode.is_null()
&& unsafe { CStr::from_ptr(mode) }.to_bytes() == POST_EXEC_STOP
{
assert_eq!(unsafe { raise(SIGSTOP) }, 0);
}
if !mode.is_null()
&& unsafe { CStr::from_ptr(mode) }.to_bytes() == POST_EXEC_DELAY
{
std::thread::sleep(Duration::from_millis(500));
}
if !mode.is_null()
&& unsafe { CStr::from_ptr(mode) }.to_bytes() == POST_EXEC_SIGKILL
{
assert_eq!(unsafe { raise(SIGKILL) }, 0);
}
panic!("the exact launcher waiter should kill this image at its exec trap");
}
fn wait_for_gate_eof(gate: &ActiveBrokerGate) {
loop {
match probe_gate(gate) {
Err(LauncherWaitError::ServiceGone) => return,
Ok(()) => poll_gate_slice(gate).unwrap(),
Err(error) => panic!("unexpected gate probe failure: {error:?}"),
}
}
}