0
Files
src/content/browser/utility_process_host_browsertest.cc
Peter Boström fc7ddc185b Migrate to NOTREACHED() in content/
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}
2024-10-31 19:37:21 +00:00

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