• 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/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