910e62b5创建于 1月15日历史提交
// 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/service_host/utility_process_host.h"

#include <string_view>

#include "base/command_line.h"
#include "base/compiler_specific.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/service_host/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_mac.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:
  class Client : public UtilityProcessHost::Client {
   public:
    explicit Client(UtilityProcessHostBrowserTest* test_class)
        : test_class_(test_class) {}

    // content::UtilityProcessHost::Client implementation:
    void OnProcessCrashed(CrashType type) override {
      test_class_->crash_was_pre_ipc_ =
          (type == CrashType::kPreIpcInitialization);
    }

   private:
    raw_ptr<UtilityProcessHostBrowserTest> test_class_;
  };
  void SetUpOnMainThread() override {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    BrowserChildProcessObserver::Add(this);
  }

  UtilityProcessHost::Options DefaultOptions() {
    return UtilityProcessHost::Options()
        .WithName(u"TestProcess")
        .WithMetricsName(kTestProcessName)
        .Pass();
  }

  void AddFailedLaunchOptions(UtilityProcessHost::Options& options) {
    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.
    options.WithSandboxType(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 AddElevatedOptions(UtilityProcessHost::Options& options) {
#if BUILDFLAG(IS_WIN)
    options.WithSandboxType(
        sandbox::mojom::Sandbox::kNoSandboxAndElevatedPrivileges);
#else
    NOTREACHED();
#endif
  }

  // After `service_` is bound, `run_test` is invoked, and then the RunLoop will
  // run.
  void RunUtilityProcess(UtilityProcessHost::Options options,
                         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());

    options.WithBoundServiceInterfaceOnChildProcess(
        service_.BindNewPipeAndPassReceiver());

    UtilityProcessHost::Start(std::move(options),
                              std::make_unique<Client>(this));

    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();
    UNSAFE_TODO(
        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_));
  }

  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;
  std::optional<bool> crash_was_pre_ipc_;

 private:
  friend Client;
  // 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)
    EXPECT_EQ(EXCEPTION_BREAKPOINT, static_cast<DWORD>(info.exit_code));
#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(
      DefaultOptions(),
      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) {
  UtilityProcessHost::Options options = DefaultOptions();
  // 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)
  options.WithZygoteForTesting(nullptr);
#endif

  base::ScopedFD read_fd;
  base::ScopedFD write_fd;
  ASSERT_TRUE(base::CreatePipe(&read_fd, &write_fd));
  RunUtilityProcess(
      options.WithFileToPreload(mojom::kTestPipeKey, std::move(write_fd))
          .Pass(),
      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.
  base::ScopedFD read_fd, write_fd;
  ASSERT_TRUE(base::CreatePipe(&read_fd, &write_fd));
  RunUtilityProcess(
      DefaultOptions()
          .WithZygoteForTesting(GetUnsandboxedZygote())
          .WithFileToPreload(mojom::kTestPipeKey, std::move(write_fd))
          .Pass(),
      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.
  base::ScopedFD read_fd, write_fd;
  ASSERT_TRUE(base::CreatePipe(&read_fd, &write_fd));
  RunUtilityProcess(
      DefaultOptions()
          .WithZygoteForTesting(GetGenericZygote())
          .WithFileToPreload(mojom::kTestPipeKey, std::move(write_fd))
          .Pass(),
      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.
// ** READ THIS **
// This is a critical test for crash reporting: if this starts to flake or fail
// on any platform please raise a Pri-0 bug in the Internals>Core component to
// track the investigation. Do not change the semantics of this test or the
// `BrowserChildProcessCrashed` function above without raising a bug in
// Internals>Core.
// ** READ THIS **
#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(
      DefaultOptions(),
      base::BindOnce(&UtilityProcessHostBrowserTest::RunCrashImmediatelyTest,
                     base::Unretained(this)));
  ASSERT_TRUE(crash_was_pre_ipc_.has_value());
  EXPECT_FALSE(crash_was_pre_ipc_.value());
}
#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) {
  UtilityProcessHost::Options options = DefaultOptions();
  AddFailedLaunchOptions(options);
  // If the ping-pong test completes, the test will fail because that means the
  // process did not fail to launch.
  RunUtilityProcess(
      std::move(options),
      base::BindOnce(&UtilityProcessHostBrowserTest::RunBasicPingPongTest,
                     base::Unretained(this)));
  // Fail to launch is not considered a crash.
  ASSERT_FALSE(crash_was_pre_ipc_.has_value());
}
#endif  // !BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_MAC)

#if BUILDFLAG(IS_WIN)

IN_PROC_BROWSER_TEST_F(UtilityProcessHostBrowserTest,
                       FailToStartNetworkProcess) {
  expect_crashed_ = true;
  RunUtilityProcess(
      DefaultOptions()
          .WithSandboxType(sandbox::mojom::Sandbox::kNetwork)
          .WithExtraCommandLineSwitches(
              {switches::kUtilityImmediateCrashForTesting})
          .Pass(),
      base::BindOnce(&UtilityProcessHostBrowserTest::RunBasicPingPongTest,
                     base::Unretained(this)));
  EXPECT_TRUE(*crash_was_pre_ipc_);
}

IN_PROC_BROWSER_TEST_F(UtilityProcessHostBrowserTest, LaunchElevatedProcess) {
  RunUtilityProcess(
      DefaultOptions()
          .WithSandboxType(
              sandbox::mojom::Sandbox::kNoSandboxAndElevatedPrivileges)
          .Pass(),
      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) {
  RunUtilityProcess(
      DefaultOptions()
          .WithSandboxType(
              sandbox::mojom::Sandbox::kNoSandboxAndElevatedPrivileges)
          .Pass(),
      base::BindOnce(&UtilityProcessHostBrowserTest::RunCrashImmediatelyTest,
                     base::Unretained(this)));
  EXPECT_TRUE(crash_was_pre_ipc_.has_value());
  EXPECT_FALSE(crash_was_pre_ipc_.value());
}
#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.
  RunUtilityProcess(
      DefaultOptions()
          .WithSandboxType(sandbox::mojom::Sandbox::kNetwork)
          .Pass(),
      base::BindOnce(&UtilityProcessHostBrowserTest::RunBasicPingPongTest,
                     base::Unretained(this)));
}
#endif

}  // namespace content