blob: 9e5a80c6cec14bee9d9ce13222e4075d06affd28 [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/chrome_cleaner/os/registry_override_manager.h"
#include <windows.h>
#include <shlwapi.h>
#include <stdint.h>
#include <string>
#include "base/command_line.h"
#include "base/guid.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/registry.h"
#include "chrome/chrome_cleaner/constants/chrome_cleaner_switches.h"
#include "chrome/chrome_cleaner/constants/version.h"
#include "chrome/chrome_cleaner/test/test_branding.h"
namespace chrome_cleaner {
namespace {
// Describes a predefined registry key to override.
struct RegistryPredefinedKey {
HKEY key;
const base::char16* name;
};
// Predefined keys to override.
// HKEY_CURRENT_USER must be last because temporary keys are created under it.
const RegistryPredefinedKey kPredefinedKeys[] = {
{HKEY_CLASSES_ROOT, L"HKEY_CLASSES_ROOT"},
{HKEY_CURRENT_CONFIG, L"HKEY_CURRENT_CONFIG"},
{HKEY_LOCAL_MACHINE, L"HKEY_LOCAL_MACHINE"},
{HKEY_USERS, L"HKEY_USERS"},
{HKEY_CURRENT_USER, L"HKEY_CURRENT_USER"},
};
const size_t kPredefinedKeysSize = base::size(kPredefinedKeys);
const base::char16 kTimestampDelimiter[] = L"$";
// Timeout to delete stale temporary keys, in hours.
const int kRegistryOverrideTimeout = 24;
// Initial size of the buffer that holds the name of a value, in characters.
const size_t kValueNameBufferSize = 256;
// Initial size of the buffer that holds the content of a value, in bytes.
const size_t kValueContentBufferSize = 512;
// Delete keys created for old temporary registries. |timestamp| should be
// base::Time::Now(), except for tests. |temp_registry_parent| is the key under
// which temporary registries are created.
void DeleteStaleTempKeys(const base::Time& timestamp,
const base::string16& temp_registry_parent) {
base::win::RegKey test_root_key;
LONG res = test_root_key.Open(HKEY_CURRENT_USER, temp_registry_parent.c_str(),
KEY_ALL_ACCESS);
if (res != ERROR_SUCCESS) {
// This will occur on first-run, but is harmless.
DCHECK_EQ(res, ERROR_FILE_NOT_FOUND);
return;
}
base::win::RegistryKeyIterator iterator_test_root_key(
HKEY_CURRENT_USER, temp_registry_parent.c_str());
for (; iterator_test_root_key.Valid(); ++iterator_test_root_key) {
const base::char16* key_name = iterator_test_root_key.Name();
std::vector<base::string16> tokens =
base::SplitString(key_name, base::string16(kTimestampDelimiter),
base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
int64_t key_name_as_number = 0;
if (!base::StringToInt64(tokens[0], &key_name_as_number)) {
test_root_key.DeleteKey(key_name);
continue;
}
base::Time key_time = base::Time::FromDeltaSinceWindowsEpoch(
base::TimeDelta::FromNanoseconds(key_name_as_number));
base::TimeDelta age = timestamp - key_time;
if (age > base::TimeDelta::FromHours(kRegistryOverrideTimeout))
test_root_key.DeleteKey(key_name);
}
}
bool CloneKeyNoRecursion(const base::win::RegKey& source_key,
HKEY source_key_root,
const base::string16& source_key_path,
base::win::RegKey* destination_key) {
DCHECK(source_key.Valid());
DCHECK(destination_key);
// Enumerate values and copy them to the temporary registry.
std::vector<uint8_t> value_content(kValueContentBufferSize);
std::vector<base::char16> value_name(kValueNameBufferSize);
for (DWORD index = 0;; ++index) {
DWORD value_name_size = static_cast<DWORD>(value_name.size());
DWORD value_content_size = static_cast<DWORD>(value_content.size());
DWORD type = REG_NONE;
LONG res_enum = ::RegEnumValue(source_key.Handle(), index, &value_name[0],
&value_name_size, nullptr, &type,
&value_content[0], &value_content_size);
if (res_enum == ERROR_MORE_DATA) {
if (value_name_size > static_cast<DWORD>(value_name.size()))
value_name.resize(value_name_size);
if (value_content_size > static_cast<DWORD>(value_content.size()))
value_content.resize(value_content_size);
res_enum = ::RegEnumValue(source_key.Handle(), index, &value_name[0],
&value_name_size, nullptr, &type,
&value_content[0], &value_content_size);
}
if (res_enum == ERROR_NO_MORE_ITEMS) {
break;
} else if (res_enum != ERROR_SUCCESS) {
DLOG(ERROR) << "Received error code " << res_enum
<< " while reading a value to clone from key "
<< source_key_path << " under root " << source_key_root
<< ".";
return false;
}
if (destination_key->WriteValue(&value_name[0], &value_content[0],
value_content_size,
type) != ERROR_SUCCESS) {
DLOG(ERROR) << "Unable to write cloned value " << &value_name[0]
<< " from key " << source_key_path << " under root "
<< source_key_root << ".";
return false;
}
}
return true;
}
bool CloneKey(const RegistryOverrideManager::RegistryKeyToClone& key,
const base::string16& temp_registry_path) {
// Open the source key.
base::win::RegKey source_key;
LONG open_key_res =
source_key.Open(key.root, key.path.c_str(), KEY_QUERY_VALUE);
if (open_key_res == ERROR_FILE_NOT_FOUND) {
// If the key doesn't exist, no need to clone it.
return true;
} else if (open_key_res != ERROR_SUCCESS) {
DLOG(WARNING) << "The source key " << key.path << " under root " << key.root
<< " cannot be opened to be cloned.";
return false;
}
// Find the name of the root key for the key to clone.
const base::char16* predefined_key_name = nullptr;
for (size_t i = 0; i < kPredefinedKeysSize; ++i) {
if (kPredefinedKeys[i].key == key.root) {
predefined_key_name = kPredefinedKeys[i].name;
break;
}
}
DCHECK(predefined_key_name);
// Create the destination key in the temporary registry.
base::string16 destination_path =
temp_registry_path + L"\\" + predefined_key_name + L"\\" + key.path;
base::win::RegKey destination_key;
destination_key.Create(HKEY_CURRENT_USER, destination_path.c_str(),
KEY_ALL_ACCESS);
// Clone the key to the temporary registry.
if (key.recursive) {
return ::SHCopyKey(source_key.Handle(), L"", destination_key.Handle(), 0) ==
ERROR_SUCCESS;
} else {
return CloneKeyNoRecursion(source_key, key.root, key.path,
&destination_key);
}
}
base::string16 GetRegistryPath(const base::string16& temp_registry_id,
const base::string16& temp_registry_parent) {
return temp_registry_parent + L"\\" + temp_registry_id;
}
} // namespace
// Key under which temporary registries are created. Applications commonly put
// their custom keys under Software\[Company]\[Application]. The name of the
// leaf key is arbitrary. Since this class is used for interacting with tests,
// "Chrome Cleanup Tool Test" is always used as the product name.
const base::char16 RegistryOverrideManager::kTempRegistryParent[] =
L"Software\\" COMPANY_SHORTNAME_STRING L"\\"
TEST_PRODUCT_FULLNAME_STRING L"\\TempTestKeys";
RegistryOverrideManager::RegistryOverrideManager()
: owns_temp_registry_(false), temp_registry_parent_(kTempRegistryParent) {
DeleteStaleTempKeys(base::Time::Now(), temp_registry_parent_);
}
RegistryOverrideManager::~RegistryOverrideManager() {
// Undo the overrides.
for (size_t i = 0; i < overrides_.size(); ++i)
::RegOverridePredefKey(overrides_[i], nullptr);
if (owns_temp_registry_) {
// Delete the temporary key if it exists.
DCHECK(!temp_registry_id_.empty());
base::win::RegKey parent_key;
if (parent_key.Open(HKEY_CURRENT_USER, temp_registry_parent_.c_str(),
KEY_ALL_ACCESS) == ERROR_SUCCESS) {
parent_key.DeleteKey(temp_registry_id_.c_str());
}
}
}
bool RegistryOverrideManager::CreateTemporaryRegistry(
const RegistryKeyVector& keys_to_clone) {
if (!temp_registry_id_.empty())
return false;
temp_registry_id_ = GenerateTempRegistryId(base::Time::Now());
owns_temp_registry_ = true;
// Clone keys to the temporary registry.
base::string16 temp_registry_path =
GetRegistryPath(temp_registry_id_, temp_registry_parent_);
for (size_t i = 0; i < keys_to_clone.size(); ++i) {
if (!CloneKey(keys_to_clone[i], temp_registry_path))
return false;
}
return true;
}
bool RegistryOverrideManager::CreateAndUseTemporaryRegistry(
const RegistryKeyVector& keys_to_clone) {
if (!temp_registry_id_.empty())
return false;
// Create the temporary registry.
if (!CreateTemporaryRegistry(keys_to_clone))
return false;
// Enable the registry override.
return UseTemporaryRegistryInternal();
}
bool RegistryOverrideManager::UseTemporaryRegistry(
const base::string16& temp_registry_parent,
const base::string16& temp_registry_id) {
if (!temp_registry_id_.empty())
return false;
owns_temp_registry_ = false;
temp_registry_parent_ = temp_registry_parent;
temp_registry_id_ = temp_registry_id;
return UseTemporaryRegistryInternal();
}
RegistryOverrideManager::RegistryOverrideManager(
const base::Time& timestamp,
const base::string16& temp_registry_parent)
: owns_temp_registry_(false), temp_registry_parent_(temp_registry_parent) {
DeleteStaleTempKeys(timestamp, temp_registry_parent_);
}
bool RegistryOverrideManager::UseTemporaryRegistryInternal() {
base::string16 temp_registry_path =
GetRegistryPath(temp_registry_id_, temp_registry_parent_);
bool error = false;
for (size_t i = 0; i < kPredefinedKeysSize; ++i) {
const RegistryPredefinedKey& key = kPredefinedKeys[i];
base::win::RegKey reg_key;
base::string16 key_path = temp_registry_path + L"\\" + key.name;
// Create the key, or open it if it already exists.
reg_key.Create(HKEY_CURRENT_USER, key_path.c_str(), KEY_ALL_ACCESS);
if (!reg_key.Valid() ||
::RegOverridePredefKey(key.key, reg_key.Handle()) != ERROR_SUCCESS) {
error = true;
continue;
}
overrides_.push_back(key.key);
}
return !error;
}
void RegistryOverrideManager::OverrideRegistryDuringTests(
base::CommandLine* command_line) {
if (command_line->HasSwitch(kUseTempRegistryPathSwitch)) {
base::string16 temp_registry_path =
command_line->GetSwitchValueNative(kUseTempRegistryPathSwitch);
base::string16 temp_registry_parent;
base::string16 temp_registry_id;
ParseTempRegistryPath(temp_registry_path, &temp_registry_parent,
&temp_registry_id);
UseTemporaryRegistry(temp_registry_parent, temp_registry_id);
}
}
base::string16 RegistryOverrideManager::GetTempRegistryPath() const {
return temp_registry_parent_ + L'\\' + temp_registry_id_;
}
// static
void RegistryOverrideManager::ParseTempRegistryPath(
const base::string16& temp_registry_path,
base::string16* temp_registry_parent,
base::string16* temp_registry_id) {
// |temp_registry_path| has the following format:
// Path\To\Temp\Registry\Root\RegistryId
auto root_id_delimiter_pos = temp_registry_path.rfind(L'\\');
DCHECK_NE(root_id_delimiter_pos, base::string16::npos);
*temp_registry_parent = temp_registry_path.substr(0, root_id_delimiter_pos);
*temp_registry_id = temp_registry_path.substr(root_id_delimiter_pos + 1);
}
// static.
base::string16 RegistryOverrideManager::GenerateTempRegistryId(
const base::Time& timestamp) {
base::string16 id = base::Int64ToString16(
timestamp.ToDeltaSinceWindowsEpoch().InNanoseconds());
id += kTimestampDelimiter + base::ASCIIToUTF16(base::GenerateGUID());
return id;
}
} // namespace chrome_cleaner