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 "chrome/browser/plugins/plugin_info_message_filter.h" 6 7 #include "base/bind.h" 8 #include "base/memory/scoped_ptr.h" 9 #include "base/metrics/histogram.h" 10 #include "base/prefs/pref_service.h" 11 #include "base/strings/utf_string_conversions.h" 12 #include "base/thread_task_runner_handle.h" 13 #include "chrome/browser/browser_process.h" 14 #include "chrome/browser/content_settings/content_settings_utils.h" 15 #include "chrome/browser/content_settings/host_content_settings_map.h" 16 #include "chrome/browser/plugins/chrome_plugin_service_filter.h" 17 #include "chrome/browser/plugins/plugin_finder.h" 18 #include "chrome/browser/plugins/plugin_metadata.h" 19 #include "chrome/browser/plugins/plugin_prefs.h" 20 #include "chrome/browser/profiles/profile.h" 21 #include "chrome/browser/ui/browser_otr_state.h" 22 #include "chrome/common/pref_names.h" 23 #include "chrome/common/render_messages.h" 24 #include "components/content_settings/core/common/content_settings.h" 25 #include "components/rappor/rappor_service.h" 26 #include "content/public/browser/browser_thread.h" 27 #include "content/public/browser/plugin_service.h" 28 #include "content/public/browser/plugin_service_filter.h" 29 #include "content/public/common/content_constants.h" 30 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" 31 #include "url/gurl.h" 32 33 #include "widevine_cdm_version.h" // In SHARED_INTERMEDIATE_DIR. 34 35 #if defined(ENABLE_EXTENSIONS) 36 #include "extensions/browser/guest_view/web_view/web_view_renderer_state.h" 37 #endif 38 39 #if defined(OS_WIN) 40 #include "base/win/metro.h" 41 #endif 42 43 #if !defined(DISABLE_NACL) 44 #include "components/nacl/common/nacl_constants.h" 45 #endif 46 47 using content::PluginService; 48 using content::WebPluginInfo; 49 50 namespace { 51 52 // For certain sandboxed Pepper plugins, use the JavaScript Content Settings. 53 bool ShouldUseJavaScriptSettingForPlugin(const WebPluginInfo& plugin) { 54 if (plugin.type != WebPluginInfo::PLUGIN_TYPE_PEPPER_IN_PROCESS && 55 plugin.type != WebPluginInfo::PLUGIN_TYPE_PEPPER_OUT_OF_PROCESS) { 56 return false; 57 } 58 59 #if !defined(DISABLE_NACL) 60 // Treat Native Client invocations like JavaScript. 61 if (plugin.name == base::ASCIIToUTF16(nacl::kNaClPluginName)) 62 return true; 63 #endif 64 65 #if defined(WIDEVINE_CDM_AVAILABLE) && defined(ENABLE_PEPPER_CDMS) 66 // Treat CDM invocations like JavaScript. 67 if (plugin.name == base::ASCIIToUTF16(kWidevineCdmDisplayName)) { 68 DCHECK(plugin.type == WebPluginInfo::PLUGIN_TYPE_PEPPER_OUT_OF_PROCESS); 69 return true; 70 } 71 #endif // defined(WIDEVINE_CDM_AVAILABLE) && defined(ENABLE_PEPPER_CDMS) 72 73 return false; 74 } 75 76 #if defined(ENABLE_PEPPER_CDMS) 77 78 enum PluginAvailabilityStatusForUMA { 79 PLUGIN_NOT_REGISTERED, 80 PLUGIN_AVAILABLE, 81 PLUGIN_DISABLED, 82 PLUGIN_AVAILABILITY_STATUS_MAX 83 }; 84 85 static void SendPluginAvailabilityUMA(const std::string& mime_type, 86 PluginAvailabilityStatusForUMA status) { 87 #if defined(WIDEVINE_CDM_AVAILABLE) 88 // Only report results for Widevine CDM. 89 if (mime_type != kWidevineCdmPluginMimeType) 90 return; 91 92 UMA_HISTOGRAM_ENUMERATION("Plugin.AvailabilityStatus.WidevineCdm", 93 status, PLUGIN_AVAILABILITY_STATUS_MAX); 94 #endif // defined(WIDEVINE_CDM_AVAILABLE) 95 } 96 97 #endif // defined(ENABLE_PEPPER_CDMS) 98 99 // Report usage metrics for Silverlight and Flash plugin instantiations to the 100 // RAPPOR service. 101 void ReportMetrics(const std::string& mime_type, 102 const GURL& url, 103 const GURL& origin_url) { 104 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 105 106 if (chrome::IsOffTheRecordSessionActive()) 107 return; 108 rappor::RapporService* rappor_service = g_browser_process->rappor_service(); 109 if (!rappor_service) 110 return; 111 112 if (StartsWithASCII(mime_type, content::kSilverlightPluginMimeTypePrefix, 113 false)) { 114 rappor_service->RecordSample( 115 "Plugins.SilverlightOriginUrl", rappor::ETLD_PLUS_ONE_RAPPOR_TYPE, 116 net::registry_controlled_domains::GetDomainAndRegistry( 117 origin_url, 118 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)); 119 } else if (mime_type == content::kFlashPluginSwfMimeType || 120 mime_type == content::kFlashPluginSplMimeType) { 121 rappor_service->RecordSample( 122 "Plugins.FlashOriginUrl", rappor::ETLD_PLUS_ONE_RAPPOR_TYPE, 123 net::registry_controlled_domains::GetDomainAndRegistry( 124 origin_url, 125 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)); 126 rappor_service->RecordSample( 127 "Plugins.FlashUrl", rappor::ETLD_PLUS_ONE_RAPPOR_TYPE, 128 net::registry_controlled_domains::GetDomainAndRegistry( 129 url, 130 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)); 131 } 132 } 133 134 } // namespace 135 136 PluginInfoMessageFilter::Context::Context(int render_process_id, 137 Profile* profile) 138 : render_process_id_(render_process_id), 139 resource_context_(profile->GetResourceContext()), 140 host_content_settings_map_(profile->GetHostContentSettingsMap()), 141 plugin_prefs_(PluginPrefs::GetForProfile(profile)) { 142 allow_outdated_plugins_.Init(prefs::kPluginsAllowOutdated, 143 profile->GetPrefs()); 144 allow_outdated_plugins_.MoveToThread( 145 content::BrowserThread::GetMessageLoopProxyForThread( 146 content::BrowserThread::IO)); 147 always_authorize_plugins_.Init(prefs::kPluginsAlwaysAuthorize, 148 profile->GetPrefs()); 149 always_authorize_plugins_.MoveToThread( 150 content::BrowserThread::GetMessageLoopProxyForThread( 151 content::BrowserThread::IO)); 152 } 153 154 PluginInfoMessageFilter::Context::~Context() { 155 } 156 157 PluginInfoMessageFilter::PluginInfoMessageFilter(int render_process_id, 158 Profile* profile) 159 : BrowserMessageFilter(ChromeMsgStart), 160 context_(render_process_id, profile), 161 weak_ptr_factory_(this), 162 main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()) { 163 } 164 165 bool PluginInfoMessageFilter::OnMessageReceived(const IPC::Message& message) { 166 IPC_BEGIN_MESSAGE_MAP(PluginInfoMessageFilter, message) 167 IPC_MESSAGE_HANDLER_DELAY_REPLY(ChromeViewHostMsg_GetPluginInfo, 168 OnGetPluginInfo) 169 #if defined(ENABLE_PEPPER_CDMS) 170 IPC_MESSAGE_HANDLER( 171 ChromeViewHostMsg_IsInternalPluginAvailableForMimeType, 172 OnIsInternalPluginAvailableForMimeType) 173 #endif 174 IPC_MESSAGE_UNHANDLED(return false) 175 IPC_END_MESSAGE_MAP() 176 return true; 177 } 178 179 void PluginInfoMessageFilter::OnDestruct() const { 180 const_cast<PluginInfoMessageFilter*>(this)-> 181 weak_ptr_factory_.InvalidateWeakPtrs(); 182 183 // Destroy on the UI thread because we contain a |PrefMember|. 184 content::BrowserThread::DeleteOnUIThread::Destruct(this); 185 } 186 187 PluginInfoMessageFilter::~PluginInfoMessageFilter() {} 188 189 struct PluginInfoMessageFilter::GetPluginInfo_Params { 190 int render_frame_id; 191 GURL url; 192 GURL top_origin_url; 193 std::string mime_type; 194 }; 195 196 void PluginInfoMessageFilter::OnGetPluginInfo( 197 int render_frame_id, 198 const GURL& url, 199 const GURL& top_origin_url, 200 const std::string& mime_type, 201 IPC::Message* reply_msg) { 202 GetPluginInfo_Params params = { 203 render_frame_id, 204 url, 205 top_origin_url, 206 mime_type 207 }; 208 PluginService::GetInstance()->GetPlugins( 209 base::Bind(&PluginInfoMessageFilter::PluginsLoaded, 210 weak_ptr_factory_.GetWeakPtr(), 211 params, reply_msg)); 212 } 213 214 void PluginInfoMessageFilter::PluginsLoaded( 215 const GetPluginInfo_Params& params, 216 IPC::Message* reply_msg, 217 const std::vector<WebPluginInfo>& plugins) { 218 ChromeViewHostMsg_GetPluginInfo_Output output; 219 // This also fills in |actual_mime_type|. 220 scoped_ptr<PluginMetadata> plugin_metadata; 221 if (context_.FindEnabledPlugin(params.render_frame_id, params.url, 222 params.top_origin_url, params.mime_type, 223 &output.status, &output.plugin, 224 &output.actual_mime_type, 225 &plugin_metadata)) { 226 context_.DecidePluginStatus(params, output.plugin, plugin_metadata.get(), 227 &output.status); 228 } 229 230 if (plugin_metadata) { 231 output.group_identifier = plugin_metadata->identifier(); 232 output.group_name = plugin_metadata->name(); 233 } 234 235 context_.MaybeGrantAccess(output.status, output.plugin.path); 236 237 ChromeViewHostMsg_GetPluginInfo::WriteReplyParams(reply_msg, output); 238 Send(reply_msg); 239 if (output.status.value != 240 ChromeViewHostMsg_GetPluginInfo_Status::kNotFound) { 241 main_thread_task_runner_->PostTask( 242 FROM_HERE, base::Bind(&ReportMetrics, output.actual_mime_type, 243 params.url, params.top_origin_url)); 244 } 245 } 246 247 #if defined(ENABLE_PEPPER_CDMS) 248 249 void PluginInfoMessageFilter::OnIsInternalPluginAvailableForMimeType( 250 const std::string& mime_type, 251 bool* is_available, 252 std::vector<base::string16>* additional_param_names, 253 std::vector<base::string16>* additional_param_values) { 254 std::vector<WebPluginInfo> plugins; 255 PluginService::GetInstance()->GetInternalPlugins(&plugins); 256 257 bool is_plugin_disabled = false; 258 for (size_t i = 0; i < plugins.size(); ++i) { 259 const WebPluginInfo& plugin = plugins[i]; 260 const std::vector<content::WebPluginMimeType>& mime_types = 261 plugin.mime_types; 262 for (size_t j = 0; j < mime_types.size(); ++j) { 263 if (mime_types[j].mime_type == mime_type) { 264 if (!context_.IsPluginEnabled(plugin)) { 265 is_plugin_disabled = true; 266 break; 267 } 268 269 *is_available = true; 270 *additional_param_names = mime_types[j].additional_param_names; 271 *additional_param_values = mime_types[j].additional_param_values; 272 SendPluginAvailabilityUMA(mime_type, PLUGIN_AVAILABLE); 273 return; 274 } 275 } 276 } 277 278 *is_available = false; 279 SendPluginAvailabilityUMA( 280 mime_type, is_plugin_disabled ? PLUGIN_DISABLED : PLUGIN_NOT_REGISTERED); 281 } 282 283 #endif // defined(ENABLE_PEPPER_CDMS) 284 285 void PluginInfoMessageFilter::Context::DecidePluginStatus( 286 const GetPluginInfo_Params& params, 287 const WebPluginInfo& plugin, 288 const PluginMetadata* plugin_metadata, 289 ChromeViewHostMsg_GetPluginInfo_Status* status) const { 290 #if defined(OS_WIN) 291 if (plugin.type == WebPluginInfo::PLUGIN_TYPE_NPAPI && 292 base::win::IsMetroProcess()) { 293 status->value = 294 ChromeViewHostMsg_GetPluginInfo_Status::kNPAPINotSupported; 295 return; 296 } 297 #endif 298 if (plugin.type == WebPluginInfo::PLUGIN_TYPE_NPAPI) { 299 CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 300 // NPAPI plugins are not supported inside <webview> guests. 301 #if defined(ENABLE_EXTENSIONS) 302 if (extensions::WebViewRendererState::GetInstance()->IsGuest( 303 render_process_id_)) { 304 status->value = 305 ChromeViewHostMsg_GetPluginInfo_Status::kNPAPINotSupported; 306 return; 307 } 308 #endif 309 } 310 311 ContentSetting plugin_setting = CONTENT_SETTING_DEFAULT; 312 bool uses_default_content_setting = true; 313 bool is_managed = false; 314 // Check plug-in content settings. The primary URL is the top origin URL and 315 // the secondary URL is the plug-in URL. 316 GetPluginContentSetting(plugin, params.top_origin_url, params.url, 317 plugin_metadata->identifier(), &plugin_setting, 318 &uses_default_content_setting, &is_managed); 319 DCHECK(plugin_setting != CONTENT_SETTING_DEFAULT); 320 321 PluginMetadata::SecurityStatus plugin_status = 322 plugin_metadata->GetSecurityStatus(plugin); 323 #if defined(ENABLE_PLUGIN_INSTALLATION) 324 // Check if the plug-in is outdated. 325 if (plugin_status == PluginMetadata::SECURITY_STATUS_OUT_OF_DATE && 326 !allow_outdated_plugins_.GetValue()) { 327 if (allow_outdated_plugins_.IsManaged()) { 328 status->value = 329 ChromeViewHostMsg_GetPluginInfo_Status::kOutdatedDisallowed; 330 } else { 331 status->value = ChromeViewHostMsg_GetPluginInfo_Status::kOutdatedBlocked; 332 } 333 return; 334 } 335 #endif 336 // Check if the plug-in or its group is enabled by policy. 337 PluginPrefs::PolicyStatus plugin_policy = 338 plugin_prefs_->PolicyStatusForPlugin(plugin.name); 339 PluginPrefs::PolicyStatus group_policy = 340 plugin_prefs_->PolicyStatusForPlugin(plugin_metadata->name()); 341 342 // Check if the plug-in requires authorization. 343 if (plugin_status == 344 PluginMetadata::SECURITY_STATUS_REQUIRES_AUTHORIZATION && 345 plugin.type != WebPluginInfo::PLUGIN_TYPE_PEPPER_IN_PROCESS && 346 plugin.type != WebPluginInfo::PLUGIN_TYPE_PEPPER_OUT_OF_PROCESS && 347 !always_authorize_plugins_.GetValue() && 348 plugin_setting != CONTENT_SETTING_BLOCK && 349 uses_default_content_setting && 350 plugin_policy != PluginPrefs::POLICY_ENABLED && 351 group_policy != PluginPrefs::POLICY_ENABLED && 352 !ChromePluginServiceFilter::GetInstance()->IsPluginRestricted( 353 plugin.path)) { 354 status->value = ChromeViewHostMsg_GetPluginInfo_Status::kUnauthorized; 355 return; 356 } 357 358 // Check if the plug-in is crashing too much. 359 if (PluginService::GetInstance()->IsPluginUnstable(plugin.path) && 360 !always_authorize_plugins_.GetValue() && 361 plugin_setting != CONTENT_SETTING_BLOCK && 362 uses_default_content_setting) { 363 status->value = ChromeViewHostMsg_GetPluginInfo_Status::kUnauthorized; 364 return; 365 } 366 367 if (plugin_setting == CONTENT_SETTING_ASK) { 368 status->value = ChromeViewHostMsg_GetPluginInfo_Status::kClickToPlay; 369 } else if (plugin_setting == CONTENT_SETTING_BLOCK) { 370 status->value = 371 is_managed ? ChromeViewHostMsg_GetPluginInfo_Status::kBlockedByPolicy 372 : ChromeViewHostMsg_GetPluginInfo_Status::kBlocked; 373 } 374 375 if (status->value == ChromeViewHostMsg_GetPluginInfo_Status::kAllowed) { 376 // Allow an embedder of <webview> to block a plugin from being loaded inside 377 // the guest. In order to do this, set the status to 'Unauthorized' here, 378 // and update the status as appropriate depending on the response from the 379 // embedder. 380 #if defined(ENABLE_EXTENSIONS) 381 if (extensions::WebViewRendererState::GetInstance()->IsGuest( 382 render_process_id_)) 383 status->value = ChromeViewHostMsg_GetPluginInfo_Status::kUnauthorized; 384 385 #endif 386 } 387 } 388 389 bool PluginInfoMessageFilter::Context::FindEnabledPlugin( 390 int render_frame_id, 391 const GURL& url, 392 const GURL& top_origin_url, 393 const std::string& mime_type, 394 ChromeViewHostMsg_GetPluginInfo_Status* status, 395 WebPluginInfo* plugin, 396 std::string* actual_mime_type, 397 scoped_ptr<PluginMetadata>* plugin_metadata) const { 398 bool allow_wildcard = true; 399 std::vector<WebPluginInfo> matching_plugins; 400 std::vector<std::string> mime_types; 401 PluginService::GetInstance()->GetPluginInfoArray( 402 url, mime_type, allow_wildcard, &matching_plugins, &mime_types); 403 if (matching_plugins.empty()) { 404 status->value = ChromeViewHostMsg_GetPluginInfo_Status::kNotFound; 405 return false; 406 } 407 408 content::PluginServiceFilter* filter = 409 PluginService::GetInstance()->GetFilter(); 410 size_t i = 0; 411 for (; i < matching_plugins.size(); ++i) { 412 if (!filter || filter->IsPluginAvailable(render_process_id_, 413 render_frame_id, 414 resource_context_, 415 url, 416 top_origin_url, 417 &matching_plugins[i])) { 418 break; 419 } 420 } 421 422 // If we broke out of the loop, we have found an enabled plug-in. 423 bool enabled = i < matching_plugins.size(); 424 if (!enabled) { 425 // Otherwise, we only found disabled plug-ins, so we take the first one. 426 i = 0; 427 status->value = ChromeViewHostMsg_GetPluginInfo_Status::kDisabled; 428 } 429 430 *plugin = matching_plugins[i]; 431 *actual_mime_type = mime_types[i]; 432 if (plugin_metadata) 433 *plugin_metadata = PluginFinder::GetInstance()->GetPluginMetadata(*plugin); 434 435 return enabled; 436 } 437 438 void PluginInfoMessageFilter::Context::GetPluginContentSetting( 439 const WebPluginInfo& plugin, 440 const GURL& policy_url, 441 const GURL& plugin_url, 442 const std::string& resource, 443 ContentSetting* setting, 444 bool* uses_default_content_setting, 445 bool* is_managed) const { 446 scoped_ptr<base::Value> value; 447 content_settings::SettingInfo info; 448 bool uses_plugin_specific_setting = false; 449 if (ShouldUseJavaScriptSettingForPlugin(plugin)) { 450 value = host_content_settings_map_->GetWebsiteSetting( 451 policy_url, 452 policy_url, 453 CONTENT_SETTINGS_TYPE_JAVASCRIPT, 454 std::string(), 455 &info); 456 } else { 457 content_settings::SettingInfo specific_info; 458 scoped_ptr<base::Value> specific_setting = 459 host_content_settings_map_->GetWebsiteSetting( 460 policy_url, 461 plugin_url, 462 CONTENT_SETTINGS_TYPE_PLUGINS, 463 resource, 464 &specific_info); 465 content_settings::SettingInfo general_info; 466 scoped_ptr<base::Value> general_setting = 467 host_content_settings_map_->GetWebsiteSetting( 468 policy_url, 469 plugin_url, 470 CONTENT_SETTINGS_TYPE_PLUGINS, 471 std::string(), 472 &general_info); 473 474 // If there is a plugin-specific setting, we use it, unless the general 475 // setting was set by policy, in which case it takes precedence. 476 uses_plugin_specific_setting = specific_setting && 477 (general_info.source != content_settings::SETTING_SOURCE_POLICY); 478 if (uses_plugin_specific_setting) { 479 value = specific_setting.Pass(); 480 info = specific_info; 481 } else { 482 value = general_setting.Pass(); 483 info = general_info; 484 } 485 } 486 *setting = content_settings::ValueToContentSetting(value.get()); 487 *uses_default_content_setting = 488 !uses_plugin_specific_setting && 489 info.primary_pattern == ContentSettingsPattern::Wildcard() && 490 info.secondary_pattern == ContentSettingsPattern::Wildcard(); 491 *is_managed = info.source == content_settings::SETTING_SOURCE_POLICY; 492 } 493 494 void PluginInfoMessageFilter::Context::MaybeGrantAccess( 495 const ChromeViewHostMsg_GetPluginInfo_Status& status, 496 const base::FilePath& path) const { 497 if (status.value == ChromeViewHostMsg_GetPluginInfo_Status::kAllowed || 498 status.value == ChromeViewHostMsg_GetPluginInfo_Status::kClickToPlay) { 499 ChromePluginServiceFilter::GetInstance()->AuthorizePlugin( 500 render_process_id_, path); 501 } 502 } 503 504 bool PluginInfoMessageFilter::Context::IsPluginEnabled( 505 const content::WebPluginInfo& plugin) const { 506 return plugin_prefs_->IsPluginEnabled(plugin); 507 } 508