#include "extensions/browser/content_verifier/content_verifier.h"
#include <algorithm>
#include <utility>
#include <vector>
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread_restrictions.h"
#include "base/trace_event/typed_macros.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/storage_partition.h"
#include "extensions/browser/content_hash_fetcher.h"
#include "extensions/browser/content_hash_reader.h"
#include "extensions/browser/content_verifier/content_verifier_delegate.h"
#include "extensions/browser/content_verifier/content_verifier_utils.h"
#include "extensions/browser/extension_file_task_runner.h"
#include "extensions/common/api/declarative_net_request/dnr_manifest_data.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/extension_l10n_util.h"
#include "extensions/common/extension_resource.h"
#include "extensions/common/file_util.h"
#include "extensions/common/manifest_handlers/background_info.h"
#include "extensions/common/manifest_handlers/content_scripts_handler.h"
#include "extensions/common/utils/base_string.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "net/base/mime_util.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "third_party/blink/public/common/mime_util/mime_util.h"
namespace extensions {
namespace {
ContentVerifier::TestObserver* g_content_verifier_test_observer = nullptr;
using content_verifier_utils::CanonicalRelativePath;
base::FilePath NormalizeRelativePath(const base::FilePath& relative_path) {
CHECK(!relative_path.IsAbsolute());
base::FilePath relative_path_normalized =
content_verifier_utils::NormalizePathComponents(relative_path);
std::vector<base::FilePath::StringType> parts =
relative_path_normalized.GetComponents();
while (!parts.empty() && parts[0] == base::FilePath::kParentDirectory) {
parts.erase(parts.begin());
}
base::FilePath::StringType normalized_relative_path =
base::JoinString(parts, base::FilePath::StringType(1, '/'));
if (relative_path.EndsWithSeparator() && !normalized_relative_path.empty()) {
normalized_relative_path.push_back('/');
}
return base::FilePath(normalized_relative_path);
}
std::unique_ptr<ContentVerifierIOData::ExtensionData> CreateIOData(
const Extension* extension,
ContentVerifierDelegate* delegate) {
ContentVerifierDelegate::VerifierSourceType source_type =
delegate->GetVerifierSourceType(*extension);
if (source_type == ContentVerifierDelegate::VerifierSourceType::NONE)
return nullptr;
std::set<base::FilePath> original_image_paths =
delegate->GetBrowserImagePaths(extension);
auto canonicalize_path = [](const base::FilePath& relative_path) {
return content_verifier_utils::CanonicalizeRelativePath(
NormalizeRelativePath(relative_path));
};
auto result = std::make_unique<ContentVerifierIOData::ExtensionData>();
for (const auto& path : original_image_paths) {
result->canonical_browser_image_paths.insert(canonicalize_path(path));
}
for (const ExtensionResource& script :
BackgroundInfo::GetBackgroundScripts(extension)) {
result->canonical_background_scripts_paths.insert(
canonicalize_path(script.relative_path()));
}
if (BackgroundInfo::HasBackgroundPage(extension)) {
result->canonical_background_page_path =
content_verifier_utils::CanonicalizeRelativePath(
extensions::file_util::ExtensionURLToRelativeFilePath(
BackgroundInfo::GetBackgroundURL(extension)));
}
if (BackgroundInfo::IsServiceWorkerBased(extension)) {
result->canonical_service_worker_script_path =
content_verifier_utils::CanonicalizeRelativePath(
file_util::ExtensionURLToRelativeFilePath(
BackgroundInfo::GetBackgroundServiceWorkerScriptURL(
extension)));
}
for (const std::unique_ptr<UserScript>& script :
ContentScriptsInfo::GetContentScripts(extension)) {
for (const std::unique_ptr<UserScript::Content>& js_file :
script->js_scripts()) {
result->canonical_content_scripts_paths.insert(
canonicalize_path(js_file->relative_path()));
}
}
using DNRManifestData = declarative_net_request::DNRManifestData;
for (const DNRManifestData::RulesetInfo& info :
DNRManifestData::GetRulesets(*extension)) {
result->canonical_indexed_ruleset_paths.insert(canonicalize_path(
file_util::GetIndexedRulesetRelativePath(info.id.value())));
}
result->version = extension->version();
result->manifest_version = extension->manifest_version();
result->source_type = source_type;
return result;
}
}
struct ContentVerifier::CacheKey {
CacheKey(const ExtensionId& extension_id,
const base::Version& version,
bool needs_force_missing_computed_hashes_creation)
: extension_id(extension_id),
version(version),
needs_force_missing_computed_hashes_creation(
needs_force_missing_computed_hashes_creation) {}
bool operator<(const CacheKey& other) const {
return std::tie(extension_id, version,
needs_force_missing_computed_hashes_creation) <
std::tie(other.extension_id, other.version,
other.needs_force_missing_computed_hashes_creation);
}
ExtensionId extension_id;
base::Version version;
bool needs_force_missing_computed_hashes_creation = false;
};
class ContentVerifier::HashHelper {
public:
explicit HashHelper(ContentVerifier* content_verifier)
: content_verifier_(content_verifier) {}
HashHelper(const HashHelper&) = delete;
HashHelper& operator=(const HashHelper&) = delete;
~HashHelper() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
}
void Cancel(const ExtensionId& extension_id,
const base::Version& extension_version) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
auto callback_key = std::make_pair(extension_id, extension_version);
auto iter = callback_infos_.find(callback_key);
if (iter == callback_infos_.end())
return;
iter->second.Cancel();
callback_infos_.erase(iter);
}
void GetContentHash(ContentHash::FetchKey fetch_key,
ContentVerifierDelegate::VerifierSourceType source_type,
bool force_missing_computed_hashes_creation,
ContentHashCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
auto callback_key =
std::make_pair(fetch_key.extension_id, fetch_key.extension_version);
auto iter = callback_infos_.find(callback_key);
if (iter != callback_infos_.end()) {
iter->second.callbacks.push_back(std::move(callback));
iter->second.force_missing_computed_hashes_creation |=
force_missing_computed_hashes_creation;
return;
}
scoped_refptr<IsCancelledChecker> checker =
base::MakeRefCounted<IsCancelledChecker>();
auto iter_pair = callback_infos_.emplace(
callback_key, CallbackInfo(checker, std::move(callback)));
DCHECK(iter_pair.second);
iter_pair.first->second.force_missing_computed_hashes_creation |=
force_missing_computed_hashes_creation;
GetExtensionFileTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
&HashHelper::ReadHashOnFileTaskRunner, std::move(fetch_key),
source_type,
base::BindRepeating(&IsCancelledChecker::IsCancelled, checker),
base::BindOnce(&HashHelper::DidReadHash, weak_factory_.GetWeakPtr(),
callback_key, checker)));
}
private:
using CallbackKey = std::pair<ExtensionId, base::Version>;
class IsCancelledChecker
: public base::RefCountedThreadSafe<IsCancelledChecker> {
public:
IsCancelledChecker() {}
IsCancelledChecker(const IsCancelledChecker&) = delete;
IsCancelledChecker& operator=(const IsCancelledChecker&) = delete;
void Cancel() {
base::AutoLock autolock(cancelled_lock_);
cancelled_ = true;
}
bool IsCancelled() {
base::AutoLock autolock(cancelled_lock_);
return cancelled_;
}
private:
friend class base::RefCountedThreadSafe<IsCancelledChecker>;
~IsCancelledChecker() {}
bool cancelled_ = false;
base::Lock cancelled_lock_;
};
struct CallbackInfo {
CallbackInfo(const scoped_refptr<IsCancelledChecker>& cancelled_checker,
ContentHashCallback callback)
: cancelled_checker(cancelled_checker) {
callbacks.push_back(std::move(callback));
}
void Cancel() { cancelled_checker->Cancel(); }
scoped_refptr<IsCancelledChecker> cancelled_checker;
std::vector<ContentHashCallback> callbacks;
bool force_missing_computed_hashes_creation = false;
};
using IsCancelledCallback = base::RepeatingCallback<bool(void)>;
static void ForwardToIO(ContentHash::CreatedCallback callback,
scoped_refptr<ContentHash> content_hash,
bool was_cancelled) {
if (was_cancelled)
return;
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback), content_hash, was_cancelled));
}
static void ReadHashOnFileTaskRunner(
ContentHash::FetchKey fetch_key,
ContentVerifierDelegate::VerifierSourceType source_type,
const IsCancelledCallback& is_cancelled,
ContentHash::CreatedCallback created_callback) {
TRACE_EVENT("extensions.content_verifier.debug",
"HashHelper::ReadHashOnFileTaskRunner",
"fetch_key_extension_id", fetch_key.extension_id,
"fetch_key_extension_root", fetch_key.extension_root,
"fetch_key_extension_version",
fetch_key.extension_version.GetString());
ContentHash::Create(
std::move(fetch_key), source_type, is_cancelled,
base::BindOnce(&HashHelper::ForwardToIO, std::move(created_callback)));
}
static void ForceBuildComputedHashesOnFileTaskRuner(
const scoped_refptr<ContentHash> content_hash,
const IsCancelledCallback& is_cancelled,
ContentHash::CreatedCallback created_callback) {
TRACE_EVENT("extensions.content_verifier.debug",
"HashHelper::ForceBuildComputedHashesOnFileTaskRuner",
"hash_extension_id", content_hash->extension_id(),
"hash_extension_root", content_hash->extension_root());
content_hash->ForceBuildComputedHashes(
is_cancelled,
base::BindOnce(&HashHelper::ForwardToIO, std::move(created_callback)));
}
void DidReadHash(const CallbackKey& key,
const scoped_refptr<IsCancelledChecker>& checker,
scoped_refptr<ContentHash> content_hash,
bool was_cancelled) {
TRACE_EVENT("extensions.content_verifier.debug", "HashHelper::DidReadHash",
"hash_extension_id", content_hash->extension_id(),
"hash_extension_root", content_hash->extension_root(),
"was_cancelled", (was_cancelled ? "true" : "false"));
DCHECK(checker);
if (was_cancelled ||
checker->IsCancelled()) {
return;
}
auto iter = callback_infos_.find(key);
CHECK(iter != callback_infos_.end());
auto& callback_info = iter->second;
if (callback_info.force_missing_computed_hashes_creation &&
content_hash->might_require_computed_hashes_force_creation()) {
GetExtensionFileTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&HashHelper::ForceBuildComputedHashesOnFileTaskRuner,
content_hash,
base::BindRepeating(&IsCancelledChecker::IsCancelled,
callback_info.cancelled_checker),
base::BindOnce(&HashHelper::CompleteDidReadHash,
weak_factory_.GetWeakPtr(), key,
callback_info.cancelled_checker)));
return;
}
CompleteDidReadHash(key, callback_info.cancelled_checker,
std::move(content_hash), was_cancelled);
}
void CompleteDidReadHash(const CallbackKey& key,
const scoped_refptr<IsCancelledChecker>& checker,
scoped_refptr<ContentHash> content_hash,
bool was_cancelled) {
TRACE_EVENT("extensions.content_verifier.debug",
"HashHelper::CompleteDidReadHash", "hash_extension_id",
content_hash->extension_id(), "hash_extension_root",
content_hash->extension_root(), "was_cancelled",
(was_cancelled ? "true" : "false"));
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
DCHECK(checker);
if (was_cancelled ||
checker->IsCancelled()) {
return;
}
auto iter = callback_infos_.find(key);
CHECK(iter != callback_infos_.end());
auto& callback_info = iter->second;
for (auto& callback : callback_info.callbacks)
std::move(callback).Run(content_hash);
callback_infos_.erase(iter);
content_verifier_->OnFetchComplete(content_hash);
}
std::map<CallbackKey, CallbackInfo> callback_infos_;
const raw_ptr<ContentVerifier> content_verifier_ = nullptr;
base::WeakPtrFactory<HashHelper> weak_factory_{this};
};
class ContentVerifier::VerifiedFileTypeHelper {
public:
explicit VerifiedFileTypeHelper(
const ContentVerifierIOData::ExtensionData& extension_data)
: data_(extension_data) {}
~VerifiedFileTypeHelper() = default;
ContentVerifier::VerifiedFileType GetVerifiedFileType(
const base::FilePath& relative_path) {
if (relative_path.empty()) {
return ContentVerifier::VerifiedFileType::kNone;
}
CanonicalRelativePath canonical_path_value =
content_verifier_utils::CanonicalizeRelativePath(relative_path);
if (canonical_path_value == manifest_file_) {
return ContentVerifier::VerifiedFileType::kNone;
}
if (canonical_path_value == data_->canonical_background_page_path) {
return ContentVerifier::VerifiedFileType::kBackgroundPage;
}
if (canonical_path_value == data_->canonical_service_worker_script_path) {
return ContentVerifier::VerifiedFileType::kServiceWorkerScript;
}
if (base::Contains(data_->canonical_background_scripts_paths,
canonical_path_value)) {
return ContentVerifier::VerifiedFileType::kBackgroundScript;
}
if (base::Contains(data_->canonical_content_scripts_paths,
canonical_path_value)) {
return ContentVerifier::VerifiedFileType::kContentScript;
}
const base::FilePath canonical_path(canonical_path_value.value());
std::string mime_type;
if (net::GetWellKnownMimeTypeFromFile(canonical_path, &mime_type)) {
if (blink::IsSupportedJavascriptMimeType(mime_type)) {
return ContentVerifier::VerifiedFileType::kMiscJsFile;
}
if (mime_type == "text/html") {
return ContentVerifier::VerifiedFileType::kMiscHtmlFile;
}
}
if (base::Contains(data_->canonical_browser_image_paths,
canonical_path_value)) {
return ContentVerifier::VerifiedFileType::kNone;
}
if (base::Contains(data_->canonical_indexed_ruleset_paths,
canonical_path_value)) {
return ContentVerifier::VerifiedFileType::kNone;
}
if (locales_relative_dir_.IsParent(canonical_path)) {
if (all_locale_candidates_.empty()) {
extension_l10n_util::GetAllLocales(&all_locale_candidates_);
DCHECK(!all_locale_candidates_.empty());
}
if (canonical_path.BaseName() == messages_file_ &&
canonical_path.DirName().DirName() == locales_relative_dir_ &&
ContainsStringIgnoreCaseASCII(
all_locale_candidates_,
canonical_path.DirName().BaseName().MaybeAsASCII())) {
return ContentVerifier::VerifiedFileType::kNone;
}
}
return ContentVerifier::VerifiedFileType::kMiscFile;
}
private:
const CanonicalRelativePath manifest_file_{
content_verifier_utils::CanonicalizeRelativePath(
base::FilePath(kManifestFilename))};
const base::FilePath messages_file_{kMessagesFilename};
const base::FilePath locales_relative_dir_{kLocaleFolder};
std::set<std::string> all_locale_candidates_;
raw_ref<const ContentVerifierIOData::ExtensionData> data_;
};
void ContentVerifier::SetObserverForTests(TestObserver* observer) {
g_content_verifier_test_observer = observer;
}
ContentVerifier::ContentVerifier(
content::BrowserContext* context,
std::unique_ptr<ContentVerifierDelegate> delegate)
: context_(context), delegate_(std::move(delegate)) {}
ContentVerifier::~ContentVerifier() {
}
void ContentVerifier::Start() {
ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
observation_.Observe(registry);
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&ContentVerifier::StartOnIO, this));
}
void ContentVerifier::StartOnIO() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
verification_enabled_ = true;
}
void ContentVerifier::Shutdown() {
shutdown_on_ui_ = true;
delegate_->Shutdown();
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&ContentVerifier::ShutdownOnIO, this));
observation_.Reset();
}
void ContentVerifier::ShutdownOnIO() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
shutdown_on_io_ = true;
io_data_.Clear();
hash_helper_.reset();
}
scoped_refptr<ContentVerifyJob> ContentVerifier::CreateAndStartJobFor(
const ExtensionId& extension_id,
const base::FilePath& extension_root,
const base::Version& extension_version,
const base::FilePath& relative_path,
scoped_refptr<ContentVerifier> verifier) {
base::FilePath normalized_unix_path = NormalizeRelativePath(relative_path);
scoped_refptr<ContentVerifyJob> job = base::MakeRefCounted<ContentVerifyJob>(
extension_id, extension_version, extension_root, normalized_unix_path);
content::GetIOThreadTaskRunner({base::TaskPriority::USER_BLOCKING})
->PostTask(FROM_HERE,
base::BindOnce(&ContentVerifier::OnJobCreated, verifier, job));
return job;
}
void ContentVerifier::OnJobCreated(scoped_refptr<ContentVerifyJob> job) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (shutdown_on_io_ || !verification_enabled_) {
return;
}
if (!ready_extensions_.contains(job->extension_id())) {
pending_jobs_[job->extension_id()].push_back(std::move(job));
return;
}
StartJob(job);
}
void ContentVerifier::CreateContentHash(
const ExtensionId& extension_id,
const base::FilePath& extension_root,
const base::Version& extension_version,
bool force_missing_computed_hashes_creation,
ContentHashCallback callback) {
TRACE_EVENT("extensions.content_verifier.debug",
"ContentVerifier::CreateContentHash", "extension_id",
extension_id, "extension_root", extension_root,
"extension_version", extension_version.GetString(),
"force_missing_computed_hashes_creation",
(force_missing_computed_hashes_creation ? "true" : "false"));
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (shutdown_on_io_) {
return;
}
const ContentVerifierIOData::ExtensionData* data =
io_data_.GetData(extension_id);
DCHECK(data);
ContentHash::FetchKey fetch_key =
GetFetchKey(extension_id, extension_root, extension_version);
CacheKey cache_key(extension_id, extension_version,
force_missing_computed_hashes_creation);
GetOrCreateHashHelper()->GetContentHash(
std::move(fetch_key), data->source_type,
force_missing_computed_hashes_creation,
base::BindOnce(&ContentVerifier::DidGetContentHash, this, cache_key,
std::move(callback)));
}
scoped_refptr<const ContentHash> ContentVerifier::GetCachedContentHash(
const ExtensionId& extension_id,
const base::Version& extension_version,
bool force_missing_computed_hashes_creation) {
TRACE_EVENT("extensions.content_verifier.debug",
"ContentVerifier::GetCachedContentHash", "extension_id",
extension_id, "extension_version", extension_version.GetString(),
"force_missing_computed_hashes_creation",
(force_missing_computed_hashes_creation ? "true" : "false"));
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (shutdown_on_io_) {
return nullptr;
}
CacheKey cache_key(extension_id, extension_version,
force_missing_computed_hashes_creation);
auto cache_iter = cache_.find(cache_key);
return cache_iter != cache_.end() ? cache_iter->second : nullptr;
}
bool ContentVerifier::ShouldComputeHashesOnInstall(const Extension& extension) {
return delegate_->GetVerifierSourceType(extension) ==
ContentVerifierDelegate::VerifierSourceType::UNSIGNED_HASHES;
}
void ContentVerifier::VerifyFailed(
const ExtensionId& extension_id,
const std::vector<VerifiedFileType>& failed_file_types,
int manifest_version,
ContentVerifyJob::FailureReason reason) {
TRACE_EVENT("extensions.content_verifier.debug",
"ContentVerifier::VerifyFailed", "extension_id", extension_id,
"ContentVerifyJob::FailureReason", reason);
if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) {
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&ContentVerifier::VerifyFailed, this, extension_id,
failed_file_types, manifest_version, reason));
return;
}
if (shutdown_on_ui_)
return;
VLOG(1) << "VerifyFailed " << extension_id << " reason:" << reason;
DCHECK_NE(ContentVerifyJob::NONE, reason);
for (VerifiedFileType file_type : failed_file_types) {
const char* histogram_suffix = nullptr;
switch (file_type) {
case VerifiedFileType::kNone:
NOTREACHED();
case VerifiedFileType::kBackgroundPage:
histogram_suffix = "BackgroundPage";
break;
case VerifiedFileType::kBackgroundScript:
histogram_suffix = "BackgroundScript";
break;
case VerifiedFileType::kServiceWorkerScript:
histogram_suffix = "ServiceWorkerScript";
break;
case VerifiedFileType::kContentScript:
histogram_suffix = "ContentScript";
break;
case VerifiedFileType::kMiscHtmlFile:
histogram_suffix = "MiscHtmlFile";
break;
case VerifiedFileType::kMiscJsFile:
histogram_suffix = "MiscJsFile";
break;
case VerifiedFileType::kMiscFile:
histogram_suffix = "MiscFile";
break;
}
if (manifest_version == 2) {
base::UmaHistogramEnumeration(
base::StringPrintf(
"Extensions.ContentVerification.VerifyFailedOnFileMV2.%s",
histogram_suffix),
reason, ContentVerifyJob::FAILURE_REASON_MAX);
base::UmaHistogramEnumeration(
"Extensions.ContentVerification.VerifyFailedOnFileTypeMV2",
file_type);
} else if (manifest_version == 3) {
base::UmaHistogramEnumeration(
base::StringPrintf(
"Extensions.ContentVerification.VerifyFailedOnFileMV3.%s",
histogram_suffix),
reason, ContentVerifyJob::FAILURE_REASON_MAX);
base::UmaHistogramEnumeration(
"Extensions.ContentVerification.VerifyFailedOnFileTypeMV3",
file_type);
}
}
delegate_->VerifyFailed(extension_id, reason);
}
void ContentVerifier::OnExtensionLoaded(
content::BrowserContext* browser_context,
const Extension* extension) {
TRACE_EVENT("extensions.content_verifier.debug",
"ContentVerifier::OnExtensionLoaded", "extension_id",
extension->id(), "extension_root", extension->path(),
"extension_version", extension->version().GetString());
if (shutdown_on_ui_)
return;
std::unique_ptr<ContentVerifierIOData::ExtensionData> io_data =
CreateIOData(extension, delegate_.get());
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&ContentVerifier::OnExtensionLoadedOnIO, this,
extension->id(), extension->path(),
extension->version(), std::move(io_data)));
}
void ContentVerifier::OnExtensionLoadedOnIO(
const ExtensionId& extension_id,
const base::FilePath& extension_root,
const base::Version& extension_version,
std::unique_ptr<ContentVerifierIOData::ExtensionData> data) {
TRACE_EVENT("extensions.content_verifier.debug",
"ContentVerifier::OnExtensionLoadedOnIO", "extension_id",
extension_id, "extension_root", extension_root,
"extension_version", extension_version.GetString());
if (shutdown_on_io_)
return;
if (data) {
io_data_.AddData(extension_id, std::move(*data));
CreateContentHash(extension_id, extension_root, extension_version,
false,
base::DoNothing());
}
OnExtensionDataReady(extension_id);
}
void ContentVerifier::OnExtensionUnloaded(
content::BrowserContext* browser_context,
const Extension* extension,
UnloadedExtensionReason reason) {
TRACE_EVENT("extensions.content_verifier.debug",
"ContentVerifier::OnExtensionUnloaded", "extension_id",
extension->id(), "extension_root", extension->path(),
"extension_version", extension->version().GetString(),
"UnloadedExtensionReason", reason);
if (shutdown_on_ui_)
return;
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&ContentVerifier::OnExtensionUnloadedOnIO, this,
extension->id(), extension->version()));
}
ContentVerifierKey ContentVerifier::GetContentVerifierKey() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
return delegate_->GetPublicKey();
}
GURL ContentVerifier::GetSignatureFetchUrlForTest(
const ExtensionId& extension_id,
const base::Version& extension_version) {
return delegate_->GetSignatureFetchUrl(extension_id, extension_version);
}
void ContentVerifier::VerifyFailedForTest(
const ExtensionId& extension_id,
ContentVerifyJob::FailureReason reason) {
VerifyFailed(extension_id, {VerifiedFileType::kMiscFile}, 3, reason);
}
void ContentVerifier::ClearCacheForTesting() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
cache_.clear();
}
void ContentVerifier::OnExtensionUnloadedOnIO(
const ExtensionId& extension_id,
const base::Version& extension_version) {
TRACE_EVENT("extensions.content_verifier.debug",
"ContentVerifier::OnExtensionUnloadedOnIO", "extension_id",
extension_id, "extension_version", extension_version.GetString());
if (shutdown_on_io_)
return;
io_data_.RemoveData(extension_id);
cache_.erase(CacheKey(extension_id, extension_version, true));
cache_.erase(CacheKey(extension_id, extension_version, false));
HashHelper* hash_helper = GetOrCreateHashHelper();
if (hash_helper)
hash_helper->Cancel(extension_id, extension_version);
ready_extensions_.erase(extension_id);
pending_jobs_.erase(extension_id);
}
void ContentVerifier::OnExtensionDataReady(const ExtensionId& extension_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
ready_extensions_.insert(extension_id);
if (auto it = pending_jobs_.find(extension_id); it != pending_jobs_.end()) {
for (const auto& job : it->second) {
StartJob(job);
}
pending_jobs_.erase(it);
}
}
void ContentVerifier::StartJob(const scoped_refptr<ContentVerifyJob>& job) {
TRACE_EVENT("extensions.content_verifier.debug", "ContentVerifier::StartJob",
"job_extension_id", job->extension_id());
const ContentVerifierIOData::ExtensionData* data =
io_data_.GetData(job->extension_id());
if (!data) {
return;
}
TRACE_EVENT_INSTANT("extensions.content_verifier.debug",
"ContentVerifier::StartJob", "job_extension_id",
job->extension_id(), "data_extension_version",
data->version.GetString());
VerifiedFileType verified_file_type =
VerifiedFileTypeHelper(*data).GetVerifiedFileType(job->relative_path());
if (verified_file_type == VerifiedFileType::kNone) {
return;
}
std::vector<VerifiedFileType> file_types({verified_file_type});
auto callback =
base::BindOnce(&ContentVerifier::VerifyFailed, this, job->extension_id(),
file_types, data->manifest_version);
const base::Version& current_extension_version = data->version;
if (base::FeatureList::IsEnabled(
extensions_features::kContentVerifyJobUseJobVersionForHashing) &&
current_extension_version != job->extension_version()) {
return;
}
job->Start(this, current_extension_version, data->manifest_version,
std::move(callback));
}
void ContentVerifier::OnFetchComplete(
const scoped_refptr<const ContentHash>& content_hash) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
ExtensionId extension_id = content_hash->extension_id();
VLOG(1) << "OnFetchComplete " << extension_id
<< " success:" << content_hash->succeeded();
std::vector<VerifiedFileType> file_hash_mismatches;
const ContentVerifierIOData::ExtensionData* data =
io_data_.GetData(extension_id);
if (data) {
VerifiedFileTypeHelper verified_file_type_helper(*data);
for (const base::FilePath& path :
content_hash->hash_mismatch_unix_paths()) {
VerifiedFileType verified_file_type =
verified_file_type_helper.GetVerifiedFileType(path);
if (verified_file_type != VerifiedFileType::kNone) {
file_hash_mismatches.push_back(verified_file_type);
}
}
}
const bool did_hash_mismatch = !file_hash_mismatches.empty();
if (g_content_verifier_test_observer) {
g_content_verifier_test_observer->OnFetchComplete(content_hash,
did_hash_mismatch);
}
if (!did_hash_mismatch)
return;
VerifyFailed(extension_id, file_hash_mismatches, data->manifest_version,
ContentVerifyJob::HASH_MISMATCH);
}
ContentHash::FetchKey ContentVerifier::GetFetchKey(
const ExtensionId& extension_id,
const base::FilePath& extension_root,
const base::Version& extension_version) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
const ContentVerifierIOData::ExtensionData* data =
io_data_.GetData(extension_id);
DCHECK(data);
if (data->source_type ==
ContentVerifierDelegate::VerifierSourceType::UNSIGNED_HASHES) {
return ContentHash::FetchKey(extension_id, extension_root,
extension_version, mojo::NullRemote(), GURL(),
ContentVerifierKey());
}
mojo::PendingRemote<network::mojom::URLLoaderFactory>
url_loader_factory_remote;
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(
&ContentVerifier::BindURLLoaderFactoryReceiverOnUIThread, this,
url_loader_factory_remote.InitWithNewPipeAndPassReceiver()));
return ContentHash::FetchKey(
extension_id, extension_root, extension_version,
std::move(url_loader_factory_remote),
delegate_->GetSignatureFetchUrl(extension_id, extension_version),
delegate_->GetPublicKey());
}
void ContentVerifier::DidGetContentHash(
const CacheKey& cache_key,
ContentHashCallback original_callback,
scoped_refptr<const ContentHash> content_hash) {
cache_[cache_key] = content_hash;
std::move(original_callback).Run(content_hash);
}
void ContentVerifier::BindURLLoaderFactoryReceiverOnUIThread(
mojo::PendingReceiver<network::mojom::URLLoaderFactory>
url_loader_factory_receiver) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (shutdown_on_ui_)
return;
context_->GetDefaultStoragePartition()
->GetURLLoaderFactoryForBrowserProcess()
->Clone(std::move(url_loader_factory_receiver));
}
ContentVerifier::HashHelper* ContentVerifier::GetOrCreateHashHelper() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
DCHECK(!shutdown_on_io_) << "Creating HashHelper after IO shutdown";
if (!hash_helper_created_) {
DCHECK(!hash_helper_);
hash_helper_ =
std::unique_ptr<HashHelper, content::BrowserThread::DeleteOnIOThread>(
new HashHelper(this));
hash_helper_created_ = true;
}
return hash_helper_.get();
}
void ContentVerifier::ResetIODataForTesting(const Extension* extension) {
std::unique_ptr<ContentVerifierIOData::ExtensionData> data =
CreateIOData(extension, delegate_.get());
CHECK(data);
io_data_.AddData(extension->id(), std::move(*data));
}
base::FilePath ContentVerifier::NormalizeRelativePathForTesting(
const base::FilePath& path) {
return NormalizeRelativePath(path);
}
bool ContentVerifier::ShouldVerifyAnyPathsForTesting(
const ExtensionId& extension_id,
const base::FilePath& extension_root,
const std::set<base::FilePath>& relative_unix_paths) {
const ContentVerifierIOData::ExtensionData* data =
io_data_.GetData(extension_id);
if (!data) {
return false;
}
VerifiedFileTypeHelper helper(*data);
return std::ranges::any_of(
relative_unix_paths, [&helper](const base::FilePath& path) {
return helper.GetVerifiedFileType(path) != VerifiedFileType::kNone;
});
}
void ContentVerifier::OverrideDelegateForTesting(
std::unique_ptr<ContentVerifierDelegate> delegate) {
delegate_ = std::move(delegate);
}
}