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.
9 #include "base/scoped_observer.h"
10 #include "chrome/browser/extensions/extension_browsertest.h"
11 #include "chrome/common/chrome_switches.h"
12 #include "content/public/test/test_utils.h"
13 #include "extensions/browser/content_verifier.h"
14 #include "extensions/browser/content_verify_job.h"
15 #include "extensions/browser/extension_prefs.h"
16 #include "extensions/browser/extension_registry.h"
17 #include "extensions/browser/extension_registry_observer.h"
19 namespace extensions
{
23 // Helper for observing extension unloads.
24 class UnloadObserver
: public ExtensionRegistryObserver
{
26 explicit UnloadObserver(ExtensionRegistry
* registry
) : observer_(this) {
27 observer_
.Add(registry
);
29 ~UnloadObserver() override
{}
31 void WaitForUnload(const ExtensionId
& id
) {
32 if (ContainsKey(observed_
, id
))
35 ASSERT_TRUE(loop_runner_
.get() == NULL
);
37 loop_runner_
= new content::MessageLoopRunner();
41 void OnExtensionUnloaded(content::BrowserContext
* browser_context
,
42 const Extension
* extension
,
43 UnloadedExtensionInfo::Reason reason
) override
{
44 observed_
.insert(extension
->id());
45 if (awaited_id_
== extension
->id())
50 ExtensionId awaited_id_
;
51 std::set
<ExtensionId
> observed_
;
52 scoped_refptr
<content::MessageLoopRunner
> loop_runner_
;
53 ScopedObserver
<ExtensionRegistry
, UnloadObserver
> observer_
;
56 // Helper for forcing ContentVerifyJob's to return an error.
57 class JobDelegate
: public ContentVerifyJob::TestDelegate
{
59 JobDelegate() : fail_next_read_(false), fail_next_done_(false) {}
61 virtual ~JobDelegate() {}
63 void set_id(const ExtensionId
& id
) { id_
= id
; }
64 void fail_next_read() { fail_next_read_
= true; }
65 void fail_next_done() { fail_next_done_
= true; }
67 ContentVerifyJob::FailureReason
BytesRead(const ExtensionId
& id
,
69 const char* data
) override
{
70 if (id
== id_
&& fail_next_read_
) {
71 fail_next_read_
= false;
72 return ContentVerifyJob::HASH_MISMATCH
;
74 return ContentVerifyJob::NONE
;
77 ContentVerifyJob::FailureReason
DoneReading(const ExtensionId
& id
) override
{
78 if (id
== id_
&& fail_next_done_
) {
79 fail_next_done_
= false;
80 return ContentVerifyJob::HASH_MISMATCH
;
82 return ContentVerifyJob::NONE
;
86 DISALLOW_COPY_AND_ASSIGN(JobDelegate
);
93 class JobObserver
: public ContentVerifyJob::TestObserver
{
96 virtual ~JobObserver();
98 enum class Result
{ SUCCESS
, FAILURE
};
100 // Call this to add an expected job result.
101 void ExpectJobResult(const std::string
& extension_id
,
102 const base::FilePath
& relative_path
,
103 Result expected_result
);
105 // Wait to see expected jobs. Returns true when we've seen all expected jobs
106 // finish, or false if there was an error or timeout.
107 bool WaitForExpectedJobs();
109 // ContentVerifyJob::TestObserver interface
110 void JobStarted(const std::string
& extension_id
,
111 const base::FilePath
& relative_path
) override
;
113 void JobFinished(const std::string
& extension_id
,
114 const base::FilePath
& relative_path
,
115 bool failed
) override
;
118 struct ExpectedResult
{
120 std::string extension_id
;
124 ExpectedResult(const std::string
& extension_id
, const base::FilePath
& path
,
126 this->extension_id
= extension_id
;
128 this->result
= result
;
131 std::list
<ExpectedResult
> expectations_
;
132 content::BrowserThread::ID creation_thread_
;
133 scoped_refptr
<content::MessageLoopRunner
> loop_runner_
;
136 void JobObserver::ExpectJobResult(const std::string
& extension_id
,
137 const base::FilePath
& relative_path
,
138 Result expected_result
) {
139 expectations_
.push_back(ExpectedResult(
140 extension_id
, relative_path
, expected_result
));
143 JobObserver::JobObserver() {
145 content::BrowserThread::GetCurrentThreadIdentifier(&creation_thread_
));
148 JobObserver::~JobObserver() {
151 bool JobObserver::WaitForExpectedJobs() {
152 EXPECT_TRUE(content::BrowserThread::CurrentlyOn(creation_thread_
));
153 if (!expectations_
.empty()) {
154 loop_runner_
= new content::MessageLoopRunner();
156 loop_runner_
= nullptr;
158 return expectations_
.empty();
161 void JobObserver::JobStarted(const std::string
& extension_id
,
162 const base::FilePath
& relative_path
) {
165 void JobObserver::JobFinished(const std::string
& extension_id
,
166 const base::FilePath
& relative_path
,
168 if (!content::BrowserThread::CurrentlyOn(creation_thread_
)) {
169 content::BrowserThread::PostTask(
170 creation_thread_
, FROM_HERE
,
171 base::Bind(&JobObserver::JobFinished
, base::Unretained(this),
172 extension_id
, relative_path
, failed
));
175 Result result
= failed
? Result::FAILURE
: Result::SUCCESS
;
177 for (std::list
<ExpectedResult
>::iterator i
= expectations_
.begin();
178 i
!= expectations_
.end(); ++i
) {
179 if (i
->extension_id
== extension_id
&& i
->path
== relative_path
&&
180 i
->result
== result
) {
182 expectations_
.erase(i
);
187 if (expectations_
.empty() && loop_runner_
.get())
188 loop_runner_
->Quit();
190 LOG(WARNING
) << "Ignoring unexpected JobFinished " << extension_id
<< "/"
191 << relative_path
.value() << " failed:" << failed
;
195 class VerifierObserver
: public ContentVerifier::TestObserver
{
198 virtual ~VerifierObserver();
200 const std::set
<std::string
>& completed_fetches() {
201 return completed_fetches_
;
204 // Returns when we've seen OnFetchComplete for |extension_id|.
205 void WaitForFetchComplete(const std::string
& extension_id
);
207 // ContentVerifier::TestObserver
208 void OnFetchComplete(const std::string
& extension_id
, bool success
) override
;
211 std::set
<std::string
> completed_fetches_
;
212 std::string id_to_wait_for_
;
213 scoped_refptr
<content::MessageLoopRunner
> loop_runner_
;
216 VerifierObserver::VerifierObserver() {
219 VerifierObserver::~VerifierObserver() {
222 void VerifierObserver::WaitForFetchComplete(const std::string
& extension_id
) {
223 EXPECT_TRUE(id_to_wait_for_
.empty());
224 EXPECT_EQ(loop_runner_
.get(), nullptr);
225 id_to_wait_for_
= extension_id
;
226 loop_runner_
= new content::MessageLoopRunner();
228 id_to_wait_for_
.clear();
229 loop_runner_
= nullptr;
232 void VerifierObserver::OnFetchComplete(const std::string
& extension_id
,
234 completed_fetches_
.insert(extension_id
);
235 if (extension_id
== id_to_wait_for_
)
236 loop_runner_
->Quit();
241 class ContentVerifierTest
: public ExtensionBrowserTest
{
243 ContentVerifierTest() {}
244 ~ContentVerifierTest() override
{}
246 void SetUpCommandLine(base::CommandLine
* command_line
) override
{
247 ExtensionBrowserTest::SetUpCommandLine(command_line
);
248 command_line
->AppendSwitchASCII(
249 switches::kExtensionContentVerification
,
250 switches::kExtensionContentVerificationEnforce
);
253 void SetUpOnMainThread() override
{
254 ExtensionBrowserTest::SetUpOnMainThread();
257 void TearDownOnMainThread() override
{
258 ContentVerifier::SetObserverForTests(NULL
);
259 ContentVerifyJob::SetDelegateForTests(NULL
);
260 ContentVerifyJob::SetObserverForTests(NULL
);
261 ExtensionBrowserTest::TearDownOnMainThread();
264 virtual void OpenPageAndWaitForUnload() {
265 unload_observer_
.reset(
266 new UnloadObserver(ExtensionRegistry::Get(profile())));
267 const Extension
* extension
= InstallExtensionFromWebstore(
268 test_data_dir_
.AppendASCII("content_verifier/v1.crx"), 1);
269 ASSERT_TRUE(extension
);
270 id_
= extension
->id();
271 page_url_
= extension
->GetResourceURL("page.html");
272 delegate_
.set_id(id_
);
273 ContentVerifyJob::SetDelegateForTests(&delegate_
);
275 // This call passes false for |check_navigation_success|, because checking
276 // for navigation success needs the WebContents to still exist after the
277 // navigation, whereas this navigation triggers an unload which destroys
279 AddTabAtIndexToBrowser(browser(), 1, page_url_
, ui::PAGE_TRANSITION_LINK
,
282 unload_observer_
->WaitForUnload(id_
);
283 ExtensionPrefs
* prefs
= ExtensionPrefs::Get(profile());
284 int reasons
= prefs
->GetDisableReasons(id_
);
285 EXPECT_TRUE(reasons
& Extension::DISABLE_CORRUPTED
);
287 // This needs to happen before the ExtensionRegistry gets deleted, which
288 // happens before TearDownOnMainThread is called.
289 unload_observer_
.reset();
293 JobDelegate delegate_
;
294 scoped_ptr
<UnloadObserver
> unload_observer_
;
299 IN_PROC_BROWSER_TEST_F(ContentVerifierTest
, FailOnRead
) {
300 delegate_
.fail_next_read();
301 OpenPageAndWaitForUnload();
304 IN_PROC_BROWSER_TEST_F(ContentVerifierTest
, FailOnDone
) {
305 delegate_
.fail_next_done();
306 OpenPageAndWaitForUnload();
309 IN_PROC_BROWSER_TEST_F(ContentVerifierTest
, DotSlashPaths
) {
310 JobObserver job_observer
;
311 ContentVerifyJob::SetObserverForTests(&job_observer
);
312 std::string id
= "hoipipabpcoomfapcecilckodldhmpgl";
314 job_observer
.ExpectJobResult(
315 id
, base::FilePath(FILE_PATH_LITERAL("background.js")),
316 JobObserver::Result::SUCCESS
);
317 job_observer
.ExpectJobResult(id
,
318 base::FilePath(FILE_PATH_LITERAL("page.html")),
319 JobObserver::Result::SUCCESS
);
320 job_observer
.ExpectJobResult(id
, base::FilePath(FILE_PATH_LITERAL("page.js")),
321 JobObserver::Result::SUCCESS
);
322 job_observer
.ExpectJobResult(
323 id
, base::FilePath(FILE_PATH_LITERAL("dir/page2.html")),
324 JobObserver::Result::SUCCESS
);
325 job_observer
.ExpectJobResult(id
,
326 base::FilePath(FILE_PATH_LITERAL("page2.js")),
327 JobObserver::Result::SUCCESS
);
329 // Install a test extension we copied from the webstore that has actual
330 // signatures, and contains image paths with leading "./".
331 const Extension
* extension
= InstallExtensionFromWebstore(
332 test_data_dir_
.AppendASCII("content_verifier/dot_slash_paths.crx"), 1);
334 ASSERT_TRUE(extension
);
335 ASSERT_EQ(extension
->id(), id
);
337 EXPECT_TRUE(job_observer
.WaitForExpectedJobs());
339 ContentVerifyJob::SetObserverForTests(NULL
);
342 IN_PROC_BROWSER_TEST_F(ContentVerifierTest
, ContentScripts
) {
343 VerifierObserver verifier_observer
;
344 ContentVerifier::SetObserverForTests(&verifier_observer
);
346 // Install an extension with content scripts. The initial read of the content
347 // scripts will fail verification because they are read before the content
348 // verification system has completed a one-time processing of the expected
349 // hashes. (The extension only contains the root level hashes of the merkle
350 // tree, but the content verification system builds the entire tree and
351 // caches it in the extension install directory - see ContentHashFetcher for
353 std::string id
= "jmllhlobpjcnnomjlipadejplhmheiif";
354 const Extension
* extension
= InstallExtensionFromWebstore(
355 test_data_dir_
.AppendASCII("content_verifier/content_script.crx"), 1);
356 ASSERT_TRUE(extension
);
357 ASSERT_EQ(extension
->id(), id
);
359 // Wait for the content verification code to finish processing the hashes.
360 if (!ContainsKey(verifier_observer
.completed_fetches(), id
))
361 verifier_observer
.WaitForFetchComplete(id
);
363 // Now disable the extension, since content scripts are read at enable time,
364 // set up our job observer, and re-enable, expecting a success this time.
365 DisableExtension(id
);
366 JobObserver job_observer
;
367 ContentVerifyJob::SetObserverForTests(&job_observer
);
368 job_observer
.ExpectJobResult(id
,
369 base::FilePath(FILE_PATH_LITERAL("script.js")),
370 JobObserver::Result::SUCCESS
);
372 EXPECT_TRUE(job_observer
.WaitForExpectedJobs());
374 // Now alter the contents of the content script, reload the extension, and
375 // expect to see a job failure due to the content script content hash not
376 // being what was signed by the webstore.
377 base::FilePath scriptfile
= extension
->path().AppendASCII("script.js");
378 std::string extra
= "some_extra_function_call();";
379 ASSERT_TRUE(base::AppendToFile(scriptfile
, extra
.data(), extra
.size()));
380 DisableExtension(id
);
381 job_observer
.ExpectJobResult(id
,
382 base::FilePath(FILE_PATH_LITERAL("script.js")),
383 JobObserver::Result::FAILURE
);
385 EXPECT_TRUE(job_observer
.WaitForExpectedJobs());
387 ContentVerifyJob::SetObserverForTests(NULL
);
390 } // namespace extensions