Commit 0f709d22 authored by Istiaque Ahmed's avatar Istiaque Ahmed Committed by Commit Bot

[CV] Correctly verify paths in manifest.json for content verification.

This CL makes ContentVerifier::ShouldVerifyAnyPaths take
canonical relative paths instead of unix style relative paths,
so that it can take OS specific canonicalization into account.
This affects all paths specified in manifest.json: background
JS, background html, content script, locales, etc. Previously
ShouldVerifyAnyPaths would have returned false for case
variants of those resources on win/mac, and content-verification
wouldn't run for them as there wouldn't be any ContentVerifyJobs
for them.
An example of this is on win/mac, a script specified as script.js
in manifest.json, but accessed as Script.js on win/mac will
now be verified with this CL.
The same applies to _locales, locale path accessed in different
case now would correctly skip verification (e.g. en_gb instead of
en_GB), instead of incorrectly causing content verification
failure.

ContentVerifierObserver now exposes did_hash_mismatch, which would
be slightly better to observe CVDelegate::VerifyFailed from tests.

This CL adds unittests for the change in content_verifier_unittest.cc
This CL also adds a real world like unittest under //chrome, so
the browser image paths and other manifest paths are verified (and
not mocked like content_verifier_unittest) end-to-end. This is
chrome_content_verifier_unittest.cc

BUG=1051401,796395

Change-Id: I74161a043a6e65b80c7bf06c249cf42813f867a9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2049125
Commit-Queue: Istiaque Ahmed <lazyboy@chromium.org>
Reviewed-by: default avatarOleg Davydov <burunduk@chromium.org>
Cr-Commit-Position: refs/heads/master@{#744824}
parent 86972e27
// Copyright 2020 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 <algorithm>
#include "base/files/file_util.h"
#include "base/path_service.h"
#include "chrome/browser/extensions/chrome_content_verifier_delegate.h"
#include "chrome/browser/extensions/extension_service_test_with_install.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/testing_profile.h"
#include "extensions/browser/content_verifier/test_utils.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/info_map.h"
#include "extensions/common/file_util.h"
namespace extensions {
namespace {
constexpr char kCaseSensitiveManifestPathsCrx[] =
"content_verifier/case_sensitive_manifest_paths.crx";
std::set<base::FilePath> ToFilePaths(const std::set<std::string>& paths) {
std::set<base::FilePath> file_paths;
for (const auto& path : paths)
file_paths.insert(base::FilePath().AppendASCII(path));
return file_paths;
}
bool IsSuperset(const std::set<base::FilePath>& container,
const std::set<base::FilePath>& candidates) {
std::vector<base::FilePath> difference;
std::set_difference(candidates.begin(), candidates.end(), container.begin(),
container.end(), std::back_inserter(difference));
return difference.empty();
}
} // namespace
// Tests are run with //chrome layer so that manifest's //chrome specific bits
// (e.g. browser images, default_icon in actions) are present.
class ChromeContentVerifierTest : public ExtensionServiceTestWithInstall {
public:
void SetUp() override {
ExtensionServiceTestWithInstall::SetUp();
// Note: we need a separate TestingProfile (other than our base class)
// because we need it to build |content_verifier_| below in SetUp().
testing_profile_ = TestingProfile::Builder().Build();
// Set up content verification.
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
command_line->AppendSwitchASCII(
switches::kExtensionContentVerification,
switches::kExtensionContentVerificationEnforce);
auto delegate =
std::make_unique<ChromeContentVerifierDelegate>(browser_context());
delegate_raw_ = delegate.get();
content_verifier_ = base::MakeRefCounted<ContentVerifier>(
browser_context(), std::move(delegate));
info_map()->SetContentVerifier(content_verifier_.get());
content_verifier_->Start();
}
void TearDown() override {
content_verifier_->Shutdown();
ExtensionServiceTestWithInstall::TearDown();
}
testing::AssertionResult InstallExtension(const std::string& crx_path_str) {
if (extension_) {
return testing::AssertionFailure()
<< "Only one extension is allowed to be installed in this test. "
<< "Error while installing crx from: " << crx_path_str;
}
InitializeEmptyExtensionService();
base::FilePath data_dir;
if (!base::PathService::Get(chrome::DIR_TEST_DATA, &data_dir))
return testing::AssertionFailure() << "DIR_TEST_DATA not found";
base::FilePath crx_full_path =
data_dir.AppendASCII("extensions").AppendASCII(crx_path_str);
extension_ = InstallCRX(crx_full_path, INSTALL_NEW);
if (!extension_)
return testing::AssertionFailure()
<< "Failed to install extension at " << crx_full_path;
return testing::AssertionSuccess();
}
void AddExtensionToContentVerifier(
const scoped_refptr<const Extension>& extension,
VerifierObserver* verifier_observer) {
info_map()->AddExtension(extension.get(), base::Time::Now(), false, false);
EXPECT_TRUE(
ExtensionRegistry::Get(browser_context())->AddEnabled(extension));
ExtensionRegistry::Get(browser_context())->TriggerOnLoaded(extension.get());
// Ensure that content verifier has checked hashes from |extension|.
EXPECT_EQ(ChromeContentVerifierDelegate::VerifierSourceType::SIGNED_HASHES,
delegate_raw_->GetVerifierSourceType(*extension));
verifier_observer->EnsureFetchCompleted(extension->id());
}
scoped_refptr<ContentVerifier>& content_verifier() {
return content_verifier_;
}
const scoped_refptr<const Extension>& extension() { return extension_; }
bool ShouldVerifyAnyPaths(
const std::set<base::FilePath>& relative_unix_paths) const {
return content_verifier_->ShouldVerifyAnyPathsForTesting(
extension_->id(), extension_->path(), relative_unix_paths);
}
private:
InfoMap* info_map() {
return ExtensionSystem::Get(browser_context())->info_map();
}
content::BrowserContext* browser_context() { return testing_profile_.get(); }
scoped_refptr<const Extension> extension_;
// Owned by |content_verifier_|.
ChromeContentVerifierDelegate* delegate_raw_ = nullptr;
scoped_refptr<ContentVerifier> content_verifier_;
std::unique_ptr<TestingProfile> testing_profile_;
};
// Tests that an extension with mixed case resources specified in manifest.json
// (messages, browser images, browserAction.default_icon) loads correctly.
TEST_F(ChromeContentVerifierTest, CaseSensitivityInManifestPaths) {
VerifierObserver verifier_observer;
ASSERT_TRUE(InstallExtension(kCaseSensitiveManifestPathsCrx));
// Make sure computed_hashes.json does not exist as this test relies on its
// generation to discover hash_mismatch_unix_paths().
ASSERT_FALSE(
base::PathExists(file_util::GetComputedHashesPath(extension()->path())));
AddExtensionToContentVerifier(extension(), &verifier_observer);
ASSERT_TRUE(
base::PathExists(file_util::GetComputedHashesPath(extension()->path())));
// Known paths that are transcoded in |extension| crx.
std::set<std::string> transcoded_paths = {"_locales/de_AT/messages.json",
"_locales/en_GB/messages.json",
"H.png", "g.png", "i.png"};
// Ensure we've seen known paths as hash-mismatch on FetchComplete.
EXPECT_TRUE(IsSuperset(verifier_observer.hash_mismatch_unix_paths(),
ToFilePaths(transcoded_paths)));
// Sanity check: ensure they are explicitly excluded from verification.
EXPECT_FALSE(ShouldVerifyAnyPaths(ToFilePaths({"_locales/de_AT/messages.json",
"_locales/en_GB/messages.json",
"H.png", "g.png", "i.png"})));
// Make sure we haven't seen ContentVerifier::VerifyFailed
EXPECT_FALSE(verifier_observer.did_hash_mismatch());
// Ensure transcoded paths are handled correctly with different case in
// case-insensitive OS. They should still be excluded from verification (i.e.
// ShouldVerifyAnyPaths should return false for them).
if (!content_verifier_utils::IsFileAccessCaseSensitive()) {
EXPECT_FALSE(ShouldVerifyAnyPaths(ToFilePaths(
{"_locales/de_at/messages.json", "_locales/en_gb/messages.json",
"h.png", "G.png", "I.png"})));
}
// Ensure transcoded paths are handled correctly with dot-space suffix added
// to them in OS that ignores dot-space suffix (win). They should still be
// excluded from verification (i.e. ShouldVerifyAnyPaths should return false
// for them).
if (content_verifier_utils::IsDotSpaceFilenameSuffixIgnored()) {
EXPECT_FALSE(ShouldVerifyAnyPaths(ToFilePaths(
{"_locales/de_AT/messages.json.", "_locales/en_GB/messages.json ",
"H.png .", "g.png ..", "i.png.."})));
// Ensure the same with different case filenames.
if (!content_verifier_utils::IsFileAccessCaseSensitive()) {
EXPECT_FALSE(ShouldVerifyAnyPaths(ToFilePaths(
{"_locales/de_at/messages.json.", "_locales/en_gb/messages.json ",
"h.png .", "G.png ..", "I.png.."})));
}
}
}
// Tests that tampered resources cause verification failure due to hash mismatch
// during OnExtensionLoaded.
TEST_F(ChromeContentVerifierTest, VerifyFailedOnLoad) {
VerifierObserver verifier_observer;
ASSERT_TRUE(InstallExtension(kCaseSensitiveManifestPathsCrx));
// Before ContentVerifier sees |extension|, tamper with a JS file.
{
constexpr char kTamperedContent[] = "// Evil content";
base::FilePath background_script_path =
extension()->path().AppendASCII("d.js");
ASSERT_EQ(static_cast<int>(sizeof(kTamperedContent)),
base::WriteFile(background_script_path, kTamperedContent,
sizeof(kTamperedContent)));
}
AddExtensionToContentVerifier(extension(), &verifier_observer);
// Expect a hash mismatch for tampered d.js file.
EXPECT_TRUE(verifier_observer.did_hash_mismatch());
}
} // namespace extensions
...@@ -4633,6 +4633,7 @@ test("unit_tests") { ...@@ -4633,6 +4633,7 @@ test("unit_tests") {
"../browser/extensions/chrome_app_icon_unittest.cc", "../browser/extensions/chrome_app_icon_unittest.cc",
"../browser/extensions/chrome_app_sorting_unittest.cc", "../browser/extensions/chrome_app_sorting_unittest.cc",
"../browser/extensions/chrome_component_extension_resource_manager_unittest.cc", "../browser/extensions/chrome_component_extension_resource_manager_unittest.cc",
"../browser/extensions/chrome_content_verifier_unittest.cc",
"../browser/extensions/chrome_extension_function_unittest.cc", "../browser/extensions/chrome_extension_function_unittest.cc",
"../browser/extensions/chrome_info_map_unittest.cc", "../browser/extensions/chrome_info_map_unittest.cc",
"../browser/extensions/component_loader_unittest.cc", "../browser/extensions/component_loader_unittest.cc",
......
Fetched from Chrome Web Store.
CRX ID = ndbcnocfihoagadmddmdlgbpieadephg
This extension has
- locales with uppercase chars: _locales/en_GB/, _locales/en_US/
- png files that got transcoded
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
#include "content/public/browser/storage_partition.h" #include "content/public/browser/storage_partition.h"
#include "extensions/browser/content_hash_fetcher.h" #include "extensions/browser/content_hash_fetcher.h"
#include "extensions/browser/content_hash_reader.h" #include "extensions/browser/content_hash_reader.h"
#include "extensions/browser/content_verifier/content_verifier_utils.h"
#include "extensions/browser/content_verifier_delegate.h" #include "extensions/browser/content_verifier_delegate.h"
#include "extensions/browser/extension_file_task_runner.h" #include "extensions/browser/extension_file_task_runner.h"
#include "extensions/common/constants.h" #include "extensions/common/constants.h"
...@@ -39,6 +40,7 @@ namespace extensions { ...@@ -39,6 +40,7 @@ namespace extensions {
namespace { namespace {
ContentVerifier::TestObserver* g_content_verifier_test_observer = nullptr; ContentVerifier::TestObserver* g_content_verifier_test_observer = nullptr;
using content_verifier_utils::CanonicalRelativePath;
// This function converts paths like "//foo/bar", "./foo/bar", and // This function converts paths like "//foo/bar", "./foo/bar", and
// "/foo/bar" to "foo/bar". It also converts path separators to "/". // "/foo/bar" to "foo/bar". It also converts path separators to "/".
...@@ -92,29 +94,34 @@ std::unique_ptr<ContentVerifierIOData::ExtensionData> CreateIOData( ...@@ -92,29 +94,34 @@ std::unique_ptr<ContentVerifierIOData::ExtensionData> CreateIOData(
// comparing to actual relative paths work later on. // comparing to actual relative paths work later on.
std::set<base::FilePath> original_image_paths = std::set<base::FilePath> original_image_paths =
delegate->GetBrowserImagePaths(extension); delegate->GetBrowserImagePaths(extension);
auto canonicalize_path = [](const base::FilePath& relative_path) {
return content_verifier_utils::CanonicalizeRelativePath(
NormalizeRelativePath(relative_path));
};
auto image_paths = std::make_unique<std::set<base::FilePath>>(); auto image_paths = std::make_unique<std::set<CanonicalRelativePath>>();
for (const auto& path : original_image_paths) { for (const auto& path : original_image_paths) {
image_paths->insert(NormalizeRelativePath(path)); image_paths->insert(canonicalize_path(path));
} }
auto background_or_content_paths = auto background_or_content_paths =
std::make_unique<std::set<base::FilePath>>(); std::make_unique<std::set<CanonicalRelativePath>>();
for (const std::string& script : for (const std::string& script :
BackgroundInfo::GetBackgroundScripts(extension)) { BackgroundInfo::GetBackgroundScripts(extension)) {
background_or_content_paths->insert( background_or_content_paths->insert(
extension->GetResource(script).relative_path()); canonicalize_path(extension->GetResource(script).relative_path()));
} }
if (BackgroundInfo::HasBackgroundPage(extension)) { if (BackgroundInfo::HasBackgroundPage(extension)) {
background_or_content_paths->insert( background_or_content_paths->insert(
extensions::file_util::ExtensionURLToRelativeFilePath( canonicalize_path(extensions::file_util::ExtensionURLToRelativeFilePath(
BackgroundInfo::GetBackgroundURL(extension))); BackgroundInfo::GetBackgroundURL(extension))));
} }
for (const std::unique_ptr<UserScript>& script : for (const std::unique_ptr<UserScript>& script :
ContentScriptsInfo::GetContentScripts(extension)) { ContentScriptsInfo::GetContentScripts(extension)) {
for (const std::unique_ptr<UserScript::File>& js_file : for (const std::unique_ptr<UserScript::File>& js_file :
script->js_scripts()) { script->js_scripts()) {
background_or_content_paths->insert(js_file->relative_path()); background_or_content_paths->insert(
canonicalize_path(js_file->relative_path()));
} }
} }
...@@ -123,6 +130,27 @@ std::unique_ptr<ContentVerifierIOData::ExtensionData> CreateIOData( ...@@ -123,6 +130,27 @@ std::unique_ptr<ContentVerifierIOData::ExtensionData> CreateIOData(
extension->version(), source_type); extension->version(), source_type);
} }
// Returns all locales, possibly with lowercasing them for case-insensitive OS.
std::set<std::string> GetAllLocaleCandidates() {
std::set<std::string> all_locales;
// TODO(asargent) - see if we can cache this list longer to avoid
// having to fetch it more than once for a given run of the
// browser. Maybe it can never change at runtime? (Or if it can, maybe
// there is an event we can listen for to know to drop our cache).
extension_l10n_util::GetAllLocales(&all_locales);
if (content_verifier_utils::IsFileAccessCaseSensitive())
return all_locales;
// Lower-case the locales candidate so we can search in
// case-insensitive manner for win/mac.
std::set<std::string> all_locales_candidate;
std::transform(
all_locales.begin(), all_locales.end(),
std::inserter(all_locales_candidate, all_locales_candidate.begin()),
[](const std::string& locale) { return base::ToLowerASCII(locale); });
return all_locales_candidate;
}
} // namespace } // namespace
struct ContentVerifier::CacheKey { struct ContentVerifier::CacheKey {
...@@ -444,10 +472,10 @@ scoped_refptr<ContentVerifyJob> ContentVerifier::CreateAndStartJobFor( ...@@ -444,10 +472,10 @@ scoped_refptr<ContentVerifyJob> ContentVerifier::CreateAndStartJobFor(
base::FilePath normalized_unix_path = NormalizeRelativePath(relative_path); base::FilePath normalized_unix_path = NormalizeRelativePath(relative_path);
std::set<base::FilePath> unix_paths; if (!ShouldVerifyAnyPaths(extension_id, extension_root,
unix_paths.insert(normalized_unix_path); {normalized_unix_path})) {
if (!ShouldVerifyAnyPaths(extension_id, extension_root, unix_paths))
return nullptr; return nullptr;
}
// TODO(asargent) - we can probably get some good performance wins by having // TODO(asargent) - we can probably get some good performance wins by having
// a cache of ContentHashReader's that we hold onto past the end of each job. // a cache of ContentHashReader's that we hold onto past the end of each job.
...@@ -604,17 +632,17 @@ void ContentVerifier::OnFetchComplete( ...@@ -604,17 +632,17 @@ void ContentVerifier::OnFetchComplete(
const scoped_refptr<const ContentHash>& content_hash) { const scoped_refptr<const ContentHash>& content_hash) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO); DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
ExtensionId extension_id = content_hash->extension_id(); ExtensionId extension_id = content_hash->extension_id();
if (g_content_verifier_test_observer) {
g_content_verifier_test_observer->OnFetchComplete(
extension_id, content_hash->succeeded());
}
VLOG(1) << "OnFetchComplete " << extension_id VLOG(1) << "OnFetchComplete " << extension_id
<< " success:" << content_hash->succeeded(); << " success:" << content_hash->succeeded();
const bool did_hash_mismatch = const bool did_hash_mismatch =
ShouldVerifyAnyPaths(extension_id, content_hash->extension_root(), ShouldVerifyAnyPaths(extension_id, content_hash->extension_root(),
content_hash->hash_mismatch_unix_paths()); content_hash->hash_mismatch_unix_paths());
if (g_content_verifier_test_observer) {
g_content_verifier_test_observer->OnFetchComplete(content_hash,
did_hash_mismatch);
}
if (!did_hash_mismatch) if (!did_hash_mismatch)
return; return;
...@@ -683,20 +711,29 @@ bool ContentVerifier::ShouldVerifyAnyPaths( ...@@ -683,20 +711,29 @@ bool ContentVerifier::ShouldVerifyAnyPaths(
if (!data) if (!data)
return false; return false;
const std::set<base::FilePath>& browser_images = *(data->browser_image_paths); const std::set<CanonicalRelativePath>& browser_images =
const std::set<base::FilePath>& background_or_content_paths = *(data->canonical_browser_image_paths);
*(data->background_or_content_paths); const std::set<CanonicalRelativePath>& background_or_content_paths =
*(data->canonical_background_or_content_paths);
std::unique_ptr<std::set<std::string>> all_locales; base::Optional<std::set<std::string>> all_locale_candidates;
const base::FilePath manifest_file(kManifestFilename); const CanonicalRelativePath manifest_file =
content_verifier_utils::CanonicalizeRelativePath(
base::FilePath(kManifestFilename));
const base::FilePath messages_file(kMessagesFilename); const base::FilePath messages_file(kMessagesFilename);
const base::FilePath locales_relative_dir(kLocaleFolder); const base::FilePath locales_relative_dir(kLocaleFolder);
const CanonicalRelativePath indexed_ruleset_path =
content_verifier_utils::CanonicalizeRelativePath(
file_util::GetIndexedRulesetRelativePath());
for (const base::FilePath& relative_unix_path : relative_unix_paths) { for (const base::FilePath& relative_unix_path : relative_unix_paths) {
if (relative_unix_path.empty()) if (relative_unix_path.empty())
continue; continue;
if (relative_unix_path == manifest_file) CanonicalRelativePath canonical_path_value =
content_verifier_utils::CanonicalizeRelativePath(relative_unix_path);
if (canonical_path_value == manifest_file)
continue; continue;
// JavaScript and HTML files should always be verified. // JavaScript and HTML files should always be verified.
...@@ -707,37 +744,27 @@ bool ContentVerifier::ShouldVerifyAnyPaths( ...@@ -707,37 +744,27 @@ bool ContentVerifier::ShouldVerifyAnyPaths(
// Background pages, scripts and content scripts should always be verified // Background pages, scripts and content scripts should always be verified
// regardless of their file type. // regardless of their file type.
if (base::Contains(background_or_content_paths, relative_unix_path)) if (base::Contains(background_or_content_paths, canonical_path_value))
return true; return true;
if (base::Contains(browser_images, relative_unix_path)) if (base::Contains(browser_images, canonical_path_value))
continue; continue;
base::FilePath relative_path = relative_unix_path.NormalizePathSeparators(); if (canonical_path_value == indexed_ruleset_path)
base::FilePath full_path = extension_root.Append(relative_path);
if (full_path ==
extension_root.Append(file_util::GetIndexedRulesetRelativePath())) {
continue; continue;
}
if (locales_relative_dir.IsParent(relative_path)) { const base::FilePath canonical_path(canonical_path_value.value());
if (!all_locales) { if (locales_relative_dir.IsParent(canonical_path)) {
// TODO(asargent) - see if we can cache this list longer to avoid if (!all_locale_candidates)
// having to fetch it more than once for a given run of the all_locale_candidates = GetAllLocaleCandidates();
// browser. Maybe it can never change at runtime? (Or if it can, maybe
// there is an event we can listen for to know to drop our cache).
all_locales.reset(new std::set<std::string>);
extension_l10n_util::GetAllLocales(all_locales.get());
}
// Since message catalogs get transcoded during installation, we want // Since message catalogs get transcoded during installation, we want
// to skip those paths. See if this path looks like // to skip those paths. See if this path looks like
// _locales/<some locale>/messages.json - if so then skip it. // _locales/<some locale>/messages.json - if so then skip it.
if (relative_path.BaseName() == messages_file && if (canonical_path.BaseName() == messages_file &&
relative_path.DirName().DirName() == locales_relative_dir && canonical_path.DirName().DirName() == locales_relative_dir &&
base::Contains(*all_locales, base::Contains(*all_locale_candidates,
relative_path.DirName().BaseName().MaybeAsASCII())) { canonical_path.DirName().BaseName().MaybeAsASCII())) {
continue; continue;
} }
} }
......
...@@ -64,8 +64,9 @@ class ContentVerifier : public base::RefCountedThreadSafe<ContentVerifier>, ...@@ -64,8 +64,9 @@ class ContentVerifier : public base::RefCountedThreadSafe<ContentVerifier>,
public: public:
class TestObserver { class TestObserver {
public: public:
virtual void OnFetchComplete(const std::string& extension_id, virtual void OnFetchComplete(
bool success) = 0; const scoped_refptr<const ContentHash>& content_hash,
bool did_hash_mismatch) = 0;
}; };
static void SetObserverForTests(TestObserver* observer); static void SetObserverForTests(TestObserver* observer);
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include "crypto/sha2.h" #include "crypto/sha2.h"
#include "extensions/browser/content_hash_fetcher.h" #include "extensions/browser/content_hash_fetcher.h"
#include "extensions/browser/content_hash_tree.h" #include "extensions/browser/content_hash_tree.h"
#include "extensions/browser/content_verifier/content_verifier_utils.h"
#include "extensions/browser/extension_file_task_runner.h" #include "extensions/browser/extension_file_task_runner.h"
#include "extensions/common/file_util.h" #include "extensions/common/file_util.h"
#include "net/base/load_flags.h" #include "net/base/load_flags.h"
...@@ -324,18 +325,20 @@ std::set<base::FilePath> ContentHash::GetMismatchedComputedHashes( ...@@ -324,18 +325,20 @@ std::set<base::FilePath> ContentHash::GetMismatchedComputedHashes(
DCHECK(computed_hashes_data); DCHECK(computed_hashes_data);
if (source_type_ != if (source_type_ !=
ContentVerifierDelegate::VerifierSourceType::SIGNED_HASHES) { ContentVerifierDelegate::VerifierSourceType::SIGNED_HASHES) {
return std::set<base::FilePath>(); return {};
} }
std::set<base::FilePath> mismatched_hashes; std::set<base::FilePath> mismatched_hashes;
for (const auto& resource_info : computed_hashes_data->items()) { for (const auto& resource_info : computed_hashes_data->items()) {
const ComputedHashes::Data::HashInfo& hash_info = resource_info.second; const ComputedHashes::Data::HashInfo& hash_info = resource_info.second;
const content_verifier_utils::CanonicalRelativePath&
canonical_relative_path = resource_info.first;
std::string root = ComputeTreeHashRoot(hash_info.hashes, std::string root = ComputeTreeHashRoot(hash_info.hashes,
block_size_ / crypto::kSHA256Length); block_size_ / crypto::kSHA256Length);
if (!verified_contents_->TreeHashRootEquals(hash_info.relative_unix_path, if (!verified_contents_->TreeHashRootEqualsForCanonicalPath(
root)) { canonical_relative_path, root)) {
mismatched_hashes.insert(hash_info.relative_unix_path); mismatched_hashes.insert(hash_info.relative_unix_path);
} }
} }
......
...@@ -248,16 +248,20 @@ void VerifierObserver::EnsureFetchCompleted(const ExtensionId& extension_id) { ...@@ -248,16 +248,20 @@ void VerifierObserver::EnsureFetchCompleted(const ExtensionId& extension_id) {
loop_runner_ = nullptr; loop_runner_ = nullptr;
} }
void VerifierObserver::OnFetchComplete(const ExtensionId& extension_id, void VerifierObserver::OnFetchComplete(
bool success) { const scoped_refptr<const ContentHash>& content_hash,
bool did_hash_mismatch) {
if (!content::BrowserThread::CurrentlyOn(creation_thread_)) { if (!content::BrowserThread::CurrentlyOn(creation_thread_)) {
base::PostTask( base::PostTask(FROM_HERE, {creation_thread_},
FROM_HERE, {creation_thread_}, base::BindOnce(&VerifierObserver::OnFetchComplete,
base::BindOnce(&VerifierObserver::OnFetchComplete, base::Unretained(this), content_hash,
base::Unretained(this), extension_id, success)); did_hash_mismatch));
return; return;
} }
const ExtensionId extension_id = content_hash->extension_id();
completed_fetches_.insert(extension_id); completed_fetches_.insert(extension_id);
content_hash_ = content_hash;
did_hash_mismatch_ = did_hash_mismatch;
if (extension_id == id_to_wait_for_) { if (extension_id == id_to_wait_for_) {
DCHECK(loop_runner_); DCHECK(loop_runner_);
loop_runner_->Quit(); loop_runner_->Quit();
......
...@@ -183,16 +183,25 @@ class VerifierObserver : public ContentVerifier::TestObserver { ...@@ -183,16 +183,25 @@ class VerifierObserver : public ContentVerifier::TestObserver {
VerifierObserver(); VerifierObserver();
virtual ~VerifierObserver(); virtual ~VerifierObserver();
const std::set<base::FilePath>& hash_mismatch_unix_paths() {
DCHECK(content_hash_);
return content_hash_->hash_mismatch_unix_paths();
}
bool did_hash_mismatch() const { return did_hash_mismatch_; }
// Ensures that |extension_id| has seen OnFetchComplete, waits for it to // Ensures that |extension_id| has seen OnFetchComplete, waits for it to
// complete if it hasn't already. // complete if it hasn't already.
void EnsureFetchCompleted(const ExtensionId& extension_id); void EnsureFetchCompleted(const ExtensionId& extension_id);
// ContentVerifier::TestObserver // ContentVerifier::TestObserver
void OnFetchComplete(const ExtensionId& extension_id, bool success) override; void OnFetchComplete(const scoped_refptr<const ContentHash>& content_hash,
bool did_hash_mismatch) override;
private: private:
std::set<ExtensionId> completed_fetches_; std::set<ExtensionId> completed_fetches_;
ExtensionId id_to_wait_for_; ExtensionId id_to_wait_for_;
scoped_refptr<const ContentHash> content_hash_;
bool did_hash_mismatch_ = true;
// Created and accessed on |creation_thread_|. // Created and accessed on |creation_thread_|.
scoped_refptr<content::MessageLoopRunner> loop_runner_; scoped_refptr<content::MessageLoopRunner> loop_runner_;
......
...@@ -11,12 +11,15 @@ ...@@ -11,12 +11,15 @@
namespace extensions { namespace extensions {
ContentVerifierIOData::ExtensionData::ExtensionData( ContentVerifierIOData::ExtensionData::ExtensionData(
std::unique_ptr<std::set<base::FilePath>> browser_image_paths, std::unique_ptr<std::set<CanonicalRelativePath>>
std::unique_ptr<std::set<base::FilePath>> background_or_content_paths, canonical_browser_image_paths,
std::unique_ptr<std::set<CanonicalRelativePath>>
canonical_background_or_content_paths,
const base::Version& version, const base::Version& version,
ContentVerifierDelegate::VerifierSourceType source_type) ContentVerifierDelegate::VerifierSourceType source_type)
: browser_image_paths(std::move(browser_image_paths)), : canonical_browser_image_paths(std::move(canonical_browser_image_paths)),
background_or_content_paths(std::move(background_or_content_paths)), canonical_background_or_content_paths(
std::move(canonical_background_or_content_paths)),
version(version), version(version),
source_type(source_type) {} source_type(source_type) {}
...@@ -32,7 +35,7 @@ ContentVerifierIOData::~ContentVerifierIOData() { ...@@ -32,7 +35,7 @@ ContentVerifierIOData::~ContentVerifierIOData() {
void ContentVerifierIOData::AddData(const std::string& extension_id, void ContentVerifierIOData::AddData(const std::string& extension_id,
std::unique_ptr<ExtensionData> data) { std::unique_ptr<ExtensionData> data) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO); DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
CHECK(data->browser_image_paths.get()); CHECK(data->canonical_browser_image_paths.get());
data_map_[extension_id] = std::move(data); data_map_[extension_id] = std::move(data);
} }
......
...@@ -13,27 +13,34 @@ ...@@ -13,27 +13,34 @@
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/version.h" #include "base/version.h"
#include "extensions/browser/content_verifier/content_verifier_utils.h"
#include "extensions/browser/content_verifier_delegate.h" #include "extensions/browser/content_verifier_delegate.h"
namespace extensions { namespace extensions {
using CanonicalRelativePath = content_verifier_utils::CanonicalRelativePath;
// A helper class for keeping track of data for the ContentVerifier that should // A helper class for keeping track of data for the ContentVerifier that should
// only be accessed on the IO thread. // only be accessed on the IO thread.
class ContentVerifierIOData { class ContentVerifierIOData {
public: public:
struct ExtensionData { struct ExtensionData {
// Set of images file paths used within the browser process. // Set of canonical file paths used as images within the browser process.
std::unique_ptr<std::set<base::FilePath>> browser_image_paths; std::unique_ptr<std::set<CanonicalRelativePath>>
// Set of file paths used as background scripts, pages or content scripts. canonical_browser_image_paths;
std::unique_ptr<std::set<base::FilePath>> background_or_content_paths; // Set of canonical file paths used as background scripts, pages or
// content scripts.
std::unique_ptr<std::set<CanonicalRelativePath>>
canonical_background_or_content_paths;
base::Version version; base::Version version;
ContentVerifierDelegate::VerifierSourceType source_type; ContentVerifierDelegate::VerifierSourceType source_type;
ExtensionData( ExtensionData(std::unique_ptr<std::set<CanonicalRelativePath>>
std::unique_ptr<std::set<base::FilePath>> browser_image_paths, canonical_browser_image_paths,
std::unique_ptr<std::set<base::FilePath>> background_or_content_paths, std::unique_ptr<std::set<CanonicalRelativePath>>
const base::Version& version, canonical_background_or_content_paths,
ContentVerifierDelegate::VerifierSourceType source_type); const base::Version& version,
ContentVerifierDelegate::VerifierSourceType source_type);
~ExtensionData(); ~ExtensionData();
}; };
......
...@@ -194,7 +194,7 @@ bool VerifiedContents::HasTreeHashRoot( ...@@ -194,7 +194,7 @@ bool VerifiedContents::HasTreeHashRoot(
bool VerifiedContents::TreeHashRootEquals(const base::FilePath& relative_path, bool VerifiedContents::TreeHashRootEquals(const base::FilePath& relative_path,
const std::string& expected) const { const std::string& expected) const {
return TreeHashRootEqualsImpl( return TreeHashRootEqualsForCanonicalPath(
content_verifier_utils::CanonicalizeRelativePath(relative_path), content_verifier_utils::CanonicalizeRelativePath(relative_path),
expected); expected);
} }
...@@ -333,7 +333,7 @@ bool VerifiedContents::VerifySignature(const std::string& protected_value, ...@@ -333,7 +333,7 @@ bool VerifiedContents::VerifySignature(const std::string& protected_value,
return true; return true;
} }
bool VerifiedContents::TreeHashRootEqualsImpl( bool VerifiedContents::TreeHashRootEqualsForCanonicalPath(
const content_verifier_utils::CanonicalRelativePath& const content_verifier_utils::CanonicalRelativePath&
canonical_relative_path, canonical_relative_path,
const std::string& expected) const { const std::string& expected) const {
......
...@@ -48,6 +48,10 @@ class VerifiedContents { ...@@ -48,6 +48,10 @@ class VerifiedContents {
bool TreeHashRootEquals(const base::FilePath& relative_path, bool TreeHashRootEquals(const base::FilePath& relative_path,
const std::string& expected) const; const std::string& expected) const;
bool TreeHashRootEqualsForCanonicalPath(
const CanonicalRelativePath& canonical_relative_path,
const std::string& expected) const;
// If InitFrom has not been called yet, or was used in "ignore invalid // If InitFrom has not been called yet, or was used in "ignore invalid
// signature" mode, this can return false. // signature" mode, this can return false.
bool valid_signature() { return valid_signature_; } bool valid_signature() { return valid_signature_; }
...@@ -68,10 +72,6 @@ class VerifiedContents { ...@@ -68,10 +72,6 @@ class VerifiedContents {
const std::string& payload, const std::string& payload,
const std::string& signature_bytes); const std::string& signature_bytes);
bool TreeHashRootEqualsImpl(
const CanonicalRelativePath& canonical_relative_path,
const std::string& expected) const;
// The public key we should use for signature verification. // The public key we should use for signature verification.
base::span<const uint8_t> public_key_; base::span<const uint8_t> public_key_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment