• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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