| // 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 |