blob: 5e1ea9918a0e07c4c52b0f9f91c3a5931305a16f [file] [log] [blame]
// Copyright 2015 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 "chrome/browser/extensions/chrome_content_verifier_delegate.h"
#include <algorithm>
#include <memory>
#include <set>
#include <vector>
#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h"
#include "base/syslog_logging.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/version.h"
#include "build/build_config.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/extension_constants.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/management_policy.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_urls.h"
#include "extensions/common/extensions_client.h"
#include "extensions/common/manifest.h"
#include "extensions/common/manifest_url_handlers.h"
#include "net/base/backoff_entry.h"
#include "net/base/escape.h"
#if defined(OS_CHROMEOS)
#include "chrome/browser/extensions/extension_assets_manager_chromeos.h"
#endif
namespace {
const char kContentVerificationExperimentName[] =
"ExtensionContentVerification";
const net::BackoffEntry::Policy kPolicyReinstallBackoffPolicy = {
// num_errors_to_ignore
1,
// initial_delay_ms (note that we set 'always_use_initial_delay' to false
// below)
100,
// multiply_factor
2,
// jitter_factor
0.1,
// maximum_backoff_ms (30 minutes)
1000 * 60 * 30,
// entry_lifetime_ms (6 hours)
1000 * 60 * 60 * 6,
// always_use_initial_delay
false,
};
base::Callback<void(base::TimeDelta delay)>* g_reinstall_action_for_test =
nullptr;
} // namespace
namespace extensions {
// static
ContentVerifierDelegate::Mode ChromeContentVerifierDelegate::GetDefaultMode() {
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
Mode experiment_value;
#if defined(GOOGLE_CHROME_BUILD)
experiment_value = ContentVerifierDelegate::ENFORCE_STRICT;
#else
experiment_value = ContentVerifierDelegate::NONE;
#endif
const std::string group =
base::FieldTrialList::FindFullName(kContentVerificationExperimentName);
if (group == "EnforceStrict")
experiment_value = ContentVerifierDelegate::ENFORCE_STRICT;
else if (group == "Enforce")
experiment_value = ContentVerifierDelegate::ENFORCE;
else if (group == "Bootstrap")
experiment_value = ContentVerifierDelegate::BOOTSTRAP;
else if (group == "None")
experiment_value = ContentVerifierDelegate::NONE;
// The field trial value that normally comes from the server can be
// overridden on the command line, which we don't want to allow since
// malware can set chrome command line flags. There isn't currently a way
// to find out what the server-provided value is in this case, so we
// conservatively default to the strictest mode if we detect our experiment
// name being overridden.
if (command_line->HasSwitch(switches::kForceFieldTrials)) {
std::string forced_trials =
command_line->GetSwitchValueASCII(switches::kForceFieldTrials);
if (forced_trials.find(kContentVerificationExperimentName) !=
std::string::npos)
experiment_value = ContentVerifierDelegate::ENFORCE_STRICT;
}
Mode cmdline_value = NONE;
if (command_line->HasSwitch(switches::kExtensionContentVerification)) {
std::string switch_value = command_line->GetSwitchValueASCII(
switches::kExtensionContentVerification);
if (switch_value == switches::kExtensionContentVerificationBootstrap)
cmdline_value = ContentVerifierDelegate::BOOTSTRAP;
else if (switch_value == switches::kExtensionContentVerificationEnforce)
cmdline_value = ContentVerifierDelegate::ENFORCE;
else if (switch_value ==
switches::kExtensionContentVerificationEnforceStrict)
cmdline_value = ContentVerifierDelegate::ENFORCE_STRICT;
else
// If no value was provided (or the wrong one), just default to enforce.
cmdline_value = ContentVerifierDelegate::ENFORCE;
}
// We don't want to allow the command-line flags to eg disable enforcement
// if the experiment group says it should be on, or malware may just modify
// the command line flags. So return the more restrictive of the 2 values.
return std::max(experiment_value, cmdline_value);
}
ChromeContentVerifierDelegate::ChromeContentVerifierDelegate(
content::BrowserContext* context)
: context_(context), default_mode_(GetDefaultMode()) {}
ChromeContentVerifierDelegate::~ChromeContentVerifierDelegate() {
}
ContentVerifierDelegate::Mode ChromeContentVerifierDelegate::ShouldBeVerified(
const Extension& extension) {
#if defined(OS_CHROMEOS)
if (ExtensionAssetsManagerChromeOS::IsSharedInstall(&extension))
return ContentVerifierDelegate::ENFORCE_STRICT;
#endif
if (!extension.is_extension() && !extension.is_legacy_packaged_app())
return ContentVerifierDelegate::NONE;
if (!Manifest::IsAutoUpdateableLocation(extension.location()))
return ContentVerifierDelegate::NONE;
if (!ManifestURL::UpdatesFromGallery(&extension)) {
// It's possible that the webstore update url was overridden for testing
// so also consider extensions with the default (production) update url
// to be from the store as well.
if (ManifestURL::GetUpdateURL(&extension) !=
extension_urls::GetDefaultWebstoreUpdateUrl()) {
return ContentVerifierDelegate::NONE;
}
}
return default_mode_;
}
ContentVerifierKey ChromeContentVerifierDelegate::GetPublicKey() {
return ContentVerifierKey(kWebstoreSignaturesPublicKey,
kWebstoreSignaturesPublicKeySize);
}
GURL ChromeContentVerifierDelegate::GetSignatureFetchUrl(
const std::string& extension_id,
const base::Version& version) {
// TODO(asargent) Factor out common code from the extension updater's
// ManifestFetchData class that can be shared for use here.
std::vector<std::string> parts;
parts.push_back("uc");
parts.push_back("installsource=signature");
parts.push_back("id=" + extension_id);
parts.push_back("v=" + version.GetString());
std::string x_value =
net::EscapeQueryParamValue(base::JoinString(parts, "&"), true);
std::string query = "response=redirect&x=" + x_value;
GURL base_url = extension_urls::GetWebstoreUpdateUrl();
GURL::Replacements replacements;
replacements.SetQuery(query.c_str(), url::Component(0, query.length()));
return base_url.ReplaceComponents(replacements);
}
std::set<base::FilePath> ChromeContentVerifierDelegate::GetBrowserImagePaths(
const extensions::Extension* extension) {
return ExtensionsClient::Get()->GetBrowserImagePaths(extension);
}
void ChromeContentVerifierDelegate::VerifyFailed(
const std::string& extension_id,
ContentVerifyJob::FailureReason reason) {
ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
const Extension* extension =
registry->enabled_extensions().GetByID(extension_id);
if (!extension)
return;
ExtensionSystem* system = ExtensionSystem::Get(context_);
ExtensionService* service = system->extension_service();
Mode mode = ShouldBeVerified(*extension);
if (mode >= ContentVerifierDelegate::ENFORCE) {
if (system->management_policy()->MustRemainEnabled(extension, NULL)) {
PendingExtensionManager* pending_manager =
service->pending_extension_manager();
if (pending_manager->IsPolicyReinstallForCorruptionExpected(extension_id))
return;
SYSLOG(WARNING) << "Corruption detected in policy extension "
<< extension_id << " installed at: "
<< extension->path().value();
pending_manager->ExpectPolicyReinstallForCorruption(extension_id);
service->DisableExtension(extension_id, Extension::DISABLE_CORRUPTED);
net::BackoffEntry* backoff_entry = nullptr;
auto iter = policy_reinstall_backoff_.find(extension_id);
if (iter != policy_reinstall_backoff_.end()) {
backoff_entry = iter->second.get();
} else {
auto new_backoff_entry =
base::MakeUnique<net::BackoffEntry>(&kPolicyReinstallBackoffPolicy);
backoff_entry = new_backoff_entry.get();
policy_reinstall_backoff_[extension_id] = std::move(new_backoff_entry);
}
backoff_entry->InformOfRequest(false);
base::TimeDelta reinstall_delay = backoff_entry->GetTimeUntilRelease();
if (g_reinstall_action_for_test) {
g_reinstall_action_for_test->Run(reinstall_delay);
} else {
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, base::Bind(&ExtensionService::CheckForExternalUpdates,
service->AsWeakPtr()),
reinstall_delay);
}
return;
}
DLOG(WARNING) << "Disabling extension " << extension_id << " ('"
<< extension->name()
<< "') due to content verification failure. In tests you "
<< "might want to use a ScopedIgnoreContentVerifierForTest "
<< "instance to prevent this.";
service->DisableExtension(extension_id, Extension::DISABLE_CORRUPTED);
ExtensionPrefs::Get(context_)->IncrementCorruptedDisableCount();
UMA_HISTOGRAM_BOOLEAN("Extensions.CorruptExtensionBecameDisabled", true);
UMA_HISTOGRAM_ENUMERATION("Extensions.CorruptExtensionDisabledReason",
reason, ContentVerifyJob::FAILURE_REASON_MAX);
} else if (!base::ContainsKey(would_be_disabled_ids_, extension_id)) {
UMA_HISTOGRAM_BOOLEAN("Extensions.CorruptExtensionWouldBeDisabled", true);
would_be_disabled_ids_.insert(extension_id);
}
}
// static
void ChromeContentVerifierDelegate::set_policy_reinstall_action_for_test(
base::Callback<void(base::TimeDelta delay)>* action) {
g_reinstall_action_for_test = action;
}
} // namespace extensions