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