process_util_linux.cc (5566B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 // Copyright (c) 2008 The Chromium Authors. All rights reserved. 4 // Use of this source code is governed by a BSD-style license that can be 5 // found in the LICENSE file. 6 7 #include "base/process_util.h" 8 9 #include <string> 10 #include <sys/wait.h> 11 #include <unistd.h> 12 13 #if defined(MOZ_CODE_COVERAGE) 14 # include "nsString.h" 15 #endif 16 17 #include "mozilla/ipc/LaunchError.h" 18 19 #if defined(XP_LINUX) && defined(MOZ_SANDBOX) 20 # include "mozilla/SandboxLaunch.h" 21 #endif 22 23 #if defined(MOZ_CODE_COVERAGE) 24 # include "prenv.h" 25 # include "mozilla/ipc/EnvironmentMap.h" 26 #endif 27 28 #include "base/command_line.h" 29 #include "base/eintr_wrapper.h" 30 #include "base/logging.h" 31 #include "mozilla/ipc/FileDescriptor.h" 32 #include "mozilla/ipc/FileDescriptorShuffle.h" 33 #include "mozilla/UniquePtr.h" 34 #include "mozilla/StaticPtr.h" 35 #include "mozilla/Result.h" 36 37 // WARNING: despite the name, this file is also used on the BSDs and 38 // Solaris (basically, Unixes that aren't Mac OS), not just Linux. 39 40 namespace { 41 42 MOZ_RUNINIT static mozilla::EnvironmentLog gProcessLog("MOZ_PROCESS_LOG"); 43 44 } // namespace 45 46 namespace base { 47 48 Result<Ok, LaunchError> LaunchApp(const std::vector<std::string>& argv, 49 LaunchOptions&& options, 50 ProcessHandle* process_handle) { 51 mozilla::UniquePtr<char*[]> argv_cstr(new char*[argv.size() + 1]); 52 53 #if defined(XP_LINUX) && defined(MOZ_SANDBOX) 54 mozilla::SandboxLaunch launcher; 55 if (!launcher.Prepare(&options)) { 56 return Err(LaunchError("SL::Prepare", errno)); 57 } 58 #else 59 struct { 60 pid_t Fork() { return fork(); } 61 } launcher; 62 #endif 63 64 EnvironmentArray env_storage; 65 const EnvironmentArray& envp = 66 options.full_env ? options.full_env 67 : (env_storage = BuildEnvironmentArray(options.env_map)); 68 69 // Init() there will call fcntl(F_DUPFD/F_DUPFD_CLOEXEC) under the hood in 70 // https://searchfox.org/mozilla-central/rev/55d5c4b9dffe5e59eb6b019c1a930ec9ada47e10/ipc/glue/FileDescriptorShuffle.cpp#72 71 // so it will set errno. 72 mozilla::ipc::FileDescriptorShuffle shuffle; 73 if (!shuffle.Init(options.fds_to_remap)) { 74 CHROMIUM_LOG(WARNING) << "FileDescriptorShuffle::Init failed"; 75 return Err(LaunchError("FileDescriptorShuffle", errno)); 76 } 77 78 #ifdef MOZ_CODE_COVERAGE 79 // Before gcc/clang 10 there is a gcda dump before the fork. 80 // This dump mustn't be interrupted by a SIGUSR1 else we may 81 // have a dead lock (see bug 1637377). 82 // So we just remove the handler and restore it after the fork 83 // It's up the child process to set it up. 84 // Once we switch to gcc/clang 10, we could just remove it in the child 85 // process 86 void (*ccovSigHandler)(int) = signal(SIGUSR1, SIG_IGN); 87 const char* gcov_child_prefix = PR_GetEnv("GCOV_CHILD_PREFIX"); 88 #endif 89 90 pid_t pid = launcher.Fork(); 91 // WARNING: if pid == 0, only async signal safe operations are permitted from 92 // here until exec or _exit. 93 // 94 // Specifically, heap allocation is not safe: the sandbox's fork substitute 95 // won't run the pthread_atfork handlers that fix up the malloc locks. 96 97 if (pid < 0) { 98 CHROMIUM_LOG(WARNING) << "fork() failed: " << strerror(errno); 99 return Err(LaunchError("fork", errno)); 100 } 101 102 if (pid == 0) { 103 // In the child: 104 if (!options.workdir.empty()) { 105 if (chdir(options.workdir.c_str()) != 0) { 106 // See under execve about logging unsafety. 107 DLOG(ERROR) << "chdir failed " << options.workdir; 108 _exit(127); 109 } 110 } 111 112 for (const auto& fds : shuffle.Dup2Sequence()) { 113 if (HANDLE_EINTR(dup2(fds.first, fds.second)) != fds.second) { 114 // This shouldn't happen, but check for it. And see below 115 // about logging being unsafe here, so this is debug only. 116 DLOG(ERROR) << "dup2 failed"; 117 _exit(127); 118 } 119 } 120 121 CloseSuperfluousFds(&shuffle, [](void* aCtx, int aFd) { 122 return static_cast<decltype(&shuffle)>(aCtx)->MapsTo(aFd); 123 }); 124 125 for (size_t i = 0; i < argv.size(); i++) 126 argv_cstr[i] = const_cast<char*>(argv[i].c_str()); 127 argv_cstr[argv.size()] = NULL; 128 129 #ifdef MOZ_CODE_COVERAGE 130 if (gcov_child_prefix && !options.full_env) { 131 const pid_t child_pid = getpid(); 132 nsAutoCString new_gcov_prefix(gcov_child_prefix); 133 new_gcov_prefix.Append(std::to_string((size_t)child_pid)); 134 EnvironmentMap new_map = options.env_map; 135 new_map[ENVIRONMENT_LITERAL("GCOV_PREFIX")] = 136 ENVIRONMENT_STRING(new_gcov_prefix.get()); 137 // FIXME(bug 1783305): this won't work if full_env is set, and 138 // in general this block of code is doing things it shouldn't 139 // be (async signal unsafety). 140 env_storage = BuildEnvironmentArray(new_map); 141 } 142 #endif 143 144 execve(argv_cstr[0], argv_cstr.get(), envp.get()); 145 // if we get here, we're in serious trouble and should complain loudly 146 // NOTE: This is async signal unsafe; it could deadlock instead. (But 147 // only on debug builds; otherwise it's a signal-safe no-op.) 148 DLOG(ERROR) << "FAILED TO exec() CHILD PROCESS, path: " << argv_cstr[0]; 149 _exit(127); 150 } 151 152 // In the parent: 153 154 #ifdef MOZ_CODE_COVERAGE 155 // Restore the handler for SIGUSR1 156 signal(SIGUSR1, ccovSigHandler); 157 #endif 158 159 gProcessLog.print("==> process %d launched child process %d\n", 160 GetCurrentProcId(), pid); 161 if (options.wait) HANDLE_EINTR(waitpid(pid, 0, 0)); 162 163 if (process_handle) *process_handle = pid; 164 165 return Ok(); 166 } 167 168 } // namespace base