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