blob: d1b86897d0b3f6364b362a6fa12b13ba69a713b3 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <list>
#include <memory>
#include <set>
#include <string>
#include "base/bind_helpers.h"
#include "base/callback_helpers.h"
#include "base/macros.h"
#include "base/strings/string_split.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/scoped_task_environment.h"
#include "base/threading/thread_restrictions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "chrome/browser/extensions/browsertest_util.h"
#include "chrome/browser/extensions/chrome_content_verifier_delegate.h"
#include "chrome/browser/extensions/content_verifier_test_utils.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/extensions/extension_management_test_util.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/policy_extension_reinstaller.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/policy/core/browser/browser_policy_connector.h"
#include "components/policy/core/common/mock_configuration_policy_provider.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/content_verifier.h"
#include "extensions/browser/content_verifier/test_utils.h"
#include "extensions/browser/content_verify_job.h"
#include "extensions/browser/crx_file_info.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/external_install_info.h"
#include "extensions/browser/management_policy.h"
#include "extensions/browser/mock_external_provider.h"
#include "extensions/browser/test_extension_registry_observer.h"
#include "extensions/browser/updater/extension_downloader.h"
#include "extensions/browser/updater/extension_downloader_test_delegate.h"
#include "extensions/browser/updater/manifest_fetch_data.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/extension_urls.h"
namespace extensions {
class ContentVerifierTest : public ExtensionBrowserTest {
public:
ContentVerifierTest() {}
~ContentVerifierTest() override {}
void SetUp() override {
scoped_feature_list_.InitAndDisableFeature(
extensions_features::kNewExtensionUpdaterService);
// Override content verification mode before ExtensionSystemImpl initializes
// ChromeContentVerifierDelegate.
ChromeContentVerifierDelegate::SetDefaultModeForTesting(
ContentVerifierDelegate::ENFORCE);
ExtensionBrowserTest::SetUp();
}
void TearDown() override {
ExtensionBrowserTest::TearDown();
ChromeContentVerifierDelegate::SetDefaultModeForTesting(base::nullopt);
}
bool ShouldEnableContentVerification() override { return true; }
void TestContentScriptExtension(const std::string& crx_relpath,
const std::string& id,
const std::string& script_relpath) {
VerifierObserver verifier_observer;
// Install the 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).
const Extension* extension = InstallExtensionFromWebstore(
test_data_dir_.AppendASCII(crx_relpath), 1);
ASSERT_TRUE(extension);
EXPECT_EQ(id, extension->id());
// Wait for the content verification code to finish processing the hashes.
if (!base::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);
using Result = TestContentVerifyJobObserver::Result;
TestContentVerifyJobObserver job_observer;
base::FilePath script_relfilepath =
base::FilePath().AppendASCII(script_relpath);
job_observer.ExpectJobResult(id, script_relfilepath, 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_relpath);
std::string extra = "some_extra_function_call();";
{
base::ScopedAllowBlockingForTesting allow_blocking;
ASSERT_TRUE(base::AppendToFile(scriptfile, extra.data(), extra.size()));
}
DisableExtension(id);
job_observer.ExpectJobResult(id, script_relfilepath, Result::FAILURE);
EnableExtension(id);
EXPECT_TRUE(job_observer.WaitForExpectedJobs());
}
protected:
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(ContentVerifierTest, DotSlashPaths) {
TestContentVerifyJobObserver job_observer;
std::string id = "hoipipabpcoomfapcecilckodldhmpgl";
using Result = TestContentVerifyJobObserver::Result;
job_observer.ExpectJobResult(
id, base::FilePath(FILE_PATH_LITERAL("background.js")), Result::SUCCESS);
job_observer.ExpectJobResult(
id, base::FilePath(FILE_PATH_LITERAL("page.html")), Result::SUCCESS);
job_observer.ExpectJobResult(id, base::FilePath(FILE_PATH_LITERAL("page.js")),
Result::SUCCESS);
job_observer.ExpectJobResult(
id, base::FilePath(FILE_PATH_LITERAL("dir/page2.html")), Result::SUCCESS);
job_observer.ExpectJobResult(
id, base::FilePath(FILE_PATH_LITERAL("page2.js")), Result::SUCCESS);
job_observer.ExpectJobResult(id, base::FilePath(FILE_PATH_LITERAL("cs1.js")),
Result::SUCCESS);
job_observer.ExpectJobResult(id, base::FilePath(FILE_PATH_LITERAL("cs2.js")),
Result::SUCCESS);
auto verifier_observer = std::make_unique<VerifierObserver>();
// Install a test extension we copied from the webstore that has actual
// signatures, and contains paths with a leading "./" in various places.
const Extension* extension = InstallExtensionFromWebstore(
test_data_dir_.AppendASCII("content_verifier/dot_slash_paths.crx"), 1);
ASSERT_TRUE(extension);
ASSERT_EQ(extension->id(), id);
// The content scripts might fail verification the first time since the
// one-time processing might not be finished yet - if that's the case then
// we want to wait until that work is done.
if (!base::ContainsKey(verifier_observer->completed_fetches(), id))
verifier_observer->WaitForFetchComplete(id);
// It is important to destroy |verifier_observer| here so that it doesn't see
// any fetch from EnableExtension call below (the observer pointer in
// content_verifier.cc isn't thread safe, so it might asynchronously call
// OnFetchComplete after this test's body executes).
verifier_observer.reset();
EXPECT_TRUE(job_observer.WaitForExpectedJobs());
// Set expectations for extension enablement below.
job_observer.ExpectJobResult(id, base::FilePath(FILE_PATH_LITERAL("cs1.js")),
Result::SUCCESS);
job_observer.ExpectJobResult(id, base::FilePath(FILE_PATH_LITERAL("cs2.js")),
Result::SUCCESS);
// Now disable/re-enable the extension to cause the content scripts to be
// read again.
DisableExtension(id);
EnableExtension(id);
EXPECT_TRUE(job_observer.WaitForExpectedJobs());
}
IN_PROC_BROWSER_TEST_F(ContentVerifierTest, ContentScripts) {
TestContentScriptExtension("content_verifier/content_script.crx",
"jmllhlobpjcnnomjlipadejplhmheiif", "script.js");
}
// crbug.com/897059 tracks test flakiness.
#if defined(OS_WIN)
#define MAYBE_ContentScriptsInLocales DISABLED_ContentScriptsInLocales
#else
#define MAYBE_ContentScriptsInLocales ContentScriptsInLocales
#endif
IN_PROC_BROWSER_TEST_F(ContentVerifierTest, MAYBE_ContentScriptsInLocales) {
TestContentScriptExtension("content_verifier/content_script_locales.crx",
"jaghonccckpcikmliipifpoodmeofoon",
"_locales/en/content_script.js");
}
// Tests the case of a corrupt extension that is force-installed by policy and
// should not be allowed to be manually uninstalled/disabled by the user.
IN_PROC_BROWSER_TEST_F(ContentVerifierTest, PolicyCorrupted) {
ExtensionSystem* system = ExtensionSystem::Get(profile());
ExtensionService* service = system->extension_service();
// The id of our test extension.
ExtensionId kExtensionId("dkjgfphccejbobpbljnpjcmhmagkdoia");
// Setup fake policy and update check objects.
content_verifier_test::ForceInstallProvider policy(kExtensionId);
content_verifier_test::DownloaderTestDelegate downloader;
system->management_policy()->RegisterProvider(&policy);
ExtensionDownloader::set_test_delegate(&downloader);
auto external_provider = std::make_unique<MockExternalProvider>(
service, Manifest::EXTERNAL_POLICY_DOWNLOAD);
external_provider->UpdateOrAddExtension(
std::make_unique<ExternalInstallInfoUpdateUrl>(
kExtensionId, std::string() /* install_parameter */,
extension_urls::GetWebstoreUpdateUrl(),
Manifest::EXTERNAL_POLICY_DOWNLOAD, 0 /* creation_flags */,
true /* mark_acknowldged */));
service->AddProviderForTesting(std::move(external_provider));
base::FilePath crx_path =
test_data_dir_.AppendASCII("content_verifier/v1.crx");
const Extension* extension =
InstallExtension(crx_path, 1, Manifest::EXTERNAL_POLICY_DOWNLOAD);
ASSERT_TRUE(extension);
downloader.AddResponse(kExtensionId, extension->VersionString(), crx_path);
EXPECT_EQ(kExtensionId, extension->id());
TestExtensionRegistryObserver registry_observer(
ExtensionRegistry::Get(profile()), kExtensionId);
ContentVerifier* verifier = system->content_verifier();
verifier->VerifyFailed(kExtensionId, ContentVerifyJob::HASH_MISMATCH);
// Make sure the extension first got disabled due to corruption.
EXPECT_TRUE(registry_observer.WaitForExtensionUnloaded());
ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
int reasons = prefs->GetDisableReasons(kExtensionId);
EXPECT_TRUE(reasons & disable_reason::DISABLE_CORRUPTED);
// Make sure the extension then got re-installed, and that after reinstall it
// is no longer disabled due to corruption.
EXPECT_TRUE(registry_observer.WaitForExtensionInstalled());
reasons = prefs->GetDisableReasons(kExtensionId);
EXPECT_FALSE(reasons & disable_reason::DISABLE_CORRUPTED);
// Make sure that the update check request properly included a parameter
// indicating that this was a corrupt policy reinstall.
bool found = false;
for (const auto& request : downloader.requests()) {
if (request->Includes(kExtensionId)) {
std::string query = request->full_url().query();
for (const auto& part : base::SplitString(
query, "&", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL)) {
if (base::StartsWith(part, "x=", base::CompareCase::SENSITIVE) &&
part.find(std::string("id%3D") + kExtensionId) !=
std::string::npos) {
found = true;
EXPECT_NE(std::string::npos, part.find("installsource%3Dreinstall"));
}
}
}
}
EXPECT_TRUE(found);
}
// Tests that verification failure during navigating to an extension resource
// correctly disables the extension.
IN_PROC_BROWSER_TEST_F(ContentVerifierTest, VerificationFailureOnNavigate) {
const Extension* extension = InstallExtensionFromWebstore(
test_data_dir_.AppendASCII("content_verifier/dot_slash_paths.crx"), 1);
ASSERT_TRUE(extension);
const ExtensionId kExtensionId = extension->id();
const base::FilePath::CharType kResource[] = FILE_PATH_LITERAL("page.html");
{
// Modify content so that content verification fails.
base::ScopedAllowBlockingForTesting allow_blocking;
base::FilePath real_path = extension->path().Append(kResource);
std::string extra = "some_extra_function_call();";
ASSERT_TRUE(base::AppendToFile(real_path, extra.data(), extra.size()));
}
GURL page_url = extension->GetResourceURL("page.html");
TestExtensionRegistryObserver unload_observer(
ExtensionRegistry::Get(profile()), kExtensionId);
// Wait for 0 navigations to complete because with PlzNavigate it's racy
// when the didstop IPC arrives relative to the tab being closed. The
// wait call below is what the tests care about.
ui_test_utils::NavigateToURLWithDispositionBlockUntilNavigationsComplete(
browser(), page_url, 0, WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_NONE);
EXPECT_TRUE(unload_observer.WaitForExtensionUnloaded());
ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
int reasons = prefs->GetDisableReasons(kExtensionId);
EXPECT_TRUE(reasons & disable_reason::DISABLE_CORRUPTED);
}
class ContentVerifierPolicyTest : public ContentVerifierTest {
public:
// We need to do this work here because the force-install policy values are
// checked pretty early on in the startup of the ExtensionService, which
// happens between SetUpInProcessBrowserTestFixture and SetUpOnMainThread.
void SetUpInProcessBrowserTestFixture() override {
ContentVerifierTest::SetUpInProcessBrowserTestFixture();
EXPECT_CALL(policy_provider_, IsInitializationComplete(testing::_))
.WillRepeatedly(testing::Return(true));
policy::BrowserPolicyConnector::SetPolicyProviderForTesting(
&policy_provider_);
// ExtensionManagementPolicyUpdater requires a single-threaded context to
// call RunLoop::RunUntilIdle internally, and it isn't ready at this setup
// moment.
base::test::ScopedTaskEnvironment env;
ExtensionManagementPolicyUpdater management_policy(&policy_provider_);
management_policy.SetIndividualExtensionAutoInstalled(
id_, extension_urls::kChromeWebstoreUpdateURL, true /* forced */);
ExtensionDownloader::set_test_delegate(&downloader_);
base::FilePath crx_path =
test_data_dir_.AppendASCII("content_verifier/v1.crx");
std::string version = "2";
downloader_.AddResponse(id_, version, crx_path);
}
void SetUpOnMainThread() override {
extensions::browsertest_util::CreateAndInitializeLocalCache();
}
protected:
// The id of the extension we want to have force-installed.
std::string id_ = "dkjgfphccejbobpbljnpjcmhmagkdoia";
private:
policy::MockConfigurationPolicyProvider policy_provider_;
content_verifier_test::DownloaderTestDelegate downloader_;
};
// We want to test what happens at startup with a corroption-disabled policy
// force installed extension. So we set that up in the PRE test here.
IN_PROC_BROWSER_TEST_F(ContentVerifierPolicyTest,
PRE_PolicyCorruptedOnStartup) {
ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
TestExtensionRegistryObserver registry_observer(registry, id_);
// Wait for the extension to be installed by policy we set up in
// SetUpInProcessBrowserTestFixture.
if (!registry->GetInstalledExtension(id_))
EXPECT_TRUE(registry_observer.WaitForExtensionInstalled());
// Simulate corruption of the extension so that we can test what happens
// at startup in the non-PRE test.
ExtensionSystem* system = ExtensionSystem::Get(profile());
ContentVerifier* verifier = system->content_verifier();
verifier->VerifyFailed(id_, ContentVerifyJob::HASH_MISMATCH);
EXPECT_TRUE(registry_observer.WaitForExtensionUnloaded());
ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
int reasons = prefs->GetDisableReasons(id_);
EXPECT_TRUE(reasons & disable_reason::DISABLE_CORRUPTED);
}
// Now actually test what happens on the next startup after the PRE test above.
IN_PROC_BROWSER_TEST_F(ContentVerifierPolicyTest, PolicyCorruptedOnStartup) {
// Depdending on timing, the extension may have already been reinstalled
// between SetUpInProcessBrowserTestFixture and now (usually not during local
// testing on a developer machine, but sometimes on a heavily loaded system
// such as the build waterfall / trybots). If the reinstall didn't already
// happen, wait for it.
ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
int disable_reasons = prefs->GetDisableReasons(id_);
if (disable_reasons & disable_reason::DISABLE_CORRUPTED) {
TestExtensionRegistryObserver registry_observer(registry, id_);
EXPECT_TRUE(registry_observer.WaitForExtensionInstalled());
disable_reasons = prefs->GetDisableReasons(id_);
}
EXPECT_FALSE(disable_reasons & disable_reason::DISABLE_CORRUPTED);
EXPECT_TRUE(registry->enabled_extensions().Contains(id_));
}
IN_PROC_BROWSER_TEST_F(ContentVerifierPolicyTest, Backoff) {
ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
ExtensionSystem* system = ExtensionSystem::Get(profile());
ContentVerifier* verifier = system->content_verifier();
// Wait for the extension to be installed by the policy we set up in
// SetUpInProcessBrowserTestFixture.
if (!registry->GetInstalledExtension(id_)) {
TestExtensionRegistryObserver registry_observer(registry, id_);
EXPECT_TRUE(registry_observer.WaitForExtensionInstalled());
}
// Setup to intercept reinstall action, so we can see what the delay would
// have been for the real action.
content_verifier_test::DelayTracker delay_tracker;
// Do 4 iterations of disabling followed by reinstall.
const size_t iterations = 4;
for (size_t i = 0; i < iterations; i++) {
TestExtensionRegistryObserver registry_observer(registry, id_);
verifier->VerifyFailed(id_, ContentVerifyJob::HASH_MISMATCH);
EXPECT_TRUE(registry_observer.WaitForExtensionUnloaded());
// Resolve the request to |delay_tracker|, so the reinstallation can
// proceed.
delay_tracker.Proceed();
EXPECT_TRUE(registry_observer.WaitForExtensionInstalled());
}
const std::vector<base::TimeDelta>& calls = delay_tracker.calls();
// After |delay_tracker| resolves the 4 (|iterations|) reinstallation
// requests, it will get an additional request (right away) for retrying
// reinstallation.
// Note: the additional request in non-test environment will arrive with
// a (backoff) delay. But during test, |delay_tracker| issues the request
// immediately.
ASSERT_EQ(iterations, calls.size() - 1);
// Assert that the first reinstall action happened with a delay of 0, and
// then kept growing each additional time.
EXPECT_EQ(base::TimeDelta(), delay_tracker.calls()[0]);
for (size_t i = 1; i < delay_tracker.calls().size(); i++) {
EXPECT_LT(calls[i - 1], calls[i]);
}
}
// Tests that if CheckForExternalUpdates() fails, then we retry reinstalling
// corrupted policy extensions. For example: if network is unavailable,
// CheckForExternalUpdates() will fail.
IN_PROC_BROWSER_TEST_F(ContentVerifierPolicyTest, FailedUpdateRetries) {
ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
ExtensionSystem* system = ExtensionSystem::Get(profile());
ExtensionService* service = system->extension_service();
ContentVerifier* verifier = system->content_verifier();
// Wait for the extension to be installed by the policy we set up in
// SetUpInProcessBrowserTestFixture.
if (!registry->GetInstalledExtension(id_)) {
TestExtensionRegistryObserver registry_observer(registry, id_);
EXPECT_TRUE(registry_observer.WaitForExtensionInstalled());
}
content_verifier_test::DelayTracker delay_tracker;
service->set_external_updates_disabled_for_test(true);
TestExtensionRegistryObserver registry_observer(registry, id_);
verifier->VerifyFailed(id_, ContentVerifyJob::HASH_MISMATCH);
EXPECT_TRUE(registry_observer.WaitForExtensionUnloaded());
const std::vector<base::TimeDelta>& calls = delay_tracker.calls();
ASSERT_EQ(1u, calls.size());
EXPECT_EQ(base::TimeDelta(), delay_tracker.calls()[0]);
delay_tracker.Proceed();
// Remove the override and set ExtensionService to update again. The extension
// should be now installed.
PolicyExtensionReinstaller::set_policy_reinstall_action_for_test(nullptr);
service->set_external_updates_disabled_for_test(false);
delay_tracker.Proceed();
EXPECT_TRUE(registry_observer.WaitForExtensionInstalled());
}
} // namespace extensions