use std::convert::Infallible;
use std::ffi::{CStr, CString, c_char, c_int, c_void};
use super::auth_adapter::broker_plan::{
LAUNCHER_PLAN_PREFIX_BYTES, LauncherExecParts, MAX_BROKER_PLAN_BYTES, ReceivedLauncherExecPlan,
parse_launcher_plan_prefix,
};
use super::broker_entry::broker_launcher::{
INSTALLED_LAUNCHER_DEATH_ARGUMENT, INSTALLED_LAUNCHER_MODE, INSTALLED_LAUNCHER_PLAN_ARGUMENT,
LAUNCHER_DEATH_FD, LAUNCHER_PLAN_FD,
};
use super::is_deployer_helper_path;
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;
const PT_TRACE_ME: c_int = 0;
const SIGSTOP: c_int = 17;
const RLIMIT_NPROC: c_int = 7;
const F_GETFD: c_int = 1;
const F_SETFD: c_int = 2;
const F_GETFL: c_int = 3;
const F_SETFL: c_int = 4;
const FD_CLOEXEC: c_int = 1;
const O_NONBLOCK: c_int = 0x0000_0004;
const EINTR: c_int = 4;
const EAGAIN: c_int = 35;
const LAUNCHER_SANDBOX_PROFILE: &str = concat!(
"(version 1)\n",
"(allow default)\n",
"(deny signal)\n",
"(deny mach-lookup)\n",
"(deny mach-register)\n",
);
const LAUNCHER_PROCESS_LIMIT: ResourceLimit = ResourceLimit {
current: 1,
maximum: 1,
};
#[repr(C)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct ResourceLimit {
current: u64,
maximum: u64,
}
unsafe extern "C" {
fn _exit(status: c_int) -> !;
fn close(fd: c_int) -> c_int;
fn execve(
path: *const c_char,
arguments: *const *const c_char,
environment: *const *const c_char,
) -> c_int;
fn fcntl(fd: c_int, command: c_int, ...) -> c_int;
fn getegid() -> u32;
fn geteuid() -> u32;
fn getrlimit(resource: c_int, limit: *mut ResourceLimit) -> c_int;
fn ptrace(request: c_int, pid: c_int, address: *mut c_void, data: c_int) -> c_int;
fn raise(signal: c_int) -> c_int;
fn read(fd: c_int, buffer: *mut u8, count: usize) -> isize;
fn sandbox_init(profile: *const c_char, flags: u64, errorbuf: *mut *mut c_char) -> c_int;
fn setrlimit(resource: c_int, limit: *const ResourceLimit) -> c_int;
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) enum LauncherEntryError {
InvalidArguments,
InvalidDescriptor,
BrokerGone,
TraceRefused,
Plan,
IdentityMismatch,
InvalidTarget,
ContainmentRefused,
}
impl LauncherEntryError {
const fn status(self) -> c_int {
match self {
Self::InvalidArguments => 64,
Self::InvalidDescriptor => 65,
Self::BrokerGone => 0,
Self::TraceRefused => 66,
Self::Plan => 67,
Self::IdentityMismatch => 68,
Self::InvalidTarget => 69,
Self::ContainmentRefused => 70,
}
}
}
pub(in crate::backend::macos) unsafe fn run_fixed_launcher_process(installed_path: &CStr) -> ! {
let status = match unsafe { run_launcher(installed_path) } {
Ok(infallible) => match infallible {},
Err(error) => error.status(),
};
unsafe { _exit(status) }
}
unsafe fn run_launcher(installed_path: &CStr) -> Result<Infallible, LauncherEntryError> {
validate_fixed_arguments(installed_path, std::env::args_os())?;
unsafe {
require_live(LAUNCHER_DEATH_FD)?;
require_live(LAUNCHER_PLAN_FD)?;
set_close_on_exec(LAUNCHER_DEATH_FD)?;
set_close_on_exec(LAUNCHER_PLAN_FD)?;
adopt_death_pipe()?;
}
probe_broker_death()?;
if unsafe { ptrace(PT_TRACE_ME, 0, std::ptr::null_mut(), 0) } != 0 {
return Err(LauncherEntryError::TraceRefused);
}
probe_broker_death()?;
if unsafe { raise(SIGSTOP) } != 0 {
return Err(LauncherEntryError::TraceRefused);
}
probe_broker_death()?;
let parts = read_exact_plan()?;
let prepared = PreparedTarget::from_plan(parts)?;
verify_own_identity(&prepared)?;
probe_broker_death()?;
unsafe { apply_containment() }?;
probe_broker_death()?;
unsafe {
close(LAUNCHER_DEATH_FD);
close(LAUNCHER_PLAN_FD);
}
let _ = unsafe {
execve(
prepared.executable.as_ptr(),
prepared.argument_pointers.as_ptr(),
prepared.environment_pointers.as_ptr(),
)
};
std::process::abort();
}
struct PreparedTarget {
effective_uid: u32,
effective_gid: u32,
executable: CString,
_arguments: Vec<CString>,
_environment: Vec<CString>,
argument_pointers: Vec<*const c_char>,
environment_pointers: Vec<*const c_char>,
}
impl PreparedTarget {
fn from_plan(parts: LauncherExecParts) -> Result<Self, LauncherEntryError> {
let executable = CString::new(parts.installed_executable)
.map_err(|_| LauncherEntryError::InvalidTarget)?;
let arguments = parts
.arguments
.into_iter()
.map(CString::new)
.collect::<Result<Vec<_>, _>>()
.map_err(|_| LauncherEntryError::InvalidTarget)?;
if arguments.is_empty() {
return Err(LauncherEntryError::InvalidTarget);
}
let environment = parts
.environment
.into_iter()
.map(|entry| {
let mut bytes = entry.key().to_vec();
bytes.push(b'=');
bytes.extend_from_slice(entry.value());
CString::new(bytes)
})
.collect::<Result<Vec<_>, _>>()
.map_err(|_| LauncherEntryError::InvalidTarget)?;
let mut argument_pointers = arguments
.iter()
.map(|value| value.as_ptr())
.collect::<Vec<_>>();
argument_pointers.push(std::ptr::null());
let mut environment_pointers = environment
.iter()
.map(|value| value.as_ptr())
.collect::<Vec<_>>();
environment_pointers.push(std::ptr::null());
Ok(Self {
effective_uid: parts.effective_uid,
effective_gid: parts.effective_gid,
_arguments: arguments,
_environment: environment,
executable,
argument_pointers,
environment_pointers,
})
}
}
fn verify_own_identity(prepared: &PreparedTarget) -> Result<(), LauncherEntryError> {
let (uid, gid) = unsafe { (geteuid(), getegid()) };
if uid == 0 || gid == 0 || uid != prepared.effective_uid || gid != prepared.effective_gid {
return Err(LauncherEntryError::IdentityMismatch);
}
Ok(())
}
unsafe fn apply_containment() -> Result<(), LauncherEntryError> {
let profile = CString::new(LAUNCHER_SANDBOX_PROFILE)
.map_err(|_| LauncherEntryError::ContainmentRefused)?;
let mut error: *mut c_char = std::ptr::null_mut();
if unsafe { sandbox_init(profile.as_ptr(), 0, &raw mut error) } != 0 {
return Err(LauncherEntryError::ContainmentRefused);
}
let limit = LAUNCHER_PROCESS_LIMIT;
if unsafe { setrlimit(RLIMIT_NPROC, &raw const limit) } != 0 {
return Err(LauncherEntryError::ContainmentRefused);
}
let mut observed = ResourceLimit {
current: 0,
maximum: 0,
};
if unsafe { getrlimit(RLIMIT_NPROC, &raw mut observed) } != 0
|| observed != LAUNCHER_PROCESS_LIMIT
{
return Err(LauncherEntryError::ContainmentRefused);
}
Ok(())
}
fn read_exact_plan() -> Result<LauncherExecParts, LauncherEntryError> {
let mut prefix = [0_u8; LAUNCHER_PLAN_PREFIX_BYTES];
read_exact(LAUNCHER_PLAN_FD, &mut prefix)?;
let parsed = parse_launcher_plan_prefix_bytes(&prefix)?;
let mut frame = vec![0_u8; parsed];
frame[..LAUNCHER_PLAN_PREFIX_BYTES].copy_from_slice(&prefix);
read_exact(LAUNCHER_PLAN_FD, &mut frame[LAUNCHER_PLAN_PREFIX_BYTES..])?;
require_plan_eof()?;
let prefix_binding = parse_launcher_plan_prefix(&prefix, parsed)
.map_err(|_| LauncherEntryError::Plan)?
.deadline;
let received = ReceivedLauncherExecPlan::decode_with_deadline(&frame, prefix_binding)
.map_err(|_| LauncherEntryError::Plan)?;
Ok(received.into_parts())
}
fn parse_launcher_plan_prefix_bytes(
prefix: &[u8; LAUNCHER_PLAN_PREFIX_BYTES],
) -> Result<usize, LauncherEntryError> {
let length = u32::from_le_bytes(prefix[12..16].try_into().unwrap_or([0; 4]));
let length = usize::try_from(length).map_err(|_| LauncherEntryError::Plan)?;
if !(LAUNCHER_PLAN_PREFIX_BYTES..=MAX_BROKER_PLAN_BYTES).contains(&length) {
return Err(LauncherEntryError::Plan);
}
parse_launcher_plan_prefix(prefix, length).map_err(|_| LauncherEntryError::Plan)?;
Ok(length)
}
fn require_plan_eof() -> Result<(), LauncherEntryError> {
let mut extra = [0_u8; 1];
loop {
let count = unsafe { read(LAUNCHER_PLAN_FD, extra.as_mut_ptr(), 1) };
if count == 0 {
return Ok(());
}
if count > 0 {
return Err(LauncherEntryError::Plan);
}
if last_errno() != EINTR {
return Err(LauncherEntryError::Plan);
}
}
}
fn read_exact(fd: c_int, mut bytes: &mut [u8]) -> Result<(), LauncherEntryError> {
while !bytes.is_empty() {
let count = unsafe { read(fd, bytes.as_mut_ptr(), bytes.len()) };
if count == 0 {
return Err(LauncherEntryError::BrokerGone);
}
if count < 0 {
if last_errno() == EINTR {
continue;
}
return Err(LauncherEntryError::Plan);
}
let count = usize::try_from(count).map_err(|_| LauncherEntryError::Plan)?;
bytes = bytes.get_mut(count..).ok_or(LauncherEntryError::Plan)?;
}
Ok(())
}
fn probe_broker_death() -> Result<(), LauncherEntryError> {
let mut byte = 0_u8;
loop {
let count = unsafe { read(LAUNCHER_DEATH_FD, &raw mut byte, 1) };
if count == 0 {
return Err(LauncherEntryError::BrokerGone);
}
if count > 0 {
return Err(LauncherEntryError::InvalidDescriptor);
}
return match last_errno() {
EINTR => continue,
EAGAIN => Ok(()),
_ => Err(LauncherEntryError::InvalidDescriptor),
};
}
}
unsafe fn adopt_death_pipe() -> Result<(), LauncherEntryError> {
let flags = unsafe { fcntl(LAUNCHER_DEATH_FD, F_GETFL) };
if flags < 0 {
return Err(LauncherEntryError::InvalidDescriptor);
}
if unsafe { fcntl(LAUNCHER_DEATH_FD, F_SETFL, flags | O_NONBLOCK) } != 0 {
return Err(LauncherEntryError::InvalidDescriptor);
}
Ok(())
}
unsafe fn require_live(fd: c_int) -> Result<(), LauncherEntryError> {
if unsafe { fcntl(fd, F_GETFD) } < 0 {
return Err(LauncherEntryError::InvalidDescriptor);
}
Ok(())
}
unsafe fn set_close_on_exec(fd: c_int) -> Result<(), LauncherEntryError> {
let flags = unsafe { fcntl(fd, F_GETFD) };
if flags < 0 {
return Err(LauncherEntryError::InvalidDescriptor);
}
if unsafe { fcntl(fd, F_SETFD, flags | FD_CLOEXEC) } != 0 {
return Err(LauncherEntryError::InvalidDescriptor);
}
Ok(())
}
fn validate_fixed_arguments(
installed_path: &CStr,
arguments: impl IntoIterator<Item = impl AsRef<OsStr>>,
) -> Result<(), LauncherEntryError> {
if !is_deployer_helper_path(installed_path) {
return Err(LauncherEntryError::InvalidArguments);
}
let mut arguments = arguments.into_iter();
let expected = [
installed_path.to_bytes(),
INSTALLED_LAUNCHER_MODE.as_bytes(),
INSTALLED_LAUNCHER_DEATH_ARGUMENT.as_bytes(),
INSTALLED_LAUNCHER_PLAN_ARGUMENT.as_bytes(),
];
for expected in expected {
let Some(argument) = arguments.next() else {
return Err(LauncherEntryError::InvalidArguments);
};
if argument.as_ref().as_bytes() != expected {
return Err(LauncherEntryError::InvalidArguments);
}
}
if arguments.next().is_some() {
return Err(LauncherEntryError::InvalidArguments);
}
Ok(())
}
fn last_errno() -> c_int {
std::io::Error::last_os_error().raw_os_error().unwrap_or(0)
}
#[cfg(test)]
#[path = "supervisor_launcher_entry_test.rs"]
mod tests;
#[cfg(all(test, target_arch = "aarch64"))]
#[path = "supervisor_launcher_lifecycle_test.rs"]
mod lifecycle_tests;