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 <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 | void SetThreadName(std::thread &thread, const std::string &name) { |
169 | // TODO ? |
170 | // Darwin allows to set the name only on the current thread it seems |
171 | } |
172 | |
173 | } // namespace fuzzer |
174 | |
175 | #endif // LIBFUZZER_APPLE |
176 | |