Home | History | Annotate | Download | only in browser
      1 // Copyright 2014 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 "extensions/browser/content_verifier.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/command_line.h"
     10 #include "base/files/file_path.h"
     11 #include "base/metrics/field_trial.h"
     12 #include "content/public/browser/browser_thread.h"
     13 #include "content/public/common/content_switches.h"
     14 #include "extensions/browser/content_hash_fetcher.h"
     15 #include "extensions/browser/content_hash_reader.h"
     16 #include "extensions/browser/content_verifier_delegate.h"
     17 #include "extensions/browser/extension_registry.h"
     18 #include "extensions/common/constants.h"
     19 #include "extensions/common/extension_l10n_util.h"
     20 #include "extensions/common/switches.h"
     21 
     22 namespace {
     23 
     24 const char kExperimentName[] = "ExtensionContentVerification";
     25 
     26 }  // namespace
     27 
     28 namespace extensions {
     29 
     30 ContentVerifier::ContentVerifier(content::BrowserContext* context,
     31                                  ContentVerifierDelegate* delegate)
     32     : mode_(GetMode()),
     33       context_(context),
     34       delegate_(delegate),
     35       fetcher_(new ContentHashFetcher(
     36           context,
     37           delegate,
     38           base::Bind(&ContentVerifier::OnFetchComplete, this))) {
     39 }
     40 
     41 ContentVerifier::~ContentVerifier() {
     42 }
     43 
     44 void ContentVerifier::Start() {
     45   if (mode_ >= BOOTSTRAP)
     46     fetcher_->Start();
     47 }
     48 
     49 void ContentVerifier::Shutdown() {
     50   fetcher_.reset();
     51   delegate_.reset();
     52 }
     53 
     54 ContentVerifyJob* ContentVerifier::CreateJobFor(
     55     const std::string& extension_id,
     56     const base::FilePath& extension_root,
     57     const base::FilePath& relative_path) {
     58   if (mode_ < BOOTSTRAP || !delegate_)
     59     return NULL;
     60 
     61   ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
     62   const Extension* extension =
     63       registry->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
     64 
     65   std::set<base::FilePath> paths;
     66   paths.insert(relative_path);
     67   if (!ShouldVerifyAnyPaths(extension, paths))
     68     return NULL;
     69 
     70   // TODO(asargent) - we can probably get some good performance wins by having
     71   // a cache of ContentHashReader's that we hold onto past the end of each job.
     72   return new ContentVerifyJob(
     73       new ContentHashReader(extension_id,
     74                             *extension->version(),
     75                             extension_root,
     76                             relative_path,
     77                             delegate_->PublicKey()),
     78       base::Bind(&ContentVerifier::VerifyFailed, this, extension->id()));
     79 }
     80 
     81 void ContentVerifier::VerifyFailed(const std::string& extension_id,
     82                                    ContentVerifyJob::FailureReason reason) {
     83   if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) {
     84     content::BrowserThread::PostTask(
     85         content::BrowserThread::UI,
     86         FROM_HERE,
     87         base::Bind(&ContentVerifier::VerifyFailed, this, extension_id, reason));
     88     return;
     89   }
     90 
     91   VLOG(1) << "VerifyFailed " << extension_id << " reason:" << reason;
     92 
     93   if (!delegate_ || !fetcher_.get() || mode_ < ENFORCE)
     94     return;
     95 
     96   if (reason == ContentVerifyJob::MISSING_ALL_HASHES) {
     97     // If we failed because there were no hashes yet for this extension, just
     98     // request some.
     99     ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
    100     const Extension* extension =
    101         registry->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
    102     if (extension)
    103       fetcher_->DoFetch(extension, true /* force */);
    104   } else {
    105     delegate_->VerifyFailed(extension_id);
    106   }
    107 }
    108 
    109 void ContentVerifier::OnFetchComplete(
    110     const std::string& extension_id,
    111     bool success,
    112     bool was_force_check,
    113     const std::set<base::FilePath>& hash_mismatch_paths) {
    114   VLOG(1) << "OnFetchComplete " << extension_id << " success:" << success;
    115 
    116   if (!delegate_ || mode_ < ENFORCE)
    117     return;
    118 
    119   if (!success && mode_ < ENFORCE_STRICT)
    120     return;
    121 
    122   ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
    123   const Extension* extension =
    124       registry->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
    125   if (!extension)
    126     return;
    127 
    128   if ((was_force_check && !success) ||
    129       ShouldVerifyAnyPaths(extension, hash_mismatch_paths))
    130     delegate_->VerifyFailed(extension_id);
    131 }
    132 
    133 bool ContentVerifier::ShouldVerifyAnyPaths(
    134     const Extension* extension,
    135     const std::set<base::FilePath>& relative_paths) {
    136   if (!extension || !extension->version() ||
    137       !delegate_->ShouldBeVerified(*extension))
    138     return false;
    139 
    140   // Images used in the browser get transcoded during install, so skip
    141   // checking them for now.  TODO(asargent) - see if we can cache this list
    142   // for a given extension id/version pair.
    143   std::set<base::FilePath> browser_images =
    144       delegate_->GetBrowserImagePaths(extension);
    145 
    146   base::FilePath locales_dir = extension->path().Append(kLocaleFolder);
    147   scoped_ptr<std::set<std::string> > all_locales;
    148 
    149   for (std::set<base::FilePath>::const_iterator i = relative_paths.begin();
    150        i != relative_paths.end();
    151        ++i) {
    152     const base::FilePath& relative_path = *i;
    153 
    154     if (relative_path == base::FilePath(kManifestFilename))
    155       continue;
    156 
    157     if (ContainsKey(browser_images, relative_path))
    158       continue;
    159 
    160     base::FilePath full_path = extension->path().Append(relative_path);
    161     if (locales_dir.IsParent(full_path)) {
    162       if (!all_locales) {
    163         // TODO(asargent) - see if we can cache this list longer to avoid
    164         // having to fetch it more than once for a given run of the
    165         // browser. Maybe it can never change at runtime? (Or if it can, maybe
    166         // there is an event we can listen for to know to drop our cache).
    167         all_locales.reset(new std::set<std::string>);
    168         extension_l10n_util::GetAllLocales(all_locales.get());
    169       }
    170 
    171       // Since message catalogs get transcoded during installation, we want
    172       // to skip those paths.
    173       if (full_path.DirName().DirName() == locales_dir &&
    174           !extension_l10n_util::ShouldSkipValidation(
    175               locales_dir, full_path.DirName(), *all_locales))
    176         continue;
    177     }
    178     return true;
    179   }
    180   return false;
    181 }
    182 
    183 // static
    184 ContentVerifier::Mode ContentVerifier::GetMode() {
    185   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
    186 
    187   Mode experiment_value = NONE;
    188   const std::string group = base::FieldTrialList::FindFullName(kExperimentName);
    189   if (group == "EnforceStrict")
    190     experiment_value = ENFORCE_STRICT;
    191   else if (group == "Enforce")
    192     experiment_value = ENFORCE;
    193   else if (group == "Bootstrap")
    194     experiment_value = BOOTSTRAP;
    195 
    196   // The field trial value that normally comes from the server can be
    197   // overridden on the command line, which we don't want to allow since malware
    198   // can set chrome command line flags. There isn't currently a way to find out
    199   // what the server-provided value is in this case, so we conservatively
    200   // default to the strictest mode if we detect our experiment name being
    201   // overridden.
    202   if (command_line->HasSwitch(::switches::kForceFieldTrials)) {
    203     std::string forced_trials =
    204         command_line->GetSwitchValueASCII(::switches::kForceFieldTrials);
    205     if (forced_trials.find(kExperimentName) != std::string::npos)
    206       experiment_value = ENFORCE_STRICT;
    207   }
    208 
    209   Mode cmdline_value = NONE;
    210   if (command_line->HasSwitch(switches::kExtensionContentVerification)) {
    211     std::string switch_value = command_line->GetSwitchValueASCII(
    212         switches::kExtensionContentVerification);
    213     if (switch_value == switches::kExtensionContentVerificationBootstrap)
    214       cmdline_value = BOOTSTRAP;
    215     else if (switch_value == switches::kExtensionContentVerificationEnforce)
    216       cmdline_value = ENFORCE;
    217     else if (switch_value ==
    218              switches::kExtensionContentVerificationEnforceStrict)
    219       cmdline_value = ENFORCE_STRICT;
    220     else
    221       // If no value was provided (or the wrong one), just default to enforce.
    222       cmdline_value = ENFORCE;
    223   }
    224 
    225   // We don't want to allow the command-line flags to eg disable enforcement if
    226   // the experiment group says it should be on, or malware may just modify the
    227   // command line flags. So return the more restrictive of the 2 values.
    228   return std::max(experiment_value, cmdline_value);
    229 }
    230 
    231 }  // namespace extensions
    232