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