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