// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/win/conflicts/inspection_results_cache.h"
#include <string>
#include <string_view>
#include <utility>
#include "base/containers/span.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/important_file_writer.h"
#include "base/hash/md5.h"
#include "base/pickle.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_util.h"
namespace {
// The current version of the cache format. Must be incremented every time the
// format changes.
constexpr int kVersion = 1;
// Serializes a [ModuleInfoKey, ModuleInspectionResult, time stamp] tuple into
// |pickle|. The |time_stamp| represents the last time this specific inspection
// result was used, and was calculated using CalculateTimeStamp().
void SerializeInspectionResult(const ModuleInfoKey& module_key,
const ModuleInspectionResult& inspection_result,
uint32_t time_stamp,
base::Pickle* pickle) {
// ModuleInfoKey:
module_key.module_path.WriteToPickle(pickle);
pickle->WriteUInt32(module_key.module_size);
pickle->WriteUInt32(module_key.module_time_date_stamp);
// ModuleInspectionResult:
pickle->WriteString16(inspection_result.location);
pickle->WriteString16(inspection_result.basename);
pickle->WriteString16(inspection_result.product_name);
pickle->WriteString16(inspection_result.description);
pickle->WriteString16(inspection_result.version);
pickle->WriteInt(static_cast<int>(inspection_result.certificate_info.type));
inspection_result.certificate_info.path.WriteToPickle(pickle);
pickle->WriteString16(inspection_result.certificate_info.subject);
// Time stamp:
pickle->WriteUInt32(time_stamp);
}
// Deserializes a [ModuleInfoKey, ModuleInspectionResult] pair into |result| by
// reading from |pickle_iterator|. Skips pairs whose time stamp is older than
// |min_time_stamp|. Returns true if |result| contains a valid inspection
// result.
bool DeserializeInspectionResult(uint32_t min_time_stamp,
base::PickleIterator* pickle_iterator,
InspectionResultsCache* result) {
DCHECK(pickle_iterator);
DCHECK(result);
// ModuleInfoKey:
base::FilePath module_path;
uint32_t module_size = 0;
uint32_t module_time_date_stamp = 0;
if (!module_path.ReadFromPickle(pickle_iterator) ||
!pickle_iterator->ReadUInt32(&module_size) ||
!pickle_iterator->ReadUInt32(&module_time_date_stamp)) {
return false;
}
std::pair<ModuleInfoKey, std::pair<ModuleInspectionResult, uint32_t>> value(
std::piecewise_construct,
std::forward_as_tuple(std::move(module_path), module_size,
module_time_date_stamp),
std::forward_as_tuple());
// ModuleInspectionResult and time stamp:
ModuleInspectionResult& inspection_result = value.second.first;
uint32_t& time_stamp = value.second.second;
std::u16string location, basename;
if (!pickle_iterator->ReadString16(&inspection_result.location) ||
!pickle_iterator->ReadString16(&inspection_result.basename) ||
!pickle_iterator->ReadString16(&inspection_result.product_name) ||
!pickle_iterator->ReadString16(&inspection_result.description) ||
!pickle_iterator->ReadString16(&inspection_result.version) ||
!pickle_iterator->ReadInt(
reinterpret_cast<int*>(&inspection_result.certificate_info.type)) ||
!inspection_result.certificate_info.path.ReadFromPickle(
pickle_iterator) ||
!pickle_iterator->ReadString16(
&inspection_result.certificate_info.subject) ||
!pickle_iterator->ReadUInt32(&time_stamp)) {
return false;
}
// Only keep this element if it is not expired. An expired entry is not an
// error.
if (time_stamp >= min_time_stamp)
result->insert(std::move(value));
return true;
}
// Serializes an InspectionResultsCache into a base::Pickle. The serialized data
// contains a version number and a MD5 checksum at the end.
base::Pickle SerializeInspectionResultsCache(
const InspectionResultsCache& inspection_results_cache) {
base::Pickle pickle;
pickle.WriteInt(kVersion);
pickle.WriteUInt64(inspection_results_cache.size());
for (const auto& inspection_result : inspection_results_cache) {
SerializeInspectionResult(inspection_result.first,
inspection_result.second.first,
inspection_result.second.second, &pickle);
}
// Append the md5 digest of the data to detect serializations errors.
base::MD5Digest md5_digest;
base::MD5Sum(pickle.payload_bytes(), &md5_digest);
pickle.WriteBytes(&md5_digest, sizeof(md5_digest));
return pickle;
}
// Deserializes an InspectionResultsCache from |pickle|. This function ensures
// that both the version and the checksum of the data are valid. Returns a
// ReadCacheResult value indicating what failed if unsuccessful.
ReadCacheResult DeserializeInspectionResultsCache(
uint32_t min_time_stamp,
const base::Pickle& pickle,
InspectionResultsCache* result) {
DCHECK(result);
base::PickleIterator pickle_iterator(pickle);
// Check the version number.
int version = 0;
if (!pickle_iterator.ReadInt(&version))
return ReadCacheResult::kFailDeserializeVersion;
if (version != kVersion)
return ReadCacheResult::kFailInvalidVersion;
// Retrieve the count of inspection results.
uint64_t inspection_result_count = 0;
if (!pickle_iterator.ReadUInt64(&inspection_result_count))
return ReadCacheResult::kFailDeserializeCount;
if (inspection_result_count < 0)
return ReadCacheResult::kFailInvalidCount;
// Now deserialize all the ModuleInspectionResults. Failure to deserialize one
// inspection result counts as an invalid cache.
for (uint64_t i = 0; i < inspection_result_count; i++) {
if (!DeserializeInspectionResult(min_time_stamp, &pickle_iterator, result))
return ReadCacheResult::kFailDeserializeInspectionResult;
}
// Now check the md5 checksum.
const base::MD5Digest* read_md5_digest = nullptr;
if (!pickle_iterator.ReadBytes(
reinterpret_cast<const char**>(&read_md5_digest),
sizeof(*read_md5_digest))) {
return ReadCacheResult::kFailDeserializeMD5;
}
// Check if the md5 checksum matches.
base::MD5Digest md5_digest;
base::span<const uint8_t> payload = pickle.payload_bytes();
base::MD5Sum(payload.first(payload.size() - sizeof(md5_digest)), &md5_digest);
if (!base::ranges::equal(read_md5_digest->a, md5_digest.a))
return ReadCacheResult::kFailInvalidMD5;
return ReadCacheResult::kSuccess;
}
} // namespace
void AddInspectionResultToCache(
const ModuleInfoKey& module_key,
const ModuleInspectionResult& inspection_result,
InspectionResultsCache* inspection_results_cache) {
auto insert_result = inspection_results_cache->insert(std::make_pair(
module_key, std::make_pair(inspection_result,
CalculateTimeStamp(base::Time::Now()))));
// An insertion should always succeed because the user of this code never
// tries to insert an existing value.
DCHECK(insert_result.second);
}
std::optional<ModuleInspectionResult> GetInspectionResultFromCache(
const ModuleInfoKey& module_key,
InspectionResultsCache* inspection_results_cache) {
std::optional<ModuleInspectionResult> inspection_result;
auto it = inspection_results_cache->find(module_key);
if (it != inspection_results_cache->end()) {
// Update the time stamp.
it->second.second = CalculateTimeStamp(base::Time::Now());
inspection_result = it->second.first;
}
return inspection_result;
}
ReadCacheResult ReadInspectionResultsCache(
const base::FilePath& file_path,
uint32_t min_time_stamp,
InspectionResultsCache* inspection_results_cache) {
std::string contents;
if (!ReadFileToString(file_path, &contents))
return ReadCacheResult::kFailReadFile;
base::Pickle pickle =
base::Pickle::WithUnownedBuffer(base::as_byte_span(contents));
InspectionResultsCache temporary_result;
ReadCacheResult read_result = DeserializeInspectionResultsCache(
min_time_stamp, pickle, &temporary_result);
// Only update the output cache when successful.
if (read_result == ReadCacheResult::kSuccess)
*inspection_results_cache = std::move(temporary_result);
return read_result;
}
bool WriteInspectionResultsCache(
const base::FilePath& file_path,
const InspectionResultsCache& inspection_results_cache) {
base::Pickle pickle =
SerializeInspectionResultsCache(inspection_results_cache);
// TODO(crbug.com/40106434): Investigate if using WriteFileAtomically() in a
// CONTINUE_ON_SHUTDOWN sequence can cause too many corrupted caches.
return base::ImportantFileWriter::WriteFileAtomically(
file_path, std::string_view(pickle.data_as_char(), pickle.size()));
}