Compare commits

...

1 Commits

Author SHA1 Message Date
Jeremy Rose
695c183fcb utils: reset exception ports in pre_exec 2026-01-29 20:10:07 -08:00
2 changed files with 106 additions and 0 deletions

View File

@@ -119,6 +119,8 @@ async fn spawn_process_with_stdin_mode(
unsafe {
command.pre_exec(move || {
crate::process_group::detach_from_tty()?;
#[cfg(target_os = "macos")]
let _ = crate::process_group::reset_to_system_crash_reporter();
#[cfg(target_os = "linux")]
crate::process_group::set_parent_death_signal(parent_pid)?;
Ok(())

View File

@@ -19,6 +19,13 @@ use std::io;
use tokio::process::Child;
#[cfg(target_os = "macos")]
use libc::c_char;
#[cfg(target_os = "macos")]
use libc::c_int;
#[cfg(target_os = "macos")]
use libc::c_uint;
#[cfg(target_os = "linux")]
/// Ensure the child receives SIGTERM when the original parent dies.
///
@@ -44,6 +51,103 @@ pub fn set_parent_death_signal(_parent_pid: i32) -> io::Result<()> {
Ok(())
}
#[cfg(target_os = "macos")]
/// Reset exception ports to the system crash reporter.
///
/// This breaks any inherited connection to a wrapping crash handler before
/// `exec`, so crashes in the spawned process are reported by the system.
pub fn reset_to_system_crash_reporter() -> bool {
type MachPort = c_uint;
type KernReturn = c_int;
type ExceptionMask = c_uint;
type ExceptionBehavior = c_int;
type ThreadStateFlavor = c_int;
const KERN_SUCCESS: KernReturn = 0;
const MACH_PORT_NULL: MachPort = 0;
const MACH_PORT_DEAD: MachPort = MachPort::MAX;
const EXC_CRASH: u32 = 10;
const EXC_RESOURCE: u32 = 11;
const EXC_GUARD: u32 = 12;
const EXC_MASK_CRASH: ExceptionMask = 1 << EXC_CRASH;
const EXC_MASK_RESOURCE: ExceptionMask = 1 << EXC_RESOURCE;
const EXC_MASK_GUARD: ExceptionMask = 1 << EXC_GUARD;
const EXCEPTION_STATE_IDENTITY: ExceptionBehavior = 3;
const MACH_EXCEPTION_CODES: ExceptionBehavior = 0x8000_0000u32 as ExceptionBehavior;
#[cfg(target_arch = "aarch64")]
const MACHINE_THREAD_STATE: ThreadStateFlavor = 1;
#[cfg(target_arch = "x86_64")]
const MACHINE_THREAD_STATE: ThreadStateFlavor = 7;
#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
const MACHINE_THREAD_STATE: ThreadStateFlavor = 1;
unsafe extern "C" {
static bootstrap_port: MachPort;
fn bootstrap_look_up(
bp: MachPort,
service_name: *const c_char,
sp: *mut MachPort,
) -> KernReturn;
fn mach_task_self() -> MachPort;
fn task_set_exception_ports(
task: MachPort,
exception_mask: ExceptionMask,
new_port: MachPort,
behavior: ExceptionBehavior,
new_flavor: ThreadStateFlavor,
) -> KernReturn;
}
const REPORT_CRASH_SERVICE: &[u8] = b"com.apple.ReportCrash\0";
let service_name = REPORT_CRASH_SERVICE.as_ptr().cast::<c_char>();
let mut report_crash: MachPort = MACH_PORT_NULL;
let lookup = unsafe {
bootstrap_look_up(
bootstrap_port,
service_name,
&mut report_crash as *mut MachPort,
)
};
if lookup != KERN_SUCCESS || report_crash == MACH_PORT_DEAD || report_crash == MACH_PORT_NULL {
return false;
}
// Best-effort: include resource/guard masks, then fall back to crash-only
// when those exceptions are unsupported in this context.
let mask = EXC_MASK_CRASH | EXC_MASK_RESOURCE | EXC_MASK_GUARD;
let behavior = EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES;
let task = unsafe { mach_task_self() };
let primary = unsafe {
task_set_exception_ports(task, mask, report_crash, behavior, MACHINE_THREAD_STATE)
};
if primary == KERN_SUCCESS {
return true;
}
let fallback = unsafe {
task_set_exception_ports(
task,
EXC_MASK_CRASH,
report_crash,
behavior,
MACHINE_THREAD_STATE,
)
};
fallback == KERN_SUCCESS
}
#[cfg(not(target_os = "macos"))]
/// No-op on non-macOS platforms.
pub fn reset_to_system_crash_reporter() -> bool {
true
}
#[cfg(unix)]
/// Detach from the controlling TTY by starting a new session.
pub fn detach_from_tty() -> io::Result<()> {