1 // Copyright (c) 2012 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 "content/browser/zygote_host/zygote_host_impl_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/base_switches.h" 13 #include "base/command_line.h" 14 #include "base/environment.h" 15 #include "base/file_util.h" 16 #include "base/files/file_enumerator.h" 17 #include "base/linux_util.h" 18 #include "base/logging.h" 19 #include "base/memory/linked_ptr.h" 20 #include "base/memory/scoped_ptr.h" 21 #include "base/metrics/histogram.h" 22 #include "base/path_service.h" 23 #include "base/posix/eintr_wrapper.h" 24 #include "base/posix/unix_domain_socket_linux.h" 25 #include "base/process/launch.h" 26 #include "base/process/memory.h" 27 #include "base/strings/string_number_conversions.h" 28 #include "base/strings/string_util.h" 29 #include "base/strings/utf_string_conversions.h" 30 #include "base/time/time.h" 31 #include "content/browser/renderer_host/render_sandbox_host_linux.h" 32 #include "content/common/zygote_commands_linux.h" 33 #include "content/public/browser/content_browser_client.h" 34 #include "content/public/common/content_switches.h" 35 #include "content/public/common/result_codes.h" 36 #include "sandbox/linux/suid/client/setuid_sandbox_client.h" 37 #include "sandbox/linux/suid/common/sandbox.h" 38 #include "ui/base/ui_base_switches.h" 39 40 #if defined(USE_TCMALLOC) 41 #include "third_party/tcmalloc/chromium/src/gperftools/heap-profiler.h" 42 #endif 43 44 namespace content { 45 46 // static 47 ZygoteHost* ZygoteHost::GetInstance() { 48 return ZygoteHostImpl::GetInstance(); 49 } 50 51 ZygoteHostImpl::ZygoteHostImpl() 52 : control_fd_(-1), 53 pid_(-1), 54 init_(false), 55 using_suid_sandbox_(false), 56 have_read_sandbox_status_word_(false), 57 sandbox_status_(0) {} 58 59 ZygoteHostImpl::~ZygoteHostImpl() { 60 if (init_) 61 close(control_fd_); 62 } 63 64 // static 65 ZygoteHostImpl* ZygoteHostImpl::GetInstance() { 66 return Singleton<ZygoteHostImpl>::get(); 67 } 68 69 void ZygoteHostImpl::Init(const std::string& sandbox_cmd) { 70 DCHECK(!init_); 71 init_ = true; 72 73 base::FilePath chrome_path; 74 CHECK(PathService::Get(base::FILE_EXE, &chrome_path)); 75 CommandLine cmd_line(chrome_path); 76 77 cmd_line.AppendSwitchASCII(switches::kProcessType, switches::kZygoteProcess); 78 79 int fds[2]; 80 #if defined(OS_FREEBSD) || defined(OS_OPENBSD) 81 // The BSDs often don't support SOCK_SEQPACKET yet, so fall back to 82 // SOCK_DGRAM if necessary. 83 if (socketpair(PF_UNIX, SOCK_SEQPACKET, 0, fds) != 0) 84 CHECK(socketpair(PF_UNIX, SOCK_DGRAM, 0, fds) == 0); 85 #else 86 CHECK(socketpair(PF_UNIX, SOCK_SEQPACKET, 0, fds) == 0); 87 #endif 88 base::FileHandleMappingVector fds_to_map; 89 fds_to_map.push_back(std::make_pair(fds[1], kZygoteSocketPairFd)); 90 91 const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess(); 92 if (browser_command_line.HasSwitch(switches::kZygoteCmdPrefix)) { 93 cmd_line.PrependWrapper( 94 browser_command_line.GetSwitchValueNative(switches::kZygoteCmdPrefix)); 95 } 96 // Append any switches from the browser process that need to be forwarded on 97 // to the zygote/renderers. 98 // Should this list be obtained from browser_render_process_host.cc? 99 static const char* kForwardSwitches[] = { 100 switches::kAllowSandboxDebugging, 101 switches::kLoggingLevel, 102 switches::kEnableLogging, // Support, e.g., --enable-logging=stderr. 103 switches::kV, 104 switches::kVModule, 105 switches::kRegisterPepperPlugins, 106 switches::kDisableSeccompFilterSandbox, 107 108 // Zygote process needs to know what resources to have loaded when it 109 // becomes a renderer process. 110 switches::kForceDeviceScaleFactor, 111 switches::kTouchOptimizedUI, 112 113 switches::kNoSandbox, 114 }; 115 cmd_line.CopySwitchesFrom(browser_command_line, kForwardSwitches, 116 arraysize(kForwardSwitches)); 117 118 GetContentClient()->browser()->AppendExtraCommandLineSwitches(&cmd_line, -1); 119 120 sandbox_binary_ = sandbox_cmd.c_str(); 121 122 // A non empty sandbox_cmd means we want a SUID sandbox. 123 using_suid_sandbox_ = !sandbox_cmd.empty(); 124 125 if (using_suid_sandbox_) { 126 struct stat st; 127 if (stat(sandbox_binary_.c_str(), &st) != 0) { 128 LOG(FATAL) << "The SUID sandbox helper binary is missing: " 129 << sandbox_binary_ << " Aborting now."; 130 } 131 132 if (access(sandbox_binary_.c_str(), X_OK) == 0 && 133 (st.st_uid == 0) && 134 (st.st_mode & S_ISUID) && 135 (st.st_mode & S_IXOTH)) { 136 cmd_line.PrependWrapper(sandbox_binary_); 137 138 scoped_ptr<sandbox::SetuidSandboxClient> 139 sandbox_client(sandbox::SetuidSandboxClient::Create()); 140 sandbox_client->SetupLaunchEnvironment(); 141 } else { 142 LOG(FATAL) << "The SUID sandbox helper binary was found, but is not " 143 "configured correctly. Rather than run without sandboxing " 144 "I'm aborting now. You need to make sure that " 145 << sandbox_binary_ << " is owned by root and has mode 4755."; 146 } 147 } 148 149 // Start up the sandbox host process and get the file descriptor for the 150 // renderers to talk to it. 151 const int sfd = RenderSandboxHostLinux::GetInstance()->GetRendererSocket(); 152 fds_to_map.push_back(std::make_pair(sfd, kZygoteRendererSocketFd)); 153 154 int dummy_fd = -1; 155 if (using_suid_sandbox_) { 156 dummy_fd = socket(PF_UNIX, SOCK_DGRAM, 0); 157 CHECK(dummy_fd >= 0); 158 fds_to_map.push_back(std::make_pair(dummy_fd, kZygoteIdFd)); 159 } 160 161 base::ProcessHandle process = -1; 162 base::LaunchOptions options; 163 options.fds_to_remap = &fds_to_map; 164 base::LaunchProcess(cmd_line.argv(), options, &process); 165 CHECK(process != -1) << "Failed to launch zygote process"; 166 167 if (using_suid_sandbox_) { 168 // In the SUID sandbox, the real zygote is forked from the sandbox. 169 // We need to look for it. 170 // But first, wait for the zygote to tell us it's running. 171 // The sending code is in content/browser/zygote_main_linux.cc. 172 std::vector<int> fds_vec; 173 const int kExpectedLength = sizeof(kZygoteHelloMessage); 174 char buf[kExpectedLength]; 175 const ssize_t len = UnixDomainSocket::RecvMsg(fds[0], buf, sizeof(buf), 176 &fds_vec); 177 CHECK(len == kExpectedLength) << "Incorrect zygote magic length"; 178 CHECK(0 == strcmp(buf, kZygoteHelloMessage)) 179 << "Incorrect zygote hello"; 180 181 std::string inode_output; 182 ino_t inode = 0; 183 // Figure out the inode for |dummy_fd|, close |dummy_fd| on our end, 184 // and find the zygote process holding |dummy_fd|. 185 if (base::FileDescriptorGetInode(&inode, dummy_fd)) { 186 close(dummy_fd); 187 std::vector<std::string> get_inode_cmdline; 188 get_inode_cmdline.push_back(sandbox_binary_); 189 get_inode_cmdline.push_back(base::kFindInodeSwitch); 190 get_inode_cmdline.push_back(base::Int64ToString(inode)); 191 CommandLine get_inode_cmd(get_inode_cmdline); 192 if (base::GetAppOutput(get_inode_cmd, &inode_output)) { 193 base::StringToInt(inode_output, &pid_); 194 } 195 } 196 CHECK(pid_ > 0) << "Did not find zygote process (using sandbox binary " 197 << sandbox_binary_ << ")"; 198 199 if (process != pid_) { 200 // Reap the sandbox. 201 base::EnsureProcessGetsReaped(process); 202 } 203 } else { 204 // Not using the SUID sandbox. 205 pid_ = process; 206 } 207 208 close(fds[1]); 209 control_fd_ = fds[0]; 210 211 Pickle pickle; 212 pickle.WriteInt(kZygoteCommandGetSandboxStatus); 213 if (!SendMessage(pickle, NULL)) 214 LOG(FATAL) << "Cannot communicate with zygote"; 215 // We don't wait for the reply. We'll read it in ReadReply. 216 } 217 218 bool ZygoteHostImpl::SendMessage(const Pickle& data, 219 const std::vector<int>* fds) { 220 CHECK(data.size() <= kZygoteMaxMessageLength) 221 << "Trying to send too-large message to zygote (sending " << data.size() 222 << " bytes, max is " << kZygoteMaxMessageLength << ")"; 223 CHECK(!fds || fds->size() <= UnixDomainSocket::kMaxFileDescriptors) 224 << "Trying to send message with too many file descriptors to zygote " 225 << "(sending " << fds->size() << ", max is " 226 << UnixDomainSocket::kMaxFileDescriptors << ")"; 227 228 return UnixDomainSocket::SendMsg(control_fd_, 229 data.data(), data.size(), 230 fds ? *fds : std::vector<int>()); 231 } 232 233 ssize_t ZygoteHostImpl::ReadReply(void* buf, size_t buf_len) { 234 // At startup we send a kZygoteCommandGetSandboxStatus request to the zygote, 235 // but don't wait for the reply. Thus, the first time that we read from the 236 // zygote, we get the reply to that request. 237 if (!have_read_sandbox_status_word_) { 238 if (HANDLE_EINTR(read(control_fd_, &sandbox_status_, 239 sizeof(sandbox_status_))) != 240 sizeof(sandbox_status_)) { 241 return -1; 242 } 243 have_read_sandbox_status_word_ = true; 244 } 245 246 return HANDLE_EINTR(read(control_fd_, buf, buf_len)); 247 } 248 249 pid_t ZygoteHostImpl::ForkRequest( 250 const std::vector<std::string>& argv, 251 const std::vector<FileDescriptorInfo>& mapping, 252 const std::string& process_type) { 253 DCHECK(init_); 254 Pickle pickle; 255 256 pickle.WriteInt(kZygoteCommandFork); 257 pickle.WriteString(process_type); 258 pickle.WriteInt(argv.size()); 259 for (std::vector<std::string>::const_iterator 260 i = argv.begin(); i != argv.end(); ++i) 261 pickle.WriteString(*i); 262 263 pickle.WriteInt(mapping.size()); 264 265 std::vector<int> fds; 266 // Scoped pointers cannot be stored in containers, so we have to use a 267 // linked_ptr. 268 std::vector<linked_ptr<file_util::ScopedFD> > autodelete_fds; 269 for (std::vector<FileDescriptorInfo>::const_iterator 270 i = mapping.begin(); i != mapping.end(); ++i) { 271 pickle.WriteUInt32(i->id); 272 fds.push_back(i->fd.fd); 273 if (i->fd.auto_close) { 274 // Auto-close means we need to close the FDs after they have been passed 275 // to the other process. 276 linked_ptr<file_util::ScopedFD> ptr( 277 new file_util::ScopedFD(&(fds.back()))); 278 autodelete_fds.push_back(ptr); 279 } 280 } 281 282 pid_t pid; 283 { 284 base::AutoLock lock(control_lock_); 285 if (!SendMessage(pickle, &fds)) 286 return base::kNullProcessHandle; 287 288 // Read the reply, which pickles the PID and an optional UMA enumeration. 289 static const unsigned kMaxReplyLength = 2048; 290 char buf[kMaxReplyLength]; 291 const ssize_t len = ReadReply(buf, sizeof(buf)); 292 293 Pickle reply_pickle(buf, len); 294 PickleIterator iter(reply_pickle); 295 if (len <= 0 || !reply_pickle.ReadInt(&iter, &pid)) 296 return base::kNullProcessHandle; 297 298 // If there is a nonempty UMA name string, then there is a UMA 299 // enumeration to record. 300 std::string uma_name; 301 int uma_sample; 302 int uma_boundary_value; 303 if (reply_pickle.ReadString(&iter, &uma_name) && 304 !uma_name.empty() && 305 reply_pickle.ReadInt(&iter, &uma_sample) && 306 reply_pickle.ReadInt(&iter, &uma_boundary_value)) { 307 // We cannot use the UMA_HISTOGRAM_ENUMERATION macro here, 308 // because that's only for when the name is the same every time. 309 // Here we're using whatever name we got from the other side. 310 // But since it's likely that the same one will be used repeatedly 311 // (even though it's not guaranteed), we cache it here. 312 static base::HistogramBase* uma_histogram; 313 if (!uma_histogram || uma_histogram->histogram_name() != uma_name) { 314 uma_histogram = base::LinearHistogram::FactoryGet( 315 uma_name, 1, 316 uma_boundary_value, 317 uma_boundary_value + 1, 318 base::HistogramBase::kUmaTargetedHistogramFlag); 319 } 320 uma_histogram->Add(uma_sample); 321 } 322 323 if (pid <= 0) 324 return base::kNullProcessHandle; 325 } 326 327 #if !defined(OS_OPENBSD) 328 // This is just a starting score for a renderer or extension (the 329 // only types of processes that will be started this way). It will 330 // get adjusted as time goes on. (This is the same value as 331 // chrome::kLowestRendererOomScore in chrome/chrome_constants.h, but 332 // that's not something we can include here.) 333 const int kLowestRendererOomScore = 300; 334 AdjustRendererOOMScore(pid, kLowestRendererOomScore); 335 #endif 336 337 return pid; 338 } 339 340 #if !defined(OS_OPENBSD) 341 void ZygoteHostImpl::AdjustRendererOOMScore(base::ProcessHandle pid, 342 int score) { 343 // 1) You can't change the oom_score_adj of a non-dumpable process 344 // (EPERM) unless you're root. Because of this, we can't set the 345 // oom_adj from the browser process. 346 // 347 // 2) We can't set the oom_score_adj before entering the sandbox 348 // because the zygote is in the sandbox and the zygote is as 349 // critical as the browser process. Its oom_adj value shouldn't 350 // be changed. 351 // 352 // 3) A non-dumpable process can't even change its own oom_score_adj 353 // because it's root owned 0644. The sandboxed processes don't 354 // even have /proc, but one could imagine passing in a descriptor 355 // from outside. 356 // 357 // So, in the normal case, we use the SUID binary to change it for us. 358 // However, Fedora (and other SELinux systems) don't like us touching other 359 // process's oom_score_adj (or oom_adj) values 360 // (https://bugzilla.redhat.com/show_bug.cgi?id=581256). 361 // 362 // The offical way to get the SELinux mode is selinux_getenforcemode, but I 363 // don't want to add another library to the build as it's sure to cause 364 // problems with other, non-SELinux distros. 365 // 366 // So we just check for files in /selinux. This isn't foolproof, but it's not 367 // bad and it's easy. 368 369 static bool selinux; 370 static bool selinux_valid = false; 371 372 if (!selinux_valid) { 373 const base::FilePath kSelinuxPath("/selinux"); 374 base::FileEnumerator en(kSelinuxPath, false, base::FileEnumerator::FILES); 375 bool has_selinux_files = !en.Next().empty(); 376 377 selinux = access(kSelinuxPath.value().c_str(), X_OK) == 0 && 378 has_selinux_files; 379 selinux_valid = true; 380 } 381 382 if (using_suid_sandbox_ && !selinux) { 383 #if defined(USE_TCMALLOC) 384 // If heap profiling is running, these processes are not exiting, at least 385 // on ChromeOS. The easiest thing to do is not launch them when profiling. 386 // TODO(stevenjb): Investigate further and fix. 387 if (IsHeapProfilerRunning()) 388 return; 389 #endif 390 std::vector<std::string> adj_oom_score_cmdline; 391 adj_oom_score_cmdline.push_back(sandbox_binary_); 392 adj_oom_score_cmdline.push_back(sandbox::kAdjustOOMScoreSwitch); 393 adj_oom_score_cmdline.push_back(base::Int64ToString(pid)); 394 adj_oom_score_cmdline.push_back(base::IntToString(score)); 395 396 base::ProcessHandle sandbox_helper_process; 397 if (base::LaunchProcess(adj_oom_score_cmdline, base::LaunchOptions(), 398 &sandbox_helper_process)) { 399 base::EnsureProcessGetsReaped(sandbox_helper_process); 400 } 401 } else if (!using_suid_sandbox_) { 402 if (!base::AdjustOOMScore(pid, score)) 403 PLOG(ERROR) << "Failed to adjust OOM score of renderer with pid " << pid; 404 } 405 } 406 #endif 407 408 void ZygoteHostImpl::AdjustLowMemoryMargin(int64 margin_mb) { 409 #if defined(OS_CHROMEOS) 410 // You can't change the low memory margin unless you're root. Because of this, 411 // we can't set the low memory margin from the browser process. 412 // So, we use the SUID binary to change it for us. 413 if (using_suid_sandbox_) { 414 #if defined(USE_TCMALLOC) 415 // If heap profiling is running, these processes are not exiting, at least 416 // on ChromeOS. The easiest thing to do is not launch them when profiling. 417 // TODO(stevenjb): Investigate further and fix. 418 if (IsHeapProfilerRunning()) 419 return; 420 #endif 421 std::vector<std::string> adj_low_mem_commandline; 422 adj_low_mem_commandline.push_back(sandbox_binary_); 423 adj_low_mem_commandline.push_back(sandbox::kAdjustLowMemMarginSwitch); 424 adj_low_mem_commandline.push_back(base::Int64ToString(margin_mb)); 425 426 base::ProcessHandle sandbox_helper_process; 427 if (base::LaunchProcess(adj_low_mem_commandline, base::LaunchOptions(), 428 &sandbox_helper_process)) { 429 base::EnsureProcessGetsReaped(sandbox_helper_process); 430 } else { 431 LOG(ERROR) << "Unable to run suid sandbox to set low memory margin."; 432 } 433 } 434 // Don't adjust memory margin if we're not running with the sandbox: this 435 // isn't very common, and not doing it has little impact. 436 #else 437 // Low memory notification is currently only implemented on ChromeOS. 438 NOTREACHED() << "AdjustLowMemoryMargin not implemented"; 439 #endif // defined(OS_CHROMEOS) 440 } 441 442 443 void ZygoteHostImpl::EnsureProcessTerminated(pid_t process) { 444 DCHECK(init_); 445 Pickle pickle; 446 447 pickle.WriteInt(kZygoteCommandReap); 448 pickle.WriteInt(process); 449 if (!SendMessage(pickle, NULL)) 450 LOG(ERROR) << "Failed to send Reap message to zygote"; 451 } 452 453 base::TerminationStatus ZygoteHostImpl::GetTerminationStatus( 454 base::ProcessHandle handle, 455 bool known_dead, 456 int* exit_code) { 457 DCHECK(init_); 458 Pickle pickle; 459 pickle.WriteInt(kZygoteCommandGetTerminationStatus); 460 pickle.WriteBool(known_dead); 461 pickle.WriteInt(handle); 462 463 // Set this now to handle the early termination cases. 464 if (exit_code) 465 *exit_code = RESULT_CODE_NORMAL_EXIT; 466 467 static const unsigned kMaxMessageLength = 128; 468 char buf[kMaxMessageLength]; 469 ssize_t len; 470 { 471 base::AutoLock lock(control_lock_); 472 if (!SendMessage(pickle, NULL)) 473 LOG(ERROR) << "Failed to send GetTerminationStatus message to zygote"; 474 len = ReadReply(buf, sizeof(buf)); 475 } 476 477 if (len == -1) { 478 LOG(WARNING) << "Error reading message from zygote: " << errno; 479 return base::TERMINATION_STATUS_NORMAL_TERMINATION; 480 } else if (len == 0) { 481 LOG(WARNING) << "Socket closed prematurely."; 482 return base::TERMINATION_STATUS_NORMAL_TERMINATION; 483 } 484 485 Pickle read_pickle(buf, len); 486 int status, tmp_exit_code; 487 PickleIterator iter(read_pickle); 488 if (!read_pickle.ReadInt(&iter, &status) || 489 !read_pickle.ReadInt(&iter, &tmp_exit_code)) { 490 LOG(WARNING) << "Error parsing GetTerminationStatus response from zygote."; 491 return base::TERMINATION_STATUS_NORMAL_TERMINATION; 492 } 493 494 if (exit_code) 495 *exit_code = tmp_exit_code; 496 497 return static_cast<base::TerminationStatus>(status); 498 } 499 500 pid_t ZygoteHostImpl::GetPid() const { 501 return pid_; 502 } 503 504 pid_t ZygoteHostImpl::GetSandboxHelperPid() const { 505 return RenderSandboxHostLinux::GetInstance()->pid(); 506 } 507 508 int ZygoteHostImpl::GetSandboxStatus() const { 509 if (have_read_sandbox_status_word_) 510 return sandbox_status_; 511 return 0; 512 } 513 514 } // namespace content 515