Home | History | Annotate | Download | only in browser
      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/browser/plugin_process_host.h"
      6 
      7 #if defined(OS_WIN)
      8 #include <windows.h>
      9 #elif defined(OS_POSIX)
     10 #include <utility>  // for pair<>
     11 #endif
     12 
     13 #include <vector>
     14 
     15 #include "base/base_switches.h"
     16 #include "base/bind.h"
     17 #include "base/command_line.h"
     18 #include "base/files/file_path.h"
     19 #include "base/lazy_instance.h"
     20 #include "base/logging.h"
     21 #include "base/metrics/histogram.h"
     22 #include "base/path_service.h"
     23 #include "base/strings/string_number_conversions.h"
     24 #include "base/strings/string_util.h"
     25 #include "base/strings/utf_string_conversions.h"
     26 #include "base/synchronization/lock.h"
     27 #include "content/browser/browser_child_process_host_impl.h"
     28 #include "content/browser/loader/resource_message_filter.h"
     29 #include "content/browser/gpu/gpu_data_manager_impl.h"
     30 #include "content/browser/plugin_service_impl.h"
     31 #include "content/common/child_process_host_impl.h"
     32 #include "content/common/plugin_process_messages.h"
     33 #include "content/common/resource_messages.h"
     34 #include "content/public/browser/browser_thread.h"
     35 #include "content/public/browser/content_browser_client.h"
     36 #include "content/public/browser/notification_types.h"
     37 #include "content/public/browser/plugin_service.h"
     38 #include "content/public/browser/resource_context.h"
     39 #include "content/public/common/content_switches.h"
     40 #include "content/public/common/process_type.h"
     41 #include "content/public/common/sandboxed_process_launcher_delegate.h"
     42 #include "ipc/ipc_switches.h"
     43 #include "net/url_request/url_request_context_getter.h"
     44 #include "ui/base/ui_base_switches.h"
     45 #include "ui/gfx/native_widget_types.h"
     46 #include "ui/gfx/switches.h"
     47 #include "ui/gl/gl_switches.h"
     48 
     49 #if defined(OS_MACOSX)
     50 #include "base/mac/mac_util.h"
     51 #include "ui/gfx/rect.h"
     52 #endif
     53 
     54 #if defined(OS_WIN)
     55 #include "base/win/windows_version.h"
     56 #include "content/common/plugin_constants_win.h"
     57 #endif
     58 
     59 namespace content {
     60 
     61 namespace {
     62 
     63 base::LazyInstance<std::map<base::ProcessId, WebPluginInfo> >
     64     g_process_webplugin_info = LAZY_INSTANCE_INITIALIZER;
     65 base::LazyInstance<base::Lock>::Leaky
     66     g_process_webplugin_info_lock = LAZY_INSTANCE_INITIALIZER;
     67 }
     68 
     69 bool PluginProcessHost::GetWebPluginInfoFromPluginPid(base::ProcessId pid,
     70                                                       WebPluginInfo* info) {
     71   base::AutoLock lock(g_process_webplugin_info_lock.Get());
     72   if (!g_process_webplugin_info.Get().count(pid))
     73     return false;
     74 
     75   *info = g_process_webplugin_info.Get()[pid];
     76   return true;
     77 }
     78 
     79 #if defined(OS_WIN)
     80 void PluginProcessHost::OnPluginWindowDestroyed(HWND window, HWND parent) {
     81   // The window is destroyed at this point, we just care about its parent, which
     82   // is the intermediate window we created.
     83   std::set<HWND>::iterator window_index =
     84       plugin_parent_windows_set_.find(parent);
     85   if (window_index == plugin_parent_windows_set_.end())
     86     return;
     87 
     88   plugin_parent_windows_set_.erase(window_index);
     89   PostMessage(parent, WM_CLOSE, 0, 0);
     90 }
     91 
     92 void PluginProcessHost::AddWindow(HWND window) {
     93   plugin_parent_windows_set_.insert(window);
     94 }
     95 #endif  // defined(OS_WIN)
     96 
     97 // NOTE: changes to this class need to be reviewed by the security team.
     98 class PluginSandboxedProcessLauncherDelegate
     99     : public SandboxedProcessLauncherDelegate {
    100  public:
    101   explicit PluginSandboxedProcessLauncherDelegate(ChildProcessHost* host)
    102 #if defined(OS_POSIX)
    103       : ipc_fd_(host->TakeClientFileDescriptor())
    104 #endif  // OS_POSIX
    105   {}
    106 
    107   virtual ~PluginSandboxedProcessLauncherDelegate() {}
    108 
    109 #if defined(OS_WIN)
    110   virtual bool ShouldSandbox() OVERRIDE {
    111     return false;
    112   }
    113 
    114 #elif defined(OS_POSIX)
    115   virtual int GetIpcFd() OVERRIDE {
    116     return ipc_fd_;
    117   }
    118 #endif  // OS_WIN
    119 
    120  private:
    121 #if defined(OS_POSIX)
    122   int ipc_fd_;
    123 #endif  // OS_POSIX
    124 
    125   DISALLOW_COPY_AND_ASSIGN(PluginSandboxedProcessLauncherDelegate);
    126 };
    127 
    128 PluginProcessHost::PluginProcessHost()
    129     : pid_(base::kNullProcessId)
    130 #if defined(OS_MACOSX)
    131     , plugin_cursor_visible_(true)
    132 #endif
    133 {
    134   process_.reset(new BrowserChildProcessHostImpl(PROCESS_TYPE_PLUGIN, this));
    135 }
    136 
    137 PluginProcessHost::~PluginProcessHost() {
    138 #if defined(OS_WIN)
    139   // We erase HWNDs from the plugin_parent_windows_set_ when we receive a
    140   // notification that the window is being destroyed. If we don't receive this
    141   // notification and the PluginProcessHost instance is being destroyed, it
    142   // means that the plugin process crashed. We paint a sad face in this case in
    143   // the renderer process. To ensure that the sad face shows up, and we don't
    144   // leak HWNDs, we should destroy existing plugin parent windows.
    145   std::set<HWND>::iterator window_index;
    146   for (window_index = plugin_parent_windows_set_.begin();
    147        window_index != plugin_parent_windows_set_.end();
    148        ++window_index) {
    149     PostMessage(*window_index, WM_CLOSE, 0, 0);
    150   }
    151 #elif defined(OS_MACOSX)
    152   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    153   // If the plugin process crashed but had fullscreen windows open at the time,
    154   // make sure that the menu bar is visible.
    155   for (size_t i = 0; i < plugin_fullscreen_windows_set_.size(); ++i) {
    156     BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
    157                             base::Bind(base::mac::ReleaseFullScreen,
    158                                        base::mac::kFullScreenModeHideAll));
    159   }
    160   // If the plugin hid the cursor, reset that.
    161   if (!plugin_cursor_visible_) {
    162     BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
    163                             base::Bind(base::mac::SetCursorVisibility, true));
    164   }
    165 #endif
    166   // Cancel all pending and sent requests.
    167   CancelRequests();
    168 
    169   {
    170     base::AutoLock lock(g_process_webplugin_info_lock.Get());
    171     g_process_webplugin_info.Get()[pid_] = info_;
    172   }
    173 }
    174 
    175 bool PluginProcessHost::Send(IPC::Message* message) {
    176   return process_->Send(message);
    177 }
    178 
    179 bool PluginProcessHost::Init(const WebPluginInfo& info) {
    180   info_ = info;
    181   process_->SetName(info_.name);
    182 
    183   std::string channel_id = process_->GetHost()->CreateChannel();
    184   if (channel_id.empty())
    185     return false;
    186 
    187   // Build command line for plugin. When we have a plugin launcher, we can't
    188   // allow "self" on linux and we need the real file path.
    189   const base::CommandLine& browser_command_line =
    190       *base::CommandLine::ForCurrentProcess();
    191   base::CommandLine::StringType plugin_launcher =
    192       browser_command_line.GetSwitchValueNative(switches::kPluginLauncher);
    193 
    194 #if defined(OS_MACOSX)
    195   // Run the plug-in process in a mode tolerant of heap execution without
    196   // explicit mprotect calls. Some plug-ins still rely on this quaint and
    197   // archaic "feature." See http://crbug.com/93551.
    198   int flags = ChildProcessHost::CHILD_ALLOW_HEAP_EXECUTION;
    199 #elif defined(OS_LINUX)
    200   int flags = plugin_launcher.empty() ? ChildProcessHost::CHILD_ALLOW_SELF :
    201                                         ChildProcessHost::CHILD_NORMAL;
    202 #else
    203   int flags = ChildProcessHost::CHILD_NORMAL;
    204 #endif
    205 
    206   base::FilePath exe_path = ChildProcessHost::GetChildPath(flags);
    207   if (exe_path.empty())
    208     return false;
    209 
    210   base::CommandLine* cmd_line = new base::CommandLine(exe_path);
    211   // Put the process type and plugin path first so they're easier to see
    212   // in process listings using native process management tools.
    213   cmd_line->AppendSwitchASCII(switches::kProcessType, switches::kPluginProcess);
    214   cmd_line->AppendSwitchPath(switches::kPluginPath, info.path);
    215 
    216   // Propagate the following switches to the plugin command line (along with
    217   // any associated values) if present in the browser command line
    218   static const char* const kSwitchNames[] = {
    219     switches::kDisableBreakpad,
    220     switches::kDisableDirectNPAPIRequests,
    221     switches::kEnableStatsTable,
    222     switches::kFullMemoryCrashReport,
    223     switches::kLoggingLevel,
    224     switches::kLogPluginMessages,
    225     switches::kNoSandbox,
    226     switches::kPluginStartupDialog,
    227     switches::kTraceStartup,
    228     switches::kUseGL,
    229     switches::kForceDeviceScaleFactor,
    230 #if defined(OS_MACOSX)
    231     switches::kDisableCoreAnimationPlugins,
    232     switches::kEnableSandboxLogging,
    233 #endif
    234   };
    235 
    236   cmd_line->CopySwitchesFrom(browser_command_line, kSwitchNames,
    237                              arraysize(kSwitchNames));
    238 
    239   GpuDataManagerImpl::GetInstance()->AppendPluginCommandLine(cmd_line);
    240 
    241   // If specified, prepend a launcher program to the command line.
    242   if (!plugin_launcher.empty())
    243     cmd_line->PrependWrapper(plugin_launcher);
    244 
    245   std::string locale = GetContentClient()->browser()->GetApplicationLocale();
    246   if (!locale.empty()) {
    247     // Pass on the locale so the null plugin will use the right language in the
    248     // prompt to install the desired plugin.
    249     cmd_line->AppendSwitchASCII(switches::kLang, locale);
    250   }
    251 
    252   cmd_line->AppendSwitchASCII(switches::kProcessChannelID, channel_id);
    253 
    254   process_->Launch(
    255       new PluginSandboxedProcessLauncherDelegate(process_->GetHost()),
    256       cmd_line);
    257 
    258   // The plugin needs to be shutdown gracefully, i.e. NP_Shutdown needs to be
    259   // called on the plugin. The plugin process exits when it receives the
    260   // OnChannelError notification indicating that the browser plugin channel has
    261   // been destroyed.
    262   process_->SetTerminateChildOnShutdown(false);
    263 
    264   ResourceMessageFilter::GetContextsCallback get_contexts_callback(
    265       base::Bind(&PluginProcessHost::GetContexts,
    266       base::Unretained(this)));
    267 
    268   // TODO(jam): right now we're passing NULL for appcache, blob storage, and
    269   // file system. If NPAPI plugins actually use this, we'll have to plumb them.
    270   ResourceMessageFilter* resource_message_filter = new ResourceMessageFilter(
    271       process_->GetData().id, PROCESS_TYPE_PLUGIN, NULL, NULL, NULL, NULL,
    272       get_contexts_callback);
    273   process_->AddFilter(resource_message_filter);
    274   return true;
    275 }
    276 
    277 void PluginProcessHost::ForceShutdown() {
    278   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    279   Send(new PluginProcessMsg_NotifyRenderersOfPendingShutdown());
    280   process_->ForceShutdown();
    281 }
    282 
    283 bool PluginProcessHost::OnMessageReceived(const IPC::Message& msg) {
    284   bool handled = true;
    285   IPC_BEGIN_MESSAGE_MAP(PluginProcessHost, msg)
    286     IPC_MESSAGE_HANDLER(PluginProcessHostMsg_ChannelCreated, OnChannelCreated)
    287     IPC_MESSAGE_HANDLER(PluginProcessHostMsg_ChannelDestroyed,
    288                         OnChannelDestroyed)
    289 #if defined(OS_WIN)
    290     IPC_MESSAGE_HANDLER(PluginProcessHostMsg_PluginWindowDestroyed,
    291                         OnPluginWindowDestroyed)
    292 #endif
    293 #if defined(OS_MACOSX)
    294     IPC_MESSAGE_HANDLER(PluginProcessHostMsg_PluginShowWindow,
    295                         OnPluginShowWindow)
    296     IPC_MESSAGE_HANDLER(PluginProcessHostMsg_PluginHideWindow,
    297                         OnPluginHideWindow)
    298     IPC_MESSAGE_HANDLER(PluginProcessHostMsg_PluginSetCursorVisibility,
    299                         OnPluginSetCursorVisibility)
    300 #endif
    301     IPC_MESSAGE_UNHANDLED(handled = false)
    302   IPC_END_MESSAGE_MAP()
    303 
    304   return handled;
    305 }
    306 
    307 void PluginProcessHost::OnChannelConnected(int32 peer_pid) {
    308   for (size_t i = 0; i < pending_requests_.size(); ++i) {
    309     RequestPluginChannel(pending_requests_[i]);
    310   }
    311 
    312   pending_requests_.clear();
    313 
    314   pid_ = peer_pid;
    315   {
    316     base::AutoLock lock(g_process_webplugin_info_lock.Get());
    317     g_process_webplugin_info.Get()[pid_] = info_;
    318   }
    319 }
    320 
    321 void PluginProcessHost::OnChannelError() {
    322   CancelRequests();
    323 }
    324 
    325 bool PluginProcessHost::CanShutdown() {
    326   return sent_requests_.empty();
    327 }
    328 
    329 void PluginProcessHost::OnProcessCrashed(int exit_code) {
    330   PluginServiceImpl::GetInstance()->RegisterPluginCrash(info_.path);
    331 }
    332 
    333 void PluginProcessHost::CancelRequests() {
    334   for (size_t i = 0; i < pending_requests_.size(); ++i)
    335     pending_requests_[i]->OnError();
    336   pending_requests_.clear();
    337 
    338   while (!sent_requests_.empty()) {
    339     Client* client = sent_requests_.front();
    340     if (client)
    341       client->OnError();
    342     sent_requests_.pop_front();
    343   }
    344 }
    345 
    346 void PluginProcessHost::OpenChannelToPlugin(Client* client) {
    347   BrowserThread::PostTask(
    348       BrowserThread::UI, FROM_HERE,
    349       base::Bind(&BrowserChildProcessHostImpl::NotifyProcessInstanceCreated,
    350                  process_->GetData()));
    351   client->SetPluginInfo(info_);
    352   if (process_->GetHost()->IsChannelOpening()) {
    353     // The channel is already in the process of being opened.  Put
    354     // this "open channel" request into a queue of requests that will
    355     // be run once the channel is open.
    356     pending_requests_.push_back(client);
    357     return;
    358   }
    359 
    360   // We already have an open channel, send a request right away to plugin.
    361   RequestPluginChannel(client);
    362 }
    363 
    364 void PluginProcessHost::CancelPendingRequest(Client* client) {
    365   std::vector<Client*>::iterator it = pending_requests_.begin();
    366   while (it != pending_requests_.end()) {
    367     if (client == *it) {
    368       pending_requests_.erase(it);
    369       return;
    370     }
    371     ++it;
    372   }
    373   DCHECK(it != pending_requests_.end());
    374 }
    375 
    376 void PluginProcessHost::CancelSentRequest(Client* client) {
    377   std::list<Client*>::iterator it = sent_requests_.begin();
    378   while (it != sent_requests_.end()) {
    379     if (client == *it) {
    380       *it = NULL;
    381       return;
    382     }
    383     ++it;
    384   }
    385   DCHECK(it != sent_requests_.end());
    386 }
    387 
    388 void PluginProcessHost::RequestPluginChannel(Client* client) {
    389   // We can't send any sync messages from the browser because it might lead to
    390   // a hang.  However this async messages must be answered right away by the
    391   // plugin process (i.e. unblocks a Send() call like a sync message) otherwise
    392   // a deadlock can occur if the plugin creation request from the renderer is
    393   // a result of a sync message by the plugin process.
    394   PluginProcessMsg_CreateChannel* msg =
    395       new PluginProcessMsg_CreateChannel(
    396           client->ID(),
    397           client->OffTheRecord());
    398   msg->set_unblock(true);
    399   if (Send(msg)) {
    400     sent_requests_.push_back(client);
    401     client->OnSentPluginChannelRequest();
    402   } else {
    403     client->OnError();
    404   }
    405 }
    406 
    407 void PluginProcessHost::OnChannelCreated(
    408     const IPC::ChannelHandle& channel_handle) {
    409   Client* client = sent_requests_.front();
    410 
    411   if (client) {
    412     if (!resource_context_map_.count(client->ID())) {
    413       ResourceContextEntry entry;
    414       entry.ref_count = 0;
    415       entry.resource_context = client->GetResourceContext();
    416       resource_context_map_[client->ID()] = entry;
    417     }
    418     resource_context_map_[client->ID()].ref_count++;
    419     client->OnChannelOpened(channel_handle);
    420   }
    421   sent_requests_.pop_front();
    422 }
    423 
    424 void PluginProcessHost::OnChannelDestroyed(int renderer_id) {
    425   resource_context_map_[renderer_id].ref_count--;
    426   if (!resource_context_map_[renderer_id].ref_count)
    427     resource_context_map_.erase(renderer_id);
    428 }
    429 
    430 void PluginProcessHost::GetContexts(const ResourceHostMsg_Request& request,
    431                                     ResourceContext** resource_context,
    432                                     net::URLRequestContext** request_context) {
    433   *resource_context =
    434       resource_context_map_[request.origin_pid].resource_context;
    435   *request_context = (*resource_context)->GetRequestContext();
    436 }
    437 
    438 }  // namespace content
    439