1 //===- FuzzerUtilDarwin.cpp - Misc utils ----------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // Misc utils for Darwin. 9 //===----------------------------------------------------------------------===// 10 #include "FuzzerDefs.h" 11 #if LIBFUZZER_APPLE 12 #include "FuzzerCommand.h" 13 #include "FuzzerIO.h" 14 #include <mutex> 15 #include <signal.h> 16 #include <spawn.h> 17 #include <stdlib.h> 18 #include <string.h> 19 #include <sys/wait.h> 20 #include <unistd.h> 21 22 // There is no header for this on macOS so declare here 23 extern "C" char **environ; 24 25 namespace fuzzer { 26 27 static std::mutex SignalMutex; 28 // Global variables used to keep track of how signal handling should be 29 // restored. They should **not** be accessed without holding `SignalMutex`. 30 static int ActiveThreadCount = 0; 31 static struct sigaction OldSigIntAction; 32 static struct sigaction OldSigQuitAction; 33 static sigset_t OldBlockedSignalsSet; 34 35 // This is a reimplementation of Libc's `system()`. On Darwin the Libc 36 // implementation contains a mutex which prevents it from being used 37 // concurrently. This implementation **can** be used concurrently. It sets the 38 // signal handlers when the first thread enters and restores them when the last 39 // thread finishes execution of the function and ensures this is not racey by 40 // using a mutex. 41 int ExecuteCommand(const Command &Cmd) { 42 std::string CmdLine = Cmd.toString(); 43 posix_spawnattr_t SpawnAttributes; 44 if (posix_spawnattr_init(&SpawnAttributes)) 45 return -1; 46 // Block and ignore signals of the current process when the first thread 47 // enters. 48 { 49 std::lock_guard<std::mutex> Lock(SignalMutex); 50 if (ActiveThreadCount == 0) { 51 static struct sigaction IgnoreSignalAction; 52 sigset_t BlockedSignalsSet; 53 memset(&IgnoreSignalAction, 0, sizeof(IgnoreSignalAction)); 54 IgnoreSignalAction.sa_handler = SIG_IGN; 55 56 if (sigaction(SIGINT, &IgnoreSignalAction, &OldSigIntAction) == -1) { 57 Printf("Failed to ignore SIGINT\n"); 58 (void)posix_spawnattr_destroy(&SpawnAttributes); 59 return -1; 60 } 61 if (sigaction(SIGQUIT, &IgnoreSignalAction, &OldSigQuitAction) == -1) { 62 Printf("Failed to ignore SIGQUIT\n"); 63 // Try our best to restore the signal handlers. 64 (void)sigaction(SIGINT, &OldSigIntAction, NULL); 65 (void)posix_spawnattr_destroy(&SpawnAttributes); 66 return -1; 67 } 68 69 (void)sigemptyset(&BlockedSignalsSet); 70 (void)sigaddset(&BlockedSignalsSet, SIGCHLD); 71 if (sigprocmask(SIG_BLOCK, &BlockedSignalsSet, &OldBlockedSignalsSet) == 72 -1) { 73 Printf("Failed to block SIGCHLD\n"); 74 // Try our best to restore the signal handlers. 75 (void)sigaction(SIGQUIT, &OldSigQuitAction, NULL); 76 (void)sigaction(SIGINT, &OldSigIntAction, NULL); 77 (void)posix_spawnattr_destroy(&SpawnAttributes); 78 return -1; 79 } 80 } 81 ++ActiveThreadCount; 82 } 83 84 // NOTE: Do not introduce any new `return` statements past this 85 // point. It is important that `ActiveThreadCount` always be decremented 86 // when leaving this function. 87 88 // Make sure the child process uses the default handlers for the 89 // following signals rather than inheriting what the parent has. 90 sigset_t DefaultSigSet; 91 (void)sigemptyset(&DefaultSigSet); 92 (void)sigaddset(&DefaultSigSet, SIGQUIT); 93 (void)sigaddset(&DefaultSigSet, SIGINT); 94 (void)posix_spawnattr_setsigdefault(&SpawnAttributes, &DefaultSigSet); 95 // Make sure the child process doesn't block SIGCHLD 96 (void)posix_spawnattr_setsigmask(&SpawnAttributes, &OldBlockedSignalsSet); 97 short SpawnFlags = POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK; 98 (void)posix_spawnattr_setflags(&SpawnAttributes, SpawnFlags); 99 100 pid_t Pid; 101 char **Environ = environ; // Read from global 102 const char *CommandCStr = CmdLine.c_str(); 103 char *const Argv[] = { 104 strdup("sh"), 105 strdup("-c"), 106 strdup(CommandCStr), 107 NULL 108 }; 109 int ErrorCode = 0, ProcessStatus = 0; 110 // FIXME: We probably shouldn't hardcode the shell path. 111 ErrorCode = posix_spawn(&Pid, "/bin/sh", NULL, &SpawnAttributes, 112 Argv, Environ); 113 (void)posix_spawnattr_destroy(&SpawnAttributes); 114 if (!ErrorCode) { 115 pid_t SavedPid = Pid; 116 do { 117 // Repeat until call completes uninterrupted. 118 Pid = waitpid(SavedPid, &ProcessStatus, /*options=*/0); 119 } while (Pid == -1 && errno == EINTR); 120 if (Pid == -1) { 121 // Fail for some other reason. 122 ProcessStatus = -1; 123 } 124 } else if (ErrorCode == ENOMEM || ErrorCode == EAGAIN) { 125 // Fork failure. 126 ProcessStatus = -1; 127 } else { 128 // Shell execution failure. 129 ProcessStatus = W_EXITCODE(127, 0); 130 } 131 for (unsigned i = 0, n = sizeof(Argv) / sizeof(Argv[0]); i < n; ++i) 132 free(Argv[i]); 133 134 // Restore the signal handlers of the current process when the last thread 135 // using this function finishes. 136 { 137 std::lock_guard<std::mutex> Lock(SignalMutex); 138 --ActiveThreadCount; 139 if (ActiveThreadCount == 0) { 140 bool FailedRestore = false; 141 if (sigaction(SIGINT, &OldSigIntAction, NULL) == -1) { 142 Printf("Failed to restore SIGINT handling\n"); 143 FailedRestore = true; 144 } 145 if (sigaction(SIGQUIT, &OldSigQuitAction, NULL) == -1) { 146 Printf("Failed to restore SIGQUIT handling\n"); 147 FailedRestore = true; 148 } 149 if (sigprocmask(SIG_BLOCK, &OldBlockedSignalsSet, NULL) == -1) { 150 Printf("Failed to unblock SIGCHLD\n"); 151 FailedRestore = true; 152 } 153 if (FailedRestore) 154 ProcessStatus = -1; 155 } 156 } 157 return ProcessStatus; 158 } 159 160 void DiscardOutput(int Fd) { 161 FILE* Temp = fopen("/dev/null", "w"); 162 if (!Temp) 163 return; 164 dup2(fileno(Temp), Fd); 165 fclose(Temp); 166 } 167 168 } // namespace fuzzer 169 170 #endif // LIBFUZZER_APPLE 171