4242# include <sys/wait.h>
4343# include <unistd.h>
4444# if defined(SENTRY_PLATFORM_MACOS )
45+ # include <crt_externs.h>
4546# include <mach-o/dyld.h>
47+ # include <spawn.h>
4648# endif
4749#elif defined(SENTRY_PLATFORM_WINDOWS )
4850# include <dbghelp.h>
@@ -3062,8 +3064,8 @@ sentry__crash_daemon_main(
30623064 pid_t app_pid , uint64_t app_tid , int notify_eventfd , int ready_eventfd )
30633065#elif defined(SENTRY_PLATFORM_MACOS )
30643066int
3065- sentry__crash_daemon_main (
3066- pid_t app_pid , uint64_t app_tid , int notify_pipe_read , int ready_pipe_write )
3067+ sentry__crash_daemon_main (pid_t app_pid , uint64_t app_tid , int notify_pipe_read ,
3068+ int ready_pipe_write , int shm_fd )
30673069#elif defined(SENTRY_PLATFORM_WINDOWS )
30683070int
30693071sentry__crash_daemon_main (pid_t app_pid , uint64_t app_tid , HANDLE event_handle ,
@@ -3077,7 +3079,7 @@ sentry__crash_daemon_main(pid_t app_pid, uint64_t app_tid, HANDLE event_handle,
30773079 app_pid , app_tid , notify_eventfd , ready_eventfd );
30783080#elif defined(SENTRY_PLATFORM_MACOS )
30793081 sentry_crash_ipc_t * ipc = sentry__crash_ipc_init_daemon (
3080- app_pid , app_tid , notify_pipe_read , ready_pipe_write );
3082+ app_pid , app_tid , notify_pipe_read , ready_pipe_write , shm_fd );
30813083#elif defined(SENTRY_PLATFORM_WINDOWS )
30823084 sentry_crash_ipc_t * ipc = sentry__crash_ipc_init_daemon (
30833085 app_pid , app_tid , event_handle , ready_event_handle );
@@ -3329,29 +3331,112 @@ sentry__crash_daemon_start(pid_t app_pid, uint64_t app_tid, int notify_eventfd,
33293331#elif defined(SENTRY_PLATFORM_MACOS )
33303332pid_t
33313333sentry__crash_daemon_start (pid_t app_pid , uint64_t app_tid ,
3332- int notify_pipe_read , int ready_pipe_write , const char * handler_path )
3334+ int notify_pipe_read , int ready_pipe_write , int shm_fd ,
3335+ const char * handler_path )
33333336#elif defined(SENTRY_PLATFORM_WINDOWS )
33343337pid_t
33353338sentry__crash_daemon_start (pid_t app_pid , uint64_t app_tid , HANDLE event_handle ,
33363339 HANDLE ready_event_handle , const char * handler_path )
33373340#endif
33383341{
3339- #if defined(SENTRY_PLATFORM_UNIX )
3340- // Fork and exec sentry-crash executable
3341- // Using exec (not just fork) avoids inheriting sanitizer state and is
3342- // cleaner
3342+ #if defined(SENTRY_PLATFORM_MACOS )
3343+ // macOS: Use posix_spawn instead of fork+exec for App Sandbox
3344+ // compatibility. posix_spawn is Apple's recommended API and works correctly
3345+ // in sandboxed processes, unlike fork() which can have issues with sandbox
3346+ // inheritance.
3347+
3348+ // Resolve daemon path
3349+ char daemon_path [SENTRY_CRASH_MAX_PATH ];
3350+ if (handler_path && handler_path [0 ] != '\0' ) {
3351+ strncpy (daemon_path , handler_path , sizeof (daemon_path ) - 1 );
3352+ daemon_path [sizeof (daemon_path ) - 1 ] = '\0' ;
3353+ } else {
3354+ char exe_path [SENTRY_CRASH_MAX_PATH ];
3355+ uint32_t exe_size = sizeof (exe_path );
3356+ if (_NSGetExecutablePath (exe_path , & exe_size ) != 0 ) {
3357+ SENTRY_WARN ("Failed to get executable path for daemon" );
3358+ return -1 ;
3359+ }
3360+ const char * slash = strrchr (exe_path , '/' );
3361+ if (!slash
3362+ || (size_t )(slash - exe_path + 1 ) + strlen ("sentry-crash" )
3363+ >= sizeof (daemon_path )) {
3364+ SENTRY_WARN ("Daemon path too long" );
3365+ return -1 ;
3366+ }
3367+ size_t dir_len = (size_t )(slash - exe_path + 1 );
3368+ memcpy (daemon_path , exe_path , dir_len );
3369+ strcpy (daemon_path + dir_len , "sentry-crash" );
3370+ }
3371+
3372+ // Build argument strings (6 args: pid, tid, notify_fd, ready_fd, shm_fd)
3373+ char pid_str [32 ], tid_str [32 ], notify_str [32 ], ready_str [32 ], shm_str [32 ];
3374+ snprintf (pid_str , sizeof (pid_str ), "%d" , (int )app_pid );
3375+ snprintf (tid_str , sizeof (tid_str ), "%" PRIx64 , app_tid );
3376+ snprintf (notify_str , sizeof (notify_str ), "%d" , notify_pipe_read );
3377+ snprintf (ready_str , sizeof (ready_str ), "%d" , ready_pipe_write );
3378+ snprintf (shm_str , sizeof (shm_str ), "%d" , shm_fd );
3379+
3380+ char * spawn_argv [] = { "sentry-crash" , pid_str , tid_str , notify_str ,
3381+ ready_str , shm_str , NULL };
3382+
3383+ // Set up posix_spawn attributes
3384+ posix_spawnattr_t attr ;
3385+ posix_spawnattr_init (& attr );
3386+ // POSIX_SPAWN_SETSID: create new session (like setsid() after fork)
3387+ // POSIX_SPAWN_CLOEXEC_DEFAULT: close all fds except explicitly inherited
3388+ short spawn_flags = POSIX_SPAWN_SETSID | POSIX_SPAWN_CLOEXEC_DEFAULT ;
3389+ posix_spawnattr_setflags (& attr , spawn_flags );
3390+
3391+ // Explicitly inherit only the fds the daemon needs
3392+ posix_spawn_file_actions_t file_actions ;
3393+ posix_spawn_file_actions_init (& file_actions );
3394+ posix_spawn_file_actions_addinherit_np (& file_actions , notify_pipe_read );
3395+ posix_spawn_file_actions_addinherit_np (& file_actions , ready_pipe_write );
3396+ posix_spawn_file_actions_addinherit_np (& file_actions , shm_fd );
3397+ // Open /dev/null on stdin/stdout/stderr so the daemon starts with valid
3398+ // standard fds. Without this, POSIX_SPAWN_CLOEXEC_DEFAULT closes them,
3399+ // and the first fopen() in the daemon would get fd 0, which the daemon's
3400+ // own close(STDIN_FILENO) would then destroy.
3401+ // Skip if an IPC fd occupies that slot (e.g. caller closed stdin before
3402+ // sentry_init), to avoid clobbering it with /dev/null.
3403+ int std_fds [3 ] = { STDIN_FILENO , STDOUT_FILENO , STDERR_FILENO };
3404+ int std_modes [3 ] = { O_RDONLY , O_WRONLY , O_WRONLY };
3405+ for (int i = 0 ; i < 3 ; i ++ ) {
3406+ if (std_fds [i ] != notify_pipe_read && std_fds [i ] != ready_pipe_write
3407+ && std_fds [i ] != shm_fd ) {
3408+ posix_spawn_file_actions_addopen (
3409+ & file_actions , std_fds [i ], "/dev/null" , std_modes [i ], 0 );
3410+ }
3411+ }
3412+
3413+ pid_t daemon_pid ;
3414+ int spawn_result = posix_spawn (& daemon_pid , daemon_path , & file_actions ,
3415+ & attr , spawn_argv , * _NSGetEnviron ());
3416+
3417+ posix_spawn_file_actions_destroy (& file_actions );
3418+ posix_spawnattr_destroy (& attr );
3419+
3420+ if (spawn_result != 0 ) {
3421+ SENTRY_WARNF ("posix_spawn failed for %s: %s" , daemon_path ,
3422+ strerror (spawn_result ));
3423+ return -1 ;
3424+ }
3425+
3426+ return daemon_pid ;
3427+
3428+ #elif defined(SENTRY_PLATFORM_LINUX ) || defined(SENTRY_PLATFORM_ANDROID )
3429+ // Linux: Use fork+exec
33433430 pid_t daemon_pid = fork ();
33443431
33453432 if (daemon_pid < 0 ) {
3346- // Fork failed
33473433 SENTRY_WARN ("Failed to fork daemon process" );
33483434 return -1 ;
33493435 } else if (daemon_pid == 0 ) {
33503436 // Child process - exec sentry-crash
33513437 setsid ();
33523438
33533439 // Clear FD_CLOEXEC on notify and ready fds so they survive exec
3354- # if defined(SENTRY_PLATFORM_LINUX ) || defined(SENTRY_PLATFORM_ANDROID )
33553440 int notify_flags = fcntl (notify_eventfd , F_GETFD );
33563441 if (notify_flags != -1 ) {
33573442 fcntl (notify_eventfd , F_SETFD , notify_flags & ~FD_CLOEXEC );
@@ -3360,73 +3445,38 @@ sentry__crash_daemon_start(pid_t app_pid, uint64_t app_tid, HANDLE event_handle,
33603445 if (ready_flags != -1 ) {
33613446 fcntl (ready_eventfd , F_SETFD , ready_flags & ~FD_CLOEXEC );
33623447 }
3363- # elif defined(SENTRY_PLATFORM_MACOS )
3364- int notify_flags = fcntl (notify_pipe_read , F_GETFD );
3365- if (notify_flags != -1 ) {
3366- fcntl (notify_pipe_read , F_SETFD , notify_flags & ~FD_CLOEXEC );
3367- }
3368- int ready_flags = fcntl (ready_pipe_write , F_GETFD );
3369- if (ready_flags != -1 ) {
3370- fcntl (ready_pipe_write , F_SETFD , ready_flags & ~FD_CLOEXEC );
3371- }
3372- # endif
33733448
33743449 // Convert arguments to strings for exec
33753450 char pid_str [32 ], tid_str [32 ], notify_str [32 ], ready_str [32 ];
33763451 snprintf (pid_str , sizeof (pid_str ), "%d" , (int )app_pid );
33773452 snprintf (tid_str , sizeof (tid_str ), "%" PRIx64 , app_tid );
3378- # if defined(SENTRY_PLATFORM_LINUX ) || defined(SENTRY_PLATFORM_ANDROID )
33793453 snprintf (notify_str , sizeof (notify_str ), "%d" , notify_eventfd );
33803454 snprintf (ready_str , sizeof (ready_str ), "%d" , ready_eventfd );
3381- # elif defined(SENTRY_PLATFORM_MACOS )
3382- snprintf (notify_str , sizeof (notify_str ), "%d" , notify_pipe_read );
3383- snprintf (ready_str , sizeof (ready_str ), "%d" , ready_pipe_write );
3384- # endif
33853455
33863456 char * argv []
33873457 = { "sentry-crash" , pid_str , tid_str , notify_str , ready_str , NULL };
33883458
3389- // If handler_path was explicitly set via options, use it directly.
3390- // Otherwise, look for sentry-crash next to the current executable
3391- // (matching crashpad's behavior). No fallback chain — fail hard so
3392- // configuration issues are visible.
33933459 if (handler_path && handler_path [0 ] != '\0' ) {
33943460 execv (handler_path , argv );
33953461 } else {
33963462 char exe_path [SENTRY_CRASH_MAX_PATH ];
3397- char daemon_path [SENTRY_CRASH_MAX_PATH ];
3463+ char daemon_exec_path [SENTRY_CRASH_MAX_PATH ];
33983464
3399- # if defined(SENTRY_PLATFORM_LINUX ) || defined(SENTRY_PLATFORM_ANDROID )
34003465 ssize_t exe_len
34013466 = readlink ("/proc/self/exe" , exe_path , sizeof (exe_path ) - 1 );
34023467 if (exe_len > 0 ) {
34033468 exe_path [exe_len ] = '\0' ;
34043469 const char * slash = strrchr (exe_path , '/' );
34053470 if (slash ) {
3406- size_t dir_len = slash - exe_path + 1 ;
3471+ size_t dir_len = ( size_t )( slash - exe_path + 1 ) ;
34073472 if (dir_len + strlen ("sentry-crash" )
3408- < sizeof (daemon_path )) {
3409- memcpy (daemon_path , exe_path , dir_len );
3410- strcpy (daemon_path + dir_len , "sentry-crash" );
3411- execv (daemon_path , argv );
3473+ < sizeof (daemon_exec_path )) {
3474+ memcpy (daemon_exec_path , exe_path , dir_len );
3475+ strcpy (daemon_exec_path + dir_len , "sentry-crash" );
3476+ execv (daemon_exec_path , argv );
34123477 }
34133478 }
34143479 }
3415- # elif defined(SENTRY_PLATFORM_MACOS )
3416- uint32_t exe_size = sizeof (exe_path );
3417- if (_NSGetExecutablePath (exe_path , & exe_size ) == 0 ) {
3418- const char * slash = strrchr (exe_path , '/' );
3419- if (slash ) {
3420- size_t dir_len = slash - exe_path + 1 ;
3421- if (dir_len + strlen ("sentry-crash" )
3422- < sizeof (daemon_path )) {
3423- memcpy (daemon_path , exe_path , dir_len );
3424- strcpy (daemon_path + dir_len , "sentry-crash" );
3425- execv (daemon_path , argv );
3426- }
3427- }
3428- }
3429- # endif
34303480 }
34313481
34323482 // exec failed - exit with error
@@ -3553,13 +3603,24 @@ sentry__crash_daemon_start(pid_t app_pid, uint64_t app_tid, HANDLE event_handle,
35533603int
35543604main (int argc , char * * argv )
35553605{
3556- // Expected arguments: <app_pid> <app_tid> <notify_handle> <ready_handle>
3606+ // Expected arguments:
3607+ // Linux: <app_pid> <app_tid> <notify_handle> <ready_handle>
3608+ // macOS: <app_pid> <app_tid> <notify_handle> <ready_handle> <shm_fd>
3609+ # if defined(SENTRY_PLATFORM_MACOS )
3610+ if (argc < 6 ) {
3611+ fprintf (stderr ,
3612+ "Usage: sentry-crash <app_pid> <app_tid> <notify_pipe> "
3613+ "<ready_pipe> <shm_fd>\n" );
3614+ return 1 ;
3615+ }
3616+ # else
35573617 if (argc < 5 ) {
35583618 fprintf (stderr ,
35593619 "Usage: sentry-crash <app_pid> <app_tid> <notify_handle> "
35603620 "<ready_handle>\n" );
35613621 return 1 ;
35623622 }
3623+ # endif
35633624
35643625 // Parse arguments
35653626 pid_t app_pid = (pid_t )strtoul (argv [1 ], NULL , 10 );
@@ -3573,8 +3634,9 @@ main(int argc, char **argv)
35733634# elif defined(SENTRY_PLATFORM_MACOS )
35743635 int notify_pipe_read = atoi (argv [3 ]);
35753636 int ready_pipe_write = atoi (argv [4 ]);
3637+ int shm_fd_arg = atoi (argv [5 ]);
35763638 return sentry__crash_daemon_main (
3577- app_pid , app_tid , notify_pipe_read , ready_pipe_write );
3639+ app_pid , app_tid , notify_pipe_read , ready_pipe_write , shm_fd_arg );
35783640# elif defined(SENTRY_PLATFORM_WINDOWS )
35793641 unsigned long long event_handle_val = strtoull (argv [3 ], NULL , 10 );
35803642 unsigned long long ready_event_val = strtoull (argv [4 ], NULL , 10 );
0 commit comments