1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/process/launch.h"
6
7 #include <crt_externs.h>
8 #include <mach/mach.h>
9 #include <os/availability.h>
10 #include <spawn.h>
11 #include <string.h>
12 #include <sys/wait.h>
13
14 #include "base/command_line.h"
15 #include "base/files/scoped_file.h"
16 #include "base/logging.h"
17 #include "base/mac/mach_port_rendezvous.h"
18 #include "base/posix/eintr_wrapper.h"
19 #include "base/process/environment_internal.h"
20 #include "base/threading/scoped_blocking_call.h"
21 #include "base/threading/thread_restrictions.h"
22 #include "base/trace_event/base_tracing.h"
23
24 extern "C" {
25 // Changes the current thread's directory to a path or directory file
26 // descriptor. libpthread only exposes a syscall wrapper starting in
27 // macOS 10.12, but the system call dates back to macOS 10.5. On older OSes,
28 // the syscall is issued directly.
29 int pthread_chdir_np(const char* dir) API_AVAILABLE(macosx(10.12));
30 int pthread_fchdir_np(int fd) API_AVAILABLE(macosx(10.12));
31
32 int responsibility_spawnattrs_setdisclaim(posix_spawnattr_t attrs, int disclaim)
33 API_AVAILABLE(macosx(10.14));
34 } // extern "C"
35
36 namespace base {
37
38 namespace {
39
40 // DPSXCHECK is a Debug Posix Spawn Check macro. The posix_spawn* family of
41 // functions return an errno value, as opposed to setting errno directly. This
42 // macro emulates a DPCHECK().
43 #define DPSXCHECK(expr) \
44 do { \
45 int rv = (expr); \
46 DCHECK_EQ(rv, 0) << #expr << ": -" << rv << " " << strerror(rv); \
47 } while (0)
48
49 class PosixSpawnAttr {
50 public:
PosixSpawnAttr()51 PosixSpawnAttr() { DPSXCHECK(posix_spawnattr_init(&attr_)); }
52
~PosixSpawnAttr()53 ~PosixSpawnAttr() { DPSXCHECK(posix_spawnattr_destroy(&attr_)); }
54
get()55 posix_spawnattr_t* get() { return &attr_; }
56
57 private:
58 posix_spawnattr_t attr_;
59 };
60
61 class PosixSpawnFileActions {
62 public:
PosixSpawnFileActions()63 PosixSpawnFileActions() {
64 DPSXCHECK(posix_spawn_file_actions_init(&file_actions_));
65 }
66
67 PosixSpawnFileActions(const PosixSpawnFileActions&) = delete;
68 PosixSpawnFileActions& operator=(const PosixSpawnFileActions&) = delete;
69
~PosixSpawnFileActions()70 ~PosixSpawnFileActions() {
71 DPSXCHECK(posix_spawn_file_actions_destroy(&file_actions_));
72 }
73
Open(int filedes,const char * path,int mode)74 void Open(int filedes, const char* path, int mode) {
75 DPSXCHECK(posix_spawn_file_actions_addopen(&file_actions_, filedes, path,
76 mode, 0));
77 }
78
Dup2(int filedes,int newfiledes)79 void Dup2(int filedes, int newfiledes) {
80 DPSXCHECK(
81 posix_spawn_file_actions_adddup2(&file_actions_, filedes, newfiledes));
82 }
83
Inherit(int filedes)84 void Inherit(int filedes) {
85 DPSXCHECK(posix_spawn_file_actions_addinherit_np(&file_actions_, filedes));
86 }
87
88 #if BUILDFLAG(IS_MAC)
Chdir(const char * path)89 void Chdir(const char* path) API_AVAILABLE(macos(10.15)) {
90 DPSXCHECK(posix_spawn_file_actions_addchdir_np(&file_actions_, path));
91 }
92 #endif
93
get() const94 const posix_spawn_file_actions_t* get() const { return &file_actions_; }
95
96 private:
97 posix_spawn_file_actions_t file_actions_;
98 };
99
ChangeCurrentThreadDirectory(const char * path)100 int ChangeCurrentThreadDirectory(const char* path) {
101 return pthread_chdir_np(path);
102 }
103
104 // The recommended way to unset a per-thread cwd is to set a new value to an
105 // invalid file descriptor, per libpthread-218.1.3/private/private.h.
ResetCurrentThreadDirectory()106 int ResetCurrentThreadDirectory() {
107 return pthread_fchdir_np(-1);
108 }
109
110 struct GetAppOutputOptions {
111 // Whether to pipe stderr to stdout in |output|.
112 bool include_stderr = false;
113 // Caller-supplied string poiter for the output.
114 std::string* output = nullptr;
115 // Result exit code of Process::Wait().
116 int exit_code = 0;
117 };
118
GetAppOutputInternal(const std::vector<std::string> & argv,GetAppOutputOptions * gao_options)119 bool GetAppOutputInternal(const std::vector<std::string>& argv,
120 GetAppOutputOptions* gao_options) {
121 TRACE_EVENT0("base", "GetAppOutput");
122
123 ScopedFD read_fd, write_fd;
124 {
125 int pipefds[2];
126 if (pipe(pipefds) != 0) {
127 DPLOG(ERROR) << "pipe";
128 return false;
129 }
130 read_fd.reset(pipefds[0]);
131 write_fd.reset(pipefds[1]);
132 }
133
134 LaunchOptions launch_options;
135 launch_options.fds_to_remap.emplace_back(write_fd.get(), STDOUT_FILENO);
136 if (gao_options->include_stderr) {
137 launch_options.fds_to_remap.emplace_back(write_fd.get(), STDERR_FILENO);
138 }
139
140 Process process = LaunchProcess(argv, launch_options);
141
142 // Close the parent process' write descriptor, so that EOF is generated in
143 // read loop below.
144 write_fd.reset();
145
146 // Read the child's output before waiting for its exit, otherwise the pipe
147 // buffer may fill up if the process is producing a lot of output.
148 std::string* output = gao_options->output;
149 output->clear();
150
151 const size_t kBufferSize = 1024;
152 size_t total_bytes_read = 0;
153 ssize_t read_this_pass = 0;
154 do {
155 output->resize(output->size() + kBufferSize);
156 read_this_pass = HANDLE_EINTR(
157 read(read_fd.get(), &(*output)[total_bytes_read], kBufferSize));
158 if (read_this_pass >= 0) {
159 total_bytes_read += static_cast<size_t>(read_this_pass);
160 output->resize(total_bytes_read);
161 }
162 } while (read_this_pass > 0);
163
164 // It is okay to allow this process to wait on the launched process as a
165 // process launched with GetAppOutput*() shouldn't wait back on the process
166 // that launched it.
167 internal::GetAppOutputScopedAllowBaseSyncPrimitives allow_wait;
168 if (!process.WaitForExit(&gao_options->exit_code)) {
169 return false;
170 }
171
172 return read_this_pass == 0;
173 }
174
175 } // namespace
176
LaunchProcess(const CommandLine & cmdline,const LaunchOptions & options)177 Process LaunchProcess(const CommandLine& cmdline,
178 const LaunchOptions& options) {
179 return LaunchProcess(cmdline.argv(), options);
180 }
181
LaunchProcess(const std::vector<std::string> & argv,const LaunchOptions & options)182 Process LaunchProcess(const std::vector<std::string>& argv,
183 const LaunchOptions& options) {
184 TRACE_EVENT0("base", "LaunchProcess");
185
186 PosixSpawnAttr attr;
187
188 short flags = POSIX_SPAWN_CLOEXEC_DEFAULT;
189 if (options.new_process_group) {
190 flags |= POSIX_SPAWN_SETPGROUP;
191 DPSXCHECK(posix_spawnattr_setpgroup(attr.get(), 0));
192 }
193 DPSXCHECK(posix_spawnattr_setflags(attr.get(), flags));
194
195 PosixSpawnFileActions file_actions;
196
197 // Process file descriptors for the child. By default, LaunchProcess will
198 // open stdin to /dev/null and inherit stdout and stderr.
199 bool inherit_stdout = true, inherit_stderr = true;
200 bool null_stdin = true;
201 for (const auto& dup2_pair : options.fds_to_remap) {
202 if (dup2_pair.second == STDIN_FILENO) {
203 null_stdin = false;
204 } else if (dup2_pair.second == STDOUT_FILENO) {
205 inherit_stdout = false;
206 } else if (dup2_pair.second == STDERR_FILENO) {
207 inherit_stderr = false;
208 }
209
210 if (dup2_pair.first == dup2_pair.second) {
211 file_actions.Inherit(dup2_pair.second);
212 } else {
213 file_actions.Dup2(dup2_pair.first, dup2_pair.second);
214 }
215 }
216
217 if (null_stdin) {
218 file_actions.Open(STDIN_FILENO, "/dev/null", O_RDONLY);
219 }
220 if (inherit_stdout) {
221 file_actions.Inherit(STDOUT_FILENO);
222 }
223 if (inherit_stderr) {
224 file_actions.Inherit(STDERR_FILENO);
225 }
226
227 #if BUILDFLAG(IS_MAC)
228 if (options.disclaim_responsibility) {
229 if (__builtin_available(macOS 10.14, *)) {
230 DPSXCHECK(responsibility_spawnattrs_setdisclaim(attr.get(), 1));
231 }
232 }
233 #endif
234
235 std::vector<char*> argv_cstr;
236 argv_cstr.reserve(argv.size() + 1);
237 for (const auto& arg : argv)
238 argv_cstr.push_back(const_cast<char*>(arg.c_str()));
239 argv_cstr.push_back(nullptr);
240
241 std::unique_ptr<char*[]> owned_environ;
242 char* empty_environ = nullptr;
243 char** new_environ =
244 options.clear_environment ? &empty_environ : *_NSGetEnviron();
245 if (!options.environment.empty()) {
246 owned_environ =
247 internal::AlterEnvironment(new_environ, options.environment);
248 new_environ = owned_environ.get();
249 }
250
251 const char* executable_path = !options.real_path.empty()
252 ? options.real_path.value().c_str()
253 : argv_cstr[0];
254
255 if (__builtin_available(macOS 11.0, *)) {
256 if (options.enable_cpu_security_mitigations) {
257 DPSXCHECK(posix_spawnattr_set_csm_np(attr.get(), POSIX_SPAWN_NP_CSM_ALL));
258 }
259 }
260
261 if (!options.current_directory.empty()) {
262 const char* chdir_str = options.current_directory.value().c_str();
263 #if BUILDFLAG(IS_MAC)
264 if (__builtin_available(macOS 10.15, *)) {
265 file_actions.Chdir(chdir_str);
266 } else
267 #endif
268 {
269 // If the chdir posix_spawn_file_actions extension is not available,
270 // change the thread-specific working directory. The new process will
271 // inherit it during posix_spawnp().
272 int rv = ChangeCurrentThreadDirectory(chdir_str);
273 if (rv != 0) {
274 DPLOG(ERROR) << "pthread_chdir_np";
275 return Process();
276 }
277 }
278 }
279
280 int rv;
281 pid_t pid;
282 {
283 // If |options.mach_ports_for_rendezvous| is specified : the server's lock
284 // must be held for the duration of posix_spawnp() so that new child's PID
285 // can be recorded with the set of ports.
286 const bool has_mach_ports_for_rendezvous =
287 !options.mach_ports_for_rendezvous.empty();
288 AutoLockMaybe rendezvous_lock(
289 has_mach_ports_for_rendezvous
290 ? &MachPortRendezvousServer::GetInstance()->GetLock()
291 : nullptr);
292
293 // Use posix_spawnp as some callers expect to have PATH consulted.
294 rv = posix_spawnp(&pid, executable_path, file_actions.get(), attr.get(),
295 &argv_cstr[0], new_environ);
296
297 if (has_mach_ports_for_rendezvous) {
298 if (rv == 0) {
299 MachPortRendezvousServer::GetInstance()->GetLock().AssertAcquired();
300 MachPortRendezvousServer::GetInstance()->RegisterPortsForPid(
301 pid, options.mach_ports_for_rendezvous);
302 } else {
303 // Because |options| is const-ref, the collection has to be copied here.
304 // The caller expects to relinquish ownership of any strong rights if
305 // LaunchProcess() were to succeed, so these rights should be manually
306 // destroyed on failure.
307 MachPortsForRendezvous ports = options.mach_ports_for_rendezvous;
308 for (auto& port : ports) {
309 port.second.Destroy();
310 }
311 }
312 }
313 }
314
315 // Restore the thread's working directory if it was changed.
316 if (!options.current_directory.empty()) {
317 if (__builtin_available(macOS 10.15, *)) {
318 // Nothing to do because no global state was changed, but
319 // __builtin_available is special and cannot be negated.
320 } else {
321 ResetCurrentThreadDirectory();
322 }
323 }
324
325 if (rv != 0) {
326 DLOG(ERROR) << "posix_spawnp(" << executable_path << "): -" << rv << " "
327 << strerror(rv);
328 return Process();
329 }
330
331 if (options.wait) {
332 // While this isn't strictly disk IO, waiting for another process to
333 // finish is the sort of thing ThreadRestrictions is trying to prevent.
334 ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
335 pid_t ret = HANDLE_EINTR(waitpid(pid, nullptr, 0));
336 DPCHECK(ret > 0);
337 }
338
339 return Process(pid);
340 }
341
GetAppOutput(const CommandLine & cl,std::string * output)342 bool GetAppOutput(const CommandLine& cl, std::string* output) {
343 return GetAppOutput(cl.argv(), output);
344 }
345
GetAppOutputAndError(const CommandLine & cl,std::string * output)346 bool GetAppOutputAndError(const CommandLine& cl, std::string* output) {
347 return GetAppOutputAndError(cl.argv(), output);
348 }
349
GetAppOutputWithExitCode(const CommandLine & cl,std::string * output,int * exit_code)350 bool GetAppOutputWithExitCode(const CommandLine& cl,
351 std::string* output,
352 int* exit_code) {
353 GetAppOutputOptions options;
354 options.output = output;
355 bool rv = GetAppOutputInternal(cl.argv(), &options);
356 *exit_code = options.exit_code;
357 return rv;
358 }
359
GetAppOutput(const std::vector<std::string> & argv,std::string * output)360 bool GetAppOutput(const std::vector<std::string>& argv, std::string* output) {
361 GetAppOutputOptions options;
362 options.output = output;
363 return GetAppOutputInternal(argv, &options) &&
364 options.exit_code == EXIT_SUCCESS;
365 }
366
GetAppOutputAndError(const std::vector<std::string> & argv,std::string * output)367 bool GetAppOutputAndError(const std::vector<std::string>& argv,
368 std::string* output) {
369 GetAppOutputOptions options;
370 options.include_stderr = true;
371 options.output = output;
372 return GetAppOutputInternal(argv, &options) &&
373 options.exit_code == EXIT_SUCCESS;
374 }
375
RaiseProcessToHighPriority()376 void RaiseProcessToHighPriority() {
377 // Historically this has not been implemented on POSIX and macOS. This could
378 // influence the Mach task policy in the future.
379 }
380
381 } // namespace base
382