chromium/content/browser/plugin_service_impl_browsertest.cc

// Copyright 2018 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/plugin_service_impl.h"

#include <memory>
#include <optional>
#include <string>
#include <utility>

#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/webplugininfo.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/content_browser_test.h"
#include "ppapi/buildflags/buildflags.h"
#include "testing/gtest/include/gtest/gtest.h"

#if BUILDFLAG(ENABLE_PPAPI)
#include "content/browser/ppapi_plugin_process_host.h"
#endif  // BUILDFLAG(ENABLE_PPAPI)

namespace content {

namespace {

#if BUILDFLAG(ENABLE_PPAPI)
class TestPluginClient : public PpapiPluginProcessHost::PluginClient {
 public:
  void GetPpapiChannelInfo(base::ProcessHandle* renderer_handle,
                           int* renderer_id) override {}
  void OnPpapiChannelOpened(const IPC::ChannelHandle& channel_handle,
                            base::ProcessId plugin_pid,
                            int plugin_child_id) override {
    plugin_pid_ = plugin_pid;
    run_loop_->Quit();
  }
  bool Incognito() override { return false; }

  base::ProcessId plugin_pid() const { return plugin_pid_; }
  void SetRunLoop(base::RunLoop* run_loop) { run_loop_ = run_loop; }
  void WaitForQuit() { run_loop_->Run(); }

 private:
  base::ProcessId plugin_pid_ = 0;
  raw_ptr<base::RunLoop> run_loop_ = nullptr;
};
#endif  // BUILDFLAG(ENABLE_PPAPI)

}  // anonymous namespace

class PluginServiceImplBrowserTest : public ContentBrowserTest {};

IN_PROC_BROWSER_TEST_F(PluginServiceImplBrowserTest, GetPluginInfoByPath) {}

#if BUILDFLAG(ENABLE_PPAPI)
IN_PROC_BROWSER_TEST_F(PluginServiceImplBrowserTest, OriginLock) {
  RegisterFakePlugin();

  url::Origin origin1 = url::Origin::Create(GURL("http://google.com/"));
  url::Origin origin2 = url::Origin::Create(GURL("http://youtube.com/"));

  TestPluginClient client1;
  OpenChannelToFakePlugin(origin1, &client1);
  EXPECT_NE(base::kNullProcessId, client1.plugin_pid());

  TestPluginClient client2a;
  OpenChannelToFakePlugin(origin2, &client2a);
  EXPECT_NE(base::kNullProcessId, client2a.plugin_pid());

  TestPluginClient client2b;
  OpenChannelToFakePlugin(origin2, &client2b);
  EXPECT_NE(base::kNullProcessId, client2b.plugin_pid());

  // Actual test: how plugins got lumped into two pids.
  EXPECT_NE(client1.plugin_pid(), client2a.plugin_pid());
  EXPECT_NE(client1.plugin_pid(), client2b.plugin_pid());
  EXPECT_EQ(client2a.plugin_pid(), client2b.plugin_pid());

  // Empty origins all go to same pid.
  TestPluginClient client3a;
  OpenChannelToFakePlugin(std::nullopt, &client3a);
  EXPECT_NE(base::kNullProcessId, client3a.plugin_pid());

  TestPluginClient client3b;
  OpenChannelToFakePlugin(std::nullopt, &client3b);
  EXPECT_NE(base::kNullProcessId, client3b.plugin_pid());

  // Actual test: how empty origins got lumped into pids.
  EXPECT_NE(client1.plugin_pid(), client3a.plugin_pid());
  EXPECT_NE(client1.plugin_pid(), client3b.plugin_pid());
  EXPECT_NE(client2a.plugin_pid(), client3a.plugin_pid());
  EXPECT_NE(client2a.plugin_pid(), client3b.plugin_pid());
  EXPECT_EQ(client3a.plugin_pid(), client3b.plugin_pid());
}

IN_PROC_BROWSER_TEST_F(PluginServiceImplBrowserTest, NoForkBombs) {
  RegisterFakePlugin();

  PluginServiceImpl* service = PluginServiceImpl::GetInstance();
  service->SetMaxPpapiProcessesPerProfileForTesting(4);

  static constexpr char kFakeURLTemplate[] = "https://foo.fake%d.com/";
  TestPluginClient client;
  for (int i = 0; i < 4; ++i) {
    std::string url = base::StringPrintf(kFakeURLTemplate, i);
    url::Origin origin = url::Origin::Create(GURL(url));
    OpenChannelToFakePlugin(origin, &client);
    EXPECT_NE(base::kNullProcessId, client.plugin_pid());
  }

  // After a while we stop handing out processes per-origin.
  for (int i = 4; i < 8; ++i) {
    std::string url = base::StringPrintf(kFakeURLTemplate, i);
    url::Origin origin = url::Origin::Create(GURL(url));
    OpenChannelToFakePlugin(origin, &client);
    EXPECT_EQ(base::kNullProcessId, client.plugin_pid());
  }

  // But there's always room for the empty origin case.
  OpenChannelToFakePlugin(std::nullopt, &client);
  EXPECT_NE(base::kNullProcessId, client.plugin_pid());

  // And re-using existing processes is always possible.
  for (int i = 0; i < 4; ++i) {
    std::string url = base::StringPrintf(kFakeURLTemplate, i);
    url::Origin origin = url::Origin::Create(GURL(url));
    OpenChannelToFakePlugin(origin, &client);
    EXPECT_NE(base::kNullProcessId, client.plugin_pid());
  }
}
#endif  // BUILDFLAG(ENABLE_PPAPI)

}  // namespace content