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 "ipc/ipc_switches.h" 25 #include "net/base/network_change_notifier.h" 26 #include "ppapi/proxy/ppapi_messages.h" 27 #include "ui/base/ui_base_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 35 namespace content { 36 37 #if defined(OS_WIN) 38 // NOTE: changes to this class need to be reviewed by the security team. 39 class 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 71 class 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 108 PpapiPluginProcessHost::~PpapiPluginProcessHost() { 109 DVLOG(1) << "PpapiPluginProcessHost" << (is_broker_ ? "[broker]" : "") 110 << "~PpapiPluginProcessHost()"; 111 CancelRequests(); 112 } 113 114 // static 115 PpapiPluginProcessHost* PpapiPluginProcessHost::CreatePluginHost( 116 const PepperPluginInfo& info, 117 const base::FilePath& profile_data_directory) { 118 PpapiPluginProcessHost* plugin_host = new PpapiPluginProcessHost( 119 info, profile_data_directory); 120 DCHECK(plugin_host); 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 129 PpapiPluginProcessHost* 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 141 void 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 164 void 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 181 void PpapiPluginProcessHost::FindByName( 182 const base::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 190 bool PpapiPluginProcessHost::Send(IPC::Message* message) { 191 return process_->Send(message); 192 } 193 194 void 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 207 PpapiPluginProcessHost::PpapiPluginProcessHost( 208 const PepperPluginInfo& info, 209 const base::FilePath& profile_data_directory) 210 : permissions_( 211 ppapi::PpapiPermissions::GetForCommandLine(info.permissions)), 212 profile_data_directory_(profile_data_directory), 213 is_broker_(false) { 214 process_.reset(new BrowserChildProcessHostImpl( 215 PROCESS_TYPE_PPAPI_PLUGIN, this)); 216 217 host_impl_.reset(new BrowserPpapiHostImpl(this, permissions_, info.name, 218 info.path, profile_data_directory, 219 false /* in_process */, 220 false /* external_plugin */)); 221 222 filter_ = new PepperMessageFilter(); 223 process_->AddFilter(filter_.get()); 224 process_->GetHost()->AddFilter(host_impl_->message_filter().get()); 225 226 GetContentClient()->browser()->DidCreatePpapiPlugin(host_impl_.get()); 227 228 // Only request network status updates if the plugin has dev permissions. 229 if (permissions_.HasPermission(ppapi::PERMISSION_DEV)) 230 network_observer_.reset(new PluginNetworkObserver(this)); 231 } 232 233 PpapiPluginProcessHost::PpapiPluginProcessHost() 234 : is_broker_(true) { 235 process_.reset(new BrowserChildProcessHostImpl( 236 PROCESS_TYPE_PPAPI_BROKER, this)); 237 238 ppapi::PpapiPermissions permissions; // No permissions. 239 // The plugin name, path and profile data directory shouldn't be needed for 240 // the broker. 241 host_impl_.reset(new BrowserPpapiHostImpl(this, permissions, 242 std::string(), base::FilePath(), 243 base::FilePath(), 244 false /* in_process */, 245 false /* external_plugin */)); 246 } 247 248 bool 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 VLOG(1) << "Could not create pepper host channel."; 259 return false; 260 } 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 VLOG(1) << "Pepper plugin exe path is empty."; 275 return false; 276 } 277 278 CommandLine* cmd_line = new CommandLine(exe_path); 279 cmd_line->AppendSwitchASCII(switches::kProcessType, 280 is_broker_ ? switches::kPpapiBrokerProcess 281 : switches::kPpapiPluginProcess); 282 cmd_line->AppendSwitchASCII(switches::kProcessChannelID, channel_id); 283 284 // These switches are forwarded to both plugin and broker pocesses. 285 static const char* kCommonForwardSwitches[] = { 286 switches::kVModule 287 }; 288 cmd_line->CopySwitchesFrom(browser_command_line, kCommonForwardSwitches, 289 arraysize(kCommonForwardSwitches)); 290 291 if (!is_broker_) { 292 static const char* kPluginForwardSwitches[] = { 293 switches::kDisableSeccompFilterSandbox, 294 #if defined(OS_MACOSX) 295 switches::kEnableSandboxLogging, 296 #endif 297 switches::kNoSandbox, 298 switches::kPpapiStartupDialog, 299 }; 300 cmd_line->CopySwitchesFrom(browser_command_line, kPluginForwardSwitches, 301 arraysize(kPluginForwardSwitches)); 302 303 // Copy any flash args over and introduce field trials if necessary. 304 // TODO(vtl): Stop passing flash args in the command line, or windows is 305 // going to explode. 306 std::string field_trial = 307 base::FieldTrialList::FindFullName(kFlashHwVideoDecodeFieldTrialName); 308 std::string existing_args = 309 browser_command_line.GetSwitchValueASCII(switches::kPpapiFlashArgs); 310 if (field_trial == kFlashHwVideoDecodeFieldTrialEnabledName) { 311 // Arguments passed to Flash are comma delimited. 312 if (!existing_args.empty()) 313 existing_args.append(","); 314 existing_args.append("enable_hw_video_decode=1"); 315 } 316 cmd_line->AppendSwitchASCII(switches::kPpapiFlashArgs, existing_args); 317 } 318 319 std::string locale = GetContentClient()->browser()->GetApplicationLocale(); 320 if (!locale.empty()) { 321 // Pass on the locale so the plugin will know what language we're using. 322 cmd_line->AppendSwitchASCII(switches::kLang, locale); 323 } 324 325 if (!plugin_launcher.empty()) 326 cmd_line->PrependWrapper(plugin_launcher); 327 328 // On posix, never use the zygote for the broker. Also, only use the zygote if 329 // the plugin is sandboxed, and we are not using a plugin launcher - having a 330 // plugin launcher means we need to use another process instead of just 331 // forking the zygote. 332 #if defined(OS_POSIX) 333 bool use_zygote = !is_broker_ && plugin_launcher.empty() && info.is_sandboxed; 334 if (!info.is_sandboxed) 335 cmd_line->AppendSwitchASCII(switches::kNoSandbox, std::string()); 336 #endif // OS_POSIX 337 process_->Launch( 338 #if defined(OS_WIN) 339 new PpapiPluginSandboxedProcessLauncherDelegate(is_broker_), 340 #elif defined(OS_POSIX) 341 use_zygote, 342 base::EnvironmentMap(), 343 #endif 344 cmd_line); 345 return true; 346 } 347 348 void PpapiPluginProcessHost::RequestPluginChannel(Client* client) { 349 base::ProcessHandle process_handle; 350 int renderer_child_id; 351 client->GetPpapiChannelInfo(&process_handle, &renderer_child_id); 352 353 base::ProcessId process_id = (process_handle == base::kNullProcessHandle) ? 354 0 : base::GetProcId(process_handle); 355 356 // We can't send any sync messages from the browser because it might lead to 357 // a hang. See the similar code in PluginProcessHost for more description. 358 PpapiMsg_CreateChannel* msg = new PpapiMsg_CreateChannel( 359 process_id, renderer_child_id, client->OffTheRecord()); 360 msg->set_unblock(true); 361 if (Send(msg)) { 362 sent_requests_.push(client); 363 } else { 364 client->OnPpapiChannelOpened(IPC::ChannelHandle(), base::kNullProcessId, 0); 365 } 366 } 367 368 void PpapiPluginProcessHost::OnProcessLaunched() { 369 VLOG(2) << "ppapi plugin process launched."; 370 host_impl_->set_plugin_process_handle(process_->GetHandle()); 371 } 372 373 void PpapiPluginProcessHost::OnProcessCrashed(int exit_code) { 374 VLOG(1) << "ppapi plugin process crashed."; 375 PluginServiceImpl::GetInstance()->RegisterPluginCrash(plugin_path_); 376 } 377 378 bool PpapiPluginProcessHost::OnMessageReceived(const IPC::Message& msg) { 379 bool handled = true; 380 IPC_BEGIN_MESSAGE_MAP(PpapiPluginProcessHost, msg) 381 IPC_MESSAGE_HANDLER(PpapiHostMsg_ChannelCreated, 382 OnRendererPluginChannelCreated) 383 IPC_MESSAGE_UNHANDLED(handled = false) 384 IPC_END_MESSAGE_MAP() 385 DCHECK(handled); 386 return handled; 387 } 388 389 // Called when the browser <--> plugin channel has been established. 390 void PpapiPluginProcessHost::OnChannelConnected(int32 peer_pid) { 391 bool supports_dev_channel = 392 GetContentClient()->browser()->IsPluginAllowedToUseDevChannelAPIs(); 393 // This will actually load the plugin. Errors will actually not be reported 394 // back at this point. Instead, the plugin will fail to establish the 395 // connections when we request them on behalf of the renderer(s). 396 Send(new PpapiMsg_LoadPlugin(plugin_path_, permissions_, 397 supports_dev_channel)); 398 399 // Process all pending channel requests from the renderers. 400 for (size_t i = 0; i < pending_requests_.size(); i++) 401 RequestPluginChannel(pending_requests_[i]); 402 pending_requests_.clear(); 403 } 404 405 // Called when the browser <--> plugin channel has an error. This normally 406 // means the plugin has crashed. 407 void PpapiPluginProcessHost::OnChannelError() { 408 VLOG(1) << "PpapiPluginProcessHost" << (is_broker_ ? "[broker]" : "") 409 << "::OnChannelError()"; 410 // We don't need to notify the renderers that were communicating with the 411 // plugin since they have their own channels which will go into the error 412 // state at the same time. Instead, we just need to notify any renderers 413 // that have requested a connection but have not yet received one. 414 CancelRequests(); 415 } 416 417 void PpapiPluginProcessHost::CancelRequests() { 418 DVLOG(1) << "PpapiPluginProcessHost" << (is_broker_ ? "[broker]" : "") 419 << "CancelRequests()"; 420 for (size_t i = 0; i < pending_requests_.size(); i++) { 421 pending_requests_[i]->OnPpapiChannelOpened(IPC::ChannelHandle(), 422 base::kNullProcessId, 0); 423 } 424 pending_requests_.clear(); 425 426 while (!sent_requests_.empty()) { 427 sent_requests_.front()->OnPpapiChannelOpened(IPC::ChannelHandle(), 428 base::kNullProcessId, 0); 429 sent_requests_.pop(); 430 } 431 } 432 433 // Called when a new plugin <--> renderer channel has been created. 434 void PpapiPluginProcessHost::OnRendererPluginChannelCreated( 435 const IPC::ChannelHandle& channel_handle) { 436 if (sent_requests_.empty()) 437 return; 438 439 // All requests should be processed FIFO, so the next item in the 440 // sent_requests_ queue should be the one that the plugin just created. 441 Client* client = sent_requests_.front(); 442 sent_requests_.pop(); 443 444 const ChildProcessData& data = process_->GetData(); 445 client->OnPpapiChannelOpened(channel_handle, base::GetProcId(data.handle), 446 data.id); 447 } 448 449 } // namespace content 450