1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "content/ppapi_plugin/ppapi_thread.h" 6 7#include <limits> 8 9#include "base/command_line.h" 10#include "base/cpu.h" 11#include "base/debug/crash_logging.h" 12#include "base/file_util.h" 13#include "base/logging.h" 14#include "base/metrics/histogram.h" 15#include "base/metrics/sparse_histogram.h" 16#include "base/rand_util.h" 17#include "base/strings/stringprintf.h" 18#include "base/strings/utf_string_conversions.h" 19#include "base/threading/platform_thread.h" 20#include "base/time/time.h" 21#include "content/child/browser_font_resource_trusted.h" 22#include "content/child/child_process.h" 23#include "content/common/child_process_messages.h" 24#include "content/common/sandbox_util.h" 25#include "content/ppapi_plugin/broker_process_dispatcher.h" 26#include "content/ppapi_plugin/plugin_process_dispatcher.h" 27#include "content/ppapi_plugin/ppapi_webkitplatformsupport_impl.h" 28#include "content/public/common/content_client.h" 29#include "content/public/common/content_switches.h" 30#include "content/public/common/pepper_plugin_info.h" 31#include "content/public/common/sandbox_init.h" 32#include "content/public/plugin/content_plugin_client.h" 33#include "ipc/ipc_channel_handle.h" 34#include "ipc/ipc_platform_file.h" 35#include "ipc/ipc_sync_channel.h" 36#include "ipc/ipc_sync_message_filter.h" 37#include "ppapi/c/dev/ppp_network_state_dev.h" 38#include "ppapi/c/pp_errors.h" 39#include "ppapi/c/ppp.h" 40#include "ppapi/proxy/interface_list.h" 41#include "ppapi/proxy/plugin_globals.h" 42#include "ppapi/proxy/plugin_message_filter.h" 43#include "ppapi/proxy/ppapi_messages.h" 44#include "ppapi/proxy/resource_reply_thread_registrar.h" 45#include "third_party/WebKit/public/web/WebKit.h" 46#include "ui/base/ui_base_switches.h" 47 48#if defined(OS_WIN) 49#include "base/win/win_util.h" 50#include "base/win/windows_version.h" 51#include "sandbox/win/src/sandbox.h" 52#elif defined(OS_MACOSX) 53#include "content/common/sandbox_init_mac.h" 54#endif 55 56#if defined(OS_WIN) 57const char kWidevineCdmAdapterFileName[] = "widevinecdmadapter.dll"; 58 59extern sandbox::TargetServices* g_target_services; 60 61// Used by EnumSystemLocales for warming up. 62static BOOL CALLBACK EnumLocalesProc(LPTSTR lpLocaleString) { 63 return TRUE; 64} 65 66static BOOL CALLBACK EnumLocalesProcEx( 67 LPWSTR lpLocaleString, 68 DWORD dwFlags, 69 LPARAM lParam) { 70 return TRUE; 71} 72 73// Warm up language subsystems before the sandbox is turned on. 74static void WarmupWindowsLocales(const ppapi::PpapiPermissions& permissions) { 75 ::GetUserDefaultLangID(); 76 ::GetUserDefaultLCID(); 77 78 if (permissions.HasPermission(ppapi::PERMISSION_FLASH)) { 79 if (base::win::GetVersion() >= base::win::VERSION_VISTA) { 80 typedef BOOL (WINAPI *PfnEnumSystemLocalesEx) 81 (LOCALE_ENUMPROCEX, DWORD, LPARAM, LPVOID); 82 83 HMODULE handle_kern32 = GetModuleHandleW(L"Kernel32.dll"); 84 PfnEnumSystemLocalesEx enum_sys_locales_ex = 85 reinterpret_cast<PfnEnumSystemLocalesEx> 86 (GetProcAddress(handle_kern32, "EnumSystemLocalesEx")); 87 88 enum_sys_locales_ex(EnumLocalesProcEx, LOCALE_WINDOWS, 0, 0); 89 } else { 90 EnumSystemLocalesW(EnumLocalesProc, LCID_INSTALLED); 91 } 92 } 93} 94#else 95extern void* g_target_services; 96#endif 97 98namespace content { 99 100typedef int32_t (*InitializeBrokerFunc) 101 (PP_ConnectInstance_Func* connect_instance_func); 102 103PpapiThread::PpapiThread(const CommandLine& command_line, bool is_broker) 104 : is_broker_(is_broker), 105 connect_instance_func_(NULL), 106 local_pp_module_( 107 base::RandInt(0, std::numeric_limits<PP_Module>::max())), 108 next_plugin_dispatcher_id_(1) { 109 ppapi::proxy::PluginGlobals* globals = ppapi::proxy::PluginGlobals::Get(); 110 globals->set_plugin_proxy_delegate(this); 111 globals->set_command_line( 112 command_line.GetSwitchValueASCII(switches::kPpapiFlashArgs)); 113 114 webkit_platform_support_.reset(new PpapiWebKitPlatformSupportImpl); 115 blink::initialize(webkit_platform_support_.get()); 116 117 if (!is_broker_) { 118 channel()->AddFilter( 119 new ppapi::proxy::PluginMessageFilter( 120 NULL, globals->resource_reply_thread_registrar())); 121 } 122} 123 124PpapiThread::~PpapiThread() { 125} 126 127void PpapiThread::Shutdown() { 128 ppapi::proxy::PluginGlobals::Get()->set_plugin_proxy_delegate(NULL); 129 if (plugin_entry_points_.shutdown_module) 130 plugin_entry_points_.shutdown_module(); 131 webkit_platform_support_->Shutdown(); 132 blink::shutdown(); 133} 134 135bool PpapiThread::Send(IPC::Message* msg) { 136 // Allow access from multiple threads. 137 if (base::MessageLoop::current() == message_loop()) 138 return ChildThread::Send(msg); 139 140 return sync_message_filter()->Send(msg); 141} 142 143// Note that this function is called only for messages from the channel to the 144// browser process. Messages from the renderer process are sent via a different 145// channel that ends up at Dispatcher::OnMessageReceived. 146bool PpapiThread::OnControlMessageReceived(const IPC::Message& msg) { 147 bool handled = true; 148 IPC_BEGIN_MESSAGE_MAP(PpapiThread, msg) 149 IPC_MESSAGE_HANDLER(PpapiMsg_LoadPlugin, OnLoadPlugin) 150 IPC_MESSAGE_HANDLER(PpapiMsg_CreateChannel, OnCreateChannel) 151 IPC_MESSAGE_HANDLER(PpapiMsg_SetNetworkState, OnSetNetworkState) 152 IPC_MESSAGE_HANDLER(PpapiMsg_Crash, OnCrash) 153 IPC_MESSAGE_HANDLER(PpapiMsg_Hang, OnHang) 154 IPC_MESSAGE_UNHANDLED(handled = false) 155 IPC_END_MESSAGE_MAP() 156 return handled; 157} 158 159void PpapiThread::OnChannelConnected(int32 peer_pid) { 160 ChildThread::OnChannelConnected(peer_pid); 161#if defined(OS_WIN) 162 if (is_broker_) 163 peer_handle_.Set(::OpenProcess(PROCESS_DUP_HANDLE, FALSE, peer_pid)); 164#endif 165} 166 167base::MessageLoopProxy* PpapiThread::GetIPCMessageLoop() { 168 return ChildProcess::current()->io_message_loop_proxy(); 169} 170 171base::WaitableEvent* PpapiThread::GetShutdownEvent() { 172 return ChildProcess::current()->GetShutDownEvent(); 173} 174 175IPC::PlatformFileForTransit PpapiThread::ShareHandleWithRemote( 176 base::PlatformFile handle, 177 base::ProcessId peer_pid, 178 bool should_close_source) { 179#if defined(OS_WIN) 180 if (peer_handle_.IsValid()) { 181 DCHECK(is_broker_); 182 return IPC::GetFileHandleForProcess(handle, peer_handle_, 183 should_close_source); 184 } 185#endif 186 187 DCHECK(peer_pid != base::kNullProcessId); 188 return BrokerGetFileHandleForProcess(handle, peer_pid, should_close_source); 189} 190 191std::set<PP_Instance>* PpapiThread::GetGloballySeenInstanceIDSet() { 192 return &globally_seen_instance_ids_; 193} 194 195IPC::Sender* PpapiThread::GetBrowserSender() { 196 return this; 197} 198 199std::string PpapiThread::GetUILanguage() { 200 CommandLine* command_line = CommandLine::ForCurrentProcess(); 201 return command_line->GetSwitchValueASCII(switches::kLang); 202} 203 204void PpapiThread::PreCacheFont(const void* logfontw) { 205#if defined(OS_WIN) 206 Send(new ChildProcessHostMsg_PreCacheFont( 207 *static_cast<const LOGFONTW*>(logfontw))); 208#endif 209} 210 211void PpapiThread::SetActiveURL(const std::string& url) { 212 GetContentClient()->SetActiveURL(GURL(url)); 213} 214 215PP_Resource PpapiThread::CreateBrowserFont( 216 ppapi::proxy::Connection connection, 217 PP_Instance instance, 218 const PP_BrowserFont_Trusted_Description& desc, 219 const ppapi::Preferences& prefs) { 220 if (!BrowserFontResource_Trusted::IsPPFontDescriptionValid(desc)) 221 return 0; 222 return (new BrowserFontResource_Trusted( 223 connection, instance, desc, prefs))->GetReference(); 224} 225 226uint32 PpapiThread::Register(ppapi::proxy::PluginDispatcher* plugin_dispatcher) { 227 if (!plugin_dispatcher || 228 plugin_dispatchers_.size() >= std::numeric_limits<uint32>::max()) { 229 return 0; 230 } 231 232 uint32 id = 0; 233 do { 234 // Although it is unlikely, make sure that we won't cause any trouble when 235 // the counter overflows. 236 id = next_plugin_dispatcher_id_++; 237 } while (id == 0 || 238 plugin_dispatchers_.find(id) != plugin_dispatchers_.end()); 239 plugin_dispatchers_[id] = plugin_dispatcher; 240 return id; 241} 242 243void PpapiThread::Unregister(uint32 plugin_dispatcher_id) { 244 plugin_dispatchers_.erase(plugin_dispatcher_id); 245} 246 247void PpapiThread::OnLoadPlugin(const base::FilePath& path, 248 const ppapi::PpapiPermissions& permissions) { 249 // In case of crashes, the crash dump doesn't indicate which plugin 250 // it came from. 251 base::debug::SetCrashKeyValue("ppapi_path", path.MaybeAsASCII()); 252 253 SavePluginName(path); 254 255 // This must be set before calling into the plugin so it can get the 256 // interfaces it has permission for. 257 ppapi::proxy::InterfaceList::SetProcessGlobalPermissions(permissions); 258 permissions_ = permissions; 259 260 // Trusted Pepper plugins may be "internal", i.e. built-in to the browser 261 // binary. If we're being asked to load such a plugin (e.g. the Chromoting 262 // client) then fetch the entry points from the embedder, rather than a DLL. 263 std::vector<PepperPluginInfo> plugins; 264 GetContentClient()->AddPepperPlugins(&plugins); 265 for (size_t i = 0; i < plugins.size(); ++i) { 266 if (plugins[i].is_internal && plugins[i].path == path) { 267 // An internal plugin is being loaded, so fetch the entry points. 268 plugin_entry_points_ = plugins[i].internal_entry_points; 269 } 270 } 271 272 // If the plugin isn't internal then load it from |path|. 273 base::ScopedNativeLibrary library; 274 if (plugin_entry_points_.initialize_module == NULL) { 275 // Load the plugin from the specified library. 276 base::NativeLibraryLoadError error; 277 library.Reset(base::LoadNativeLibrary(path, &error)); 278 if (!library.is_valid()) { 279 LOG(ERROR) << "Failed to load Pepper module from " << path.value() 280 << " (error: " << error.ToString() << ")"; 281 if (!base::PathExists(path)) { 282 ReportLoadResult(path, FILE_MISSING); 283 return; 284 } 285 ReportLoadResult(path, LOAD_FAILED); 286 // Report detailed reason for load failure. 287 ReportLoadErrorCode(path, error); 288 return; 289 } 290 291 // Get the GetInterface function (required). 292 plugin_entry_points_.get_interface = 293 reinterpret_cast<PP_GetInterface_Func>( 294 library.GetFunctionPointer("PPP_GetInterface")); 295 if (!plugin_entry_points_.get_interface) { 296 LOG(WARNING) << "No PPP_GetInterface in plugin library"; 297 ReportLoadResult(path, ENTRY_POINT_MISSING); 298 return; 299 } 300 301 // The ShutdownModule/ShutdownBroker function is optional. 302 plugin_entry_points_.shutdown_module = 303 is_broker_ ? 304 reinterpret_cast<PP_ShutdownModule_Func>( 305 library.GetFunctionPointer("PPP_ShutdownBroker")) : 306 reinterpret_cast<PP_ShutdownModule_Func>( 307 library.GetFunctionPointer("PPP_ShutdownModule")); 308 309 if (!is_broker_) { 310 // Get the InitializeModule function (required for non-broker code). 311 plugin_entry_points_.initialize_module = 312 reinterpret_cast<PP_InitializeModule_Func>( 313 library.GetFunctionPointer("PPP_InitializeModule")); 314 if (!plugin_entry_points_.initialize_module) { 315 LOG(WARNING) << "No PPP_InitializeModule in plugin library"; 316 ReportLoadResult(path, ENTRY_POINT_MISSING); 317 return; 318 } 319 } 320 } 321 322#if defined(OS_WIN) 323 // If code subsequently tries to exit using abort(), force a crash (since 324 // otherwise these would be silent terminations and fly under the radar). 325 base::win::SetAbortBehaviorForCrashReporting(); 326 327 // Once we lower the token the sandbox is locked down and no new modules 328 // can be loaded. TODO(cpu): consider changing to the loading style of 329 // regular plugins. 330 if (g_target_services) { 331 // Let Flash and Widevine CDM adapter load DXVA before lockdown on Vista+. 332 if (permissions.HasPermission(ppapi::PERMISSION_FLASH) || 333 path.BaseName().MaybeAsASCII() == kWidevineCdmAdapterFileName) { 334 if (base::win::OSInfo::GetInstance()->version() >= 335 base::win::VERSION_VISTA) { 336 LoadLibraryA("dxva2.dll"); 337 } 338 } 339 340 if (permissions.HasPermission(ppapi::PERMISSION_FLASH)) { 341 if (base::win::OSInfo::GetInstance()->version() >= 342 base::win::VERSION_WIN7) { 343 base::CPU cpu; 344 if (cpu.vendor_name() == "AuthenticAMD") { 345 // The AMD crypto acceleration is only AMD Bulldozer and above. 346#if defined(_WIN64) 347 LoadLibraryA("amdhcp64.dll"); 348#else 349 LoadLibraryA("amdhcp32.dll"); 350#endif 351 } 352 } 353 } 354 355 // Cause advapi32 to load before the sandbox is turned on. 356 unsigned int dummy_rand; 357 rand_s(&dummy_rand); 358 359 WarmupWindowsLocales(permissions); 360 361 g_target_services->LowerToken(); 362 } 363#endif 364 365 if (is_broker_) { 366 // Get the InitializeBroker function (required). 367 InitializeBrokerFunc init_broker = 368 reinterpret_cast<InitializeBrokerFunc>( 369 library.GetFunctionPointer("PPP_InitializeBroker")); 370 if (!init_broker) { 371 LOG(WARNING) << "No PPP_InitializeBroker in plugin library"; 372 ReportLoadResult(path, ENTRY_POINT_MISSING); 373 return; 374 } 375 376 int32_t init_error = init_broker(&connect_instance_func_); 377 if (init_error != PP_OK) { 378 LOG(WARNING) << "InitBroker failed with error " << init_error; 379 ReportLoadResult(path, INIT_FAILED); 380 return; 381 } 382 if (!connect_instance_func_) { 383 LOG(WARNING) << "InitBroker did not provide PP_ConnectInstance_Func"; 384 ReportLoadResult(path, INIT_FAILED); 385 return; 386 } 387 } else { 388#if defined(OS_MACOSX) 389 // We need to do this after getting |PPP_GetInterface()| (or presumably 390 // doing something nontrivial with the library), else the sandbox 391 // intercedes. 392 CHECK(InitializeSandbox()); 393#endif 394 395 int32_t init_error = plugin_entry_points_.initialize_module( 396 local_pp_module_, 397 &ppapi::proxy::PluginDispatcher::GetBrowserInterface); 398 if (init_error != PP_OK) { 399 LOG(WARNING) << "InitModule failed with error " << init_error; 400 ReportLoadResult(path, INIT_FAILED); 401 return; 402 } 403 } 404 405 // Initialization succeeded, so keep the plugin DLL loaded. 406 library_.Reset(library.Release()); 407 408 ReportLoadResult(path, LOAD_SUCCESS); 409} 410 411void PpapiThread::OnCreateChannel(base::ProcessId renderer_pid, 412 int renderer_child_id, 413 bool incognito) { 414 IPC::ChannelHandle channel_handle; 415 416 if (!plugin_entry_points_.get_interface || // Plugin couldn't be loaded. 417 !SetupRendererChannel(renderer_pid, renderer_child_id, incognito, 418 &channel_handle)) { 419 Send(new PpapiHostMsg_ChannelCreated(IPC::ChannelHandle())); 420 return; 421 } 422 423 Send(new PpapiHostMsg_ChannelCreated(channel_handle)); 424} 425 426void PpapiThread::OnSetNetworkState(bool online) { 427 // Note the browser-process side shouldn't send us these messages in the 428 // first unless the plugin has dev permissions, so we don't need to check 429 // again here. We don't want random plugins depending on this dev interface. 430 if (!plugin_entry_points_.get_interface) 431 return; 432 const PPP_NetworkState_Dev* ns = static_cast<const PPP_NetworkState_Dev*>( 433 plugin_entry_points_.get_interface(PPP_NETWORK_STATE_DEV_INTERFACE)); 434 if (ns) 435 ns->SetOnLine(PP_FromBool(online)); 436} 437 438void PpapiThread::OnCrash() { 439 // Intentionally crash upon the request of the browser. 440 volatile int* null_pointer = NULL; 441 *null_pointer = 0; 442} 443 444void PpapiThread::OnHang() { 445 // Intentionally hang upon the request of the browser. 446 for (;;) 447 base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1)); 448} 449 450bool PpapiThread::SetupRendererChannel(base::ProcessId renderer_pid, 451 int renderer_child_id, 452 bool incognito, 453 IPC::ChannelHandle* handle) { 454 DCHECK(is_broker_ == (connect_instance_func_ != NULL)); 455 IPC::ChannelHandle plugin_handle; 456 plugin_handle.name = IPC::Channel::GenerateVerifiedChannelID( 457 base::StringPrintf( 458 "%d.r%d", base::GetCurrentProcId(), renderer_child_id)); 459 460 ppapi::proxy::ProxyChannel* dispatcher = NULL; 461 bool init_result = false; 462 if (is_broker_) { 463 BrokerProcessDispatcher* broker_dispatcher = 464 new BrokerProcessDispatcher(plugin_entry_points_.get_interface, 465 connect_instance_func_); 466 init_result = broker_dispatcher->InitBrokerWithChannel(this, 467 renderer_pid, 468 plugin_handle, 469 false); 470 dispatcher = broker_dispatcher; 471 } else { 472 PluginProcessDispatcher* plugin_dispatcher = 473 new PluginProcessDispatcher(plugin_entry_points_.get_interface, 474 permissions_, 475 incognito); 476 init_result = plugin_dispatcher->InitPluginWithChannel(this, 477 renderer_pid, 478 plugin_handle, 479 false); 480 dispatcher = plugin_dispatcher; 481 } 482 483 if (!init_result) { 484 delete dispatcher; 485 return false; 486 } 487 488 handle->name = plugin_handle.name; 489#if defined(OS_POSIX) 490 // On POSIX, transfer ownership of the renderer-side (client) FD. 491 // This ensures this process will be notified when it is closed even if a 492 // connection is not established. 493 handle->socket = base::FileDescriptor(dispatcher->TakeRendererFD(), true); 494 if (handle->socket.fd == -1) 495 return false; 496#endif 497 498 // From here, the dispatcher will manage its own lifetime according to the 499 // lifetime of the attached channel. 500 return true; 501} 502 503void PpapiThread::SavePluginName(const base::FilePath& path) { 504 ppapi::proxy::PluginGlobals::Get()->set_plugin_name( 505 path.BaseName().AsUTF8Unsafe()); 506 507 // plugin() is NULL when in-process, which is fine, because this is 508 // just a hook for setting the process name. 509 if (GetContentClient()->plugin()) { 510 GetContentClient()->plugin()->PluginProcessStarted( 511 path.BaseName().RemoveExtension().LossyDisplayName()); 512 } 513} 514 515void PpapiThread::ReportLoadResult(const base::FilePath& path, 516 LoadResult result) { 517 DCHECK_LT(result, LOAD_RESULT_MAX); 518 std::string histogram_name = std::string("Plugin.Ppapi") + 519 (is_broker_ ? "Broker" : "Plugin") + 520 "LoadResult_" + path.BaseName().MaybeAsASCII(); 521 522 // Note: This leaks memory, which is expected behavior. 523 base::HistogramBase* histogram = 524 base::LinearHistogram::FactoryGet( 525 histogram_name, 526 1, 527 LOAD_RESULT_MAX, 528 LOAD_RESULT_MAX + 1, 529 base::HistogramBase::kUmaTargetedHistogramFlag); 530 531 histogram->Add(result); 532} 533 534void PpapiThread::ReportLoadErrorCode( 535 const base::FilePath& path, 536 const base::NativeLibraryLoadError& error) { 537#if defined(OS_WIN) 538 // Only report load error code on Windows because that's the only platform 539 // that has a numerical error value. 540 std::string histogram_name = 541 std::string("Plugin.Ppapi") + (is_broker_ ? "Broker" : "Plugin") + 542 "LoadErrorCode_" + path.BaseName().MaybeAsASCII(); 543 544 // For sparse histograms, we can use the macro, as it does not incorporate a 545 // static. 546 UMA_HISTOGRAM_SPARSE_SLOWLY(histogram_name, error.code); 547#endif 548} 549 550} // namespace content 551