diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-12 14:27:29 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-13 09:35:20 +0000 |
commit | c30a6232df03e1efbd9f3b226777b07e087a1122 (patch) | |
tree | e992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/content/zygote | |
parent | 7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff) | |
download | qtwebengine-chromium-c30a6232df03e1efbd9f3b226777b07e087a1122.tar.gz |
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/content/zygote')
-rw-r--r-- | chromium/content/zygote/BUILD.gn | 33 | ||||
-rw-r--r-- | chromium/content/zygote/DEPS | 5 | ||||
-rw-r--r-- | chromium/content/zygote/OWNERS | 5 | ||||
-rw-r--r-- | chromium/content/zygote/zygote_browsertest.cc | 107 | ||||
-rw-r--r-- | chromium/content/zygote/zygote_linux.cc | 660 | ||||
-rw-r--r-- | chromium/content/zygote/zygote_linux.h | 152 | ||||
-rw-r--r-- | chromium/content/zygote/zygote_main.h | 24 | ||||
-rw-r--r-- | chromium/content/zygote/zygote_main_linux.cc | 247 |
8 files changed, 1122 insertions, 111 deletions
diff --git a/chromium/content/zygote/BUILD.gn b/chromium/content/zygote/BUILD.gn new file mode 100644 index 00000000000..1673fc1378e --- /dev/null +++ b/chromium/content/zygote/BUILD.gn @@ -0,0 +1,33 @@ +# Copyright 2018 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//build/config/nacl/config.gni") +import("//content/public/common/zygote/features.gni") + +if (is_linux) { + source_set("zygote") { + sources = [ + "zygote_linux.cc", + "zygote_linux.h", + "zygote_main.h", + "zygote_main_linux.cc", + ] + + deps = [ + "//base", + "//base:i18n", + "//content/public/common:common_sources", + "//ipc", + "//sandbox", + "//services/service_manager/embedder", + "//services/service_manager/sandbox", + "//third_party/icu", + ] + + configs += [ "//content:content_implementation" ] + } +} else { + group("zygote") { + } +} diff --git a/chromium/content/zygote/DEPS b/chromium/content/zygote/DEPS index 9af3bd99586..cbd32378dba 100644 --- a/chromium/content/zygote/DEPS +++ b/chromium/content/zygote/DEPS @@ -1,3 +1,4 @@ include_rules = [ - "+services/service_manager/sandbox", -]
\ No newline at end of file + "+content/public/common/zygote", + "+services/service_manager/sandbox", +] diff --git a/chromium/content/zygote/OWNERS b/chromium/content/zygote/OWNERS index 696f688510e..aad33ebfd00 100644 --- a/chromium/content/zygote/OWNERS +++ b/chromium/content/zygote/OWNERS @@ -1,3 +1,4 @@ -file://services/service_manager/zygote/OWNERS -# COMPONENT: Internals>Sandbox +file://sandbox/linux/OWNERS + # TEAM: security-dev@chromium.org +# COMPONENT: Internals>Sandbox diff --git a/chromium/content/zygote/zygote_browsertest.cc b/chromium/content/zygote/zygote_browsertest.cc deleted file mode 100644 index 4f0de6ce3a4..00000000000 --- a/chromium/content/zygote/zygote_browsertest.cc +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include <string> -#include <vector> - -#include "base/command_line.h" -#include "base/strings/string_split.h" -#include "content/public/common/content_switches.h" -#include "content/public/test/browser_test.h" -#include "content/public/test/browser_test_utils.h" -#include "content/public/test/content_browser_test.h" -#include "content/public/test/content_browser_test_utils.h" -#include "content/shell/browser/shell.h" -#include "services/service_manager/embedder/switches.h" -#include "services/service_manager/sandbox/linux/sandbox_linux.h" -#include "services/service_manager/sandbox/switches.h" -#include "services/service_manager/zygote/common/zygote_buildflags.h" -#if BUILDFLAG(USE_ZYGOTE_HANDLE) -#include "services/service_manager/zygote/common/zygote_handle.h" -#include "services/service_manager/zygote/host/zygote_communication_linux.h" -#include "services/service_manager/zygote/host/zygote_host_impl_linux.h" -#endif - -namespace content { - -class LinuxZygoteBrowserTest : public ContentBrowserTest { - public: - LinuxZygoteBrowserTest() = default; - ~LinuxZygoteBrowserTest() override = default; - - private: - DISALLOW_COPY_AND_ASSIGN(LinuxZygoteBrowserTest); -}; - -// https://crbug.com/638303 -IN_PROC_BROWSER_TEST_F(LinuxZygoteBrowserTest, GetLocalTimeHasTimeZone) { - const char kTestCommand[] = - "window.domAutomationController.send(new Date().toString());"; - - EXPECT_TRUE(NavigateToURL(shell(), GURL("data:text/html,start page"))); - std::string result; - ASSERT_TRUE(ExecuteScriptAndExtractString(shell(), kTestCommand, &result)); - std::vector<std::string> parts = base::SplitString( - result, "()", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); - ASSERT_EQ(3U, parts.size()); - EXPECT_FALSE(parts[0].empty()); - EXPECT_FALSE(parts[1].empty()); - EXPECT_TRUE(parts[2].empty()); -} - -#if BUILDFLAG(USE_ZYGOTE_HANDLE) -IN_PROC_BROWSER_TEST_F(LinuxZygoteBrowserTest, ZygoteSandboxes) { - // We need zygotes and the standard sandbox config to run this test. - if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kNoZygote) || - base::CommandLine::ForCurrentProcess()->HasSwitch( - service_manager::switches::kNoSandbox)) { - return; - } - - // Sanity check the sandbox flags we expect to be everywhere. - const int flags = service_manager::GetGenericZygote()->GetSandboxStatus(); - constexpr int kExpectedFlags = service_manager::SandboxLinux::kPIDNS | - service_manager::SandboxLinux::kNetNS | - service_manager::SandboxLinux::kUserNS; - EXPECT_EQ(kExpectedFlags, flags & kExpectedFlags); - - EXPECT_EQ(service_manager::GetUnsandboxedZygote()->GetSandboxStatus(), 0); -} -#endif - -class LinuxZygoteDisabledBrowserTest : public ContentBrowserTest { - public: - LinuxZygoteDisabledBrowserTest() = default; - ~LinuxZygoteDisabledBrowserTest() override = default; - - protected: - void SetUpCommandLine(base::CommandLine* command_line) override { - ContentBrowserTest::SetUpCommandLine(command_line); - command_line->AppendSwitch(switches::kNoZygote); - command_line->AppendSwitch(service_manager::switches::kNoSandbox); - } - - private: - DISALLOW_COPY_AND_ASSIGN(LinuxZygoteDisabledBrowserTest); -}; - -// https://crbug.com/712779 -#if !defined(THREAD_SANITIZER) -// Test that the renderer doesn't crash during launch if zygote is disabled. -IN_PROC_BROWSER_TEST_F(LinuxZygoteDisabledBrowserTest, - NoCrashWhenZygoteDisabled) { - EXPECT_TRUE(NavigateToURL(shell(), GURL("data:text/html,start page"))); -} -#endif - -#if BUILDFLAG(USE_ZYGOTE_HANDLE) -IN_PROC_BROWSER_TEST_F(LinuxZygoteDisabledBrowserTest, - NoZygoteWhenZygoteDisabled) { - EXPECT_TRUE(NavigateToURL(shell(), GURL("data:text/html,start page"))); - - EXPECT_FALSE(service_manager::ZygoteHostImpl::GetInstance()->HasZygote()); -} -#endif - -} // namespace content diff --git a/chromium/content/zygote/zygote_linux.cc b/chromium/content/zygote/zygote_linux.cc new file mode 100644 index 00000000000..48ab8fce3e7 --- /dev/null +++ b/chromium/content/zygote/zygote_linux.cc @@ -0,0 +1,660 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/zygote/zygote_linux.h" + +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <signal.h> +#include <stdint.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include <utility> + +#include "base/command_line.h" +#include "base/files/file_util.h" +#include "base/linux_util.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/pickle.h" +#include "base/posix/eintr_wrapper.h" +#include "base/posix/global_descriptors.h" +#include "base/posix/unix_domain_socket.h" +#include "base/process/kill.h" +#include "base/process/launch.h" +#include "base/process/process.h" +#include "base/process/process_handle.h" +#include "base/time/time.h" +#include "base/trace_event/trace_event.h" +#include "build/build_config.h" +#include "content/common/zygote/zygote_commands_linux.h" +#include "content/public/common/zygote/send_zygote_child_ping_linux.h" +#include "content/public/common/zygote/zygote_fork_delegate_linux.h" +#include "ipc/ipc_channel.h" +#include "sandbox/linux/services/credentials.h" +#include "sandbox/linux/services/namespace_sandbox.h" +#include "services/service_manager/embedder/descriptors.h" +#include "services/service_manager/embedder/result_codes.h" +#include "services/service_manager/embedder/set_process_title.h" +#include "services/service_manager/embedder/switches.h" +#include "services/service_manager/sandbox/linux/sandbox_linux.h" +#include "services/service_manager/sandbox/sandbox.h" +#include "third_party/icu/source/i18n/unicode/timezone.h" + +// See +// https://chromium.googlesource.com/chromium/src/+/master/docs/linux/zygote.md + +namespace content { + +namespace { + +// NOP function. See below where this handler is installed. +void SIGCHLDHandler(int signal) {} + +int LookUpFd(const base::GlobalDescriptors::Mapping& fd_mapping, uint32_t key) { + for (size_t index = 0; index < fd_mapping.size(); ++index) { + if (fd_mapping[index].key == key) + return fd_mapping[index].fd; + } + return -1; +} + +void KillAndReap(pid_t pid, ZygoteForkDelegate* helper) { + if (helper) { + // Helper children may be forked in another PID namespace, so |pid| might + // be meaningless to us; or we just might not be able to directly send it + // signals. So we can't kill it. + // Additionally, we're not its parent, so we can't reap it anyway. + // TODO(mdempsky): Extend the ZygoteForkDelegate API to handle this. + LOG(WARNING) << "Unable to kill or reap helper children"; + return; + } + + // Kill the child process in case it's not already dead, so we can safely + // perform a blocking wait. + PCHECK(0 == kill(pid, SIGKILL)); + PCHECK(pid == HANDLE_EINTR(waitpid(pid, nullptr, 0))); +} + +} // namespace + +Zygote::Zygote(int sandbox_flags, + std::vector<std::unique_ptr<ZygoteForkDelegate>> helpers, + const base::GlobalDescriptors::Descriptor& ipc_backchannel) + : sandbox_flags_(sandbox_flags), + helpers_(std::move(helpers)), + initial_uma_index_(0), + to_reap_(), + ipc_backchannel_(ipc_backchannel) {} + +Zygote::~Zygote() {} + +bool Zygote::ProcessRequests() { + // A SOCK_SEQPACKET socket is installed in fd 3. We get commands from the + // browser on it. + // A SOCK_DGRAM is installed in fd 5. This is the sandbox IPC channel. + // See + // https://chromium.googlesource.com/chromium/src/+/master/docs/linux/sandbox_ipc.md + + // We need to accept SIGCHLD, even though our handler is a no-op because + // otherwise we cannot wait on children. (According to POSIX 2001.) + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_handler = &SIGCHLDHandler; + PCHECK(sigaction(SIGCHLD, &action, nullptr) == 0); + + // Block SIGCHLD until a child might be ready to reap. + sigset_t sigset; + sigset_t orig_sigmask; + PCHECK(sigemptyset(&sigset) == 0); + PCHECK(sigaddset(&sigset, SIGCHLD) == 0); + PCHECK(sigprocmask(SIG_BLOCK, &sigset, &orig_sigmask) == 0); + + if (UsingSUIDSandbox() || UsingNSSandbox()) { + // Let the ZygoteHost know we are ready to go. + // The receiving code is in + // content/browser/zygote_host/zygote_host_impl_linux.cc. + bool r = base::UnixDomainSocket::SendMsg( + kZygoteSocketPairFd, kZygoteHelloMessage, sizeof(kZygoteHelloMessage), + std::vector<int>()); +#if defined(OS_CHROMEOS) + LOG_IF(WARNING, !r) << "Sending zygote magic failed"; + // Exit normally on chromeos because session manager may send SIGTERM + // right after the process starts and it may fail to send zygote magic + // number to browser process. + if (!r) + _exit(service_manager::RESULT_CODE_NORMAL_EXIT); +#else + CHECK(r) << "Sending zygote magic failed"; +#endif + } + + sigset_t ppoll_sigmask = orig_sigmask; + PCHECK(sigdelset(&ppoll_sigmask, SIGCHLD) == 0); + struct pollfd pfd; + pfd.fd = kZygoteSocketPairFd; + pfd.events = POLLIN; + + struct timespec timeout; + timeout.tv_sec = 2; + timeout.tv_nsec = 0; + + for (;;) { + struct timespec* timeout_ptr = nullptr; + if (!to_reap_.empty()) + timeout_ptr = &timeout; + int rc = ppoll(&pfd, 1, timeout_ptr, &ppoll_sigmask); + PCHECK(rc >= 0 || errno == EINTR); + ReapChildren(); + + if (pfd.revents & POLLIN) { + // This function call can return multiple times, once per fork(). + if (HandleRequestFromBrowser(kZygoteSocketPairFd)) { + PCHECK(sigprocmask(SIG_SETMASK, &orig_sigmask, nullptr) == 0); + return true; + } + } + } + // The loop should not be exited unless a request was successfully processed. + NOTREACHED(); + return false; +} + +bool Zygote::ReapChild(const base::TimeTicks& now, ZygoteProcessInfo* child) { + pid_t pid = child->internal_pid; + pid_t r = HANDLE_EINTR(waitpid(pid, nullptr, WNOHANG)); + if (r > 0) { + if (r != pid) { + DLOG(ERROR) << "While waiting for " << pid + << " to terminate, " + "waitpid returned " + << r; + } + return r == pid; + } + if ((now - child->time_of_reap_request).InSeconds() < 2) { + return false; + } + // If the process has been requested reaped >= 2 seconds ago, kill it. + if (!child->sent_sigkill) { + if (kill(pid, SIGKILL) != 0) + DPLOG(ERROR) << "Sending SIGKILL to process " << pid << " failed"; + + child->sent_sigkill = true; + } + return false; +} + +void Zygote::ReapChildren() { + base::TimeTicks now = base::TimeTicks::Now(); + std::vector<ZygoteProcessInfo>::iterator it = to_reap_.begin(); + while (it != to_reap_.end()) { + if (ReapChild(now, &(*it))) { + it = to_reap_.erase(it); + } else { + it++; + } + } +} + +bool Zygote::GetProcessInfo(base::ProcessHandle pid, + ZygoteProcessInfo* process_info) { + DCHECK(process_info); + const ZygoteProcessMap::const_iterator it = process_info_map_.find(pid); + if (it == process_info_map_.end()) { + return false; + } + *process_info = it->second; + return true; +} + +bool Zygote::UsingSUIDSandbox() const { + return sandbox_flags_ & service_manager::SandboxLinux::kSUID; +} + +bool Zygote::UsingNSSandbox() const { + return sandbox_flags_ & service_manager::SandboxLinux::kUserNS; +} + +bool Zygote::HandleRequestFromBrowser(int fd) { + std::vector<base::ScopedFD> fds; + char buf[kZygoteMaxMessageLength]; + const ssize_t len = + base::UnixDomainSocket::RecvMsg(fd, buf, sizeof(buf), &fds); + + if (len == 0 || (len == -1 && errno == ECONNRESET)) { + // EOF from the browser. We should die. + // TODO(eugenis): call __sanititizer_cov_dump() here to obtain code + // coverage for the Zygote. Currently it's not possible because of + // confusion over who is responsible for closing the file descriptor. + _exit(0); + return false; + } + + if (len == -1) { + PLOG(ERROR) << "Error reading message from browser"; + return false; + } + + base::Pickle pickle(buf, len); + base::PickleIterator iter(pickle); + + int kind; + if (iter.ReadInt(&kind)) { + switch (kind) { + case kZygoteCommandFork: + // This function call can return multiple times, once per fork(). + return HandleForkRequest(fd, iter, std::move(fds)); + + case kZygoteCommandReap: + if (!fds.empty()) + break; + HandleReapRequest(fd, iter); + return false; + case kZygoteCommandGetTerminationStatus: + if (!fds.empty()) + break; + HandleGetTerminationStatus(fd, iter); + return false; + case kZygoteCommandGetSandboxStatus: + HandleGetSandboxStatus(fd, iter); + return false; + case kZygoteCommandForkRealPID: + // This shouldn't happen in practice, but some failure paths in + // HandleForkRequest (e.g., if ReadArgsAndFork fails during depickling) + // could leave this command pending on the socket. + LOG(ERROR) << "Unexpected real PID message from browser"; + NOTREACHED(); + return false; + default: + NOTREACHED(); + break; + } + } + + LOG(WARNING) << "Error parsing message from browser"; + return false; +} + +void Zygote::HandleReapRequest(int fd, base::PickleIterator iter) { + base::ProcessId child; + + if (!iter.ReadInt(&child)) { + LOG(WARNING) << "Error parsing reap request from browser"; + return; + } + + ZygoteProcessInfo child_info; + if (!GetProcessInfo(child, &child_info)) { + LOG(ERROR) << "Child not found!"; + NOTREACHED(); + return; + } + child_info.time_of_reap_request = base::TimeTicks::Now(); + + if (!child_info.started_from_helper) { + to_reap_.push_back(child_info); + } else { + // For processes from the helper, send a GetTerminationStatus request + // with known_dead set to true. + // This is not perfect, as the process may be killed instantly, but is + // better than ignoring the request. + base::TerminationStatus status; + int exit_code; + bool got_termination_status = + GetTerminationStatus(child, true /* known_dead */, &status, &exit_code); + DCHECK(got_termination_status); + } + process_info_map_.erase(child); +} + +bool Zygote::GetTerminationStatus(base::ProcessHandle real_pid, + bool known_dead, + base::TerminationStatus* status, + int* exit_code) { + ZygoteProcessInfo child_info; + if (!GetProcessInfo(real_pid, &child_info)) { + LOG(ERROR) << "Zygote::GetTerminationStatus for unknown PID " << real_pid; + NOTREACHED(); + return false; + } + // We know about |real_pid|. + const base::ProcessHandle child = child_info.internal_pid; + if (child_info.started_from_helper) { + if (!child_info.started_from_helper->GetTerminationStatus( + child, known_dead, status, exit_code)) { + return false; + } + } else { + // Handle the request directly. + if (known_dead) { + *status = base::GetKnownDeadTerminationStatus(child, exit_code); + } else { + // We don't know if the process is dying, so get its status but don't + // wait. + *status = base::GetTerminationStatus(child, exit_code); + } + } + // Successfully got a status for |real_pid|. + if (*status != base::TERMINATION_STATUS_STILL_RUNNING) { + // Time to forget about this process. + process_info_map_.erase(real_pid); + } + + if (WIFEXITED(*exit_code)) { + const int exit_status = WEXITSTATUS(*exit_code); + if (exit_status == sandbox::NamespaceSandbox::SignalExitCode(SIGINT) || + exit_status == sandbox::NamespaceSandbox::SignalExitCode(SIGTERM)) { + *status = base::TERMINATION_STATUS_PROCESS_WAS_KILLED; + } + } + + return true; +} + +void Zygote::HandleGetTerminationStatus(int fd, base::PickleIterator iter) { + bool known_dead; + base::ProcessHandle child_requested; + + if (!iter.ReadBool(&known_dead) || !iter.ReadInt(&child_requested)) { + LOG(WARNING) << "Error parsing GetTerminationStatus request " + << "from browser"; + return; + } + + base::TerminationStatus status; + int exit_code; + + bool got_termination_status = + GetTerminationStatus(child_requested, known_dead, &status, &exit_code); + if (!got_termination_status) { + // Assume that if we can't find the child in the sandbox, then + // it terminated normally. + NOTREACHED(); + status = base::TERMINATION_STATUS_NORMAL_TERMINATION; + exit_code = service_manager::RESULT_CODE_NORMAL_EXIT; + } + + base::Pickle write_pickle; + write_pickle.WriteInt(static_cast<int>(status)); + write_pickle.WriteInt(exit_code); + ssize_t written = + HANDLE_EINTR(write(fd, write_pickle.data(), write_pickle.size())); + if (written != static_cast<ssize_t>(write_pickle.size())) + PLOG(ERROR) << "write"; +} + +int Zygote::ForkWithRealPid(const std::string& process_type, + const base::GlobalDescriptors::Mapping& fd_mapping, + const std::string& channel_id, + base::ScopedFD pid_oracle, + std::string* uma_name, + int* uma_sample, + int* uma_boundary_value) { + ZygoteForkDelegate* helper = nullptr; + for (auto i = helpers_.begin(); i != helpers_.end(); ++i) { + if ((*i)->CanHelp(process_type, uma_name, uma_sample, uma_boundary_value)) { + helper = i->get(); + break; + } + } + + base::ScopedFD read_pipe, write_pipe; + base::ProcessId pid = 0; + if (helper) { + int mojo_channel_fd = + LookUpFd(fd_mapping, service_manager::kMojoIPCChannel); + if (mojo_channel_fd < 0) { + DLOG(ERROR) << "Failed to find kMojoIPCChannel in FD mapping"; + return -1; + } + std::vector<int> fds; + fds.push_back(mojo_channel_fd); // kBrowserFDIndex + fds.push_back(pid_oracle.get()); // kPIDOracleFDIndex + pid = helper->Fork(process_type, fds, channel_id); + + // Helpers should never return in the child process. + CHECK_NE(pid, 0); + } else { + PCHECK(base::CreatePipe(&read_pipe, &write_pipe)); + if (sandbox_flags_ & service_manager::SandboxLinux::kPIDNS && + sandbox_flags_ & service_manager::SandboxLinux::kUserNS) { + pid = sandbox::NamespaceSandbox::ForkInNewPidNamespace( + /*drop_capabilities_in_child=*/true); + } else { + pid = sandbox::Credentials::ForkAndDropCapabilitiesInChild(); + } + } + + if (pid == 0) { + // In the child process. + + // If the process is the init process inside a PID namespace, it must have + // explicit signal handlers. + if (getpid() == 1) { + static const int kTerminationSignals[] = { + SIGINT, SIGTERM, SIGHUP, SIGQUIT, SIGABRT, SIGPIPE, SIGUSR1, SIGUSR2}; + for (const int sig : kTerminationSignals) { + sandbox::NamespaceSandbox::InstallTerminationSignalHandler( + sig, sandbox::NamespaceSandbox::SignalExitCode(sig)); + } + } + + write_pipe.reset(); + + // Ping the PID oracle socket so the browser can find our PID. + CHECK(SendZygoteChildPing(pid_oracle.get())); + + // Now read back our real PID from the zygote. + base::ProcessId real_pid; + if (!base::ReadFromFD(read_pipe.get(), reinterpret_cast<char*>(&real_pid), + sizeof(real_pid))) { + LOG(FATAL) << "Failed to synchronise with parent zygote process"; + } + if (real_pid <= 0) { + LOG(FATAL) << "Invalid pid from parent zygote"; + } + // Sandboxed processes need to send the global, non-namespaced PID when + // setting up an IPC channel to their parent. + IPC::Channel::SetGlobalPid(real_pid); + // Force the real PID so chrome event data have a PID that corresponds + // to system trace event data. + base::trace_event::TraceLog::GetInstance()->SetProcessID( + static_cast<int>(real_pid)); + base::InitUniqueIdForProcessInPidNamespace(real_pid); + return 0; + } + + // In the parent process. + if (pid < 0) { + // Fork failed. + return -1; + } + + read_pipe.reset(); + pid_oracle.reset(); + + // Always receive a real PID from the zygote host, though it might + // be invalid (see below). + base::ProcessId real_pid = -1; + { + std::vector<base::ScopedFD> recv_fds; + char buf[kZygoteMaxMessageLength]; + const ssize_t len = base::UnixDomainSocket::RecvMsg( + kZygoteSocketPairFd, buf, sizeof(buf), &recv_fds); + + if (len > 0) { + CHECK(recv_fds.empty()); + + base::Pickle pickle(buf, len); + base::PickleIterator iter(pickle); + + int kind; + CHECK(iter.ReadInt(&kind)); + CHECK(kind == kZygoteCommandForkRealPID); + CHECK(iter.ReadInt(&real_pid)); + } + } + + // If we successfully forked a child, but it crashed without sending + // a message to the browser, the browser won't have found its PID. + if (real_pid < 0) { + KillAndReap(pid, helper); + return -1; + } + + // If we're not using a helper, send the PID back to the child process. + if (!helper) { + ssize_t written = + HANDLE_EINTR(write(write_pipe.get(), &real_pid, sizeof(real_pid))); + if (written != sizeof(real_pid)) { + KillAndReap(pid, helper); + return -1; + } + } + + // Now set-up this process to be tracked by the Zygote. + if (process_info_map_.find(real_pid) != process_info_map_.end()) { + LOG(ERROR) << "Already tracking PID " << real_pid; + NOTREACHED(); + } + process_info_map_[real_pid].internal_pid = pid; + process_info_map_[real_pid].started_from_helper = helper; + + return real_pid; +} + +base::ProcessId Zygote::ReadArgsAndFork(base::PickleIterator iter, + std::vector<base::ScopedFD> fds, + std::string* uma_name, + int* uma_sample, + int* uma_boundary_value) { + std::vector<std::string> args; + int argc = 0; + int numfds = 0; + base::GlobalDescriptors::Mapping mapping; + std::string process_type; + std::string channel_id; + const std::string channel_id_prefix = + std::string("--") + + service_manager::switches::kServiceRequestChannelToken + std::string("="); + + if (!iter.ReadString(&process_type)) + return -1; + if (!iter.ReadInt(&argc)) + return -1; + + for (int i = 0; i < argc; ++i) { + std::string arg; + if (!iter.ReadString(&arg)) + return -1; + args.push_back(arg); + if (arg.compare(0, channel_id_prefix.length(), channel_id_prefix) == 0) + channel_id = arg.substr(channel_id_prefix.length()); + } + + // timezone_id is obtained from ICU in zygote host so that it can't be + // invalid. For an unknown reason, if an invalid ID is passed down here, the + // worst result would be that timezone would be set to Etc/Unknown. + base::string16 timezone_id; + if (!iter.ReadString16(&timezone_id)) + return -1; + icu::TimeZone::adoptDefault(icu::TimeZone::createTimeZone( + icu::UnicodeString(FALSE, timezone_id.data(), timezone_id.length()))); + + if (!iter.ReadInt(&numfds)) + return -1; + if (numfds != static_cast<int>(fds.size())) + return -1; + + // First FD is the PID oracle socket. + if (fds.size() < 1) + return -1; + base::ScopedFD pid_oracle(std::move(fds[0])); + + // Remaining FDs are for the global descriptor mapping. + for (int i = 1; i < numfds; ++i) { + base::GlobalDescriptors::Key key; + if (!iter.ReadUInt32(&key)) + return -1; + mapping.push_back(base::GlobalDescriptors::Descriptor(key, fds[i].get())); + } + + mapping.push_back(ipc_backchannel_); + + // Returns twice, once per process. + base::ProcessId child_pid = + ForkWithRealPid(process_type, mapping, channel_id, std::move(pid_oracle), + uma_name, uma_sample, uma_boundary_value); + if (!child_pid) { + // This is the child process. + + // Our socket from the browser. + PCHECK(0 == IGNORE_EINTR(close(kZygoteSocketPairFd))); + + // Pass ownership of file descriptors from fds to GlobalDescriptors. + for (base::ScopedFD& fd : fds) + ignore_result(fd.release()); + base::GlobalDescriptors::GetInstance()->Reset(mapping); + + // Reset the process-wide command line to our new command line. + base::CommandLine::Reset(); + base::CommandLine::Init(0, nullptr); + base::CommandLine::ForCurrentProcess()->InitFromArgv(args); + + // Update the process title. The argv was already cached by the call to + // SetProcessTitleFromCommandLine in ChromeMain, so we can pass NULL here + // (we don't have the original argv at this point). + service_manager::SetProcessTitleFromCommandLine(nullptr); + } else if (child_pid < 0) { + LOG(ERROR) << "Zygote could not fork: process_type " << process_type + << " numfds " << numfds << " child_pid " << child_pid; + } + return child_pid; +} + +bool Zygote::HandleForkRequest(int fd, + base::PickleIterator iter, + std::vector<base::ScopedFD> fds) { + std::string uma_name; + int uma_sample; + int uma_boundary_value; + base::ProcessId child_pid = ReadArgsAndFork(iter, std::move(fds), &uma_name, + &uma_sample, &uma_boundary_value); + if (child_pid == 0) + return true; + // If there's no UMA report for this particular fork, then check if any + // helpers have an initial UMA report for us to send instead. + while (uma_name.empty() && initial_uma_index_ < helpers_.size()) { + helpers_[initial_uma_index_++]->InitialUMA(&uma_name, &uma_sample, + &uma_boundary_value); + } + // Must always send reply, as ZygoteHost blocks while waiting for it. + base::Pickle reply_pickle; + reply_pickle.WriteInt(child_pid); + reply_pickle.WriteString(uma_name); + if (!uma_name.empty()) { + reply_pickle.WriteInt(uma_sample); + reply_pickle.WriteInt(uma_boundary_value); + } + if (HANDLE_EINTR(write(fd, reply_pickle.data(), reply_pickle.size())) != + static_cast<ssize_t>(reply_pickle.size())) + PLOG(ERROR) << "write"; + return false; +} + +bool Zygote::HandleGetSandboxStatus(int fd, base::PickleIterator iter) { + if (HANDLE_EINTR(write(fd, &sandbox_flags_, sizeof(sandbox_flags_))) != + sizeof(sandbox_flags_)) { + PLOG(ERROR) << "write"; + } + + return false; +} + +} // namespace content diff --git a/chromium/content/zygote/zygote_linux.h b/chromium/content/zygote/zygote_linux.h new file mode 100644 index 00000000000..8e451fa6afe --- /dev/null +++ b/chromium/content/zygote/zygote_linux.h @@ -0,0 +1,152 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_ZYGOTE_ZYGOTE_LINUX_H_ +#define CONTENT_ZYGOTE_ZYGOTE_LINUX_H_ + +#include <stddef.h> + +#include <memory> +#include <string> +#include <vector> + +#include "base/containers/small_map.h" +#include "base/files/scoped_file.h" +#include "base/posix/global_descriptors.h" +#include "base/process/kill.h" +#include "base/process/process.h" +#include "base/process/process_handle.h" +#include "base/time/time.h" + +namespace base { +class PickleIterator; +} + +namespace content { + +class ZygoteForkDelegate; + +// This is the object which implements the zygote. The ZygoteMain function, +// which is called from ChromeMain, simply constructs one of these objects and +// runs it. +class Zygote { + public: + Zygote(int sandbox_flags, + std::vector<std::unique_ptr<ZygoteForkDelegate>> helpers, + const base::GlobalDescriptors::Descriptor& ipc_backchannel); + ~Zygote(); + + bool ProcessRequests(); + + private: + struct ZygoteProcessInfo { + // Pid from inside the Zygote's PID namespace. + base::ProcessHandle internal_pid; + // Keeps track of which fork delegate helper the process was started from. + ZygoteForkDelegate* started_from_helper; + // Records when the browser requested the zygote to reap this process. + base::TimeTicks time_of_reap_request; + // Notes whether the zygote has sent SIGKILL to this process. + bool sent_sigkill; + }; + using ZygoteProcessMap = + base::small_map<std::map<base::ProcessHandle, ZygoteProcessInfo>>; + + // Retrieve a ZygoteProcessInfo from the process_info_map_. + // Returns true and write to process_info if |pid| can be found, return + // false otherwise. + bool GetProcessInfo(base::ProcessHandle pid, ZygoteProcessInfo* process_info); + + // Returns true if the SUID sandbox is active. + bool UsingSUIDSandbox() const; + // Returns true if the NS sandbox is active. + bool UsingNSSandbox() const; + + // --------------------------------------------------------------------------- + // Requests from the browser... + + // Read and process a request from the browser. Returns true if we are in a + // new process and thus need to unwind back into ChromeMain. + bool HandleRequestFromBrowser(int fd); + + void HandleReapRequest(int fd, base::PickleIterator iter); + + // Get the termination status of |real_pid|. |real_pid| is the PID as it + // appears outside of the sandbox. + // Return true if it managed to get the termination status and return the + // status in |status| and the exit code in |exit_code|. + bool GetTerminationStatus(base::ProcessHandle real_pid, + bool known_dead, + base::TerminationStatus* status, + int* exit_code); + + void HandleGetTerminationStatus(int fd, base::PickleIterator iter); + + // This is equivalent to fork(), except that, when using the SUID sandbox, it + // returns the real PID of the child process as it appears outside the + // sandbox, rather than returning the PID inside the sandbox. The child's + // real PID is determined by having it call + // service_manager::SendZygoteChildPing(int) using the |pid_oracle| + // descriptor. + // Finally, when using a ZygoteForkDelegate helper, |uma_name|, |uma_sample|, + // and |uma_boundary_value| may be set if the helper wants to make a UMA + // report via UMA_HISTOGRAM_ENUMERATION. + int ForkWithRealPid(const std::string& process_type, + const base::GlobalDescriptors::Mapping& fd_mapping, + const std::string& channel_id, + base::ScopedFD pid_oracle, + std::string* uma_name, + int* uma_sample, + int* uma_boundary_value); + + // Unpacks process type and arguments from |iter| and forks a new process. + // Returns -1 on error, otherwise returns twice, returning 0 to the child + // process and the child process ID to the parent process, like fork(). + base::ProcessId ReadArgsAndFork(base::PickleIterator iter, + std::vector<base::ScopedFD> fds, + std::string* uma_name, + int* uma_sample, + int* uma_boundary_value); + + // Handle a 'fork' request from the browser: this means that the browser + // wishes to start a new renderer. Returns true if we are in a new process, + // otherwise writes the child_pid back to the browser via |fd|. Writes a + // child_pid of -1 on error. + bool HandleForkRequest(int fd, + base::PickleIterator iter, + std::vector<base::ScopedFD> fds); + + bool HandleGetSandboxStatus(int fd, base::PickleIterator iter); + + // Attempt to reap the child process by calling waitpid, and return + // whether successful. If the process has not terminated within + // 2 seconds of its reap request, send it SIGKILL. + bool ReapChild(const base::TimeTicks& now, ZygoteProcessInfo* child); + + // Attempt to reap all outstanding children in |to_reap_|. + void ReapChildren(); + + // The Zygote needs to keep some information about each process. Most + // notably what the PID of the process is inside the PID namespace of + // the Zygote and whether or not a process was started by the + // ZygoteForkDelegate helper. + ZygoteProcessMap process_info_map_; + + const int sandbox_flags_; + std::vector<std::unique_ptr<ZygoteForkDelegate>> helpers_; + + // Count of how many fork delegates for which we've invoked InitialUMA(). + size_t initial_uma_index_; + + // The vector contains the child processes that need to be reaped. + std::vector<ZygoteProcessInfo> to_reap_; + + // Sandbox IPC channel for renderers to invoke services from the browser. See + // https://chromium.googlesource.com/chromium/src/+/master/docs/linux/sandbox_ipc.md + base::GlobalDescriptors::Descriptor ipc_backchannel_; +}; + +} // namespace content + +#endif // CONTENT_ZYGOTE_ZYGOTE_LINUX_H_ diff --git a/chromium/content/zygote/zygote_main.h b/chromium/content/zygote/zygote_main.h new file mode 100644 index 00000000000..1959950a6c0 --- /dev/null +++ b/chromium/content/zygote/zygote_main.h @@ -0,0 +1,24 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_ZYGOTE_ZYGOTE_MAIN_H_ +#define CONTENT_ZYGOTE_ZYGOTE_MAIN_H_ + +#include <memory> +#include <vector> + +#include "build/build_config.h" +#include "content/common/content_export.h" + +namespace content { + +class ZygoteForkDelegate; + +// |delegate| must outlive this call. +CONTENT_EXPORT bool ZygoteMain( + std::vector<std::unique_ptr<ZygoteForkDelegate>> fork_delegates); + +} // namespace content + +#endif // CONTENT_ZYGOTE_ZYGOTE_MAIN_H_ diff --git a/chromium/content/zygote/zygote_main_linux.cc b/chromium/content/zygote/zygote_main_linux.cc new file mode 100644 index 00000000000..b7dc390c499 --- /dev/null +++ b/chromium/content/zygote/zygote_main_linux.cc @@ -0,0 +1,247 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/zygote/zygote_main.h" + +#include <dlfcn.h> +#include <fcntl.h> +#include <pthread.h> +#include <signal.h> +#include <stddef.h> +#include <stdint.h> +#include <string.h> +#include <sys/prctl.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +#include <utility> + +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/posix/unix_domain_socket.h" +#include "base/rand_util.h" +#include "base/strings/safe_sprintf.h" +#include "base/strings/string_number_conversions.h" +#include "base/system/sys_info.h" +#include "build/build_config.h" +#include "content/common/zygote/zygote_commands_linux.h" +#include "content/public/common/zygote/sandbox_support_linux.h" +#include "content/public/common/zygote/zygote_fork_delegate_linux.h" +#include "content/zygote/zygote_linux.h" +#include "sandbox/linux/services/credentials.h" +#include "sandbox/linux/services/init_process_reaper.h" +#include "sandbox/linux/services/libc_interceptor.h" +#include "sandbox/linux/services/namespace_sandbox.h" +#include "sandbox/linux/services/thread_helpers.h" +#include "sandbox/linux/suid/client/setuid_sandbox_client.h" +#include "services/service_manager/embedder/descriptors.h" +#include "services/service_manager/embedder/switches.h" +#include "services/service_manager/sandbox/linux/sandbox_debug_handling_linux.h" +#include "services/service_manager/sandbox/linux/sandbox_linux.h" +#include "services/service_manager/sandbox/sandbox.h" +#include "services/service_manager/sandbox/switches.h" +#include "third_party/icu/source/i18n/unicode/timezone.h" + +namespace content { + +namespace { + +void CloseFds(const std::vector<int>& fds) { + for (const auto& it : fds) { + PCHECK(0 == IGNORE_EINTR(close(it))); + } +} + +base::OnceClosure ClosureFromTwoClosures(base::OnceClosure one, + base::OnceClosure two) { + return base::BindOnce( + [](base::OnceClosure one, base::OnceClosure two) { + if (!one.is_null()) + std::move(one).Run(); + if (!two.is_null()) + std::move(two).Run(); + }, + std::move(one), std::move(two)); +} + +} // namespace + +// This function triggers the static and lazy construction of objects that need +// to be created before imposing the sandbox. +static void ZygotePreSandboxInit() { + base::RandUint64(); + + base::SysInfo::AmountOfPhysicalMemory(); + base::SysInfo::NumberOfProcessors(); + + // ICU DateFormat class (used in base/time_format.cc) needs to get the + // Olson timezone ID by accessing the zoneinfo files on disk. After + // TimeZone::createDefault is called once here, the timezone ID is + // cached and there's no more need to access the file system. + std::unique_ptr<icu::TimeZone> zone(icu::TimeZone::createDefault()); +} + +static bool CreateInitProcessReaper( + base::OnceClosure post_fork_parent_callback) { + // The current process becomes init(1), this function returns from a + // newly created process. + if (!sandbox::CreateInitProcessReaper(std::move(post_fork_parent_callback))) { + LOG(ERROR) << "Error creating an init process to reap zombies"; + return false; + } + return true; +} + +// Enter the setuid sandbox. This requires the current process to have been +// created through the setuid sandbox. +static bool EnterSuidSandbox(sandbox::SetuidSandboxClient* setuid_sandbox, + base::OnceClosure post_fork_parent_callback) { + DCHECK(setuid_sandbox); + DCHECK(setuid_sandbox->IsSuidSandboxChild()); + + // Use the SUID sandbox. This still allows the seccomp sandbox to + // be enabled by the process later. + + if (!setuid_sandbox->IsSuidSandboxUpToDate()) { + LOG(WARNING) << "You are using a wrong version of the setuid binary!\n" + "Please read " + "https://chromium.googlesource.com/chromium/src/+/master/" + "docs/linux/suid_sandbox_development.md." + "\n\n"; + } + + if (!setuid_sandbox->ChrootMe()) + return false; + + if (setuid_sandbox->IsInNewPIDNamespace()) { + CHECK_EQ(1, getpid()) + << "The SUID sandbox created a new PID namespace but Zygote " + "is not the init process. Please, make sure the SUID " + "binary is up to date."; + } + + if (getpid() == 1) { + // The setuid sandbox has created a new PID namespace and we need + // to assume the role of init. + CHECK(CreateInitProcessReaper(std::move(post_fork_parent_callback))); + } + + CHECK(service_manager::SandboxDebugHandling::SetDumpableStatusAndHandlers()); + return true; +} + +static void DropAllCapabilities(int proc_fd) { + CHECK(sandbox::Credentials::DropAllCapabilities(proc_fd)); +} + +static void EnterNamespaceSandbox(service_manager::SandboxLinux* linux_sandbox, + base::OnceClosure post_fork_parent_callback) { + linux_sandbox->EngageNamespaceSandbox(true /* from_zygote */); + if (getpid() == 1) { + CHECK(CreateInitProcessReaper(ClosureFromTwoClosures( + base::BindOnce(DropAllCapabilities, linux_sandbox->proc_fd()), + std::move(post_fork_parent_callback)))); + } +} + +static void EnterLayerOneSandbox(service_manager::SandboxLinux* linux_sandbox, + const bool using_layer1_sandbox, + base::OnceClosure post_fork_parent_callback) { + DCHECK(linux_sandbox); + + ZygotePreSandboxInit(); + +// Check that the pre-sandbox initialization didn't spawn threads. +// It's not just our code which may do so - some system-installed libraries +// are known to be culprits, e.g. lttng. +#if !defined(THREAD_SANITIZER) + CHECK(sandbox::ThreadHelpers::IsSingleThreaded()); +#endif + + sandbox::SetuidSandboxClient* setuid_sandbox = + linux_sandbox->setuid_sandbox_client(); + if (setuid_sandbox->IsSuidSandboxChild()) { + CHECK( + EnterSuidSandbox(setuid_sandbox, std::move(post_fork_parent_callback))) + << "Failed to enter setuid sandbox"; + } else if (sandbox::NamespaceSandbox::InNewUserNamespace()) { + EnterNamespaceSandbox(linux_sandbox, std::move(post_fork_parent_callback)); + } else { + CHECK(!using_layer1_sandbox); + } +} + +bool ZygoteMain( + std::vector<std::unique_ptr<ZygoteForkDelegate>> fork_delegates) { + sandbox::SetAmZygoteOrRenderer(true, GetSandboxFD()); + + auto* linux_sandbox = service_manager::SandboxLinux::GetInstance(); + + // Skip pre-initializing sandbox when sandbox is disabled for + // https://crbug.com/444900. + if (!base::CommandLine::ForCurrentProcess()->HasSwitch( + service_manager::switches::kNoSandbox) && + !base::CommandLine::ForCurrentProcess()->HasSwitch( + service_manager::switches::kNoZygoteSandbox)) { + // This will pre-initialize the various sandboxes that need it. + linux_sandbox->PreinitializeSandbox(); + } + + const bool using_setuid_sandbox = + linux_sandbox->setuid_sandbox_client()->IsSuidSandboxChild(); + const bool using_namespace_sandbox = + sandbox::NamespaceSandbox::InNewUserNamespace(); + const bool using_layer1_sandbox = + using_setuid_sandbox || using_namespace_sandbox; + + if (using_setuid_sandbox) { + linux_sandbox->setuid_sandbox_client()->CloseDummyFile(); + } + + if (using_layer1_sandbox) { + // Let the ZygoteHost know we're booting up. + if (!base::UnixDomainSocket::SendMsg( + kZygoteSocketPairFd, kZygoteBootMessage, sizeof(kZygoteBootMessage), + std::vector<int>())) { + // This is not a CHECK failure because the browser process could either + // crash or quickly exit while the zygote is starting. In either case a + // zygote crash is not useful. https://crbug.com/692227 + PLOG(ERROR) << "Failed sending zygote boot message"; + _exit(1); + } + } + + VLOG(1) << "ZygoteMain: initializing " << fork_delegates.size() + << " fork delegates"; + for (const auto& fork_delegate : fork_delegates) { + fork_delegate->Init(GetSandboxFD(), using_layer1_sandbox); + } + + // Turn on the first layer of the sandbox if the configuration warrants it. + EnterLayerOneSandbox( + linux_sandbox, using_layer1_sandbox, + base::BindOnce(CloseFds, linux_sandbox->GetFileDescriptorsToClose())); + + const int sandbox_flags = linux_sandbox->GetStatus(); + const bool setuid_sandbox_engaged = + !!(sandbox_flags & service_manager::SandboxLinux::kSUID); + CHECK_EQ(using_setuid_sandbox, setuid_sandbox_engaged); + + const bool namespace_sandbox_engaged = + !!(sandbox_flags & service_manager::SandboxLinux::kUserNS); + CHECK_EQ(using_namespace_sandbox, namespace_sandbox_engaged); + + Zygote zygote(sandbox_flags, std::move(fork_delegates), + base::GlobalDescriptors::Descriptor( + static_cast<uint32_t>(service_manager::kSandboxIPCChannel), + GetSandboxFD())); + + // This function call can return multiple times, once per fork(). + return zygote.ProcessRequests(); +} + +} // namespace content |