macOS: spawn instead of fork+exec. * * fork() duplicates the parent's whole address space bookkeeping, and an * ASan-instrumented parent carries an enormous shadow mapping. Past a * footprint threshold the child is killed (jetsam) BEFORE exec replaces the * image, so the call fails with the child already gone (ESRCH on reap) — a * spawn failure that looks like the launched tool crashing. The
| 969 | * because dup2 clears close-on-exec. |
| 970 | * posix_spawnp keeps execvp's PATH semantics for a bare tool name. */ |
| 971 | static int cbm_posix_spawn_apple(cbm_subprocess_t *process, int input, int output, pid_t *pid_out) { |
| 972 | posix_spawn_file_actions_t actions; |
| 973 | posix_spawnattr_t attr; |
| 974 | if (posix_spawn_file_actions_init(&actions) != 0) { |
| 975 | return -1; |
| 976 | } |
| 977 | if (posix_spawnattr_init(&attr) != 0) { |
| 978 | (void)posix_spawn_file_actions_destroy(&actions); |
| 979 | return -1; |
| 980 | } |
| 981 | sigset_t empty_mask; |
| 982 | sigset_t all_signals; |
| 983 | sigemptyset(&empty_mask); |
| 984 | sigfillset(&all_signals); |
| 985 | short flags = (short)(POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK | |
| 986 | POSIX_SPAWN_CLOEXEC_DEFAULT); |
| 987 | bool configured = posix_spawnattr_setflags(&attr, flags) == 0 && |
| 988 | posix_spawnattr_setpgroup(&attr, 0) == 0 && |
| 989 | posix_spawnattr_setsigmask(&attr, &empty_mask) == 0 && |
| 990 | posix_spawnattr_setsigdefault(&attr, &all_signals) == 0 && |
| 991 | posix_spawn_file_actions_adddup2(&actions, input, STDIN_FILENO) == 0 && |
| 992 | posix_spawn_file_actions_adddup2(&actions, output, STDOUT_FILENO) == 0 && |
| 993 | posix_spawn_file_actions_adddup2(&actions, output, STDERR_FILENO) == 0; |
| 994 | pid_t pid = -1; |
| 995 | int rc = |
| 996 | configured ? posix_spawnp(&pid, process->bin, &actions, &attr, process->argv, environ) : -1; |
| 997 | (void)posix_spawn_file_actions_destroy(&actions); |
| 998 | (void)posix_spawnattr_destroy(&attr); |
| 999 | if (configured && rc == 0 && pid > 0) { |
| 1000 | *pid_out = pid; |
| 1001 | return 0; |
| 1002 | } |
| 1003 | /* posix_spawn reports an unusable binary itself, where fork+exec instead |
| 1004 | * produces a child that exits 127. Callers (and tests) rely on the latter: |
| 1005 | * "spawn_failed" means the SPAWN mechanism failed, not that the tool was |
| 1006 | * missing. Fall back to fork+exec for exec-class errors so macOS and Linux |
| 1007 | * classify a bogus binary identically; the ASan-fork hazard does not apply |
| 1008 | * here, since this child exits immediately. */ |
| 1009 | if (configured && (rc == ENOENT || rc == EACCES || rc == ENOEXEC || rc == EISDIR || |
| 1010 | rc == ELOOP || rc == ENAMETOOLONG || rc == ENOTDIR)) { |
| 1011 | return 1; |
| 1012 | } |
| 1013 | return -1; |
| 1014 | } |
| 1015 | #endif |
| 1016 | |
| 1017 | static int cbm_subprocess_spawn_posix(cbm_subprocess_t *process) { |
no outgoing calls
no test coverage detected