Home | History | Annotate | Download | only in plugins
      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