blob: f9cec8b78cc1c66fdc7ad5f02b2e02248bef10b5 [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_util.h"
#include <stdint.h>
#include <map>
#include <string>
#include "base/base_paths.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/registry.h"
#include "chrome/chrome_cleaner/os/disk_util.h"
#include "chrome/chrome_cleaner/os/registry.h"
#include "chrome/chrome_cleaner/os/system_util.h"
#include "chrome/chrome_cleaner/strings/string_util.h"
#include "components/chrome_cleaner/public/constants/constants.h"
namespace chrome_cleaner {
namespace {
// The maximal number of tries to read a registry key before failing.
const unsigned int kMaxRegistryReadIterations = 5;
// Initial size of the buffer that holds the name of a value, in characters.
const size_t kValueNameBufferSize = 256;
// The delimiter used within registry key path.
const wchar_t kRegistrySubkeyDelimiter = L'\\';
// The number of extra bytes to add in the buffer used to read registry strings.
const size_t kNumExtraBytesForRegistryStrings = 3;
// Split the pattern into path components. For example, with the pattern
// 'ab??/x*/abc', |head| receives the component 'ab??' and |rest| receives the
// remaining components 'x*/abc'.
void ExtractHeadingSubkeyComponent(const std::wstring& pattern,
const wchar_t escape_char,
std::wstring* head,
std::wstring* rest) {
DCHECK(head);
DCHECK(rest);
for (size_t offset = 0; offset < pattern.size(); ++offset) {
if (pattern[offset] == escape_char) {
// Skip the escape character.
++offset;
}
if (pattern[offset] == kRegistrySubkeyDelimiter) {
*head = pattern.substr(0, offset);
*rest = pattern.substr(offset + 1);
return;
}
}
*head = pattern;
*rest = L"";
}
// Retrieve matching registry keys against a pattern with wild-cards. This
// algorithm matches recursively all registry keys for the given pattern:
// |hkey|\|key_path|\|pattern|. For each recursive invocation, the algorithm
// removes the heading key path component of the pattern, enumerates matching
// subkeys and moves the component to the |key_path| part. |path_masks| receives
// the wow64access masks for each existing path.
void CollectMatchingRegistryPathsRecursive(
HKEY hkey,
const std::wstring& key_path,
const std::wstring& pattern,
const wchar_t escape_char,
REGSAM wow64access,
std::map<std::wstring, REGSAM>* path_masks) {
DCHECK(path_masks);
if (pattern.empty()) {
if (RegKeyPath(hkey, key_path.c_str(), wow64access).Exists())
(*path_masks)[key_path] |= wow64access;
return;
}
// Extract the first key_path component of the pattern.
std::wstring subkey_pattern;
std::wstring remaining_pattern;
ExtractHeadingSubkeyComponent(pattern, escape_char, &subkey_pattern,
&remaining_pattern);
std::wstring current_prefix;
if (!key_path.empty())
current_prefix = key_path + kRegistrySubkeyDelimiter;
// If there is no wild-card into the first component, append it and
// continue with the following components.
if (!NameContainsWildcards(subkey_pattern)) {
CollectMatchingRegistryPathsRecursive(hkey, current_prefix + subkey_pattern,
remaining_pattern, escape_char,
wow64access, path_masks);
return;
}
// If the first component contains a wild-card, enumerate the registry key
// and continue matching on registry keys that match the pattern.
base::win::RegistryKeyIterator subkeys_it(hkey, key_path.c_str(),
wow64access);
for (; subkeys_it.Valid(); ++subkeys_it) {
std::wstring subkey_name = subkeys_it.Name();
if (WStringWildcardMatchInsensitive(subkey_name, subkey_pattern,
escape_char)) {
CollectMatchingRegistryPathsRecursive(hkey, current_prefix + subkey_name,
remaining_pattern, escape_char,
wow64access, path_masks);
}
}
}
} // namespace
const wchar_t kUninstallerKeyPath[] =
L"software\\microsoft\\windows\\currentversion\\uninstall";
const wchar_t kChromePoliciesKeyPath[] = L"software\\policies\\google\\chrome";
const wchar_t kChromePoliciesForcelistKeyPath[] =
L"software\\policies\\google\\chrome\\ExtensionInstallForcelist";
const wchar_t kChromePoliciesWhitelistKeyPathDeprecated[] =
L"software\\policies\\google\\chrome\\ExtensionInstallWhitelist";
const wchar_t kChromePoliciesAllowlistKeyPath[] =
L"software\\policies\\google\\chrome\\ExtensionInstallAllowlist";
const wchar_t kChromiumPoliciesForcelistKeyPath[] =
L"software\\policies\\chromium\\ExtensionInstallForcelist";
const wchar_t kChromiumPoliciesWhitelistKeyPathDeprecated[] =
L"software\\policies\\chromium\\ExtensionInstallWhitelist";
const wchar_t kChromiumPoliciesAllowlistKeyPath[] =
L"software\\policies\\chromium\\ExtensionInstallAllowlist";
std::wstring RegistryValueTypeToString(DWORD value_type) {
switch (value_type) {
case REG_BINARY:
return L"REG_BINARY";
case REG_DWORD:
return L"REG_DWORD";
case REG_DWORD_BIG_ENDIAN:
return L"REG_DWORD_BIG_ENDIAN";
case REG_EXPAND_SZ:
return L"REG_EXPAND_SZ";
case REG_LINK:
return L"REG_LINK";
case REG_MULTI_SZ:
return L"REG_MULTI_SZ";
case REG_NONE:
return L"REG_NONE";
case REG_QWORD:
return L"REG_QWORD";
case REG_SZ:
return L"REG_SZ";
default:
LOG(WARNING) << "Unknown registry value type (" << value_type << ").";
return base::NumberToWString(value_type);
}
}
void CollectMatchingRegistryNames(const base::win::RegKey& key,
const std::wstring& pattern,
const wchar_t escape_char,
std::vector<std::wstring>* names) {
DCHECK(names);
// If there is no wild-card, return the pattern as-is.
if (!NameContainsWildcards(pattern)) {
names->push_back(pattern);
return;
}
// Enumerates value names under the registry key |key|.
DWORD index = 0;
std::vector<wchar_t> value_name(kValueNameBufferSize);
while (true) {
for (unsigned int iteration = 0; iteration < kMaxRegistryReadIterations;
++iteration) {
DWORD value_name_size = static_cast<DWORD>(value_name.size());
LONG res_enum =
::RegEnumValue(key.Handle(), index, &value_name[0], &value_name_size,
nullptr, nullptr, nullptr, nullptr);
if (res_enum == ERROR_NO_MORE_ITEMS) {
return;
} else if (res_enum == ERROR_MORE_DATA &&
value_name_size > static_cast<DWORD>(value_name.size())) {
value_name.resize(value_name_size);
// Try an other iteration.
continue;
} else if (res_enum != ERROR_SUCCESS) {
DLOG(ERROR) << "Received error code " << res_enum
<< " while enumerating value names from key.";
return;
} else {
// Check whether the value matches the given pattern.
if (NameMatchesPattern(&value_name[0], pattern, escape_char))
names->push_back(&value_name[0]);
break;
}
}
// Move to the next registry value name.
++index;
}
}
void CollectMatchingRegistryPaths(HKEY hkey,
const std::wstring& pattern,
const wchar_t escape_char,
std::vector<RegKeyPath>* key_paths) {
DCHECK(key_paths);
// We can query for key reflection, but not redirection. To avoid many special
// cases here about which keys are remapped, we scan the Win32 and Win64 space
// independently and remove duplicates after the fact.
std::map<std::wstring, REGSAM> key_path_masks;
if (!NameContainsWildcards(pattern)) {
// If there is no wild-card, just check whether the key exists.
if (RegKeyPath(hkey, pattern.c_str(), KEY_WOW64_32KEY).Exists())
key_path_masks[pattern] |= KEY_WOW64_32KEY;
if (RegKeyPath(hkey, pattern.c_str(), KEY_WOW64_64KEY).Exists())
key_path_masks[pattern] |= KEY_WOW64_64KEY;
} else {
// Recursively scan both the 32 and 64-bit view of the registry.
CollectMatchingRegistryPathsRecursive(hkey, L"", pattern, escape_char,
KEY_WOW64_32KEY, &key_path_masks);
if (IsX64Architecture()) {
CollectMatchingRegistryPathsRecursive(hkey, L"", pattern, escape_char,
KEY_WOW64_64KEY, &key_path_masks);
}
}
// Remove duplicates where no key remapping was performed.
for (const auto& it : key_path_masks) {
if (it.second == (KEY_WOW64_32KEY | KEY_WOW64_64KEY)) {
const RegKeyPath path32(hkey, it.first.c_str(), KEY_WOW64_32KEY);
const RegKeyPath path64(hkey, it.first.c_str(), KEY_WOW64_64KEY);
if (path32.IsEquivalent(path64)) {
key_paths->emplace_back(hkey, it.first.c_str(), KEY_WOW64_32KEY);
} else {
key_paths->push_back(path32);
key_paths->push_back(path64);
}
} else {
key_paths->emplace_back(hkey, it.first.c_str(), it.second);
}
}
}
bool ReadRegistryValue(const base::win::RegKey& reg_key,
const wchar_t* value_name,
std::wstring* content,
uint32_t* content_type,
RegistryError* error) {
DWORD content_bytes = 0;
// Always keep more bytes in the buffer so we can 1) start with a valid
// buffer to call with a 0 size request, and 2) make room to insert a
// potentially missing null wchar_t, and 3) make the size even when it's odd.
std::vector<BYTE> buffer(content_bytes + kNumExtraBytesForRegistryStrings);
DWORD type = REG_NONE;
unsigned int iteration = 0;
while (true) {
// Fail after trying to read the value too many times.
if (iteration++ >= kMaxRegistryReadIterations) {
if (error)
*error = RegistryError::UNEXPECTED_ERROR;
return false;
}
DWORD result =
reg_key.ReadValue(value_name, &buffer[0], &content_bytes, &type);
if (result == ERROR_SUCCESS)
break;
if (result == ERROR_FILE_NOT_FOUND) {
if (error)
*error = RegistryError::VALUE_NOT_FOUND;
return false;
}
if (result != ERROR_MORE_DATA) {
DLOG(ERROR) << "Unexpected result from RegQueryValueEx: " << result
<< ", value = '" << value_name << "'.";
if (error)
*error = RegistryError::UNEXPECTED_ERROR;
return false;
}
// Not enough space for the registry key content. Resize the buffer and
// try again. Add kNumExtraBytesForRegistryStrings in case we need to
// complete/add a null terminating wchar_t.
DCHECK_LT(buffer.size(), content_bytes + kNumExtraBytesForRegistryStrings);
buffer.resize(content_bytes + kNumExtraBytesForRegistryStrings);
}
// Accept empty content.
if (content_bytes == 0) {
if (content)
*content = std::wstring();
if (content_type)
*content_type = type;
if (error)
*error = RegistryError::SUCCESS;
return true;
}
if (content) {
// For non string types, simply convert the value to a string.
if (type != REG_SZ && type != REG_EXPAND_SZ && type != REG_MULTI_SZ) {
const std::wstring::value_type* wide_buffer =
reinterpret_cast<std::wstring::value_type*>(&buffer[0]);
GetRegistryValueAsString(wide_buffer, content_bytes, type, content);
} else {
// We may need to fix the null termination of the string read from the
// registry, make sure we have enough room.
DCHECK_GE(buffer.size(),
content_bytes + kNumExtraBytesForRegistryStrings);
// This can happen if other apps wrote the value as binary. There are no
// strict rules for writing strings as binaries in the registry. We have
// seen wide char strings returned with a single byte for the null
// terminating char, which must be two bytes for wchar_t.
if (content_bytes % 2)
buffer[content_bytes++] = '\0';
// Also make sure a full null terminating wchar_t has been added. It's not
// always the case either.
DCHECK_GT(content_bytes, 1UL);
if (buffer[content_bytes - 1] || buffer[content_bytes - 2]) {
buffer[content_bytes++] = '\0';
buffer[content_bytes++] = '\0';
}
DCHECK_LE(content_bytes, buffer.size());
// Returns the content of the registry value.
const std::wstring::value_type* wide_buffer =
reinterpret_cast<std::wstring::value_type*>(&buffer[0]);
*content = std::wstring(wide_buffer, content_bytes / 2 - 1);
}
}
if (content_type)
*content_type = type;
if (error)
*error = RegistryError::SUCCESS;
return true;
}
bool ReadRegistryValue(const RegKeyPath& key_path,
const wchar_t* value_name,
std::wstring* content,
uint32_t* content_type,
RegistryError* error) {
DCHECK(value_name);
base::win::RegKey reg_key;
if (!key_path.Open(KEY_QUERY_VALUE, &reg_key)) {
DLOG(ERROR) << "Failed to open registry key: " << key_path.FullPath();
if (error)
*error = RegistryError::FAILED_TO_OPEN_KEY;
return false;
}
// ReadRegistryValue() already logs to DLOG(ERROR), so no need to log here.
return ReadRegistryValue(reg_key, value_name, content, content_type, error);
}
bool WriteRegistryValue(const wchar_t* value_name,
const std::wstring& content,
uint32_t content_type,
base::win::RegKey* reg_key) {
LONG success = reg_key->WriteValue(
value_name, reinterpret_cast<const void*>(content.c_str()),
content.size() * sizeof(std::wstring::value_type), content_type);
return success == ERROR_SUCCESS;
}
void GetRegistryValueAsString(const wchar_t* raw_content,
size_t raw_content_bytes,
DWORD value_type,
std::wstring* content) {
DCHECK(raw_content);
DCHECK(content);
if (value_type == REG_SZ || value_type == REG_EXPAND_SZ ||
value_type == REG_MULTI_SZ) {
*content = raw_content;
} else if (value_type == REG_DWORD) {
DWORD dword_value = *reinterpret_cast<const DWORD*>(raw_content);
*content = base::StringPrintf(L"%08x", dword_value);
} else {
// The content displayed by this fallback is a sequence of bytes in
// little-endian, which give strange display for numeric values (i.e
// 01000000 instead of 00000001)
*content = base::ASCIIToWide(base::HexEncode(
reinterpret_cast<const char*>(raw_content), raw_content_bytes));
}
}
} // namespace chrome_cleaner