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