ppapi_thread.cc revision 5f1c94371a64b3196d4be9466099bb892df9b88e
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