
NOTREACHED() and NOTREACHED_IN_MIGRATION() are both CHECK-fatal now. The former is [[noreturn]] so this CL also performs dead-code removal after the NOTREACHED(). This CL does not attempt to do additional rewrites of any surrounding code, like: if (!foo) { NOTREACHED(); } to CHECK(foo); Those transforms take a non-trivial amount of time (and there are thousands of instances). Cleanup can be left as an exercise for the reader. This does clean up kCrashOnDanglingBrowserContext as both paths of the kill switch are currently fatal. This has been rolled out for a long time. Bug: 40580068, 40062641 Change-Id: Ib88e710d003e2e48df3fc502ca54d2341d157a0e Cq-Include-Trybots: luci.chromium.try:linux-dcheck-off-rel Low-Coverage-Reason: OTHER Should-be-unreachable code Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5974816 Reviewed-by: Łukasz Anforowicz <lukasza@chromium.org> Commit-Queue: Łukasz Anforowicz <lukasza@chromium.org> Reviewed-by: Rakina Zata Amni <rakina@chromium.org> Auto-Submit: Peter Boström <pbos@chromium.org> Reviewed-by: Sam McNally <sammc@chromium.org> Cr-Commit-Position: refs/heads/main@{#1376522}
435 lines
16 KiB
C++
435 lines
16 KiB
C++
// Copyright 2016 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "content/browser/utility_process_host.h"
|
|
|
|
#include <string_view>
|
|
|
|
#include "base/command_line.h"
|
|
#include "base/files/file_path.h"
|
|
#include "base/files/file_util.h"
|
|
#include "base/functional/bind.h"
|
|
#include "base/functional/callback.h"
|
|
#include "base/memory/read_only_shared_memory_region.h"
|
|
#include "base/memory/unsafe_shared_memory_region.h"
|
|
#include "base/memory/writable_shared_memory_region.h"
|
|
#include "base/notreached.h"
|
|
#include "base/run_loop.h"
|
|
#include "base/strings/utf_string_conversions.h"
|
|
#include "base/test/scoped_feature_list.h"
|
|
#include "build/build_config.h"
|
|
#include "content/browser/child_process_launcher.h"
|
|
#include "content/browser/utility_sandbox_delegate.h"
|
|
#include "content/common/features.h"
|
|
#include "content/public/browser/browser_child_process_observer.h"
|
|
#include "content/public/browser/browser_task_traits.h"
|
|
#include "content/public/browser/browser_thread.h"
|
|
#include "content/public/browser/child_process_data.h"
|
|
#include "content/public/browser/child_process_termination_info.h"
|
|
#include "content/public/common/content_client.h"
|
|
#include "content/public/common/content_features.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/content_browser_test.h"
|
|
#include "content/public/test/content_browser_test_utils.h"
|
|
#include "content/public/test/test_service.mojom.h"
|
|
#include "mojo/core/embedder/embedder.h"
|
|
#include "mojo/public/cpp/bindings/remote.h"
|
|
|
|
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
|
|
#include <sys/wait.h>
|
|
#endif
|
|
|
|
#if BUILDFLAG(IS_WIN)
|
|
#include <windows.h>
|
|
|
|
#include "sandbox/policy/mojom/sandbox.mojom.h"
|
|
#include "sandbox/win/src/sandbox_types.h"
|
|
#endif // BUILDFLAG(IS_WIN)
|
|
|
|
#if BUILDFLAG(USE_ZYGOTE)
|
|
#include "content/common/zygote/zygote_handle_impl_linux.h"
|
|
#include "content/public/common/zygote/zygote_handle.h"
|
|
#endif
|
|
|
|
#if BUILDFLAG(IS_MAC)
|
|
#include "base/apple/mach_port_rendezvous.h"
|
|
#endif
|
|
|
|
namespace content {
|
|
|
|
namespace {
|
|
|
|
const char kTestProcessName[] = "test_process";
|
|
|
|
constexpr std::string_view kTestMessage{"hello from shared memory"};
|
|
|
|
} // namespace
|
|
|
|
class UtilityProcessHostBrowserTest : public BrowserChildProcessObserver,
|
|
public ContentBrowserTest {
|
|
public:
|
|
void SetUpOnMainThread() override {
|
|
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
|
BrowserChildProcessObserver::Add(this);
|
|
|
|
host_ = new UtilityProcessHost(); // Owned by a global list.
|
|
host_->SetName(u"TestProcess");
|
|
host_->SetMetricsName(kTestProcessName);
|
|
}
|
|
|
|
void TearDownOnMainThread() override {
|
|
// `host_` is about to be deleted during BrowserMainRunnerImpl::Shutdown().
|
|
host_ = nullptr;
|
|
}
|
|
|
|
void SetExpectFailLaunch() {
|
|
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
|
expect_failed_launch_ = true;
|
|
|
|
#if BUILDFLAG(IS_WIN)
|
|
// The Windows sandbox does not like the child process being a different
|
|
// process, so launch unsandboxed for the purpose of this test.
|
|
host_->SetSandboxType(sandbox::mojom::Sandbox::kNoSandbox);
|
|
#endif
|
|
// Simulate a catastrophic launch failure for all child processes by
|
|
// making the path to the process non-existent.
|
|
base::CommandLine::ForCurrentProcess()->AppendSwitchPath(
|
|
switches::kBrowserSubprocessPath,
|
|
base::FilePath(FILE_PATH_LITERAL("non_existent_path")));
|
|
}
|
|
|
|
void SetElevated() {
|
|
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
|
#if BUILDFLAG(IS_WIN)
|
|
host_->SetSandboxType(
|
|
sandbox::mojom::Sandbox::kNoSandboxAndElevatedPrivileges);
|
|
#else
|
|
NOTREACHED();
|
|
#endif
|
|
}
|
|
|
|
// After `service_` is bound, `run_test` is invoked, and then the RunLoop will
|
|
// run.
|
|
void RunUtilityProcess(base::OnceClosure run_test) {
|
|
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
|
base::RunLoop run_loop;
|
|
done_closure_ =
|
|
base::BindOnce(&UtilityProcessHostBrowserTest::DoneRunning,
|
|
base::Unretained(this), run_loop.QuitClosure());
|
|
|
|
EXPECT_TRUE(host_->Start());
|
|
|
|
host_->GetChildProcess()->BindServiceInterface(
|
|
service_.BindNewPipeAndPassReceiver());
|
|
|
|
std::move(run_test).Run();
|
|
run_loop.Run();
|
|
}
|
|
|
|
void RunCrashImmediatelyTest() {
|
|
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
|
expect_crashed_ = true;
|
|
service_->DoCrashImmediately(base::BindOnce(
|
|
&UtilityProcessHostBrowserTest::OnSomething, base::Unretained(this)));
|
|
}
|
|
|
|
void RunSharedMemoryHandleTest() {
|
|
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
|
// Verify that shared memory handles can be transferred to and from the
|
|
// elevated process. This is only supported with MojoIpcz enabled.
|
|
DCHECK(mojo::core::IsMojoIpczEnabled());
|
|
auto region = base::WritableSharedMemoryRegion::Create(kTestMessage.size());
|
|
auto mapping = region.Map();
|
|
memcpy(mapping.memory(), kTestMessage.data(), kTestMessage.size());
|
|
service_->CloneSharedMemoryContents(
|
|
base::WritableSharedMemoryRegion::ConvertToReadOnly(std::move(region)),
|
|
base::BindOnce(&UtilityProcessHostBrowserTest::OnMemoryCloneReceived,
|
|
base::Unretained(this)));
|
|
}
|
|
|
|
void RunBasicPingPongTest() {
|
|
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
|
service_->DoSomething(base::BindOnce(
|
|
&UtilityProcessHostBrowserTest::OnSomething, base::Unretained(this)));
|
|
}
|
|
|
|
#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_MAC)
|
|
void RunFileDescriptorStoreTest(base::ScopedFD read_fd) {
|
|
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
|
service_->WriteToPreloadedPipe();
|
|
char buf[4];
|
|
ASSERT_TRUE(base::ReadFromFD(read_fd.get(), buf));
|
|
std::string_view msg(buf, sizeof(buf));
|
|
ASSERT_EQ(msg, "test");
|
|
OnSomething();
|
|
}
|
|
#endif // BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_MAC)
|
|
|
|
protected:
|
|
void DoneRunning(base::OnceClosure quit_closure) {
|
|
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
|
BrowserChildProcessObserver::Remove(this);
|
|
base::CommandLine::ForCurrentProcess()->RemoveSwitch(
|
|
switches::kBrowserSubprocessPath);
|
|
EXPECT_EQ(expect_crashed_, has_crashed_);
|
|
EXPECT_EQ(expect_failed_launch_, has_failed_launch_);
|
|
std::move(quit_closure).Run();
|
|
}
|
|
|
|
void ResetService() {
|
|
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
|
service_.reset();
|
|
}
|
|
|
|
void OnSomething() {
|
|
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
|
// If service crashes then this never gets called.
|
|
ASSERT_EQ(false, expect_crashed_);
|
|
ResetService();
|
|
GetUIThreadTaskRunner({})->PostTask(FROM_HERE, std::move(done_closure_));
|
|
}
|
|
|
|
void OnMemoryCloneReceived(base::UnsafeSharedMemoryRegion region) {
|
|
auto mapping = region.Map();
|
|
ASSERT_EQ(kTestMessage.size(), mapping.size());
|
|
EXPECT_EQ(kTestMessage,
|
|
std::string_view(static_cast<const char*>(mapping.memory()),
|
|
kTestMessage.size()));
|
|
ResetService();
|
|
GetUIThreadTaskRunner({})->PostTask(FROM_HERE, std::move(done_closure_));
|
|
}
|
|
|
|
raw_ptr<UtilityProcessHost, AcrossTasksDanglingUntriaged> host_;
|
|
mojo::Remote<mojom::TestService> service_;
|
|
base::OnceClosure done_closure_;
|
|
bool expect_crashed_ = false;
|
|
bool expect_failed_launch_ = false;
|
|
|
|
// Access on UI thread.
|
|
bool has_crashed_ = false;
|
|
bool has_failed_launch_ = false;
|
|
|
|
private:
|
|
// content::BrowserChildProcessObserver implementation:
|
|
void BrowserChildProcessKilled(
|
|
const ChildProcessData& data,
|
|
const ChildProcessTerminationInfo& info) override {
|
|
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
|
#if BUILDFLAG(IS_ANDROID)
|
|
// Android does not send crash notifications but sends kills. See comment in
|
|
// browser_child_process_observer.h.
|
|
BrowserChildProcessCrashed(data, info);
|
|
#else
|
|
FAIL() << "Killed notifications should only happen on Android.";
|
|
#endif
|
|
}
|
|
|
|
void BrowserChildProcessCrashed(
|
|
const ChildProcessData& data,
|
|
const ChildProcessTerminationInfo& info) override {
|
|
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
|
|
|
#if BUILDFLAG(IS_WIN)
|
|
// See crbug.com/40861868#comment17. There are two implementations of the
|
|
// DoCrashImmediately mojo interface, which causes official build to return
|
|
// a different exit_code.
|
|
#if defined(OFFICIAL_BUILD)
|
|
EXPECT_EQ(STATUS_STACK_BUFFER_OVERRUN, static_cast<DWORD>(info.exit_code));
|
|
#else
|
|
EXPECT_EQ(EXCEPTION_BREAKPOINT, static_cast<DWORD>(info.exit_code));
|
|
#endif // defined(OFFICIAL_BUILD)
|
|
#elif BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
|
|
EXPECT_TRUE(WIFSIGNALED(info.exit_code));
|
|
#if defined(OFFICIAL_BUILD)
|
|
EXPECT_EQ(SIGTRAP, WTERMSIG(info.exit_code));
|
|
#else // defined(OFFICIAL_BUILD)
|
|
EXPECT_EQ(SIGABRT, WTERMSIG(info.exit_code));
|
|
#endif // defined(OFFICIAL_BUILD)
|
|
#endif // BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
|
|
EXPECT_EQ(kTestProcessName, data.metrics_name);
|
|
EXPECT_EQ(false, has_crashed_);
|
|
has_crashed_ = true;
|
|
ResetService();
|
|
std::move(done_closure_).Run();
|
|
}
|
|
|
|
void BrowserChildProcessLaunchFailed(
|
|
const ChildProcessData& data,
|
|
const ChildProcessTerminationInfo& info) override {
|
|
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
|
EXPECT_EQ(info.status, base::TERMINATION_STATUS_LAUNCH_FAILED);
|
|
#if BUILDFLAG(IS_WIN)
|
|
// On Windows, the sandbox code handles all non-elevated process launches.
|
|
EXPECT_EQ(sandbox::SBOX_ERROR_CANNOT_LAUNCH_UNSANDBOXED_PROCESS,
|
|
info.exit_code);
|
|
// File not found because subprocess called 'non_existent_path.exe' does not
|
|
// exist.
|
|
EXPECT_EQ(DWORD{ERROR_FILE_NOT_FOUND}, info.last_error);
|
|
#else
|
|
EXPECT_EQ(LAUNCH_RESULT_FAILURE, info.exit_code);
|
|
#endif
|
|
EXPECT_EQ(kTestProcessName, data.metrics_name);
|
|
has_failed_launch_ = true;
|
|
ResetService();
|
|
std::move(done_closure_).Run();
|
|
}
|
|
};
|
|
|
|
IN_PROC_BROWSER_TEST_F(UtilityProcessHostBrowserTest, LaunchProcess) {
|
|
RunUtilityProcess(
|
|
base::BindOnce(&UtilityProcessHostBrowserTest::RunBasicPingPongTest,
|
|
base::Unretained(this)));
|
|
}
|
|
|
|
#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_MAC)
|
|
|
|
// TODO(crbug.com/40253015): Re-enable this test on Android when
|
|
// `files_to_preload` is actually fixed there.
|
|
// TODO(crbug.com/41484083): Re-enable this test on ChromeOS.
|
|
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_CHROMEOS)
|
|
#define MAYBE_FileDescriptorStore DISABLED_FileDescriptorStore
|
|
#else
|
|
#define MAYBE_FileDescriptorStore FileDescriptorStore
|
|
#endif
|
|
IN_PROC_BROWSER_TEST_F(UtilityProcessHostBrowserTest,
|
|
MAYBE_FileDescriptorStore) {
|
|
// Tests whether base::FileDescriptorStore works in content by passing it a
|
|
// file descriptor for a pipe on launch. This test ensures the process is
|
|
// launched without a zygote.
|
|
#if BUILDFLAG(USE_ZYGOTE)
|
|
host_->SetZygoteForTesting(nullptr);
|
|
#endif
|
|
|
|
base::ScopedFD read_fd;
|
|
base::ScopedFD write_fd;
|
|
ASSERT_TRUE(base::CreatePipe(&read_fd, &write_fd));
|
|
host_->AddFileToPreload(mojom::kTestPipeKey, std::move(write_fd));
|
|
RunUtilityProcess(
|
|
base::BindOnce(&UtilityProcessHostBrowserTest::RunFileDescriptorStoreTest,
|
|
base::Unretained(this), std::move(read_fd)));
|
|
}
|
|
#endif // BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_MAC)
|
|
|
|
#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_MAC) && BUILDFLAG(USE_ZYGOTE)
|
|
IN_PROC_BROWSER_TEST_F(UtilityProcessHostBrowserTest,
|
|
FileDescriptorStoreWithUnsandboxedZygote) {
|
|
// Tests whether base::FileDescriptorStore works in content by passing it a
|
|
// file descriptor for a pipe on launch. This test ensures the process is
|
|
// launched with the unsandboxed zygote.
|
|
host_->SetZygoteForTesting(GetUnsandboxedZygote());
|
|
|
|
base::ScopedFD read_fd, write_fd;
|
|
ASSERT_TRUE(base::CreatePipe(&read_fd, &write_fd));
|
|
host_->AddFileToPreload(mojom::kTestPipeKey, std::move(write_fd));
|
|
RunUtilityProcess(
|
|
base::BindOnce(&UtilityProcessHostBrowserTest::RunFileDescriptorStoreTest,
|
|
base::Unretained(this), std::move(read_fd)));
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(UtilityProcessHostBrowserTest,
|
|
FileDescriptorStoreWithGenericZygote) {
|
|
// Tests whether base::FileDescriptorStore works in content by passing it a
|
|
// file descriptor for a pipe on launch. This test ensures the process is
|
|
// launched with the generic zygote.
|
|
host_->SetZygoteForTesting(GetGenericZygote());
|
|
|
|
base::ScopedFD read_fd, write_fd;
|
|
ASSERT_TRUE(base::CreatePipe(&read_fd, &write_fd));
|
|
host_->AddFileToPreload(mojom::kTestPipeKey, std::move(write_fd));
|
|
RunUtilityProcess(
|
|
base::BindOnce(&UtilityProcessHostBrowserTest::RunFileDescriptorStoreTest,
|
|
base::Unretained(this), std::move(read_fd)));
|
|
}
|
|
#endif // BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_MAC) &&
|
|
// BUILDFLAG(USE_ZYGOTE)
|
|
|
|
// Disabled because it crashes on android-arm64-tests:
|
|
// https://crbug.com/1358585.
|
|
// TODO(crbug.com/41484083): Re-enable this test on ChromeOS.
|
|
#if !(BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_ARM64))
|
|
#if (BUILDFLAG(IS_LINUX) && defined(ARCH_CPU_X86_64)) || BUILDFLAG(IS_CHROMEOS)
|
|
#define MAYBE_LaunchProcessAndCrash DISABLED_LaunchProcessAndCrash
|
|
#else
|
|
#define MAYBE_LaunchProcessAndCrash LaunchProcessAndCrash
|
|
#endif
|
|
IN_PROC_BROWSER_TEST_F(UtilityProcessHostBrowserTest,
|
|
MAYBE_LaunchProcessAndCrash) {
|
|
RunUtilityProcess(
|
|
base::BindOnce(&UtilityProcessHostBrowserTest::RunCrashImmediatelyTest,
|
|
base::Unretained(this)));
|
|
}
|
|
#endif
|
|
|
|
// This test won't work as-is on POSIX platforms, where fork()+exec() is used to
|
|
// launch child processes, failure does not happen until exec(), therefore the
|
|
// test will see a valid child process followed by a
|
|
// TERMINATION_STATUS_ABNORMAL_TERMINATION of the forked process. However,
|
|
// posix_spawn() is used on macOS.
|
|
// See also ServiceProcessLauncherTest.FailToLaunchProcess.
|
|
#if !BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_MAC)
|
|
IN_PROC_BROWSER_TEST_F(UtilityProcessHostBrowserTest, FailToLaunchProcess) {
|
|
SetExpectFailLaunch();
|
|
// If the ping-pong test completes, the test will fail because that means the
|
|
// process did not fail to launch.
|
|
RunUtilityProcess(
|
|
base::BindOnce(&UtilityProcessHostBrowserTest::RunBasicPingPongTest,
|
|
base::Unretained(this)));
|
|
}
|
|
#endif // !BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_MAC)
|
|
|
|
#if BUILDFLAG(IS_WIN)
|
|
IN_PROC_BROWSER_TEST_F(UtilityProcessHostBrowserTest, LaunchElevatedProcess) {
|
|
SetElevated();
|
|
RunUtilityProcess(
|
|
mojo::core::IsMojoIpczEnabled()
|
|
? base::BindOnce(
|
|
&UtilityProcessHostBrowserTest::RunSharedMemoryHandleTest,
|
|
base::Unretained(this))
|
|
: base::BindOnce(&UtilityProcessHostBrowserTest::RunBasicPingPongTest,
|
|
base::Unretained(this)));
|
|
}
|
|
|
|
// Disabled because currently this causes a WER dialog to appear.
|
|
IN_PROC_BROWSER_TEST_F(UtilityProcessHostBrowserTest,
|
|
DISABLED_LaunchElevatedProcessAndCrash) {
|
|
SetElevated();
|
|
RunUtilityProcess(
|
|
base::BindOnce(&UtilityProcessHostBrowserTest::RunCrashImmediatelyTest,
|
|
base::Unretained(this)));
|
|
}
|
|
#endif // BUILDFLAG(IS_WIN)
|
|
|
|
#if BUILDFLAG(IS_MAC)
|
|
// Ensure that the network service launches and can establish Mojo IPC
|
|
// connections when peer requirements are being enforced. Since browser tests
|
|
// are run in an unsigned process this will exercise most of the mechanism, but
|
|
// code signature validation will be skipped.
|
|
class NetworkServiceProcessIdentityTest : public UtilityProcessHostBrowserTest {
|
|
public:
|
|
void SetUp() override {
|
|
scoped_feature_list_.InitWithFeatures(
|
|
{base::kMachPortRendezvousEnforcePeerRequirements,
|
|
features::kValidateNetworkServiceProcessIdentity},
|
|
{});
|
|
UtilityProcessHostBrowserTest::SetUp();
|
|
}
|
|
|
|
private:
|
|
base::test::ScopedFeatureList scoped_feature_list_;
|
|
};
|
|
|
|
IN_PROC_BROWSER_TEST_F(NetworkServiceProcessIdentityTest, LaunchService) {
|
|
// The process requirement is applied to the network service based on its
|
|
// sandbox type.
|
|
host_->SetSandboxType(sandbox::mojom::Sandbox::kNetwork);
|
|
RunUtilityProcess(
|
|
base::BindOnce(&UtilityProcessHostBrowserTest::RunBasicPingPongTest,
|
|
base::Unretained(this)));
|
|
}
|
|
#endif
|
|
|
|
} // namespace content
|