blob: 79347d2e93010efcc2c7204bee78c16ca622f71d [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// 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 "base/command_line.h"
#include "base/cpu.h"
#include "base/debug/alias.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/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/rand_util.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/platform_thread.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/broker_process_dispatcher.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_switches.h"
#include "content/public/common/pepper_plugin_info.h"
#include "content/public/common/sandbox_init.h"
#include "content/public/common/service_names.mojom.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 defined(OS_WIN)
#include "base/win/win_util.h"
#include "content/child/font_warmup_win.h"
#include "sandbox/win/src/sandbox.h"
#endif
#if defined(OS_MACOSX)
#include "sandbox/mac/seatbelt_exec.h"
#endif
#if defined(OS_WIN)
extern sandbox::TargetServices* g_target_services;
// Used by EnumSystemLocales for warming up.
static BOOL CALLBACK EnumLocalesProcEx(
LPWSTR lpLocaleString,
DWORD dwFlags,
LPARAM lParam) {
return TRUE;
}
// Warm up language subsystems before the sandbox is turned on.
static void WarmupWindowsLocales(const ppapi::PpapiPermissions& permissions) {
::GetUserDefaultLangID();
::GetUserDefaultLCID();
if (permissions.HasPermission(ppapi::PERMISSION_FLASH))
::EnumSystemLocalesEx(EnumLocalesProcEx, LOCALE_WINDOWS, 0, 0);
}
#endif
namespace content {
typedef int32_t (*InitializeBrokerFunc)
(PP_ConnectInstance_Func* connect_instance_func);
PpapiThread::PpapiThread(base::RepeatingClosure quit_closure,
const base::CommandLine& command_line,
bool is_broker)
: ChildThreadImpl(std::move(quit_closure)),
is_broker_(is_broker),
plugin_globals_(GetIOTaskRunner()),
connect_instance_func_(nullptr),
local_pp_module_(base::RandInt(0, std::numeric_limits<PP_Module>::max())),
next_plugin_dispatcher_id_(1) {
plugin_globals_.SetPluginProxyDelegate(this);
plugin_globals_.set_command_line(
command_line.GetSwitchValueASCII(switches::kPpapiFlashArgs));
blink_platform_impl_.reset(new PpapiBlinkPlatformImpl);
blink::Platform::CreateMainThreadAndInitialize(blink_platform_impl_.get());
if (!is_broker_) {
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_ = std::make_unique<
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_HANDLER(PpapiMsg_Crash, OnCrash)
IPC_MESSAGE_HANDLER(PpapiMsg_Hang, OnHang)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
void PpapiThread::OnChannelConnected(int32_t peer_pid) {
ChildThreadImpl::OnChannelConnected(peer_pid);
#if defined(OS_WIN)
if (is_broker_)
peer_handle_.Set(::OpenProcess(PROCESS_DUP_HANDLE, FALSE, peer_pid));
#endif
}
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::PreCacheFontForFlash(const void* logfontw) {
#if defined(OS_WIN)
ChildThreadImpl::PreCacheFont(*static_cast<const LOGFONTW*>(logfontw));
#endif
}
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.
static auto* 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<PepperPluginInfo> plugins;
GetContentClient()->AddPepperPlugins(&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.
base::TimeDelta load_time;
{
TRACE_EVENT1("ppapi", "PpapiThread::LoadPlugin", "path",
path.MaybeAsASCII());
base::TimeTicks start = base::TimeTicks::Now();
library = base::ScopedNativeLibrary(path);
load_time = base::TimeTicks::Now() - start;
}
if (!library.is_valid()) {
LOG(ERROR) << "Failed to load Pepper module from " << path.value()
<< " (error: " << library.GetError()->ToString() << ")";
if (!base::PathExists(path)) {
ReportLoadResult(path, FILE_MISSING);
return;
}
ReportLoadResult(path, LOAD_FAILED);
// Report detailed reason for load failure.
ReportLoadErrorCode(path, library.GetError());
return;
}
// Only report load time for success loads.
ReportLoadTime(path, load_time);
// 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";
ReportLoadResult(path, ENTRY_POINT_MISSING);
return;
}
// The ShutdownModule/ShutdownBroker function is optional.
plugin_entry_points_.shutdown_module =
is_broker_ ?
reinterpret_cast<PP_ShutdownModule_Func>(
library.GetFunctionPointer("PPP_ShutdownBroker")) :
reinterpret_cast<PP_ShutdownModule_Func>(
library.GetFunctionPointer("PPP_ShutdownModule"));
if (!is_broker_) {
// Get the InitializeModule function (required for non-broker code).
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";
ReportLoadResult(path, ENTRY_POINT_MISSING);
return;
}
}
}
#if defined(OS_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) {
if (permissions.HasPermission(ppapi::PERMISSION_FLASH)) {
// Let Flash load DXVA before lockdown.
LoadLibraryA("dxva2.dll");
base::CPU cpu;
if (cpu.vendor_name() == "AuthenticAMD") {
// The AMD crypto acceleration is only AMD Bulldozer and above.
#if defined(_WIN64)
LoadLibraryA("amdhcp64.dll");
#else
LoadLibraryA("amdhcp32.dll");
#endif
}
}
// Cause advapi32 to load before the sandbox is turned on.
unsigned int dummy_rand;
rand_s(&dummy_rand);
WarmupWindowsLocales(permissions);
if (!base::win::IsUser32AndGdi32Available() &&
permissions.HasPermission(ppapi::PERMISSION_FLASH)) {
PatchGdiFontEnumeration(path);
}
g_target_services->LowerToken();
}
#endif
if (is_broker_) {
// Get the InitializeBroker function (required).
InitializeBrokerFunc init_broker =
reinterpret_cast<InitializeBrokerFunc>(
library.GetFunctionPointer("PPP_InitializeBroker"));
if (!init_broker) {
LOG(WARNING) << "No PPP_InitializeBroker in plugin library";
ReportLoadResult(path, ENTRY_POINT_MISSING);
return;
}
int32_t init_error = init_broker(&connect_instance_func_);
if (init_error != PP_OK) {
LOG(WARNING) << "InitBroker failed with error " << init_error;
ReportLoadResult(path, INIT_FAILED);
return;
}
if (!connect_instance_func_) {
LOG(WARNING) << "InitBroker did not provide PP_ConnectInstance_Func";
ReportLoadResult(path, INIT_FAILED);
return;
}
} else {
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;
ReportLoadResult(path, INIT_FAILED);
return;
}
}
// Initialization succeeded, so keep the plugin DLL loaded.
library_ = std::move(library);
ReportLoadResult(path, LOAD_SUCCESS);
}
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));
}
void PpapiThread::OnCrash() {
// Intentionally crash upon the request of the browser.
//
// Linker's ICF feature may merge this function with other functions with the
// same definition and it may confuse the crash report processing system.
static int static_variable_to_make_this_function_unique = 0;
base::debug::Alias(&static_variable_to_make_this_function_unique);
volatile int* null_pointer = nullptr;
*null_pointer = 0;
}
void PpapiThread::OnHang() {
// Intentionally hang upon the request of the browser.
for (;;)
base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1));
}
bool PpapiThread::SetupChannel(base::ProcessId renderer_pid,
int renderer_child_id,
bool incognito,
IPC::ChannelHandle* handle) {
DCHECK(is_broker_ == (connect_instance_func_ != nullptr));
mojo::MessagePipe pipe;
ppapi::proxy::ProxyChannel* dispatcher = nullptr;
bool init_result = false;
if (is_broker_) {
bool peer_is_browser = renderer_pid == base::kNullProcessId;
BrokerProcessDispatcher* broker_dispatcher =
new BrokerProcessDispatcher(plugin_entry_points_.get_interface,
connect_instance_func_, peer_is_browser);
init_result = broker_dispatcher->InitBrokerWithChannel(
this, renderer_pid, pipe.handle0.release(), false);
dispatcher = broker_dispatcher;
} else {
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());
}
static std::string GetHistogramName(bool is_broker,
const std::string& metric_name,
const base::FilePath& path) {
return std::string("Plugin.Ppapi") + (is_broker ? "Broker" : "Plugin") +
metric_name + "_" + path.BaseName().MaybeAsASCII();
}
void PpapiThread::ReportLoadResult(const base::FilePath& path,
LoadResult result) {
DCHECK_LT(result, LOAD_RESULT_MAX);
// Note: This leaks memory, which is expected behavior.
base::HistogramBase* histogram =
base::LinearHistogram::FactoryGet(
GetHistogramName(is_broker_, "LoadResult", path),
1,
LOAD_RESULT_MAX,
LOAD_RESULT_MAX + 1,
base::HistogramBase::kUmaTargetedHistogramFlag);
histogram->Add(result);
}
void PpapiThread::ReportLoadErrorCode(
const base::FilePath& path,
const base::NativeLibraryLoadError* error) {
// Only report load error code on Windows because that's the only platform that
// has a numerical error value.
#if defined(OS_WIN)
base::UmaHistogramSparse(GetHistogramName(is_broker_, "LoadErrorCode", path),
error->code);
#endif
}
void PpapiThread::ReportLoadTime(const base::FilePath& path,
const base::TimeDelta load_time) {
// Note: This leaks memory, which is expected behavior.
base::HistogramBase* histogram =
base::Histogram::FactoryTimeGet(
GetHistogramName(is_broker_, "LoadTime", path),
base::TimeDelta::FromMilliseconds(1),
base::TimeDelta::FromSeconds(10),
50,
base::HistogramBase::kUmaTargetedHistogramFlag);
histogram->AddTime(load_time);
}
} // namespace content