diff options
Diffstat (limited to 'chromium/content/browser/zygote_host')
4 files changed, 474 insertions, 4 deletions
diff --git a/chromium/content/browser/zygote_host/OWNERS b/chromium/content/browser/zygote_host/OWNERS index 50e496afb9e..eb4b322bdf5 100644 --- a/chromium/content/browser/zygote_host/OWNERS +++ b/chromium/content/browser/zygote_host/OWNERS @@ -1,7 +1,4 @@ -jln@chromium.org -kerrnel@chromium.org -rsesek@chromium.org -tsepez@chromium.org +file://content/zygote/OWNERS # TEAM: security-dev@chromium.org # COMPONENT: Internals>Sandbox diff --git a/chromium/content/browser/zygote_host/zygote_browsertest.cc b/chromium/content/browser/zygote_host/zygote_browsertest.cc new file mode 100644 index 00000000000..f7339c77d49 --- /dev/null +++ b/chromium/content/browser/zygote_host/zygote_browsertest.cc @@ -0,0 +1,107 @@ +// 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/common/zygote/zygote_buildflags.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" +#if BUILDFLAG(USE_ZYGOTE_HANDLE) +#include "content/browser/zygote_host/zygote_host_impl_linux.h" +#include "content/common/zygote/zygote_communication_linux.h" +#include "content/common/zygote/zygote_handle_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 = GetGenericZygote()->GetSandboxStatus(); + constexpr int kExpectedFlags = service_manager::SandboxLinux::kPIDNS | + service_manager::SandboxLinux::kNetNS | + service_manager::SandboxLinux::kUserNS; + EXPECT_EQ(kExpectedFlags, flags & kExpectedFlags); + + EXPECT_EQ(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(ZygoteHostImpl::GetInstance()->HasZygote()); +} +#endif + +} // namespace content diff --git a/chromium/content/browser/zygote_host/zygote_host_impl_linux.cc b/chromium/content/browser/zygote_host/zygote_host_impl_linux.cc new file mode 100644 index 00000000000..ced1c83e82b --- /dev/null +++ b/chromium/content/browser/zygote_host/zygote_host_impl_linux.cc @@ -0,0 +1,293 @@ +// 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/browser/zygote_host/zygote_host_impl_linux.h" + +#include <stdlib.h> +#include <sys/socket.h> +#include <sys/types.h> + +#include "base/allocator/allocator_extension.h" +#include "base/files/file_enumerator.h" +#include "base/logging.h" +#include "base/posix/unix_domain_socket.h" +#include "base/process/kill.h" +#include "base/process/memory.h" +#include "base/strings/string_number_conversions.h" +#include "build/build_config.h" +#include "content/common/zygote/zygote_commands_linux.h" +#include "sandbox/linux/services/credentials.h" +#include "sandbox/linux/services/namespace_sandbox.h" +#include "sandbox/linux/suid/client/setuid_sandbox_host.h" +#include "sandbox/linux/suid/common/sandbox.h" +#include "services/service_manager/sandbox/linux/sandbox_linux.h" +#include "services/service_manager/sandbox/switches.h" + +namespace content { + +namespace { + +// Receive a fixed message on fd and return the sender's PID. +// Returns true if the message received matches the expected message. +bool ReceiveFixedMessage(int fd, + const char* expect_msg, + size_t expect_len, + base::ProcessId* sender_pid) { + // Allocate an extra byte of buffer space so we can check that we received + // exactly |expect_len| bytes, and the message wasn't just truncated to fit. + char buf[expect_len + 1]; + std::vector<base::ScopedFD> fds_vec; + + const ssize_t len = base::UnixDomainSocket::RecvMsgWithPid( + fd, buf, sizeof(buf), &fds_vec, sender_pid); + if (static_cast<size_t>(len) != expect_len) + return false; + if (memcmp(buf, expect_msg, expect_len) != 0) + return false; + if (!fds_vec.empty()) + return false; + return true; +} + +} // namespace + +// static +ZygoteHost* ZygoteHost::GetInstance() { + return ZygoteHostImpl::GetInstance(); +} + +ZygoteHostImpl::ZygoteHostImpl() + : use_namespace_sandbox_(false), + use_suid_sandbox_(false), + use_suid_sandbox_for_adj_oom_score_(false), + sandbox_binary_(), + zygote_pids_lock_(), + zygote_pids_() {} + +ZygoteHostImpl::~ZygoteHostImpl() {} + +// static +ZygoteHostImpl* ZygoteHostImpl::GetInstance() { + return base::Singleton<ZygoteHostImpl>::get(); +} + +void ZygoteHostImpl::Init(const base::CommandLine& command_line) { + if (command_line.HasSwitch(service_manager::switches::kNoSandbox)) { + return; + } + + // Exit early if running as root without --no-sandbox. See + // https://crbug.com/638180. + // When running as root with the sandbox enabled, the browser process + // crashes on zygote initialization. Running as root with the sandbox + // is not supported, and if Chrome were able to display UI it would be showing + // an error message. With the zygote crashing it doesn't even get to that, + // so print an error message on the console. + uid_t uid = 0; + gid_t gid = 0; + if (!sandbox::Credentials::GetRESIds(&uid, &gid) || uid == 0) { + LOG(ERROR) << "Running as root without --" + << service_manager::switches::kNoSandbox + << " is not supported. See https://crbug.com/638180."; + exit(EXIT_FAILURE); + } + + { + std::unique_ptr<sandbox::SetuidSandboxHost> setuid_sandbox_host( + sandbox::SetuidSandboxHost::Create()); + sandbox_binary_ = setuid_sandbox_host->GetSandboxBinaryPath().value(); + } + + if (!command_line.HasSwitch( + service_manager::switches::kDisableNamespaceSandbox) && + sandbox::Credentials::CanCreateProcessInNewUserNS()) { + use_namespace_sandbox_ = true; + } else if (!command_line.HasSwitch( + service_manager::switches::kDisableSetuidSandbox) && + !sandbox_binary_.empty()) { + use_suid_sandbox_ = true; + + // Use the SUID sandbox for adjusting OOM scores when we are using + // the setuid sandbox. This is needed beacuse the processes are + // non-dumpable, so /proc/pid/oom_score_adj can only be written by + // root. + use_suid_sandbox_for_adj_oom_score_ = use_suid_sandbox_; + } else { + LOG(FATAL) + << "No usable sandbox! Update your kernel or see " + "https://chromium.googlesource.com/chromium/src/+/master/" + "docs/linux/suid_sandbox_development.md for more information on " + "developing with the SUID sandbox. " + "If you want to live dangerously and need an immediate workaround, " + "you can try using --" + << service_manager::switches::kNoSandbox << "."; + } +} + +void ZygoteHostImpl::AddZygotePid(pid_t pid) { + base::AutoLock lock(zygote_pids_lock_); + zygote_pids_.insert(pid); +} + +bool ZygoteHostImpl::IsZygotePid(pid_t pid) { + base::AutoLock lock(zygote_pids_lock_); + return zygote_pids_.find(pid) != zygote_pids_.end(); +} + +void ZygoteHostImpl::SetRendererSandboxStatus(int status) { + renderer_sandbox_status_ = status; +} + +int ZygoteHostImpl::GetRendererSandboxStatus() { + return renderer_sandbox_status_; +} + +pid_t ZygoteHostImpl::LaunchZygote( + base::CommandLine* cmd_line, + base::ScopedFD* control_fd, + base::FileHandleMappingVector additional_remapped_fds) { + int fds[2]; + CHECK_EQ(0, socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds)); + CHECK(base::UnixDomainSocket::EnableReceiveProcessId(fds[0])); + + base::LaunchOptions options; + options.fds_to_remap = std::move(additional_remapped_fds); + options.fds_to_remap.emplace_back(fds[1], kZygoteSocketPairFd); + + const bool is_sandboxed_zygote = + !cmd_line->HasSwitch(service_manager::switches::kNoZygoteSandbox); + + base::ScopedFD dummy_fd; + if (is_sandboxed_zygote && use_suid_sandbox_) { + std::unique_ptr<sandbox::SetuidSandboxHost> sandbox_host( + sandbox::SetuidSandboxHost::Create()); + sandbox_host->PrependWrapper(cmd_line); + sandbox_host->SetupLaunchOptions(&options, &dummy_fd); + sandbox_host->SetupLaunchEnvironment(); + } + + base::Process process = + (is_sandboxed_zygote && use_namespace_sandbox_) + ? sandbox::NamespaceSandbox::LaunchProcess(*cmd_line, options) + : base::LaunchProcess(*cmd_line, options); + CHECK(process.IsValid()) << "Failed to launch zygote process"; + + dummy_fd.reset(); + close(fds[1]); + control_fd->reset(fds[0]); + + pid_t pid = process.Pid(); + + if (is_sandboxed_zygote && (use_namespace_sandbox_ || use_suid_sandbox_)) { + // The namespace and SUID sandbox will execute the zygote in a new + // PID namespace, and the main zygote process will then fork from + // there. Watch now our elaborate dance to find and validate the + // zygote's PID. + + // First we receive a message from the zygote boot process. + base::ProcessId boot_pid; + CHECK(ReceiveFixedMessage(fds[0], kZygoteBootMessage, + sizeof(kZygoteBootMessage), &boot_pid)); + + // Within the PID namespace, the zygote boot process thinks it's PID 1, + // but its real PID can never be 1. This gives us a reliable test that + // the kernel is translating the sender's PID to our namespace. + CHECK_GT(boot_pid, 1) + << "Received invalid process ID for zygote; kernel might be too old? " + "See crbug.com/357670 or try using --" + << service_manager::switches::kNoSandbox << " to workaround."; + + // Now receive the message that the zygote's ready to go, along with the + // main zygote process's ID. + pid_t real_pid; + CHECK(ReceiveFixedMessage(fds[0], kZygoteHelloMessage, + sizeof(kZygoteHelloMessage), &real_pid)); + CHECK_GT(real_pid, 1); + + if (real_pid != pid) { + // Reap the sandbox. + base::EnsureProcessGetsReaped(std::move(process)); + } + pid = real_pid; + } + + AddZygotePid(pid); + return pid; +} + +#if !defined(OS_OPENBSD) +void ZygoteHostImpl::AdjustRendererOOMScore(base::ProcessHandle pid, + int score) { + // 1) You can't change the oom_score_adj of a non-dumpable process + // (EPERM) unless you're root. Because of this, we can't set the + // oom_adj from the browser process. + // + // 2) We can't set the oom_score_adj before entering the sandbox + // because the zygote is in the sandbox and the zygote is as + // critical as the browser process. Its oom_adj value shouldn't + // be changed. + // + // 3) A non-dumpable process can't even change its own oom_score_adj + // because it's root owned 0644. The sandboxed processes don't + // even have /proc, but one could imagine passing in a descriptor + // from outside. + // + // So, in the normal case, we use the SUID binary to change it for us. + // However, Fedora (and other SELinux systems) don't like us touching other + // process's oom_score_adj (or oom_adj) values + // (https://bugzilla.redhat.com/show_bug.cgi?id=581256). + // + // The offical way to get the SELinux mode is selinux_getenforcemode, but I + // don't want to add another library to the build as it's sure to cause + // problems with other, non-SELinux distros. + // + // So we just check for files in /selinux. This isn't foolproof, but it's not + // bad and it's easy. + + static bool selinux; + static bool selinux_valid = false; + + if (!selinux_valid) { + const base::FilePath kSelinuxPath("/selinux"); + base::FileEnumerator en(kSelinuxPath, false, base::FileEnumerator::FILES); + bool has_selinux_files = !en.Next().empty(); + + selinux = + has_selinux_files && access(kSelinuxPath.value().c_str(), X_OK) == 0; + selinux_valid = true; + } + + if (!use_suid_sandbox_for_adj_oom_score_) { + if (!base::AdjustOOMScore(pid, score)) + PLOG(ERROR) << "Failed to adjust OOM score of renderer with pid " << pid; + return; + } + + if (selinux) + return; + + // If heap profiling is running, these processes are not exiting, at least + // on ChromeOS. The easiest thing to do is not launch them when profiling. + // TODO(stevenjb): Investigate further and fix. + if (base::allocator::IsHeapProfilerRunning()) + return; + + std::vector<std::string> adj_oom_score_cmdline; + adj_oom_score_cmdline.push_back(sandbox_binary_); + adj_oom_score_cmdline.push_back(sandbox::kAdjustOOMScoreSwitch); + adj_oom_score_cmdline.push_back(base::NumberToString(pid)); + adj_oom_score_cmdline.push_back(base::NumberToString(score)); + + // sandbox_helper_process is a setuid binary. + base::LaunchOptions options; + options.allow_new_privs = true; + + base::Process sandbox_helper_process = + base::LaunchProcess(adj_oom_score_cmdline, options); + if (sandbox_helper_process.IsValid()) + base::EnsureProcessGetsReaped(std::move(sandbox_helper_process)); +} +#endif + +} // namespace content diff --git a/chromium/content/browser/zygote_host/zygote_host_impl_linux.h b/chromium/content/browser/zygote_host/zygote_host_impl_linux.h new file mode 100644 index 00000000000..21b8323eb49 --- /dev/null +++ b/chromium/content/browser/zygote_host/zygote_host_impl_linux.h @@ -0,0 +1,73 @@ +// 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_BROWSER_ZYGOTE_HOST_ZYGOTE_HOST_IMPL_LINUX_H_ +#define CONTENT_BROWSER_ZYGOTE_HOST_ZYGOTE_HOST_IMPL_LINUX_H_ + +#include <sys/types.h> + +#include <set> +#include <string> + +#include "base/command_line.h" +#include "base/files/scoped_file.h" +#include "base/process/launch.h" +#include "base/process/process_handle.h" +#include "base/synchronization/lock.h" +#include "content/common/content_export.h" +#include "content/public/browser/zygote_host/zygote_host_linux.h" + +namespace base { +template <typename Type> +struct DefaultSingletonTraits; +} // namespace base + +namespace content { + +class CONTENT_EXPORT ZygoteHostImpl : public ZygoteHost { + public: + // Returns the singleton instance. + static ZygoteHostImpl* GetInstance(); + + void Init(const base::CommandLine& cmd_line); + + // Returns whether or not this pid is the pid of a zygote. + bool IsZygotePid(pid_t pid) override; + + void SetRendererSandboxStatus(int status); + int GetRendererSandboxStatus() override; + + pid_t LaunchZygote(base::CommandLine* cmd_line, + base::ScopedFD* control_fd, + base::FileHandleMappingVector additional_remapped_fds); + + void AdjustRendererOOMScore(base::ProcessHandle process_handle, + int score) override; + bool HasZygote() { return !zygote_pids_.empty(); } + + private: + friend struct base::DefaultSingletonTraits<ZygoteHostImpl>; + + ZygoteHostImpl(); + ~ZygoteHostImpl() override; + + // Tells the ZygoteHost the PIDs of all the zygotes. + void AddZygotePid(pid_t pid); + + int renderer_sandbox_status_; + + bool use_namespace_sandbox_; + bool use_suid_sandbox_; + bool use_suid_sandbox_for_adj_oom_score_; + std::string sandbox_binary_; + + // This lock protects the |zygote_pids_| set. + base::Lock zygote_pids_lock_; + // This is a set of PIDs representing all the running zygotes. + std::set<pid_t> zygote_pids_; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_ZYGOTE_HOST_ZYGOTE_HOST_IMPL_LINUX_H_ |