• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ContentVerifier(content::BrowserContext * context,ContentVerifierDelegate * delegate)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 
~ContentVerifier()41 ContentVerifier::~ContentVerifier() {
42 }
43 
Start()44 void ContentVerifier::Start() {
45   if (mode_ >= BOOTSTRAP)
46     fetcher_->Start();
47 }
48 
Shutdown()49 void ContentVerifier::Shutdown() {
50   fetcher_.reset();
51   delegate_.reset();
52 }
53 
CreateJobFor(const std::string & extension_id,const base::FilePath & extension_root,const base::FilePath & relative_path)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 
VerifyFailed(const std::string & extension_id,ContentVerifyJob::FailureReason reason)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 
OnFetchComplete(const std::string & extension_id,bool success,bool was_force_check,const std::set<base::FilePath> & hash_mismatch_paths)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 
ShouldVerifyAnyPaths(const Extension * extension,const std::set<base::FilePath> & relative_paths)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
GetMode()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