
This was generated by replacing " NOTREACHED()" with " NOTREACHED_IN_MIGRATION()" and running git cl format. This prepares for making NOTREACHED() [[noreturn]] alongside NotReachedIsFatal migration of existing inventory. Bug: 40580068 Change-Id: I3b48b89911ac5e9ffcb211622992f917f8f9e8d9 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5539619 Auto-Submit: Peter Boström <pbos@chromium.org> Commit-Queue: Lei Zhang <thestig@chromium.org> Owners-Override: Lei Zhang <thestig@chromium.org> Reviewed-by: Lei Zhang <thestig@chromium.org> Commit-Queue: Peter Boström <pbos@chromium.org> Cr-Commit-Position: refs/heads/main@{#1301096}
396 lines
14 KiB
C++
396 lines
14 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 "build/build_config.h"
|
|
#include "content/browser/child_process_launcher.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
|
|
|
|
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_IN_MIGRATION();
|
|
#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 returns
|
|
// a different exit_code.
|
|
#if defined(OFFICIAL_BUILD) && !DCHECK_IS_ON()
|
|
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) || !DCHECK_IS_ON()
|
|
|
|
#elif BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
|
|
EXPECT_TRUE(WIFSIGNALED(info.exit_code));
|
|
EXPECT_EQ(SIGTRAP, WTERMSIG(info.exit_code));
|
|
#endif
|
|
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)
|
|
|
|
} // namespace content
|