| // 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/string16.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 base::string16& pattern, |
| const wchar_t escape_char, |
| base::string16* head, |
| base::string16* 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 base::string16& key_path, |
| const base::string16& pattern, |
| const wchar_t escape_char, |
| REGSAM wow64access, |
| std::map<base::string16, 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. |
| base::string16 subkey_pattern; |
| base::string16 remaining_pattern; |
| ExtractHeadingSubkeyComponent(pattern, escape_char, &subkey_pattern, |
| &remaining_pattern); |
| |
| base::string16 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) { |
| base::string16 subkey_name = subkeys_it.Name(); |
| if (String16WildcardMatchInsensitive(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 kChromePoliciesWhitelistKeyPath[] = |
| L"software\\policies\\google\\chrome\\ExtensionInstallWhitelist"; |
| |
| const wchar_t kChromiumPoliciesForcelistKeyPath[] = |
| L"software\\policies\\chromium\\ExtensionInstallForcelist"; |
| const wchar_t kChromiumPoliciesWhitelistKeyPath[] = |
| L"software\\policies\\chromium\\ExtensionInstallWhitelist"; |
| |
| base::string16 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::NumberToString16(value_type); |
| } |
| } |
| |
| void CollectMatchingRegistryNames(const base::win::RegKey& key, |
| const base::string16& pattern, |
| const wchar_t escape_char, |
| std::vector<base::string16>* 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<base::char16> 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 base::string16& 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<base::string16, 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, |
| base::string16* 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 = base::string16(); |
| 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 base::string16::value_type* char16_buffer = |
| reinterpret_cast<base::string16::value_type*>(&buffer[0]); |
| GetRegistryValueAsString(char16_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 base::string16::value_type* char16_buffer = |
| reinterpret_cast<base::string16::value_type*>(&buffer[0]); |
| *content = base::string16(char16_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, |
| base::string16* content, |
| uint32_t* content_type, |
| RegistryError* error) { |
| DCHECK(value_name); |
| base::win::RegKey reg_key; |
| if (!key_path.Open(KEY_QUERY_VALUE, ®_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 base::string16& 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(base::string16::value_type), content_type); |
| return success == ERROR_SUCCESS; |
| } |
| |
| void GetRegistryValueAsString(const wchar_t* raw_content, |
| size_t raw_content_bytes, |
| DWORD value_type, |
| base::string16* 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::ASCIIToUTF16(base::HexEncode( |
| reinterpret_cast<const char*>(raw_content), raw_content_bytes)); |
| } |
| } |
| |
| } // namespace chrome_cleaner |