| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | |
| 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 "chrome/browser/zygote_host_linux.h" | |
| 6 | |
| 7 #include <sys/socket.h> | |
| 8 #include <sys/stat.h> | |
| 9 #include <sys/types.h> | |
| 10 #include <unistd.h> | |
| 11 | |
| 12 #include "base/command_line.h" | |
| 13 #include "base/eintr_wrapper.h" | |
| 14 #include "base/environment.h" | |
| 15 #include "base/linux_util.h" | |
| 16 #include "base/logging.h" | |
| 17 #include "base/path_service.h" | |
| 18 #include "base/pickle.h" | |
| 19 #include "base/process_util.h" | |
| 20 #include "base/string_number_conversions.h" | |
| 21 #include "base/string_util.h" | |
| 22 #include "base/scoped_ptr.h" | |
| 23 #include "base/utf_string_conversions.h" | |
| 24 #include "chrome/browser/renderer_host/render_sandbox_host_linux.h" | |
| 25 #include "chrome/common/chrome_constants.h" | |
| 26 #include "chrome/common/chrome_switches.h" | |
| 27 #include "chrome/common/process_watcher.h" | |
| 28 #include "chrome/common/result_codes.h" | |
| 29 #include "chrome/common/unix_domain_socket_posix.h" | |
| 30 #include "sandbox/linux/suid/suid_unsafe_environment_variables.h" | |
| 31 | |
| 32 static void SaveSUIDUnsafeEnvironmentVariables() { | |
| 33 // The ELF loader will clear many environment variables so we save them to | |
| 34 // different names here so that the SUID sandbox can resolve them for the | |
| 35 // renderer. | |
| 36 | |
| 37 for (unsigned i = 0; kSUIDUnsafeEnvironmentVariables[i]; ++i) { | |
| 38 const char* const envvar = kSUIDUnsafeEnvironmentVariables[i]; | |
| 39 char* const saved_envvar = SandboxSavedEnvironmentVariable(envvar); | |
| 40 if (!saved_envvar) | |
| 41 continue; | |
| 42 | |
| 43 scoped_ptr<base::Environment> env(base::Environment::Create()); | |
| 44 std::string value; | |
| 45 if (env->GetVar(envvar, &value)) | |
| 46 env->SetVar(saved_envvar, value); | |
| 47 else | |
| 48 env->UnSetVar(saved_envvar); | |
| 49 | |
| 50 free(saved_envvar); | |
| 51 } | |
| 52 } | |
| 53 | |
| 54 ZygoteHost::ZygoteHost() | |
| 55 : control_fd_(-1), | |
| 56 pid_(-1), | |
| 57 init_(false), | |
| 58 using_suid_sandbox_(false), | |
| 59 have_read_sandbox_status_word_(false), | |
| 60 sandbox_status_(0) { | |
| 61 } | |
| 62 | |
| 63 ZygoteHost::~ZygoteHost() { | |
| 64 if (init_) | |
| 65 close(control_fd_); | |
| 66 } | |
| 67 | |
| 68 // static | |
| 69 ZygoteHost* ZygoteHost::GetInstance() { | |
| 70 return Singleton<ZygoteHost>::get(); | |
| 71 } | |
| 72 | |
| 73 void ZygoteHost::Init(const std::string& sandbox_cmd) { | |
| 74 DCHECK(!init_); | |
| 75 init_ = true; | |
| 76 | |
| 77 FilePath chrome_path; | |
| 78 CHECK(PathService::Get(base::FILE_EXE, &chrome_path)); | |
| 79 CommandLine cmd_line(chrome_path); | |
| 80 | |
| 81 cmd_line.AppendSwitchASCII(switches::kProcessType, switches::kZygoteProcess); | |
| 82 | |
| 83 int fds[2]; | |
| 84 CHECK(socketpair(PF_UNIX, SOCK_SEQPACKET, 0, fds) == 0); | |
| 85 base::file_handle_mapping_vector fds_to_map; | |
| 86 fds_to_map.push_back(std::make_pair(fds[1], 3)); | |
| 87 | |
| 88 const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess(); | |
| 89 if (browser_command_line.HasSwitch(switches::kZygoteCmdPrefix)) { | |
| 90 cmd_line.PrependWrapper( | |
| 91 browser_command_line.GetSwitchValueNative(switches::kZygoteCmdPrefix)); | |
| 92 } | |
| 93 // Append any switches from the browser process that need to be forwarded on | |
| 94 // to the zygote/renderers. | |
| 95 // Should this list be obtained from browser_render_process_host.cc? | |
| 96 static const char* kForwardSwitches[] = { | |
| 97 switches::kAllowSandboxDebugging, | |
| 98 switches::kLoggingLevel, | |
| 99 switches::kEnableLogging, // Support, e.g., --enable-logging=stderr. | |
| 100 switches::kV, | |
| 101 switches::kVModule, | |
| 102 switches::kUserDataDir, // Make logs go to the right file. | |
| 103 // Load (in-process) Pepper plugins in-process in the zygote pre-sandbox. | |
| 104 switches::kRegisterPepperPlugins, | |
| 105 switches::kDisableSeccompSandbox, | |
| 106 switches::kEnableSeccompSandbox, | |
| 107 }; | |
| 108 cmd_line.CopySwitchesFrom(browser_command_line, kForwardSwitches, | |
| 109 arraysize(kForwardSwitches)); | |
| 110 | |
| 111 sandbox_binary_ = sandbox_cmd.c_str(); | |
| 112 struct stat st; | |
| 113 | |
| 114 if (!sandbox_cmd.empty() && stat(sandbox_binary_.c_str(), &st) == 0) { | |
| 115 if (access(sandbox_binary_.c_str(), X_OK) == 0 && | |
| 116 (st.st_uid == 0) && | |
| 117 (st.st_mode & S_ISUID) && | |
| 118 (st.st_mode & S_IXOTH)) { | |
| 119 using_suid_sandbox_ = true; | |
| 120 cmd_line.PrependWrapper(sandbox_binary_); | |
| 121 | |
| 122 SaveSUIDUnsafeEnvironmentVariables(); | |
| 123 } else { | |
| 124 LOG(FATAL) << "The SUID sandbox helper binary was found, but is not " | |
| 125 "configured correctly. Rather than run without sandboxing " | |
| 126 "I'm aborting now. You need to make sure that " | |
| 127 << sandbox_binary_ << " is mode 4755 and owned by root."; | |
| 128 } | |
| 129 } | |
| 130 | |
| 131 // Start up the sandbox host process and get the file descriptor for the | |
| 132 // renderers to talk to it. | |
| 133 const int sfd = RenderSandboxHostLinux::GetInstance()->GetRendererSocket(); | |
| 134 fds_to_map.push_back(std::make_pair(sfd, 5)); | |
| 135 | |
| 136 int dummy_fd = -1; | |
| 137 if (using_suid_sandbox_) { | |
| 138 dummy_fd = socket(PF_UNIX, SOCK_DGRAM, 0); | |
| 139 CHECK(dummy_fd >= 0); | |
| 140 fds_to_map.push_back(std::make_pair(dummy_fd, 7)); | |
| 141 } | |
| 142 | |
| 143 base::ProcessHandle process; | |
| 144 base::LaunchApp(cmd_line.argv(), fds_to_map, false, &process); | |
| 145 CHECK(process != -1) << "Failed to launch zygote process"; | |
| 146 | |
| 147 if (using_suid_sandbox_) { | |
| 148 // In the SUID sandbox, the real zygote is forked from the sandbox. | |
| 149 // We need to look for it. | |
| 150 // But first, wait for the zygote to tell us it's running. | |
| 151 // The sending code is in chrome/browser/zygote_main_linux.cc. | |
| 152 std::vector<int> fds_vec; | |
| 153 const int kExpectedLength = sizeof(kZygoteMagic); | |
| 154 char buf[kExpectedLength]; | |
| 155 const ssize_t len = UnixDomainSocket::RecvMsg(fds[0], buf, sizeof(buf), | |
| 156 &fds_vec); | |
| 157 CHECK(len == kExpectedLength) << "Incorrect zygote magic length"; | |
| 158 CHECK(0 == strcmp(buf, kZygoteMagic)) << "Incorrect zygote magic"; | |
| 159 | |
| 160 std::string inode_output; | |
| 161 ino_t inode = 0; | |
| 162 // Figure out the inode for |dummy_fd|, close |dummy_fd| on our end, | |
| 163 // and find the zygote process holding |dummy_fd|. | |
| 164 if (base::FileDescriptorGetInode(&inode, dummy_fd)) { | |
| 165 close(dummy_fd); | |
| 166 std::vector<std::string> get_inode_cmdline; | |
| 167 get_inode_cmdline.push_back(sandbox_binary_); | |
| 168 get_inode_cmdline.push_back(base::kFindInodeSwitch); | |
| 169 get_inode_cmdline.push_back(base::Int64ToString(inode)); | |
| 170 CommandLine get_inode_cmd(get_inode_cmdline); | |
| 171 if (base::GetAppOutput(get_inode_cmd, &inode_output)) { | |
| 172 base::StringToInt(inode_output, &pid_); | |
| 173 } | |
| 174 } | |
| 175 CHECK(pid_ > 0) << "Did not find zygote process (using sandbox binary " | |
| 176 << sandbox_binary_ << ")"; | |
| 177 | |
| 178 if (process != pid_) { | |
| 179 // Reap the sandbox. | |
| 180 ProcessWatcher::EnsureProcessGetsReaped(process); | |
| 181 } | |
| 182 } else { | |
| 183 // Not using the SUID sandbox. | |
| 184 pid_ = process; | |
| 185 } | |
| 186 | |
| 187 close(fds[1]); | |
| 188 control_fd_ = fds[0]; | |
| 189 | |
| 190 Pickle pickle; | |
| 191 pickle.WriteInt(kCmdGetSandboxStatus); | |
| 192 std::vector<int> empty_fds; | |
| 193 if (!UnixDomainSocket::SendMsg(control_fd_, pickle.data(), pickle.size(), | |
| 194 empty_fds)) | |
| 195 LOG(FATAL) << "Cannot communicate with zygote"; | |
| 196 // We don't wait for the reply. We'll read it in ReadReply. | |
| 197 } | |
| 198 | |
| 199 ssize_t ZygoteHost::ReadReply(void* buf, size_t buf_len) { | |
| 200 // At startup we send a kCmdGetSandboxStatus request to the zygote, but don't | |
| 201 // wait for the reply. Thus, the first time that we read from the zygote, we | |
| 202 // get the reply to that request. | |
| 203 if (!have_read_sandbox_status_word_) { | |
| 204 if (HANDLE_EINTR(read(control_fd_, &sandbox_status_, | |
| 205 sizeof(sandbox_status_))) != | |
| 206 sizeof(sandbox_status_)) { | |
| 207 return -1; | |
| 208 } | |
| 209 have_read_sandbox_status_word_ = true; | |
| 210 } | |
| 211 | |
| 212 return HANDLE_EINTR(read(control_fd_, buf, buf_len)); | |
| 213 } | |
| 214 | |
| 215 pid_t ZygoteHost::ForkRenderer( | |
| 216 const std::vector<std::string>& argv, | |
| 217 const base::GlobalDescriptors::Mapping& mapping) { | |
| 218 DCHECK(init_); | |
| 219 Pickle pickle; | |
| 220 | |
| 221 pickle.WriteInt(kCmdFork); | |
| 222 pickle.WriteInt(argv.size()); | |
| 223 for (std::vector<std::string>::const_iterator | |
| 224 i = argv.begin(); i != argv.end(); ++i) | |
| 225 pickle.WriteString(*i); | |
| 226 | |
| 227 pickle.WriteInt(mapping.size()); | |
| 228 | |
| 229 std::vector<int> fds; | |
| 230 for (base::GlobalDescriptors::Mapping::const_iterator | |
| 231 i = mapping.begin(); i != mapping.end(); ++i) { | |
| 232 pickle.WriteUInt32(i->first); | |
| 233 fds.push_back(i->second); | |
| 234 } | |
| 235 | |
| 236 pid_t pid; | |
| 237 { | |
| 238 base::AutoLock lock(control_lock_); | |
| 239 if (!UnixDomainSocket::SendMsg(control_fd_, pickle.data(), pickle.size(), | |
| 240 fds)) | |
| 241 return base::kNullProcessHandle; | |
| 242 | |
| 243 if (ReadReply(&pid, sizeof(pid)) != sizeof(pid)) | |
| 244 return base::kNullProcessHandle; | |
| 245 if (pid <= 0) | |
| 246 return base::kNullProcessHandle; | |
| 247 } | |
| 248 | |
| 249 const int kRendererScore = 5; | |
| 250 AdjustRendererOOMScore(pid, kRendererScore); | |
| 251 | |
| 252 return pid; | |
| 253 } | |
| 254 | |
| 255 void ZygoteHost::AdjustRendererOOMScore(base::ProcessHandle pid, int score) { | |
| 256 // 1) You can't change the oom_adj of a non-dumpable process (EPERM) unless | |
| 257 // you're root. Because of this, we can't set the oom_adj from the browser | |
| 258 // process. | |
| 259 // | |
| 260 // 2) We can't set the oom_adj before entering the sandbox because the | |
| 261 // zygote is in the sandbox and the zygote is as critical as the browser | |
| 262 // process. Its oom_adj value shouldn't be changed. | |
| 263 // | |
| 264 // 3) A non-dumpable process can't even change its own oom_adj because it's | |
| 265 // root owned 0644. The sandboxed processes don't even have /proc, but one | |
| 266 // could imagine passing in a descriptor from outside. | |
| 267 // | |
| 268 // So, in the normal case, we use the SUID binary to change it for us. | |
| 269 // However, Fedora (and other SELinux systems) don't like us touching other | |
| 270 // process's oom_adj values | |
| 271 // (https://bugzilla.redhat.com/show_bug.cgi?id=581256). | |
| 272 // | |
| 273 // The offical way to get the SELinux mode is selinux_getenforcemode, but I | |
| 274 // don't want to add another library to the build as it's sure to cause | |
| 275 // problems with other, non-SELinux distros. | |
| 276 // | |
| 277 // So we just check for /selinux. This isn't foolproof, but it's not bad | |
| 278 // and it's easy. | |
| 279 | |
| 280 static bool selinux; | |
| 281 static bool selinux_valid = false; | |
| 282 | |
| 283 if (!selinux_valid) { | |
| 284 selinux = access("/selinux", X_OK) == 0; | |
| 285 selinux_valid = true; | |
| 286 } | |
| 287 | |
| 288 if (using_suid_sandbox_ && !selinux) { | |
| 289 base::ProcessHandle sandbox_helper_process; | |
| 290 std::vector<std::string> adj_oom_score_cmdline; | |
| 291 | |
| 292 adj_oom_score_cmdline.push_back(sandbox_binary_); | |
| 293 adj_oom_score_cmdline.push_back(base::kAdjustOOMScoreSwitch); | |
| 294 adj_oom_score_cmdline.push_back(base::Int64ToString(pid)); | |
| 295 adj_oom_score_cmdline.push_back(base::IntToString(score)); | |
| 296 CommandLine adj_oom_score_cmd(adj_oom_score_cmdline); | |
| 297 if (base::LaunchApp(adj_oom_score_cmd, false, true, | |
| 298 &sandbox_helper_process)) { | |
| 299 ProcessWatcher::EnsureProcessGetsReaped(sandbox_helper_process); | |
| 300 } | |
| 301 } else if (!using_suid_sandbox_) { | |
| 302 if (!base::AdjustOOMScore(pid, score)) | |
| 303 PLOG(ERROR) << "Failed to adjust OOM score of renderer with pid " << pid; | |
| 304 } | |
| 305 } | |
| 306 | |
| 307 void ZygoteHost::EnsureProcessTerminated(pid_t process) { | |
| 308 DCHECK(init_); | |
| 309 Pickle pickle; | |
| 310 | |
| 311 pickle.WriteInt(kCmdReap); | |
| 312 pickle.WriteInt(process); | |
| 313 | |
| 314 if (HANDLE_EINTR(write(control_fd_, pickle.data(), pickle.size())) < 0) | |
| 315 PLOG(ERROR) << "write"; | |
| 316 } | |
| 317 | |
| 318 base::TerminationStatus ZygoteHost::GetTerminationStatus( | |
| 319 base::ProcessHandle handle, | |
| 320 int* exit_code) { | |
| 321 DCHECK(init_); | |
| 322 Pickle pickle; | |
| 323 pickle.WriteInt(kCmdGetTerminationStatus); | |
| 324 pickle.WriteInt(handle); | |
| 325 | |
| 326 // Set this now to handle the early termination cases. | |
| 327 if (exit_code) | |
| 328 *exit_code = ResultCodes::NORMAL_EXIT; | |
| 329 | |
| 330 static const unsigned kMaxMessageLength = 128; | |
| 331 char buf[kMaxMessageLength]; | |
| 332 ssize_t len; | |
| 333 { | |
| 334 base::AutoLock lock(control_lock_); | |
| 335 if (HANDLE_EINTR(write(control_fd_, pickle.data(), pickle.size())) < 0) | |
| 336 PLOG(ERROR) << "write"; | |
| 337 | |
| 338 len = ReadReply(buf, sizeof(buf)); | |
| 339 } | |
| 340 | |
| 341 if (len == -1) { | |
| 342 LOG(WARNING) << "Error reading message from zygote: " << errno; | |
| 343 return base::TERMINATION_STATUS_NORMAL_TERMINATION; | |
| 344 } else if (len == 0) { | |
| 345 LOG(WARNING) << "Socket closed prematurely."; | |
| 346 return base::TERMINATION_STATUS_NORMAL_TERMINATION; | |
| 347 } | |
| 348 | |
| 349 Pickle read_pickle(buf, len); | |
| 350 int status, tmp_exit_code; | |
| 351 void* iter = NULL; | |
| 352 if (!read_pickle.ReadInt(&iter, &status) || | |
| 353 !read_pickle.ReadInt(&iter, &tmp_exit_code)) { | |
| 354 LOG(WARNING) << "Error parsing GetTerminationStatus response from zygote."; | |
| 355 return base::TERMINATION_STATUS_NORMAL_TERMINATION; | |
| 356 } | |
| 357 | |
| 358 if (exit_code) | |
| 359 *exit_code = tmp_exit_code; | |
| 360 | |
| 361 return static_cast<base::TerminationStatus>(status); | |
| 362 } | |
| OLD | NEW |