chromium/content/ppapi_plugin/ppapi_thread.cc

// Copyright 2012 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/ppapi_plugin/ppapi_thread.h"

#include <stddef.h>

#include <limits>
#include <memory>

#include "base/command_line.h"
#include "base/cpu.h"
#include "base/debug/crash_logging.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/memory/discardable_memory_allocator.h"
#include "base/rand_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "components/discardable_memory/client/client_discardable_shared_memory_manager.h"
#include "content/child/browser_font_resource_trusted.h"
#include "content/child/child_process.h"
#include "content/ppapi_plugin/plugin_process_dispatcher.h"
#include "content/ppapi_plugin/ppapi_blink_platform_impl.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_plugin_info.h"
#include "content/public/common/content_switches.h"
#include "ipc/ipc_channel_handle.h"
#include "ipc/ipc_platform_file.h"
#include "ipc/ipc_sync_channel.h"
#include "ipc/ipc_sync_message_filter.h"
#include "media/media_buildflags.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "ppapi/c/dev/ppp_network_state_dev.h"
#include "ppapi/c/pp_errors.h"
#include "ppapi/c/ppp.h"
#include "ppapi/proxy/interface_list.h"
#include "ppapi/proxy/plugin_globals.h"
#include "ppapi/proxy/plugin_message_filter.h"
#include "ppapi/proxy/ppapi_messages.h"
#include "ppapi/proxy/resource_reply_thread_registrar.h"
#include "third_party/blink/public/web/blink.h"
#include "ui/base/buildflags.h"
#include "ui/base/ui_base_features.h"
#include "ui/base/ui_base_switches.h"

#if BUILDFLAG(IS_WIN)
#include "base/win/win_util.h"
#include "content/child/font_warmup_win.h"
#include "sandbox/policy/win/sandbox_warmup.h"
#include "sandbox/win/src/sandbox.h"
#endif

#if BUILDFLAG(IS_MAC)
#include "sandbox/mac/seatbelt_exec.h"
#endif

#if BUILDFLAG(IS_WIN)
extern sandbox::TargetServices* g_target_services;

// Warm up language subsystems before the sandbox is turned on.
static void WarmupWindowsLocales(const ppapi::PpapiPermissions& permissions) {
  ::GetUserDefaultLangID();
  ::GetUserDefaultLCID();
}

#endif

namespace content {

PpapiThread::PpapiThread(base::RepeatingClosure quit_closure,
                         const base::CommandLine& command_line)
    : ChildThreadImpl(std::move(quit_closure)),
      plugin_globals_(GetIOTaskRunner()),
      local_pp_module_(base::RandInt(0, std::numeric_limits<PP_Module>::max())),
      next_plugin_dispatcher_id_(1) {
  plugin_globals_.SetPluginProxyDelegate(this);

  blink_platform_impl_ = std::make_unique<PpapiBlinkPlatformImpl>();
  blink::Platform::CreateMainThreadAndInitialize(blink_platform_impl_.get());

  scoped_refptr<ppapi::proxy::PluginMessageFilter> plugin_filter(
      new ppapi::proxy::PluginMessageFilter(
          nullptr, plugin_globals_.resource_reply_thread_registrar()));
  channel()->AddFilter(plugin_filter.get());
  plugin_globals_.RegisterResourceMessageFilters(plugin_filter.get());

  // In single process, browser main loop set up the discardable memory
  // allocator.
  if (!command_line.HasSwitch(switches::kSingleProcess)) {
    mojo::PendingRemote<
        discardable_memory::mojom::DiscardableSharedMemoryManager>
        manager_remote;
    ChildThread::Get()->BindHostReceiver(
        manager_remote.InitWithNewPipeAndPassReceiver());
    discardable_shared_memory_manager_ = base::MakeRefCounted<
        discardable_memory::ClientDiscardableSharedMemoryManager>(
        std::move(manager_remote), GetIOTaskRunner());
    base::DiscardableMemoryAllocator::SetInstance(
        discardable_shared_memory_manager_.get());
  }
}

PpapiThread::~PpapiThread() {
}

void PpapiThread::Shutdown() {
  ChildThreadImpl::Shutdown();

  ppapi::proxy::PluginGlobals::Get()->ResetPluginProxyDelegate();
  if (plugin_entry_points_.shutdown_module)
    plugin_entry_points_.shutdown_module();
  blink_platform_impl_->Shutdown();
}

bool PpapiThread::Send(IPC::Message* msg) {
  // Allow access from multiple threads.
  if (main_thread_runner()->BelongsToCurrentThread())
    return ChildThreadImpl::Send(msg);

  return sync_message_filter()->Send(msg);
}

// Note that this function is called only for messages from the channel to the
// browser process. Messages from the renderer process are sent via a different
// channel that ends up at Dispatcher::OnMessageReceived.
bool PpapiThread::OnControlMessageReceived(const IPC::Message& msg) {
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(PpapiThread, msg)
    IPC_MESSAGE_HANDLER(PpapiMsg_LoadPlugin, OnLoadPlugin)
    IPC_MESSAGE_HANDLER(PpapiMsg_CreateChannel, OnCreateChannel)
    IPC_MESSAGE_HANDLER(PpapiMsg_SetNetworkState, OnSetNetworkState)
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  return handled;
}

void PpapiThread::OnChannelConnected(int32_t peer_pid) {
  ChildThreadImpl::OnChannelConnected(peer_pid);
}

base::SingleThreadTaskRunner* PpapiThread::GetIPCTaskRunner() {
  return ChildProcess::current()->io_task_runner();
}

base::WaitableEvent* PpapiThread::GetShutdownEvent() {
  return ChildProcess::current()->GetShutDownEvent();
}

IPC::PlatformFileForTransit PpapiThread::ShareHandleWithRemote(
    base::PlatformFile handle,
    base::ProcessId peer_pid,
    bool should_close_source) {
  return IPC::GetPlatformFileForTransit(handle, should_close_source);
}

base::UnsafeSharedMemoryRegion
PpapiThread::ShareUnsafeSharedMemoryRegionWithRemote(
    const base::UnsafeSharedMemoryRegion& region,
    base::ProcessId remote_pid) {
  DCHECK(remote_pid != base::kNullProcessId);
  return region.Duplicate();
}

base::ReadOnlySharedMemoryRegion
PpapiThread::ShareReadOnlySharedMemoryRegionWithRemote(
    const base::ReadOnlySharedMemoryRegion& region,
    base::ProcessId remote_pid) {
  DCHECK(remote_pid != base::kNullProcessId);
  return region.Duplicate();
}

std::set<PP_Instance>* PpapiThread::GetGloballySeenInstanceIDSet() {
  return &globally_seen_instance_ids_;
}

IPC::Sender* PpapiThread::GetBrowserSender() {
  return this;
}

std::string PpapiThread::GetUILanguage() {
  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
  return command_line->GetSwitchValueASCII(switches::kLang);
}

void PpapiThread::SetActiveURL(const std::string& url) {
  GetContentClient()->SetActiveURL(GURL(url), std::string());
}

PP_Resource PpapiThread::CreateBrowserFont(
    ppapi::proxy::Connection connection,
    PP_Instance instance,
    const PP_BrowserFont_Trusted_Description& desc,
    const ppapi::Preferences& prefs) {
  if (!BrowserFontResource_Trusted::IsPPFontDescriptionValid(desc))
    return 0;
  return (new BrowserFontResource_Trusted(
        connection, instance, desc, prefs))->GetReference();
}

uint32_t PpapiThread::Register(
    ppapi::proxy::PluginDispatcher* plugin_dispatcher) {
  if (!plugin_dispatcher ||
      plugin_dispatchers_.size() >= std::numeric_limits<uint32_t>::max()) {
    return 0;
  }

  uint32_t id = 0;
  do {
    // Although it is unlikely, make sure that we won't cause any trouble when
    // the counter overflows.
    id = next_plugin_dispatcher_id_++;
  } while (id == 0 ||
           plugin_dispatchers_.find(id) != plugin_dispatchers_.end());
  plugin_dispatchers_[id] = plugin_dispatcher;
  return id;
}

void PpapiThread::Unregister(uint32_t plugin_dispatcher_id) {
  plugin_dispatchers_.erase(plugin_dispatcher_id);
}

void PpapiThread::OnLoadPlugin(const base::FilePath& path,
                               const ppapi::PpapiPermissions& permissions) {
  // In case of crashes, the crash dump doesn't indicate which plugin
  // it came from.
  // TODO(dcheng): Would a scoped crash key be sufficient here? It's probably a
  // moot point, as this code is going to go away.
  static auto* const ppapi_path_key = base::debug::AllocateCrashKeyString(
      "ppapi_path", base::debug::CrashKeySize::Size64);
  base::debug::SetCrashKeyString(ppapi_path_key, path.MaybeAsASCII());

  SavePluginName(path);

  // This must be set before calling into the plugin so it can get the
  // interfaces it has permission for.
  ppapi::proxy::InterfaceList::SetProcessGlobalPermissions(permissions);
  permissions_ = permissions;

  // Trusted Pepper plugins may be "internal", i.e. built-in to the browser
  // binary.  If we're being asked to load such a plugin (e.g. the Chromoting
  // client) then fetch the entry points from the embedder, rather than a DLL.
  std::vector<ContentPluginInfo> plugins;
  GetContentClient()->AddPlugins(&plugins);
  for (const auto& plugin : plugins) {
    if (plugin.is_internal && plugin.path == path) {
      // An internal plugin is being loaded, so fetch the entry points.
      plugin_entry_points_ = plugin.internal_entry_points;
      break;
    }
  }

  // If the plugin isn't internal then load it from |path|.
  base::ScopedNativeLibrary library;
  if (!plugin_entry_points_.initialize_module) {
    // Load the plugin from the specified library.
    {
      TRACE_EVENT1("ppapi", "PpapiThread::LoadPlugin", "path",
                   path.MaybeAsASCII());
      library = base::ScopedNativeLibrary(path);
    }

    if (!library.is_valid()) {
      LOG(ERROR) << "Failed to load Pepper module from " << path.value()
                 << " (error: " << library.GetError()->ToString() << ")";
      return;
    }

    // Get the GetInterface function (required).
    plugin_entry_points_.get_interface =
        reinterpret_cast<PP_GetInterface_Func>(
            library.GetFunctionPointer("PPP_GetInterface"));
    if (!plugin_entry_points_.get_interface) {
      LOG(WARNING) << "No PPP_GetInterface in plugin library";
      return;
    }

    // The ShutdownModule function is optional.
    plugin_entry_points_.shutdown_module =
        reinterpret_cast<PP_ShutdownModule_Func>(
            library.GetFunctionPointer("PPP_ShutdownModule"));

    // Get the InitializeModule function.
    plugin_entry_points_.initialize_module =
        reinterpret_cast<PP_InitializeModule_Func>(
            library.GetFunctionPointer("PPP_InitializeModule"));
    if (!plugin_entry_points_.initialize_module) {
      LOG(WARNING) << "No PPP_InitializeModule in plugin library";
      return;
    }
  }

#if BUILDFLAG(IS_WIN)
  // If code subsequently tries to exit using abort(), force a crash (since
  // otherwise these would be silent terminations and fly under the radar).
  base::win::SetAbortBehaviorForCrashReporting();

  // Once we lower the token the sandbox is locked down and no new modules
  // can be loaded. TODO(cpu): consider changing to the loading style of
  // regular plugins.
  if (g_target_services) {
    sandbox::policy::WarmupRandomnessInfrastructure();

    WarmupWindowsLocales(permissions);

    g_target_services->LowerToken();
  }
#endif

  int32_t init_error = plugin_entry_points_.initialize_module(
      local_pp_module_, &ppapi::proxy::PluginDispatcher::GetBrowserInterface);
  if (init_error != PP_OK) {
    LOG(WARNING) << "InitModule failed with error " << init_error;
    return;
  }

  // Initialization succeeded, so keep the plugin DLL loaded.
  library_ = std::move(library);
}

void PpapiThread::OnCreateChannel(base::ProcessId renderer_pid,
                                  int renderer_child_id,
                                  bool incognito) {
  IPC::ChannelHandle channel_handle;

  if (!plugin_entry_points_.get_interface ||  // Plugin couldn't be loaded.
      !SetupChannel(renderer_pid, renderer_child_id, incognito,
                    &channel_handle)) {
    Send(new PpapiHostMsg_ChannelCreated(IPC::ChannelHandle()));
    return;
  }

  Send(new PpapiHostMsg_ChannelCreated(channel_handle));
}

void PpapiThread::OnSetNetworkState(bool online) {
  // Note the browser-process side shouldn't send us these messages in the
  // first unless the plugin has dev permissions, so we don't need to check
  // again here. We don't want random plugins depending on this dev interface.
  if (!plugin_entry_points_.get_interface)
    return;
  const PPP_NetworkState_Dev* ns = static_cast<const PPP_NetworkState_Dev*>(
      plugin_entry_points_.get_interface(PPP_NETWORK_STATE_DEV_INTERFACE));
  if (ns)
    ns->SetOnLine(PP_FromBool(online));
}

bool PpapiThread::SetupChannel(base::ProcessId renderer_pid,
                               int renderer_child_id,
                               bool incognito,
                               IPC::ChannelHandle* handle) {
  mojo::MessagePipe pipe;

  ppapi::proxy::ProxyChannel* dispatcher = nullptr;
  bool init_result = false;
  DCHECK_NE(base::kNullProcessId, renderer_pid);
  PluginProcessDispatcher* plugin_dispatcher = new PluginProcessDispatcher(
      plugin_entry_points_.get_interface, permissions_, incognito);
  init_result = plugin_dispatcher->InitPluginWithChannel(
      this, renderer_pid, pipe.handle0.release(), false);
  dispatcher = plugin_dispatcher;

  if (!init_result) {
    delete dispatcher;
    return false;
  }
  *handle = pipe.handle1.release();

  // From here, the dispatcher will manage its own lifetime according to the
  // lifetime of the attached channel.
  return true;
}

void PpapiThread::SavePluginName(const base::FilePath& path) {
  ppapi::proxy::PluginGlobals::Get()->set_plugin_name(
      path.BaseName().AsUTF8Unsafe());
}

}  // namespace content