Fix content verifier problem with content scripts (reland)
The problem is that we were creating the ContentVerifier too late
in ExtensionSystemImpl::Shared::Init, after the script loader had
already tried grabbing a reference to it for use on another
thead. Therefore at run time, for all content scripts we were was
seeing a null pointer for the content verifier and skipping
verification of those scripts.
Note: this is a reland of https://codereview.chromium.org/1226163010/
which was reverted in b9e8b50e4c45d8aefacac58e28c50d83bb257b7b due
to test failures on the Vista Tests bot.
BUG=509130
Review URL: https://codereview.chromium.org/1250473002
Cr-Commit-Position: refs/heads/master@{#341435}
diff --git a/chrome/browser/extensions/content_verifier_browsertest.cc b/chrome/browser/extensions/content_verifier_browsertest.cc
index a7b9ac8..91c6629 100644
--- a/chrome/browser/extensions/content_verifier_browsertest.cc
+++ b/chrome/browser/extensions/content_verifier_browsertest.cc
@@ -2,10 +2,15 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include <list>
+#include <set>
+#include <string>
+
#include "base/scoped_observer.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/common/chrome_switches.h"
#include "content/public/test/test_utils.h"
+#include "extensions/browser/content_verifier.h"
#include "extensions/browser/content_verify_job.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
@@ -90,14 +95,15 @@
JobObserver();
virtual ~JobObserver();
+ enum class Result { SUCCESS, FAILURE };
+
// Call this to add an expected job result.
void ExpectJobResult(const std::string& extension_id,
const base::FilePath& relative_path,
- bool expected_to_fail);
+ Result expected_result);
- // Wait to see expected jobs. Returns true if we saw all jobs finish as
- // expected, or false if any job completed with non-expected success/failure
- // status.
+ // Wait to see expected jobs. Returns true when we've seen all expected jobs
+ // finish, or false if there was an error or timeout.
bool WaitForExpectedJobs();
// ContentVerifyJob::TestObserver interface
@@ -109,32 +115,47 @@
bool failed) override;
private:
- typedef std::pair<std::string, base::FilePath> ExtensionFile;
- typedef std::map<ExtensionFile, bool> ExpectedJobs;
- ExpectedJobs expected_jobs_;
+ struct ExpectedResult {
+ public:
+ std::string extension_id;
+ base::FilePath path;
+ Result result;
+
+ ExpectedResult(const std::string& extension_id, const base::FilePath& path,
+ Result result) {
+ this->extension_id = extension_id;
+ this->path = path;
+ this->result = result;
+ }
+ };
+ std::list<ExpectedResult> expectations_;
+ content::BrowserThread::ID creation_thread_;
scoped_refptr<content::MessageLoopRunner> loop_runner_;
- bool saw_expected_job_results_;
};
void JobObserver::ExpectJobResult(const std::string& extension_id,
const base::FilePath& relative_path,
- bool expected_to_fail) {
- expected_jobs_.insert(std::make_pair(
- ExtensionFile(extension_id, relative_path), expected_to_fail));
+ Result expected_result) {
+ expectations_.push_back(ExpectedResult(
+ extension_id, relative_path, expected_result));
}
-JobObserver::JobObserver() : saw_expected_job_results_(false) {
+JobObserver::JobObserver() {
+ EXPECT_TRUE(
+ content::BrowserThread::GetCurrentThreadIdentifier(&creation_thread_));
}
JobObserver::~JobObserver() {
}
bool JobObserver::WaitForExpectedJobs() {
- if (!expected_jobs_.empty()) {
+ EXPECT_TRUE(content::BrowserThread::CurrentlyOn(creation_thread_));
+ if (!expectations_.empty()) {
loop_runner_ = new content::MessageLoopRunner();
loop_runner_->Run();
+ loop_runner_ = nullptr;
}
- return saw_expected_job_results_;
+ return expectations_.empty();
}
void JobObserver::JobStarted(const std::string& extension_id,
@@ -144,21 +165,75 @@
void JobObserver::JobFinished(const std::string& extension_id,
const base::FilePath& relative_path,
bool failed) {
- ExpectedJobs::iterator i = expected_jobs_.find(ExtensionFile(
- extension_id, relative_path.NormalizePathSeparatorsTo('/')));
- if (i != expected_jobs_.end()) {
- if (failed != i->second) {
- saw_expected_job_results_ = false;
- if (loop_runner_.get())
- loop_runner_->Quit();
- }
- expected_jobs_.erase(i);
- if (expected_jobs_.empty()) {
- saw_expected_job_results_ = true;
- if (loop_runner_.get())
- loop_runner_->Quit();
+ if (!content::BrowserThread::CurrentlyOn(creation_thread_)) {
+ content::BrowserThread::PostTask(
+ creation_thread_, FROM_HERE,
+ base::Bind(&JobObserver::JobFinished, base::Unretained(this),
+ extension_id, relative_path, failed));
+ return;
+ }
+ Result result = failed ? Result::FAILURE : Result::SUCCESS;
+ bool found = false;
+ for (std::list<ExpectedResult>::iterator i = expectations_.begin();
+ i != expectations_.end(); ++i) {
+ if (i->extension_id == extension_id && i->path == relative_path &&
+ i->result == result) {
+ found = true;
+ expectations_.erase(i);
+ break;
}
}
+ if (found) {
+ if (expectations_.empty() && loop_runner_.get())
+ loop_runner_->Quit();
+ } else {
+ LOG(WARNING) << "Ignoring unexpected JobFinished " << extension_id << "/"
+ << relative_path.value() << " failed:" << failed;
+ }
+}
+
+class VerifierObserver : public ContentVerifier::TestObserver {
+ public:
+ VerifierObserver();
+ virtual ~VerifierObserver();
+
+ const std::set<std::string>& completed_fetches() {
+ return completed_fetches_;
+ }
+
+ // Returns when we've seen OnFetchComplete for |extension_id|.
+ void WaitForFetchComplete(const std::string& extension_id);
+
+ // ContentVerifier::TestObserver
+ void OnFetchComplete(const std::string& extension_id, bool success) override;
+
+ private:
+ std::set<std::string> completed_fetches_;
+ std::string id_to_wait_for_;
+ scoped_refptr<content::MessageLoopRunner> loop_runner_;
+};
+
+VerifierObserver::VerifierObserver() {
+}
+
+VerifierObserver::~VerifierObserver() {
+}
+
+void VerifierObserver::WaitForFetchComplete(const std::string& extension_id) {
+ EXPECT_TRUE(id_to_wait_for_.empty());
+ EXPECT_EQ(loop_runner_.get(), nullptr);
+ id_to_wait_for_ = extension_id;
+ loop_runner_ = new content::MessageLoopRunner();
+ loop_runner_->Run();
+ id_to_wait_for_.clear();
+ loop_runner_ = nullptr;
+}
+
+void VerifierObserver::OnFetchComplete(const std::string& extension_id,
+ bool success) {
+ completed_fetches_.insert(extension_id);
+ if (extension_id == id_to_wait_for_)
+ loop_runner_->Quit();
}
} // namespace
@@ -175,12 +250,12 @@
switches::kExtensionContentVerificationEnforce);
}
- // Setup our unload observer and JobDelegate, and install a test extension.
void SetUpOnMainThread() override {
ExtensionBrowserTest::SetUpOnMainThread();
}
void TearDownOnMainThread() override {
+ ContentVerifier::SetObserverForTests(NULL);
ContentVerifyJob::SetDelegateForTests(NULL);
ContentVerifyJob::SetObserverForTests(NULL);
ExtensionBrowserTest::TearDownOnMainThread();
@@ -237,15 +312,19 @@
std::string id = "hoipipabpcoomfapcecilckodldhmpgl";
job_observer.ExpectJobResult(
- id, base::FilePath(FILE_PATH_LITERAL("background.js")), false);
+ id, base::FilePath(FILE_PATH_LITERAL("background.js")),
+ JobObserver::Result::SUCCESS);
+ job_observer.ExpectJobResult(id,
+ base::FilePath(FILE_PATH_LITERAL("page.html")),
+ JobObserver::Result::SUCCESS);
+ job_observer.ExpectJobResult(id, base::FilePath(FILE_PATH_LITERAL("page.js")),
+ JobObserver::Result::SUCCESS);
job_observer.ExpectJobResult(
- id, base::FilePath(FILE_PATH_LITERAL("page.html")), false);
- job_observer.ExpectJobResult(
- id, base::FilePath(FILE_PATH_LITERAL("page.js")), false);
- job_observer.ExpectJobResult(
- id, base::FilePath(FILE_PATH_LITERAL("dir/page2.html")), false);
- job_observer.ExpectJobResult(
- id, base::FilePath(FILE_PATH_LITERAL("page2.js")), false);
+ id, base::FilePath(FILE_PATH_LITERAL("dir/page2.html")),
+ JobObserver::Result::SUCCESS);
+ job_observer.ExpectJobResult(id,
+ base::FilePath(FILE_PATH_LITERAL("page2.js")),
+ JobObserver::Result::SUCCESS);
// Install a test extension we copied from the webstore that has actual
// signatures, and contains image paths with leading "./".
@@ -260,4 +339,52 @@
ContentVerifyJob::SetObserverForTests(NULL);
}
+IN_PROC_BROWSER_TEST_F(ContentVerifierTest, ContentScripts) {
+ VerifierObserver verifier_observer;
+ ContentVerifier::SetObserverForTests(&verifier_observer);
+
+ // Install an extension with content scripts. The initial read of the content
+ // scripts will fail verification because they are read before the content
+ // verification system has completed a one-time processing of the expected
+ // hashes. (The extension only contains the root level hashes of the merkle
+ // tree, but the content verification system builds the entire tree and
+ // caches it in the extension install directory - see ContentHashFetcher for
+ // more details).
+ std::string id = "jmllhlobpjcnnomjlipadejplhmheiif";
+ const Extension* extension = InstallExtensionFromWebstore(
+ test_data_dir_.AppendASCII("content_verifier/content_script.crx"), 1);
+ ASSERT_TRUE(extension);
+ ASSERT_EQ(extension->id(), id);
+
+ // Wait for the content verification code to finish processing the hashes.
+ if (!ContainsKey(verifier_observer.completed_fetches(), id))
+ verifier_observer.WaitForFetchComplete(id);
+
+ // Now disable the extension, since content scripts are read at enable time,
+ // set up our job observer, and re-enable, expecting a success this time.
+ DisableExtension(id);
+ JobObserver job_observer;
+ ContentVerifyJob::SetObserverForTests(&job_observer);
+ job_observer.ExpectJobResult(id,
+ base::FilePath(FILE_PATH_LITERAL("script.js")),
+ JobObserver::Result::SUCCESS);
+ EnableExtension(id);
+ EXPECT_TRUE(job_observer.WaitForExpectedJobs());
+
+ // Now alter the contents of the content script, reload the extension, and
+ // expect to see a job failure due to the content script content hash not
+ // being what was signed by the webstore.
+ base::FilePath scriptfile = extension->path().AppendASCII("script.js");
+ std::string extra = "some_extra_function_call();";
+ ASSERT_TRUE(base::AppendToFile(scriptfile, extra.data(), extra.size()));
+ DisableExtension(id);
+ job_observer.ExpectJobResult(id,
+ base::FilePath(FILE_PATH_LITERAL("script.js")),
+ JobObserver::Result::FAILURE);
+ EnableExtension(id);
+ EXPECT_TRUE(job_observer.WaitForExpectedJobs());
+
+ ContentVerifyJob::SetObserverForTests(NULL);
+}
+
} // namespace extensions