@@ -35,6 +35,10 @@ use crate::exit_status::handle_exit_status;
3535#[ cfg( target_os = "macos" ) ]
3636use seatbelt:: DenialLogger ;
3737
38+ #[ cfg( target_os = "linux" ) ]
39+ const LINUX_SANDBOX_FORWARDED_SIGNALS : & [ libc:: c_int ] =
40+ & [ libc:: SIGHUP , libc:: SIGINT , libc:: SIGQUIT , libc:: SIGTERM ] ;
41+
3842#[ cfg( target_os = "macos" ) ]
3943pub async fn run_command_under_seatbelt (
4044 command : SeatbeltCommand ,
@@ -142,6 +146,13 @@ async fn run_command_under_sandbox(
142146 // sandbox policy. In the future, we could add a CLI option to set them
143147 // separately.
144148 let sandbox_policy_cwd = cwd. clone ( ) ;
149+ if let Some ( reason) = codex_shell_command:: preserved_path_write_forbidden_reason (
150+ & command,
151+ cwd. as_path ( ) ,
152+ & config. permissions . file_system_sandbox_policy ( ) ,
153+ ) {
154+ anyhow:: bail!( "{reason}" ) ;
155+ }
145156
146157 let env = create_env (
147158 & config. permissions . shell_environment_policy ,
@@ -261,6 +272,9 @@ async fn run_command_under_sandbox(
261272 denial_logger. on_child_spawn ( & child) ;
262273 }
263274
275+ #[ cfg( target_os = "linux" ) ]
276+ let status = wait_for_debug_sandbox_child ( & mut child) . await ?;
277+ #[ cfg( not( target_os = "linux" ) ) ]
264278 let status = child. wait ( ) . await ?;
265279
266280 #[ cfg( target_os = "macos" ) ]
@@ -438,13 +452,96 @@ async fn spawn_debug_sandbox_child(
438452 cmd. env ( CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR , "1" ) ;
439453 }
440454
455+ #[ cfg( target_os = "linux" ) ]
456+ {
457+ let parent_pid = unsafe { libc:: getpid ( ) } ;
458+ // SAFETY: `pre_exec` runs in the child immediately before exec. The
459+ // closure only adjusts the child signal mask, installs a parent-death
460+ // signal, and checks the inherited parent pid to close the fork/exec
461+ // race.
462+ unsafe {
463+ cmd. pre_exec ( move || {
464+ block_linux_sandbox_forwarded_signals ( ) ?;
465+ if libc:: prctl ( libc:: PR_SET_PDEATHSIG , libc:: SIGTERM ) == -1 {
466+ return Err ( std:: io:: Error :: last_os_error ( ) ) ;
467+ }
468+ if libc:: getppid ( ) != parent_pid {
469+ libc:: raise ( libc:: SIGTERM ) ;
470+ }
471+ Ok ( ( ) )
472+ } ) ;
473+ }
474+ }
475+
441476 cmd. stdin ( Stdio :: inherit ( ) )
442477 . stdout ( Stdio :: inherit ( ) )
443478 . stderr ( Stdio :: inherit ( ) )
444479 . kill_on_drop ( true )
445480 . spawn ( )
446481}
447482
483+ #[ cfg( target_os = "linux" ) ]
484+ async fn wait_for_debug_sandbox_child (
485+ child : & mut Child ,
486+ ) -> std:: io:: Result < std:: process:: ExitStatus > {
487+ let child_pid = child. id ( ) . map ( |pid| pid as libc:: pid_t ) ;
488+ tokio:: select! {
489+ status = child. wait( ) => status,
490+ signal = recv_linux_sandbox_forwarded_signal( ) => {
491+ let signal = signal?;
492+ if let Some ( child_pid) = child_pid {
493+ signal_debug_sandbox_child( child_pid, signal) ?;
494+ }
495+ child. wait( ) . await
496+ }
497+ }
498+ }
499+
500+ #[ cfg( target_os = "linux" ) ]
501+ async fn recv_linux_sandbox_forwarded_signal ( ) -> std:: io:: Result < libc:: c_int > {
502+ use tokio:: signal:: unix:: SignalKind ;
503+ use tokio:: signal:: unix:: signal;
504+
505+ let mut sighup = signal ( SignalKind :: hangup ( ) ) ?;
506+ let mut sigint = signal ( SignalKind :: interrupt ( ) ) ?;
507+ let mut sigquit = signal ( SignalKind :: quit ( ) ) ?;
508+ let mut sigterm = signal ( SignalKind :: terminate ( ) ) ?;
509+
510+ let signal = tokio:: select! {
511+ _ = sighup. recv( ) => libc:: SIGHUP ,
512+ _ = sigint. recv( ) => libc:: SIGINT ,
513+ _ = sigquit. recv( ) => libc:: SIGQUIT ,
514+ _ = sigterm. recv( ) => libc:: SIGTERM ,
515+ } ;
516+ Ok ( signal)
517+ }
518+
519+ #[ cfg( target_os = "linux" ) ]
520+ fn signal_debug_sandbox_child ( pid : libc:: pid_t , signal : libc:: c_int ) -> std:: io:: Result < ( ) > {
521+ if unsafe { libc:: kill ( pid, signal) } < 0 {
522+ let err = std:: io:: Error :: last_os_error ( ) ;
523+ if err. raw_os_error ( ) != Some ( libc:: ESRCH ) {
524+ return Err ( err) ;
525+ }
526+ }
527+ Ok ( ( ) )
528+ }
529+
530+ #[ cfg( target_os = "linux" ) ]
531+ fn block_linux_sandbox_forwarded_signals ( ) -> std:: io:: Result < ( ) > {
532+ let mut blocked: libc:: sigset_t = unsafe { std:: mem:: zeroed ( ) } ;
533+ unsafe {
534+ libc:: sigemptyset ( & mut blocked) ;
535+ for signal in LINUX_SANDBOX_FORWARDED_SIGNALS {
536+ libc:: sigaddset ( & mut blocked, * signal) ;
537+ }
538+ if libc:: sigprocmask ( libc:: SIG_BLOCK , & blocked, std:: ptr:: null_mut ( ) ) < 0 {
539+ return Err ( std:: io:: Error :: last_os_error ( ) ) ;
540+ }
541+ }
542+ Ok ( ( ) )
543+ }
544+
448545#[ cfg( target_os = "windows" ) ]
449546mod windows_stdio_bridge {
450547 use std:: io:: Read ;
0 commit comments