ppapi_plugin_process_host.cc revision 7dbb3d5cf0c15f500944d211057644d6a2f37371
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/ppapi_plugin_process_host.h" 6 7#include <string> 8 9#include "base/base_switches.h" 10#include "base/command_line.h" 11#include "base/files/file_path.h" 12#include "base/metrics/field_trial.h" 13#include "base/process_util.h" 14#include "base/strings/utf_string_conversions.h" 15#include "content/browser/browser_child_process_host_impl.h" 16#include "content/browser/plugin_service_impl.h" 17#include "content/browser/renderer_host/render_message_filter.h" 18#include "content/common/child_process_host_impl.h" 19#include "content/common/child_process_messages.h" 20#include "content/public/browser/content_browser_client.h" 21#include "content/public/common/content_constants.h" 22#include "content/public/common/content_switches.h" 23#include "content/public/common/pepper_plugin_info.h" 24#include "content/public/common/process_type.h" 25#include "ipc/ipc_switches.h" 26#include "net/base/network_change_notifier.h" 27#include "ppapi/proxy/ppapi_messages.h" 28#include "ui/base/ui_base_switches.h" 29#include "webkit/plugins/plugin_switches.h" 30 31#if defined(OS_WIN) 32#include "content/common/sandbox_win.h" 33#include "content/public/common/sandboxed_process_launcher_delegate.h" 34#include "sandbox/win/src/sandbox_policy.h" 35#endif 36 37namespace content { 38 39#if defined(OS_WIN) 40// NOTE: changes to this class need to be reviewed by the security team. 41class PpapiPluginSandboxedProcessLauncherDelegate 42 : public content::SandboxedProcessLauncherDelegate { 43 public: 44 explicit PpapiPluginSandboxedProcessLauncherDelegate(bool is_broker) 45 : is_broker_(is_broker) {} 46 virtual ~PpapiPluginSandboxedProcessLauncherDelegate() {} 47 48 virtual void ShouldSandbox(bool* in_sandbox) OVERRIDE { 49 if (is_broker_) 50 *in_sandbox = false; 51 } 52 53 virtual void PreSpawnTarget(sandbox::TargetPolicy* policy, 54 bool* success) { 55 if (is_broker_) 56 return; 57 // The Pepper process as locked-down as a renderer execpt that it can 58 // create the server side of chrome pipes. 59 sandbox::ResultCode result; 60 result = policy->AddRule(sandbox::TargetPolicy::SUBSYS_NAMED_PIPES, 61 sandbox::TargetPolicy::NAMEDPIPES_ALLOW_ANY, 62 L"\\\\.\\pipe\\chrome.*"); 63 *success = (result == sandbox::SBOX_ALL_OK); 64 } 65 66 private: 67 bool is_broker_; 68 69 DISALLOW_COPY_AND_ASSIGN(PpapiPluginSandboxedProcessLauncherDelegate); 70}; 71#endif // OS_WIN 72 73class PpapiPluginProcessHost::PluginNetworkObserver 74 : public net::NetworkChangeNotifier::IPAddressObserver, 75 public net::NetworkChangeNotifier::ConnectionTypeObserver { 76 public: 77 explicit PluginNetworkObserver(PpapiPluginProcessHost* process_host) 78 : process_host_(process_host) { 79 net::NetworkChangeNotifier::AddIPAddressObserver(this); 80 net::NetworkChangeNotifier::AddConnectionTypeObserver(this); 81 } 82 83 virtual ~PluginNetworkObserver() { 84 net::NetworkChangeNotifier::RemoveConnectionTypeObserver(this); 85 net::NetworkChangeNotifier::RemoveIPAddressObserver(this); 86 } 87 88 // IPAddressObserver implementation. 89 virtual void OnIPAddressChanged() OVERRIDE { 90 // TODO(brettw) bug 90246: This doesn't seem correct. The online/offline 91 // notification seems like it should be sufficient, but I don't see that 92 // when I unplug and replug my network cable. Sending this notification when 93 // "something" changes seems to make Flash reasonably happy, but seems 94 // wrong. We should really be able to provide the real online state in 95 // OnConnectionTypeChanged(). 96 process_host_->Send(new PpapiMsg_SetNetworkState(true)); 97 } 98 99 // ConnectionTypeObserver implementation. 100 virtual void OnConnectionTypeChanged( 101 net::NetworkChangeNotifier::ConnectionType type) OVERRIDE { 102 process_host_->Send(new PpapiMsg_SetNetworkState( 103 type != net::NetworkChangeNotifier::CONNECTION_NONE)); 104 } 105 106 private: 107 PpapiPluginProcessHost* const process_host_; 108}; 109 110PpapiPluginProcessHost::~PpapiPluginProcessHost() { 111 DVLOG(1) << "PpapiPluginProcessHost" << (is_broker_ ? "[broker]" : "") 112 << "~PpapiPluginProcessHost()"; 113 CancelRequests(); 114} 115 116// static 117PpapiPluginProcessHost* PpapiPluginProcessHost::CreatePluginHost( 118 const PepperPluginInfo& info, 119 const base::FilePath& profile_data_directory, 120 net::HostResolver* host_resolver) { 121 PpapiPluginProcessHost* plugin_host = new PpapiPluginProcessHost( 122 info, profile_data_directory, host_resolver); 123 if (plugin_host->Init(info)) 124 return plugin_host; 125 126 NOTREACHED(); // Init is not expected to fail. 127 return NULL; 128} 129 130// static 131PpapiPluginProcessHost* PpapiPluginProcessHost::CreateBrokerHost( 132 const PepperPluginInfo& info) { 133 PpapiPluginProcessHost* plugin_host = 134 new PpapiPluginProcessHost(); 135 if (plugin_host->Init(info)) 136 return plugin_host; 137 138 NOTREACHED(); // Init is not expected to fail. 139 return NULL; 140} 141 142// static 143void PpapiPluginProcessHost::DidCreateOutOfProcessInstance( 144 int plugin_process_id, 145 int32 pp_instance, 146 const PepperRendererInstanceData& instance_data) { 147 for (PpapiPluginProcessHostIterator iter; !iter.Done(); ++iter) { 148 if (iter->process_.get() && 149 iter->process_->GetData().id == plugin_process_id) { 150 // Found the plugin. 151 iter->host_impl_->AddInstance(pp_instance, instance_data); 152 return; 153 } 154 } 155 // We'll see this passed with a 0 process ID for the browser tag stuff that 156 // is currently in the process of being removed. 157 // 158 // TODO(brettw) When old browser tag impl is removed 159 // (PepperPluginDelegateImpl::CreateBrowserPluginModule passes a 0 plugin 160 // process ID) this should be converted to a NOTREACHED(). 161 DCHECK(plugin_process_id == 0) 162 << "Renderer sent a bad plugin process host ID"; 163} 164 165// static 166void PpapiPluginProcessHost::DidDeleteOutOfProcessInstance( 167 int plugin_process_id, 168 int32 pp_instance) { 169 for (PpapiPluginProcessHostIterator iter; !iter.Done(); ++iter) { 170 if (iter->process_.get() && 171 iter->process_->GetData().id == plugin_process_id) { 172 // Found the plugin. 173 iter->host_impl_->DeleteInstance(pp_instance); 174 return; 175 } 176 } 177 // Note: It's possible that the plugin process has already been deleted by 178 // the time this message is received. For example, it could have crashed. 179 // That's OK, we can just ignore this message. 180} 181 182// static 183void PpapiPluginProcessHost::FindByName( 184 const string16& name, 185 std::vector<PpapiPluginProcessHost*>* hosts) { 186 for (PpapiPluginProcessHostIterator iter; !iter.Done(); ++iter) { 187 if (iter->process_.get() && iter->process_->GetData().name == name) 188 hosts->push_back(*iter); 189 } 190} 191 192bool PpapiPluginProcessHost::Send(IPC::Message* message) { 193 return process_->Send(message); 194} 195 196void PpapiPluginProcessHost::OpenChannelToPlugin(Client* client) { 197 if (process_->GetHost()->IsChannelOpening()) { 198 // The channel is already in the process of being opened. Put 199 // this "open channel" request into a queue of requests that will 200 // be run once the channel is open. 201 pending_requests_.push_back(client); 202 return; 203 } 204 205 // We already have an open channel, send a request right away to plugin. 206 RequestPluginChannel(client); 207} 208 209PpapiPluginProcessHost::PpapiPluginProcessHost( 210 const PepperPluginInfo& info, 211 const base::FilePath& profile_data_directory, 212 net::HostResolver* host_resolver) 213 : permissions_( 214 ppapi::PpapiPermissions::GetForCommandLine(info.permissions)), 215 profile_data_directory_(profile_data_directory), 216 is_broker_(false) { 217 process_.reset(new BrowserChildProcessHostImpl( 218 PROCESS_TYPE_PPAPI_PLUGIN, this)); 219 220 filter_ = new PepperMessageFilter(permissions_, host_resolver); 221 222 host_impl_.reset(new BrowserPpapiHostImpl(this, permissions_, info.name, 223 info.path, profile_data_directory, 224 false)); 225 226 process_->GetHost()->AddFilter(filter_.get()); 227 process_->GetHost()->AddFilter(host_impl_->message_filter().get()); 228 229 GetContentClient()->browser()->DidCreatePpapiPlugin(host_impl_.get()); 230 231 // Only request network status updates if the plugin has dev permissions. 232 if (permissions_.HasPermission(ppapi::PERMISSION_DEV)) 233 network_observer_.reset(new PluginNetworkObserver(this)); 234} 235 236PpapiPluginProcessHost::PpapiPluginProcessHost() 237 : is_broker_(true) { 238 process_.reset(new BrowserChildProcessHostImpl( 239 PROCESS_TYPE_PPAPI_BROKER, this)); 240 241 ppapi::PpapiPermissions permissions; // No permissions. 242 // The plugin name, path and profile data directory shouldn't be needed for 243 // the broker. 244 host_impl_.reset(new BrowserPpapiHostImpl(this, permissions, 245 std::string(), base::FilePath(), 246 base::FilePath(), 247 false)); 248} 249 250bool PpapiPluginProcessHost::Init(const PepperPluginInfo& info) { 251 plugin_path_ = info.path; 252 if (info.name.empty()) { 253 process_->SetName(plugin_path_.BaseName().LossyDisplayName()); 254 } else { 255 process_->SetName(UTF8ToUTF16(info.name)); 256 } 257 258 std::string channel_id = process_->GetHost()->CreateChannel(); 259 if (channel_id.empty()) 260 return false; 261 262 const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess(); 263 CommandLine::StringType plugin_launcher = 264 browser_command_line.GetSwitchValueNative(switches::kPpapiPluginLauncher); 265 266#if defined(OS_LINUX) 267 int flags = plugin_launcher.empty() ? ChildProcessHost::CHILD_ALLOW_SELF : 268 ChildProcessHost::CHILD_NORMAL; 269#else 270 int flags = ChildProcessHost::CHILD_NORMAL; 271#endif 272 base::FilePath exe_path = ChildProcessHost::GetChildPath(flags); 273 if (exe_path.empty()) 274 return false; 275 276 CommandLine* cmd_line = new CommandLine(exe_path); 277 cmd_line->AppendSwitchASCII(switches::kProcessType, 278 is_broker_ ? switches::kPpapiBrokerProcess 279 : switches::kPpapiPluginProcess); 280 cmd_line->AppendSwitchASCII(switches::kProcessChannelID, channel_id); 281 282 // These switches are forwarded to both plugin and broker pocesses. 283 static const char* kCommonForwardSwitches[] = { 284 switches::kVModule 285 }; 286 cmd_line->CopySwitchesFrom(browser_command_line, kCommonForwardSwitches, 287 arraysize(kCommonForwardSwitches)); 288 289 if (!is_broker_) { 290 static const char* kPluginForwardSwitches[] = { 291 switches::kDisableSeccompFilterSandbox, 292#if defined(OS_MACOSX) 293 switches::kEnableSandboxLogging, 294#endif 295 switches::kNoSandbox, 296 switches::kPpapiStartupDialog, 297 }; 298 cmd_line->CopySwitchesFrom(browser_command_line, kPluginForwardSwitches, 299 arraysize(kPluginForwardSwitches)); 300 301 // Copy any flash args over and introduce field trials if necessary. 302 // TODO(vtl): Stop passing flash args in the command line, or windows is 303 // going to explode. 304 std::string field_trial = 305 base::FieldTrialList::FindFullName(kLowLatencyFlashAudioFieldTrialName); 306 std::string existing_args = 307 browser_command_line.GetSwitchValueASCII(switches::kPpapiFlashArgs); 308 if (field_trial == kLowLatencyFlashAudioFieldTrialEnabledName) 309 existing_args.append(" enable_low_latency_audio=1"); 310 cmd_line->AppendSwitchASCII(switches::kPpapiFlashArgs, existing_args); 311 } 312 313 std::string locale = GetContentClient()->browser()->GetApplicationLocale(); 314 if (!locale.empty()) { 315 // Pass on the locale so the plugin will know what language we're using. 316 cmd_line->AppendSwitchASCII(switches::kLang, locale); 317 } 318 319 if (!plugin_launcher.empty()) 320 cmd_line->PrependWrapper(plugin_launcher); 321 322 // On posix, never use the zygote for the broker. Also, only use the zygote if 323 // the plugin is sandboxed, and we are not using a plugin launcher - having a 324 // plugin launcher means we need to use another process instead of just 325 // forking the zygote. 326#if defined(OS_POSIX) 327 bool use_zygote = !is_broker_ && plugin_launcher.empty() && info.is_sandboxed; 328 if (!info.is_sandboxed) 329 cmd_line->AppendSwitchASCII(switches::kNoSandbox, std::string()); 330#endif // OS_POSIX 331 process_->Launch( 332#if defined(OS_WIN) 333 new PpapiPluginSandboxedProcessLauncherDelegate(is_broker_), 334#elif defined(OS_POSIX) 335 use_zygote, 336 base::EnvironmentVector(), 337#endif 338 cmd_line); 339 return true; 340} 341 342void PpapiPluginProcessHost::RequestPluginChannel(Client* client) { 343 base::ProcessHandle process_handle; 344 int renderer_child_id; 345 client->GetPpapiChannelInfo(&process_handle, &renderer_child_id); 346 347 base::ProcessId process_id = (process_handle == base::kNullProcessHandle) ? 348 0 : base::GetProcId(process_handle); 349 350 // We can't send any sync messages from the browser because it might lead to 351 // a hang. See the similar code in PluginProcessHost for more description. 352 PpapiMsg_CreateChannel* msg = new PpapiMsg_CreateChannel( 353 process_id, renderer_child_id, client->OffTheRecord()); 354 msg->set_unblock(true); 355 if (Send(msg)) { 356 sent_requests_.push(client); 357 } else { 358 client->OnPpapiChannelOpened(IPC::ChannelHandle(), base::kNullProcessId, 0); 359 } 360} 361 362void PpapiPluginProcessHost::OnProcessLaunched() { 363 host_impl_->set_plugin_process_handle(process_->GetHandle()); 364} 365 366void PpapiPluginProcessHost::OnProcessCrashed(int exit_code) { 367 PluginServiceImpl::GetInstance()->RegisterPluginCrash(plugin_path_); 368} 369 370bool PpapiPluginProcessHost::OnMessageReceived(const IPC::Message& msg) { 371 bool handled = true; 372 IPC_BEGIN_MESSAGE_MAP(PpapiPluginProcessHost, msg) 373 IPC_MESSAGE_HANDLER(PpapiHostMsg_ChannelCreated, 374 OnRendererPluginChannelCreated) 375 IPC_MESSAGE_UNHANDLED(handled = false) 376 IPC_END_MESSAGE_MAP() 377 DCHECK(handled); 378 return handled; 379} 380 381// Called when the browser <--> plugin channel has been established. 382void PpapiPluginProcessHost::OnChannelConnected(int32 peer_pid) { 383 // This will actually load the plugin. Errors will actually not be reported 384 // back at this point. Instead, the plugin will fail to establish the 385 // connections when we request them on behalf of the renderer(s). 386 Send(new PpapiMsg_LoadPlugin(plugin_path_, permissions_)); 387 388 // Process all pending channel requests from the renderers. 389 for (size_t i = 0; i < pending_requests_.size(); i++) 390 RequestPluginChannel(pending_requests_[i]); 391 pending_requests_.clear(); 392} 393 394// Called when the browser <--> plugin channel has an error. This normally 395// means the plugin has crashed. 396void PpapiPluginProcessHost::OnChannelError() { 397 DVLOG(1) << "PpapiPluginProcessHost" << (is_broker_ ? "[broker]" : "") 398 << "::OnChannelError()"; 399 // We don't need to notify the renderers that were communicating with the 400 // plugin since they have their own channels which will go into the error 401 // state at the same time. Instead, we just need to notify any renderers 402 // that have requested a connection but have not yet received one. 403 CancelRequests(); 404} 405 406void PpapiPluginProcessHost::CancelRequests() { 407 DVLOG(1) << "PpapiPluginProcessHost" << (is_broker_ ? "[broker]" : "") 408 << "CancelRequests()"; 409 for (size_t i = 0; i < pending_requests_.size(); i++) { 410 pending_requests_[i]->OnPpapiChannelOpened(IPC::ChannelHandle(), 411 base::kNullProcessId, 0); 412 } 413 pending_requests_.clear(); 414 415 while (!sent_requests_.empty()) { 416 sent_requests_.front()->OnPpapiChannelOpened(IPC::ChannelHandle(), 417 base::kNullProcessId, 0); 418 sent_requests_.pop(); 419 } 420} 421 422// Called when a new plugin <--> renderer channel has been created. 423void PpapiPluginProcessHost::OnRendererPluginChannelCreated( 424 const IPC::ChannelHandle& channel_handle) { 425 if (sent_requests_.empty()) 426 return; 427 428 // All requests should be processed FIFO, so the next item in the 429 // sent_requests_ queue should be the one that the plugin just created. 430 Client* client = sent_requests_.front(); 431 sent_requests_.pop(); 432 433 const ChildProcessData& data = process_->GetData(); 434 client->OnPpapiChannelOpened(channel_handle, base::GetProcId(data.handle), 435 data.id); 436} 437 438} // namespace content 439