blob: 7b3d9081945a72a80740d51ed0755573a8a4983d [file] [log] [blame]
// Copyright 2018 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/conflicts/module_blacklist_cache_util_win.h"
#include <algorithm>
#include <functional>
#include <iterator>
#include <string>
#include <tuple>
#include <utility>
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/important_file_writer.h"
#include "base/logging.h"
#include "base/md5.h"
#include "base/stl_util.h"
#include "base/time/time.h"
#include "chrome/browser/conflicts/module_list_filter_win.h"
#include "chrome_elf/third_party_dlls/packed_list_format.h"
namespace {
// Wrapper for base::File::ReadAtCurrentPost() that returns true only if all
// the requested bytes were succesfully read from the file.
bool SafeRead(base::File* file, char* data, int size) {
return file->ReadAtCurrentPos(data, size) == size;
}
// Returns an iterator to the element equal to |value|, or |last| if it can't
// be found.
template <class ForwardIt, class T, class Compare = std::less<>>
ForwardIt BinaryFind(ForwardIt first,
ForwardIt last,
const T& value,
Compare comp = {}) {
first = std::lower_bound(first, last, value, comp);
return first != last && !comp(value, *first) ? first : last;
}
// Returns true if the 2 digests are equal.
bool IsMD5DigestEqual(const base::MD5Digest& lhs, const base::MD5Digest& rhs) {
return std::equal(std::begin(lhs.a), std::end(lhs.a), std::begin(rhs.a),
std::end(rhs.a));
}
// Returns MD5 hash of the cache data.
base::MD5Digest CalculateModuleBlacklistCacheMD5(
const third_party_dlls::PackedListMetadata& metadata,
const std::vector<third_party_dlls::PackedListModule>&
blacklisted_modules) {
base::MD5Context md5_context;
base::MD5Init(&md5_context);
base::MD5Update(&md5_context,
base::StringPiece(reinterpret_cast<const char*>(&metadata),
sizeof(metadata)));
base::MD5Update(&md5_context,
base::StringPiece(
reinterpret_cast<const char*>(blacklisted_modules.data()),
sizeof(third_party_dlls::PackedListModule) *
blacklisted_modules.size()));
base::MD5Digest md5_digest;
base::MD5Final(&md5_digest, &md5_context);
return md5_digest;
}
} // namespace
const base::FilePath::CharType kModuleListComponentRelativePath[] =
FILE_PATH_LITERAL("ThirdPartyModuleList")
#ifdef _WIN64
FILE_PATH_LITERAL("64");
#else
FILE_PATH_LITERAL("32");
#endif
uint32_t CalculateTimeDateStamp(base::Time time) {
const auto delta = time.ToDeltaSinceWindowsEpoch();
return delta < base::TimeDelta() ? 0 : static_cast<uint32_t>(delta.InHours());
}
ReadResult ReadModuleBlacklistCache(
const base::FilePath& module_blacklist_cache_path,
third_party_dlls::PackedListMetadata* metadata,
std::vector<third_party_dlls::PackedListModule>* blacklisted_modules,
base::MD5Digest* md5_digest) {
DCHECK(metadata);
DCHECK(blacklisted_modules);
DCHECK(md5_digest);
base::File file(module_blacklist_cache_path,
base::File::FLAG_OPEN | base::File::FLAG_READ |
base::File::FLAG_SHARE_DELETE);
if (!file.IsValid())
return ReadResult::kFailOpenFile;
third_party_dlls::PackedListMetadata read_metadata;
if (!SafeRead(&file, reinterpret_cast<char*>(&read_metadata),
sizeof(read_metadata))) {
return ReadResult::kFailReadMetadata;
}
// Make sure the version is supported.
if (read_metadata.version > third_party_dlls::PackedListVersion::kCurrent)
return ReadResult::kFailInvalidVersion;
std::vector<third_party_dlls::PackedListModule> read_blacklisted_modules(
read_metadata.module_count);
if (!SafeRead(&file, reinterpret_cast<char*>(read_blacklisted_modules.data()),
sizeof(third_party_dlls::PackedListModule) *
read_metadata.module_count)) {
return ReadResult::kFailReadModules;
}
// The list should be sorted.
if (!std::is_sorted(read_blacklisted_modules.begin(),
read_blacklisted_modules.end(), internal::ModuleLess())) {
return ReadResult::kFailModulesNotSorted;
}
base::MD5Digest read_md5_digest;
if (!SafeRead(&file, reinterpret_cast<char*>(&read_md5_digest.a),
base::size(read_md5_digest.a))) {
return ReadResult::kFailReadMD5;
}
if (!IsMD5DigestEqual(read_md5_digest,
CalculateModuleBlacklistCacheMD5(
read_metadata, read_blacklisted_modules))) {
return ReadResult::kFailInvalidMD5;
}
*metadata = read_metadata;
*blacklisted_modules = std::move(read_blacklisted_modules);
*md5_digest = read_md5_digest;
return ReadResult::kSuccess;
}
bool WriteModuleBlacklistCache(
const base::FilePath& module_blacklist_cache_path,
const third_party_dlls::PackedListMetadata& metadata,
const std::vector<third_party_dlls::PackedListModule>& blacklisted_modules,
base::MD5Digest* md5_digest) {
DCHECK(std::is_sorted(blacklisted_modules.begin(), blacklisted_modules.end(),
internal::ModuleLess()));
*md5_digest = CalculateModuleBlacklistCacheMD5(metadata, blacklisted_modules);
std::string file_contents;
file_contents.reserve(internal::CalculateExpectedFileSize(metadata));
file_contents.append(reinterpret_cast<const char*>(&metadata),
sizeof(metadata));
file_contents.append(
reinterpret_cast<const char*>(blacklisted_modules.data()),
sizeof(third_party_dlls::PackedListModule) * blacklisted_modules.size());
file_contents.append(std::begin(md5_digest->a), std::end(md5_digest->a));
return base::ImportantFileWriter::WriteFileAtomically(
module_blacklist_cache_path, file_contents);
}
void UpdateModuleBlacklistCacheData(
const ModuleListFilter& module_list_filter,
const std::vector<third_party_dlls::PackedListModule>&
newly_blacklisted_modules,
const std::vector<third_party_dlls::PackedListModule>& blocked_modules,
size_t max_modules_count,
uint32_t min_time_date_stamp,
third_party_dlls::PackedListMetadata* metadata,
std::vector<third_party_dlls::PackedListModule>* blacklisted_modules) {
DCHECK(metadata);
DCHECK(blacklisted_modules);
// Precondition for UpdateModuleBlacklistCacheTimestamp(). This is guaranteed
// if the |blacklisted_modules| comes from a valid ModuleBlacklistCache.
DCHECK(std::is_sorted(blacklisted_modules->begin(),
blacklisted_modules->end(), internal::ModuleLess()));
// Remove whitelisted modules from the ModuleBlacklistCache. This can happen
// if a module was recently added to the ModuleList's whitelist.
internal::RemoveWhitelistedEntries(module_list_filter, blacklisted_modules);
// Update the timestamp of all modules that were blocked for the current
// browser execution.
internal::UpdateModuleBlacklistCacheTimestamps(blocked_modules,
blacklisted_modules);
// Now remove expired entries. Sorting the collection by reverse time date
// stamp order makes this operation more efficient. Also removes enough of the
// oldest entries to make room for the newly blacklisted modules.
std::sort(blacklisted_modules->begin(), blacklisted_modules->end(),
internal::ModuleTimeDateStampGreater());
internal::RemoveExpiredEntries(min_time_date_stamp, max_modules_count,
newly_blacklisted_modules.size(),
blacklisted_modules);
// Insert all the newly blacklisted modules.
blacklisted_modules->insert(blacklisted_modules->end(),
newly_blacklisted_modules.begin(),
newly_blacklisted_modules.end());
// Sort the collection by its final order, then remove duplicate entries.
auto module_compare = [](const auto& lhs, const auto& rhs) {
if (internal::ModuleLess()(lhs, rhs))
return true;
if (internal::ModuleLess()(rhs, lhs))
return false;
// Ensure the newest duplicates are kept by placing them first.
return internal::ModuleTimeDateStampGreater()(lhs, rhs);
};
std::sort(blacklisted_modules->begin(), blacklisted_modules->end(),
module_compare);
internal::RemoveDuplicateEntries(blacklisted_modules);
// Update the entry count in the metadata structure.
metadata->version = third_party_dlls::PackedListVersion::kCurrent;
metadata->module_count = blacklisted_modules->size();
}
namespace internal {
int64_t CalculateExpectedFileSize(
third_party_dlls::PackedListMetadata packed_list_metadata) {
return static_cast<int64_t>(sizeof(third_party_dlls::PackedListMetadata) +
packed_list_metadata.module_count *
sizeof(third_party_dlls::PackedListModule) +
arraysize(base::MD5Digest::a));
}
bool ModuleLess::operator()(
const third_party_dlls::PackedListModule& lhs,
const third_party_dlls::PackedListModule& rhs) const {
return std::tie(lhs.basename_hash, lhs.code_id_hash) <
std::tie(rhs.basename_hash, rhs.code_id_hash);
}
bool ModuleEqual::operator()(
const third_party_dlls::PackedListModule& lhs,
const third_party_dlls::PackedListModule& rhs) const {
return lhs.basename_hash == rhs.basename_hash &&
lhs.code_id_hash == rhs.code_id_hash;
}
bool ModuleTimeDateStampGreater::operator()(
const third_party_dlls::PackedListModule& lhs,
const third_party_dlls::PackedListModule& rhs) const {
return lhs.time_date_stamp > rhs.time_date_stamp;
}
void RemoveWhitelistedEntries(
const ModuleListFilter& module_list_filter,
std::vector<third_party_dlls::PackedListModule>* blacklisted_modules) {
base::EraseIf(
*blacklisted_modules,
[&module_list_filter](const third_party_dlls::PackedListModule& module) {
return module_list_filter.IsWhitelisted(
base::StringPiece(
reinterpret_cast<const char*>(&module.basename_hash[0]),
base::size(module.basename_hash)),
base::StringPiece(
reinterpret_cast<const char*>(&module.code_id_hash[0]),
base::size(module.code_id_hash)));
});
}
void UpdateModuleBlacklistCacheTimestamps(
const std::vector<third_party_dlls::PackedListModule>& updated_modules,
std::vector<third_party_dlls::PackedListModule>* blacklisted_modules) {
DCHECK(std::is_sorted(blacklisted_modules->begin(),
blacklisted_modules->end(), ModuleLess()));
for (const auto& module : updated_modules) {
auto iter = BinaryFind(blacklisted_modules->begin(),
blacklisted_modules->end(), module, ModuleLess());
if (iter != blacklisted_modules->end())
iter->time_date_stamp = module.time_date_stamp;
}
}
void RemoveExpiredEntries(
uint32_t min_time_date_stamp,
size_t max_module_blacklist_cache_size,
size_t newly_blacklisted_modules_count,
std::vector<third_party_dlls::PackedListModule>* blacklisted_modules) {
DCHECK(std::is_sorted(blacklisted_modules->begin(),
blacklisted_modules->end(),
ModuleTimeDateStampGreater()));
// To make room for newly blacklisted modules, the oldest entries that exceed
// the max size of the module blacklist cache are considered expired and thus
// removed.
size_t new_max_size =
max_module_blacklist_cache_size - newly_blacklisted_modules_count;
if (blacklisted_modules->size() > new_max_size)
blacklisted_modules->resize(new_max_size);
// Then remove entries whose time date stamp is older than the limit.
blacklisted_modules->erase(
std::lower_bound(blacklisted_modules->begin(), blacklisted_modules->end(),
min_time_date_stamp,
[](const auto& lhs, size_t rhs) {
return lhs.time_date_stamp > rhs;
}),
blacklisted_modules->end());
}
void RemoveDuplicateEntries(
std::vector<third_party_dlls::PackedListModule>* blacklisted_modules) {
DCHECK(std::is_sorted(blacklisted_modules->begin(),
blacklisted_modules->end(), ModuleLess()));
blacklisted_modules->erase(
std::unique(blacklisted_modules->begin(), blacklisted_modules->end(),
internal::ModuleEqual()),
blacklisted_modules->end());
}
} // namespace internal