| // Copyright 2016 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_elf/nt_registry/nt_registry.h" |
| |
| #include <assert.h> |
| #include <stdlib.h> |
| |
| #include <memory> |
| #include <string> |
| |
| namespace { |
| |
| // Function pointers used for registry access. |
| RtlInitUnicodeStringFunction g_rtl_init_unicode_string = nullptr; |
| NtCreateKeyFunction g_nt_create_key = nullptr; |
| NtDeleteKeyFunction g_nt_delete_key = nullptr; |
| NtOpenKeyExFunction g_nt_open_key_ex = nullptr; |
| NtCloseFunction g_nt_close = nullptr; |
| NtQueryKeyFunction g_nt_query_key = nullptr; |
| NtEnumerateKeyFunction g_nt_enumerate_key = nullptr; |
| NtQueryValueKeyFunction g_nt_query_value_key = nullptr; |
| NtSetValueKeyFunction g_nt_set_value_key = nullptr; |
| |
| // Lazy init. No concern about concurrency in chrome_elf. |
| bool g_initialized = false; |
| bool g_system_install = false; |
| bool g_wow64_proc = false; |
| wchar_t g_kRegPathHKLM[] = L"\\Registry\\Machine\\"; |
| wchar_t g_kRegPathHKCU[nt::g_kRegMaxPathLen + 1] = L""; |
| wchar_t g_current_user_sid_string[nt::g_kRegMaxPathLen + 1] = L""; |
| |
| // Max number of tries for system API calls when STATUS_BUFFER_OVERFLOW or |
| // STATUS_BUFFER_TOO_SMALL can be returned. |
| enum { kMaxTries = 5 }; |
| |
| // For testing only. |
| wchar_t g_HKLM_override[nt::g_kRegMaxPathLen + 1] = L""; |
| wchar_t g_HKCU_override[nt::g_kRegMaxPathLen + 1] = L""; |
| |
| //------------------------------------------------------------------------------ |
| // Initialization - LOCAL |
| //------------------------------------------------------------------------------ |
| |
| // Not using install_static, to prevent circular dependency. |
| bool IsThisProcSystem() { |
| wchar_t program_dir[MAX_PATH] = {}; |
| wchar_t* cmd_line = GetCommandLineW(); |
| // If our command line starts with the "Program Files" or |
| // "Program Files (x86)" path, we're system. |
| DWORD ret = ::GetEnvironmentVariable(L"PROGRAMFILES", program_dir, MAX_PATH); |
| if (ret && ret < MAX_PATH && !::wcsncmp(cmd_line, program_dir, ret)) |
| return true; |
| |
| ret = ::GetEnvironmentVariable(L"PROGRAMFILES(X86)", program_dir, MAX_PATH); |
| if (ret && ret < MAX_PATH && !::wcsncmp(cmd_line, program_dir, ret)) |
| return true; |
| |
| return false; |
| } |
| |
| bool IsThisProcWow64() { |
| // Using BOOL type for compat with IsWow64Process() system API. |
| BOOL is_wow64 = FALSE; |
| |
| // API might not exist, so dynamic lookup. |
| using IsWow64ProcessFunction = decltype(&IsWow64Process); |
| IsWow64ProcessFunction is_wow64_process = |
| reinterpret_cast<IsWow64ProcessFunction>(::GetProcAddress( |
| ::GetModuleHandle(L"kernel32.dll"), "IsWow64Process")); |
| if (!is_wow64_process) |
| return false; |
| if (!is_wow64_process(::GetCurrentProcess(), &is_wow64)) |
| return false; |
| return is_wow64 ? true : false; |
| } |
| |
| bool InitNativeRegApi() { |
| HMODULE ntdll = ::GetModuleHandleW(L"ntdll.dll"); |
| |
| // Setup the global function pointers for registry access. |
| g_rtl_init_unicode_string = reinterpret_cast<RtlInitUnicodeStringFunction>( |
| ::GetProcAddress(ntdll, "RtlInitUnicodeString")); |
| |
| g_nt_create_key = reinterpret_cast<NtCreateKeyFunction>( |
| ::GetProcAddress(ntdll, "NtCreateKey")); |
| |
| g_nt_delete_key = reinterpret_cast<NtDeleteKeyFunction>( |
| ::GetProcAddress(ntdll, "NtDeleteKey")); |
| |
| g_nt_open_key_ex = reinterpret_cast<NtOpenKeyExFunction>( |
| ::GetProcAddress(ntdll, "NtOpenKeyEx")); |
| |
| g_nt_close = |
| reinterpret_cast<NtCloseFunction>(::GetProcAddress(ntdll, "NtClose")); |
| |
| g_nt_query_key = reinterpret_cast<NtQueryKeyFunction>( |
| ::GetProcAddress(ntdll, "NtQueryKey")); |
| |
| g_nt_enumerate_key = reinterpret_cast<NtEnumerateKeyFunction>( |
| ::GetProcAddress(ntdll, "NtEnumerateKey")); |
| |
| g_nt_query_value_key = reinterpret_cast<NtQueryValueKeyFunction>( |
| ::GetProcAddress(ntdll, "NtQueryValueKey")); |
| |
| g_nt_set_value_key = reinterpret_cast<NtSetValueKeyFunction>( |
| ::GetProcAddress(ntdll, "NtSetValueKey")); |
| |
| if (!g_rtl_init_unicode_string || !g_nt_create_key || !g_nt_open_key_ex || |
| !g_nt_delete_key || !g_nt_close || !g_nt_query_key || |
| !g_nt_enumerate_key || !g_nt_query_value_key || !g_nt_set_value_key) |
| return false; |
| |
| // We need to set HKCU based on the sid of the current user account. |
| RtlFormatCurrentUserKeyPathFunction rtl_current_user_string = |
| reinterpret_cast<RtlFormatCurrentUserKeyPathFunction>( |
| ::GetProcAddress(ntdll, "RtlFormatCurrentUserKeyPath")); |
| |
| RtlFreeUnicodeStringFunction rtl_free_unicode_str = |
| reinterpret_cast<RtlFreeUnicodeStringFunction>( |
| ::GetProcAddress(ntdll, "RtlFreeUnicodeString")); |
| |
| if (!rtl_current_user_string || !rtl_free_unicode_str) |
| return false; |
| |
| UNICODE_STRING current_user_reg_path; |
| if (!NT_SUCCESS(rtl_current_user_string(¤t_user_reg_path))) |
| return false; |
| |
| // Finish setting up global HKCU path. |
| ::wcsncat(g_kRegPathHKCU, current_user_reg_path.Buffer, nt::g_kRegMaxPathLen); |
| ::wcsncat(g_kRegPathHKCU, L"\\", |
| (nt::g_kRegMaxPathLen - ::wcslen(g_kRegPathHKCU))); |
| // Keep the sid string as well. |
| wchar_t* ptr = ::wcsrchr(current_user_reg_path.Buffer, L'\\'); |
| ptr++; |
| ::wcsncpy(g_current_user_sid_string, ptr, nt::g_kRegMaxPathLen); |
| rtl_free_unicode_str(¤t_user_reg_path); |
| |
| // Figure out if this is a system or user install. |
| g_system_install = IsThisProcSystem(); |
| |
| // Figure out if this is a WOW64 process. |
| g_wow64_proc = IsThisProcWow64(); |
| |
| g_initialized = true; |
| return true; |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Reg WOW64 Redirection - LOCAL |
| // |
| // How registry redirection works directly calling NTDLL APIs: |
| // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| // - NOTE: On >= Win7, reflection support was removed. |
| // - |
| // https://msdn.microsoft.com/en-us/library/windows/desktop/aa384253(v=vs.85).aspx |
| // |
| // - 1) 32-bit / WOW64 process: |
| // a) Default access WILL be redirected to WOW64. |
| // b) KEY_WOW64_32KEY access WILL be redirected to WOW64. |
| // c) KEY_WOW64_64KEY access will NOT be redirected to WOW64. |
| // |
| // - 2) 64-bit process: |
| // a) Default access will NOT be redirected to WOW64. |
| // b) KEY_WOW64_32KEY access will NOT be redirected to WOW64. |
| // c) KEY_WOW64_64KEY access will NOT be redirected to WOW64. |
| // |
| // - Key point from above is that NTDLL redirects and respects access |
| // overrides for WOW64 calling processes. But does NOT do any of that if the |
| // calling process is 64-bit. 2b is surprising and troublesome. |
| // |
| // How registry redirection works using these nt_registry APIs: |
| // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| // - These APIs will behave the same as NTDLL above, EXCEPT for 2b. |
| // nt_registry APIs will respect the override access flags for all processes. |
| // |
| // - How the WOW64 redirection decision trees / Nodes work below: |
| // |
| // The HKLM and HKCU decision trees represent the information at the MSDN |
| // link above... but in a way that generates a decision about whether a |
| // registry path should be subject to WOW64 redirection. The tree is |
| // traversed as you scan along the registry path in question. |
| // |
| // - Each Node contains a chunk of registry subkey(s) to match. |
| // - If it is NOT matched, traversal is done. |
| // - If it is matched: |
| // - Current state of |redirection_type| for the whole registry path is |
| // updated. |
| // - If |next| is empty, traversal is done. |
| // - Otherwise, |next| is an array of child Nodes to try to match against. |
| // Loop. |
| //------------------------------------------------------------------------------ |
| |
| // This enum defines states for how to handle redirection. |
| // NOTE: When WOW64 redirection should happen, the redirect subkey can be either |
| // before or after the latest Node match. Unfortunately not consistent. |
| enum RedirectionType { SHARED = 0, REDIRECTED_BEFORE, REDIRECTED_AFTER }; |
| |
| struct Node { |
| template <size_t len, size_t n_len> |
| constexpr Node(const wchar_t (&wcs)[len], |
| RedirectionType rt, |
| const Node (&n)[n_len]) |
| : to_match(wcs), |
| to_match_len(len - 1), |
| redirection_type(rt), |
| next(n), |
| next_len(n_len) {} |
| |
| template <size_t len> |
| constexpr Node(const wchar_t (&wcs)[len], RedirectionType rt) |
| : to_match(wcs), |
| to_match_len(len - 1), |
| redirection_type(rt), |
| next(nullptr), |
| next_len(0) {} |
| |
| const wchar_t* to_match; |
| size_t to_match_len; |
| // If a match, this is the new state of how to redirect. |
| RedirectionType redirection_type; |
| // |next| is nullptr or an array of Nodes of length |array_len|. |
| const Node* next; |
| size_t next_len; |
| }; |
| |
| // HKLM or HKCU SOFTWARE\Classes is shared by default. Specific subkeys under |
| // Classes are redirected to SOFTWARE\WOW6432Node\Classes\<subkey> though. |
| constexpr Node kClassesSubtree[] = {{L"CLSID", REDIRECTED_BEFORE}, |
| {L"DirectShow", REDIRECTED_BEFORE}, |
| {L"Interface", REDIRECTED_BEFORE}, |
| {L"Media Type", REDIRECTED_BEFORE}, |
| {L"MediaFoundation", REDIRECTED_BEFORE}}; |
| |
| // These specific HKLM\SOFTWARE subkeys are shared. Specific |
| // subkeys under Classes are redirected though... see classes_subtree. |
| constexpr Node kHklmSoftwareSubtree[] = { |
| // TODO(pennymac): when MS fixes compiler bug, or bots are all using clang, |
| // remove the "Classes" subkeys below and replace with: |
| // {L"Classes", SHARED, kClassesSubtree}, |
| // https://connect.microsoft.com/VisualStudio/feedback/details/3104499 |
| {L"Classes\\CLSID", REDIRECTED_BEFORE}, |
| {L"Classes\\DirectShow", REDIRECTED_BEFORE}, |
| {L"Classes\\Interface", REDIRECTED_BEFORE}, |
| {L"Classes\\Media Type", REDIRECTED_BEFORE}, |
| {L"Classes\\MediaFoundation", REDIRECTED_BEFORE}, |
| {L"Classes", SHARED}, |
| |
| {L"Clients", SHARED}, |
| {L"Microsoft\\COM3", SHARED}, |
| {L"Microsoft\\Cryptography\\Calais\\Current", SHARED}, |
| {L"Microsoft\\Cryptography\\Calais\\Readers", SHARED}, |
| {L"Microsoft\\Cryptography\\Services", SHARED}, |
| |
| {L"Microsoft\\CTF\\SystemShared", SHARED}, |
| {L"Microsoft\\CTF\\TIP", SHARED}, |
| {L"Microsoft\\DFS", SHARED}, |
| {L"Microsoft\\Driver Signing", SHARED}, |
| {L"Microsoft\\EnterpriseCertificates", SHARED}, |
| |
| {L"Microsoft\\EventSystem", SHARED}, |
| {L"Microsoft\\MSMQ", SHARED}, |
| {L"Microsoft\\Non-Driver Signing", SHARED}, |
| {L"Microsoft\\Notepad\\DefaultFonts", SHARED}, |
| {L"Microsoft\\OLE", SHARED}, |
| |
| {L"Microsoft\\RAS", SHARED}, |
| {L"Microsoft\\RPC", SHARED}, |
| {L"Microsoft\\SOFTWARE\\Microsoft\\Shared Tools\\MSInfo", SHARED}, |
| {L"Microsoft\\SystemCertificates", SHARED}, |
| {L"Microsoft\\TermServLicensing", SHARED}, |
| |
| {L"Microsoft\\Transaction Server", SHARED}, |
| {L"Microsoft\\Windows\\CurrentVersion\\App Paths", SHARED}, |
| {L"Microsoft\\Windows\\CurrentVersion\\Control Panel\\Cursors\\Schemes", |
| SHARED}, |
| {L"Microsoft\\Windows\\CurrentVersion\\Explorer\\AutoplayHandlers", SHARED}, |
| {L"Microsoft\\Windows\\CurrentVersion\\Explorer\\DriveIcons", SHARED}, |
| |
| {L"Microsoft\\Windows\\CurrentVersion\\Explorer\\KindMap", SHARED}, |
| {L"Microsoft\\Windows\\CurrentVersion\\Group Policy", SHARED}, |
| {L"Microsoft\\Windows\\CurrentVersion\\Policies", SHARED}, |
| {L"Microsoft\\Windows\\CurrentVersion\\PreviewHandlers", SHARED}, |
| {L"Microsoft\\Windows\\CurrentVersion\\Setup", SHARED}, |
| |
| {L"Microsoft\\Windows\\CurrentVersion\\Telephony\\Locations", SHARED}, |
| {L"Microsoft\\Windows NT\\CurrentVersion\\Console", SHARED}, |
| {L"Microsoft\\Windows NT\\CurrentVersion\\FontDpi", SHARED}, |
| {L"Microsoft\\Windows NT\\CurrentVersion\\FontLink", SHARED}, |
| {L"Microsoft\\Windows NT\\CurrentVersion\\FontMapper", SHARED}, |
| |
| {L"Microsoft\\Windows NT\\CurrentVersion\\Fonts", SHARED}, |
| {L"Microsoft\\Windows NT\\CurrentVersion\\FontSubstitutes", SHARED}, |
| {L"Microsoft\\Windows NT\\CurrentVersion\\Gre_Initialize", SHARED}, |
| {L"Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options", |
| SHARED}, |
| {L"Microsoft\\Windows NT\\CurrentVersion\\LanguagePack", SHARED}, |
| |
| {L"Microsoft\\Windows NT\\CurrentVersion\\NetworkCards", SHARED}, |
| {L"Microsoft\\Windows NT\\CurrentVersion\\Perflib", SHARED}, |
| {L"Microsoft\\Windows NT\\CurrentVersion\\Ports", SHARED}, |
| {L"Microsoft\\Windows NT\\CurrentVersion\\Print", SHARED}, |
| {L"Microsoft\\Windows NT\\CurrentVersion\\ProfileList", SHARED}, |
| |
| {L"Microsoft\\Windows NT\\CurrentVersion\\Time Zones", SHARED}, |
| {L"Policies", SHARED}, |
| {L"RegisteredApplications", SHARED}}; |
| |
| // HKCU is entirely shared, except for a few specific Classes subkeys which |
| // are redirected. See |classes_subtree|. |
| constexpr Node kRedirectionDecisionTreeHkcu = {L"SOFTWARE\\Classes", SHARED, |
| kClassesSubtree}; |
| |
| // HKLM\SOFTWARE is redirected by default to SOFTWARE\WOW6432Node. Specific |
| // subkeys under SOFTWARE are shared though... see |hklm_software_subtree|. |
| constexpr Node kRedirectionDecisionTreeHklm = {L"SOFTWARE", REDIRECTED_AFTER, |
| kHklmSoftwareSubtree}; |
| |
| // Main redirection handler function. |
| // If redirection is required, change is made to |subkey_path| in place. |
| // |
| // - This function should be called BEFORE concatenating |subkey_path| with the |
| // root hive or calling ParseFullRegPath(). |
| // - Also, |subkey_path| should be passed to SanitizeSubkeyPath() before calling |
| // this function. |
| void ProcessRedirection(nt::ROOT_KEY root, |
| ACCESS_MASK access, |
| std::wstring* subkey_path) { |
| static constexpr wchar_t kRedirectBefore[] = L"WOW6432Node\\"; |
| static constexpr wchar_t kRedirectAfter[] = L"\\WOW6432Node"; |
| |
| assert(subkey_path != nullptr); |
| assert(subkey_path->empty() || subkey_path->front() != L'\\'); |
| assert(subkey_path->empty() || subkey_path->back() != L'\\'); |
| assert(root != nt::AUTO); |
| |
| // |subkey_path| could legitimately be empty. |
| if (subkey_path->empty() || |
| (access & KEY_WOW64_32KEY && access & KEY_WOW64_64KEY)) |
| return; |
| |
| // No redirection during testing when there's already an override. |
| // Otherwise, the testing redirect directory Software\Chromium\TempTestKeys |
| // would get WOW64 redirected if root_key == HKLM in this function. |
| if (root == nt::HKCU ? *g_HKCU_override : *g_HKLM_override) |
| return; |
| |
| // WOW64 redirection only supported on x64 architecture. Return if x86. |
| SYSTEM_INFO system_info = {}; |
| ::GetNativeSystemInfo(&system_info); |
| if (system_info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL) |
| return; |
| |
| bool use_wow64 = g_wow64_proc; |
| // Consider KEY_WOW64_32KEY and KEY_WOW64_64KEY override access flags. |
| if (access & KEY_WOW64_32KEY) |
| use_wow64 = true; |
| if (access & KEY_WOW64_64KEY) |
| use_wow64 = false; |
| |
| // If !use_wow64, there's nothing more to do. |
| if (!use_wow64) |
| return; |
| |
| // The root of the decision trees are an array of 1. |
| size_t node_array_len = 1; |
| // Pick which decision tree to use. |
| const Node* current_node = (root == nt::HKCU) ? &kRedirectionDecisionTreeHkcu |
| : &kRedirectionDecisionTreeHklm; |
| |
| // The following loop works on the |subkey_path| from left to right. |
| // |position| tracks progress along |subkey_path|. |
| const wchar_t* position = subkey_path->c_str(); |
| // Hold a count of chars left after position, for efficient calculations. |
| size_t chars_left = subkey_path->length(); |
| // |redirect_state| holds the latest state of redirection requirement. |
| RedirectionType redirect_state = SHARED; |
| // |insertion_point| tracks latest spot for redirection subkey to be inserted. |
| const wchar_t* insertion_point = nullptr; |
| // |insert_string| tracks which redirection string would be inserted. |
| const wchar_t* insert_string = nullptr; |
| |
| size_t node_index = 0; |
| while (node_index < node_array_len) { |
| size_t current_to_match_len = current_node->to_match_len; |
| // Make sure the remainder of the path is at least as long as the current |
| // subkey to match. |
| if (chars_left >= current_to_match_len) { |
| // Do case insensitive comparisons. |
| if (!::wcsnicmp(position, current_node->to_match, current_to_match_len)) { |
| // Make sure not to match on a substring. |
| if (*(position + current_to_match_len) == L'\\' || |
| *(position + current_to_match_len) == L'\0') { |
| // MATCH! |
| // ------------------------------------------------------------------- |
| // 1) Update state of redirection. |
| redirect_state = current_node->redirection_type; |
| // 1.5) If new state is to redirect, the new insertion point will be |
| // either right before or right after this match. |
| if (redirect_state == REDIRECTED_BEFORE) { |
| insertion_point = position; |
| insert_string = kRedirectBefore; |
| } else if (redirect_state == REDIRECTED_AFTER) { |
| insertion_point = position + current_to_match_len; |
| insert_string = kRedirectAfter; |
| } |
| // 2) Adjust |position| along the subkey path. |
| position += current_to_match_len; |
| chars_left -= current_to_match_len; |
| // 2.5) Increment the position, to move past path seperator(s). |
| while (*position == L'\\') { |
| ++position; |
| --chars_left; |
| } |
| // 3) Move our loop parameters to the |next| array of Nodes. |
| node_array_len = current_node->next_len; |
| current_node = current_node->next; |
| node_index = 0; |
| // 4) Finish this loop and start on new array. |
| continue; |
| } |
| } |
| } |
| |
| // Move to the next node in the array if we didn't match this loop. |
| ++current_node; |
| ++node_index; |
| } |
| |
| if (redirect_state == SHARED) |
| return; |
| |
| // Insert the redirection into |subkey_path|, at |insertion_point|. |
| subkey_path->insert((insertion_point - subkey_path->c_str()), insert_string); |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Reg Path Utilities - LOCAL |
| //------------------------------------------------------------------------------ |
| |
| std::wstring ConvertRootKey(nt::ROOT_KEY root) { |
| assert(root != nt::AUTO); |
| |
| if (root == nt::HKCU && *g_HKCU_override) { |
| std::wstring temp = g_kRegPathHKCU; |
| temp.append(g_HKCU_override); |
| temp.append(L"\\"); |
| return temp; |
| } else if (root == nt::HKLM && *g_HKLM_override) { |
| // Yes, HKLM override goes into HKCU. This is not a typo. |
| std::wstring temp = g_kRegPathHKCU; |
| temp.append(g_HKLM_override); |
| temp.append(L"\\"); |
| return temp; |
| } |
| |
| return (root == nt::HKCU) ? g_kRegPathHKCU : g_kRegPathHKLM; |
| } |
| |
| // This utility should be called on an externally provided subkey path. |
| // - Ensures there are no starting or trailing backslashes, and no more than |
| // - one backslash in a row. |
| // - Note from MSDN: "Key names cannot include the backslash character (\), |
| // but any other printable character can be used. Value names and data can |
| // include the backslash character." |
| void SanitizeSubkeyPath(std::wstring* input) { |
| assert(input != nullptr); |
| |
| // Remove trailing backslashes. |
| size_t last_valid_pos = input->find_last_not_of(L'\\'); |
| if (last_valid_pos == std::wstring::npos) { |
| // The string is all backslashes, or it's empty. Clear and abort. |
| input->clear(); |
| return; |
| } |
| // Chop off the trailing backslashes. |
| input->resize(last_valid_pos + 1); |
| |
| // Remove leading backslashes. |
| input->erase(0, input->find_first_not_of(L'\\')); |
| |
| // Replace any occurances of more than 1 backslash in a row with just 1. |
| size_t index = input->find_first_of(L"\\"); |
| while (index != std::wstring::npos) { |
| // Remove a second consecutive backslash, and leave index where it is, |
| // or move to the next backslash in the string. |
| if ((*input)[index + 1] == L'\\') |
| input->erase(index + 1, 1); |
| else |
| index = input->find_first_of(L"\\", index + 1); |
| } |
| } |
| |
| // Turns a root and subkey path into the registry base hive and the rest of the |
| // subkey tokens. |
| // - |converted_root| should come directly out of ConvertRootKey function. |
| // - |subkey_path| should be passed to SanitizeSubkeyPath() first. |
| // - E.g. base hive: "\Registry\Machine\", "\Registry\User\<SID>\". |
| bool ParseFullRegPath(const std::wstring& converted_root, |
| const std::wstring& subkey_path, |
| std::wstring* out_base, |
| std::vector<std::wstring>* subkeys) { |
| out_base->clear(); |
| subkeys->clear(); |
| std::wstring temp_path; |
| |
| // Special case if there is testing redirection set up. |
| if (*g_HKCU_override || *g_HKLM_override) { |
| // Why process |converted_root|? To handle reg redirection used by tests. |
| // E.g.: |
| // |converted_root| = "\REGISTRY\USER\S-1-5-21-39260824-743453154-142223018- |
| // 716772\Software\Chromium\TempTestKeys\13110669370890870$94c6ed9d-bc34- |
| // 44f3-a0b3-9eee2d3f2f82\". |
| // |subkey_path| = "SOFTWARE\Google\Chrome\BrowserSec". |
| // |
| // Note: bypassing the starting backslash in the |converted_root|. |
| temp_path.append(converted_root, 1, converted_root.size() - 1); |
| } |
| temp_path.append(subkey_path); |
| |
| // Tokenize the full path. |
| size_t find_start = 0; |
| size_t delimiter = temp_path.find_first_of(L'\\'); |
| while (delimiter != std::wstring::npos) { |
| subkeys->emplace_back(temp_path, find_start, delimiter - find_start); |
| // Move past the backslash. |
| find_start = delimiter + 1; |
| delimiter = temp_path.find_first_of(L'\\', find_start); |
| } |
| // Get the last token if there is one. |
| if (!temp_path.empty()) |
| subkeys->emplace_back(temp_path, find_start); |
| |
| // Special case if there is testing redirection set up. |
| if (*g_HKCU_override || *g_HKLM_override) { |
| // The base hive for HKCU needs to include the user SID. |
| uint32_t num_base_tokens = 2; |
| if (0 == temp_path.compare(0, 14, L"REGISTRY\\USER\\")) |
| num_base_tokens = 3; |
| |
| if (subkeys->size() < num_base_tokens) |
| return false; |
| |
| // Pull out the base hive tokens. |
| out_base->push_back(L'\\'); |
| for (size_t i = 0; i < num_base_tokens; ++i) { |
| out_base->append((*subkeys)[i]); |
| out_base->push_back(L'\\'); |
| } |
| subkeys->erase(subkeys->begin(), subkeys->begin() + num_base_tokens); |
| } else { |
| out_base->assign(converted_root); |
| } |
| |
| return true; |
| } |
| |
| // String safety. |
| // - NOTE: only working with wchar_t here. |
| // - Also ensures the content of |value_bytes| is at least a terminator. |
| // - Pass "true" for |multi| for MULTISZ. |
| void EnsureTerminatedSZ(std::vector<BYTE>* value_bytes, bool multi) { |
| DWORD terminator_size = sizeof(wchar_t); |
| |
| if (multi) |
| terminator_size = 2 * sizeof(wchar_t); |
| |
| // Ensure content is at least the size of a terminator. |
| if (value_bytes->size() < terminator_size) { |
| value_bytes->insert(value_bytes->end(), |
| terminator_size - value_bytes->size(), 0); |
| } |
| |
| // Sanity check content size based on character size. |
| DWORD modulo = value_bytes->size() % sizeof(wchar_t); |
| value_bytes->insert(value_bytes->end(), modulo, 0); |
| |
| // Now finally check for trailing terminator. |
| bool terminated = true; |
| size_t last_element = value_bytes->size() - 1; |
| for (size_t i = 0; i < terminator_size; i++) { |
| if ((*value_bytes)[last_element - i] != 0) { |
| terminated = false; |
| break; |
| } |
| } |
| |
| if (terminated) |
| return; |
| |
| // Append a full terminator to be safe. |
| value_bytes->insert(value_bytes->end(), terminator_size, 0); |
| |
| return; |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Misc wrapper functions - LOCAL |
| //------------------------------------------------------------------------------ |
| |
| NTSTATUS CreateKeyWrapper(const std::wstring& key_path, |
| ACCESS_MASK access, |
| HANDLE* out_handle, |
| ULONG* create_or_open OPTIONAL) { |
| UNICODE_STRING key_path_uni = {}; |
| g_rtl_init_unicode_string(&key_path_uni, key_path.c_str()); |
| |
| OBJECT_ATTRIBUTES obj = {}; |
| InitializeObjectAttributes(&obj, &key_path_uni, OBJ_CASE_INSENSITIVE, NULL, |
| nullptr); |
| |
| return g_nt_create_key(out_handle, access, &obj, 0, nullptr, |
| REG_OPTION_NON_VOLATILE, create_or_open); |
| } |
| |
| } // namespace |
| |
| namespace nt { |
| |
| //------------------------------------------------------------------------------ |
| // Create, open, delete, close functions |
| //------------------------------------------------------------------------------ |
| |
| bool CreateRegKey(ROOT_KEY root, |
| const wchar_t* key_path, |
| ACCESS_MASK access, |
| HANDLE* out_handle OPTIONAL) { |
| // |key_path| can be null or empty, but it can't be longer than |
| // |g_kRegMaxPathLen| at this point. |
| if (key_path != nullptr && |
| ::wcsnlen(key_path, g_kRegMaxPathLen + 1) == g_kRegMaxPathLen + 1) |
| return false; |
| |
| if (!g_initialized && !InitNativeRegApi()) |
| return false; |
| |
| if (root == nt::AUTO) |
| root = g_system_install ? nt::HKLM : nt::HKCU; |
| |
| std::wstring redirected_key_path; |
| if (key_path) { |
| redirected_key_path = key_path; |
| SanitizeSubkeyPath(&redirected_key_path); |
| ProcessRedirection(root, access, &redirected_key_path); |
| } |
| |
| std::wstring current_path; |
| std::vector<std::wstring> subkeys; |
| if (!ParseFullRegPath(ConvertRootKey(root), redirected_key_path, |
| ¤t_path, &subkeys)) |
| return false; |
| |
| // Open the base hive first. It should always exist already. |
| HANDLE last_handle = INVALID_HANDLE_VALUE; |
| NTSTATUS status = |
| CreateKeyWrapper(current_path, access, &last_handle, nullptr); |
| if (!NT_SUCCESS(status)) |
| return false; |
| |
| size_t subkeys_size = subkeys.size(); |
| if (subkeys_size != 0) |
| g_nt_close(last_handle); |
| |
| // Recursively open/create each subkey. |
| std::vector<HANDLE> rollback; |
| bool success = true; |
| for (size_t i = 0; i < subkeys_size; i++) { |
| current_path.append(subkeys[i]); |
| current_path.push_back(L'\\'); |
| |
| // Process the latest subkey. |
| ULONG created = 0; |
| HANDLE key_handle = INVALID_HANDLE_VALUE; |
| status = |
| CreateKeyWrapper(current_path.c_str(), access, &key_handle, &created); |
| if (!NT_SUCCESS(status)) { |
| success = false; |
| break; |
| } |
| |
| if (i == subkeys_size - 1) { |
| last_handle = key_handle; |
| } else { |
| // Save any subkey handle created, in case of rollback. |
| if (created == REG_CREATED_NEW_KEY) |
| rollback.push_back(key_handle); |
| else |
| g_nt_close(key_handle); |
| } |
| } |
| |
| if (!success) { |
| // Delete any subkeys created. |
| for (HANDLE handle : rollback) { |
| g_nt_delete_key(handle); |
| } |
| } |
| for (HANDLE handle : rollback) { |
| // Close the rollback handles, on success or failure. |
| g_nt_close(handle); |
| } |
| if (!success) |
| return false; |
| |
| // See if caller wants the handle left open. |
| if (out_handle) |
| *out_handle = last_handle; |
| else |
| g_nt_close(last_handle); |
| |
| return true; |
| } |
| |
| bool OpenRegKey(ROOT_KEY root, |
| const wchar_t* key_path, |
| ACCESS_MASK access, |
| HANDLE* out_handle, |
| NTSTATUS* error_code OPTIONAL) { |
| // |key_path| can be null or empty, but it can't be longer than |
| // |g_kRegMaxPathLen| at this point. |
| if (key_path != nullptr && |
| ::wcsnlen(key_path, g_kRegMaxPathLen + 1) == g_kRegMaxPathLen + 1) |
| return false; |
| |
| if (!g_initialized && !InitNativeRegApi()) |
| return false; |
| |
| NTSTATUS status = STATUS_UNSUCCESSFUL; |
| UNICODE_STRING key_path_uni = {}; |
| OBJECT_ATTRIBUTES obj = {}; |
| *out_handle = INVALID_HANDLE_VALUE; |
| |
| if (root == nt::AUTO) |
| root = g_system_install ? nt::HKLM : nt::HKCU; |
| |
| std::wstring full_path; |
| if (key_path) { |
| full_path = key_path; |
| SanitizeSubkeyPath(&full_path); |
| ProcessRedirection(root, access, &full_path); |
| } |
| full_path.insert(0, ConvertRootKey(root)); |
| |
| g_rtl_init_unicode_string(&key_path_uni, full_path.c_str()); |
| InitializeObjectAttributes(&obj, &key_path_uni, OBJ_CASE_INSENSITIVE, NULL, |
| NULL); |
| |
| status = g_nt_open_key_ex(out_handle, access, &obj, 0); |
| // See if caller wants the NTSTATUS. |
| if (error_code) |
| *error_code = status; |
| |
| if (NT_SUCCESS(status)) |
| return true; |
| |
| return false; |
| } |
| |
| bool DeleteRegKey(HANDLE key) { |
| if (!g_initialized && !InitNativeRegApi()) |
| return false; |
| |
| NTSTATUS status = g_nt_delete_key(key); |
| |
| return NT_SUCCESS(status); |
| } |
| |
| // wrapper function |
| bool DeleteRegKey(ROOT_KEY root, |
| WOW64_OVERRIDE wow64_override, |
| const wchar_t* key_path) { |
| HANDLE key = INVALID_HANDLE_VALUE; |
| |
| if (!OpenRegKey(root, key_path, DELETE | wow64_override, &key, nullptr)) |
| return false; |
| |
| if (!DeleteRegKey(key)) { |
| CloseRegKey(key); |
| return false; |
| } |
| |
| CloseRegKey(key); |
| return true; |
| } |
| |
| void CloseRegKey(HANDLE key) { |
| if (!g_initialized) |
| InitNativeRegApi(); |
| g_nt_close(key); |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Getter functions |
| //------------------------------------------------------------------------------ |
| |
| bool QueryRegKeyValue(HANDLE key, |
| const wchar_t* value_name, |
| ULONG* out_type, |
| std::vector<BYTE>* out_buffer) { |
| if (!g_initialized && !InitNativeRegApi()) |
| return false; |
| |
| UNICODE_STRING value_uni = {}; |
| g_rtl_init_unicode_string(&value_uni, value_name); |
| |
| // Use a loop here, to be a little more tolerant of concurrent registry |
| // changes. |
| NTSTATUS ntstatus = STATUS_UNSUCCESSFUL; |
| int tries = 0; |
| KEY_VALUE_FULL_INFORMATION* value_info = nullptr; |
| DWORD size_needed = sizeof(*value_info); |
| std::vector<BYTE> buffer(size_needed); |
| do { |
| buffer.resize(size_needed); |
| value_info = reinterpret_cast<KEY_VALUE_FULL_INFORMATION*>(buffer.data()); |
| |
| ntstatus = g_nt_query_value_key(key, &value_uni, KeyValueFullInformation, |
| value_info, size_needed, &size_needed); |
| } while ((ntstatus == STATUS_BUFFER_OVERFLOW || |
| ntstatus == STATUS_BUFFER_TOO_SMALL) && |
| ++tries < kMaxTries); |
| |
| if (!NT_SUCCESS(ntstatus)) |
| return false; |
| |
| *out_type = value_info->Type; |
| DWORD data_size = value_info->DataLength; |
| |
| if (data_size) { |
| // Move the data into |out_buffer| vector. |
| BYTE* data = reinterpret_cast<BYTE*>(value_info) + value_info->DataOffset; |
| out_buffer->assign(data, data + data_size); |
| } else { |
| out_buffer->clear(); |
| } |
| |
| return true; |
| } |
| |
| // wrapper function |
| bool QueryRegValueDWORD(HANDLE key, |
| const wchar_t* value_name, |
| DWORD* out_dword) { |
| ULONG type = REG_NONE; |
| std::vector<BYTE> value_bytes; |
| |
| if (!QueryRegKeyValue(key, value_name, &type, &value_bytes) || |
| type != REG_DWORD) { |
| return false; |
| } |
| |
| if (value_bytes.size() < sizeof(*out_dword)) |
| return false; |
| |
| *out_dword = *(reinterpret_cast<DWORD*>(value_bytes.data())); |
| |
| return true; |
| } |
| |
| // wrapper function |
| bool QueryRegValueDWORD(ROOT_KEY root, |
| WOW64_OVERRIDE wow64_override, |
| const wchar_t* key_path, |
| const wchar_t* value_name, |
| DWORD* out_dword) { |
| HANDLE key = INVALID_HANDLE_VALUE; |
| |
| if (!OpenRegKey(root, key_path, KEY_QUERY_VALUE | wow64_override, &key, NULL)) |
| return false; |
| |
| if (!QueryRegValueDWORD(key, value_name, out_dword)) { |
| CloseRegKey(key); |
| return false; |
| } |
| |
| CloseRegKey(key); |
| return true; |
| } |
| |
| // wrapper function |
| bool QueryRegValueSZ(HANDLE key, |
| const wchar_t* value_name, |
| std::wstring* out_sz) { |
| std::vector<BYTE> value_bytes; |
| ULONG type = REG_NONE; |
| |
| if (!QueryRegKeyValue(key, value_name, &type, &value_bytes) || |
| (type != REG_SZ && type != REG_EXPAND_SZ)) { |
| return false; |
| } |
| |
| EnsureTerminatedSZ(&value_bytes, false); |
| |
| *out_sz = reinterpret_cast<wchar_t*>(value_bytes.data()); |
| |
| return true; |
| } |
| |
| // wrapper function |
| bool QueryRegValueSZ(ROOT_KEY root, |
| WOW64_OVERRIDE wow64_override, |
| const wchar_t* key_path, |
| const wchar_t* value_name, |
| std::wstring* out_sz) { |
| HANDLE key = INVALID_HANDLE_VALUE; |
| |
| if (!OpenRegKey(root, key_path, KEY_QUERY_VALUE | wow64_override, &key, NULL)) |
| return false; |
| |
| if (!QueryRegValueSZ(key, value_name, out_sz)) { |
| CloseRegKey(key); |
| return false; |
| } |
| |
| CloseRegKey(key); |
| return true; |
| } |
| |
| // wrapper function |
| bool QueryRegValueMULTISZ(HANDLE key, |
| const wchar_t* value_name, |
| std::vector<std::wstring>* out_multi_sz) { |
| std::vector<BYTE> value_bytes; |
| ULONG type = REG_NONE; |
| |
| if (!QueryRegKeyValue(key, value_name, &type, &value_bytes) || |
| type != REG_MULTI_SZ) { |
| return false; |
| } |
| |
| EnsureTerminatedSZ(&value_bytes, true); |
| |
| // Make sure the out vector is empty to start. |
| out_multi_sz->clear(); |
| |
| wchar_t* pointer = reinterpret_cast<wchar_t*>(value_bytes.data()); |
| std::wstring temp = pointer; |
| // Loop. Each string is separated by '\0'. Another '\0' at very end (so 2 in |
| // a row). |
| while (!temp.empty()) { |
| pointer += temp.length() + 1; |
| out_multi_sz->push_back(std::move(temp)); |
| temp = pointer; |
| } |
| |
| return true; |
| } |
| |
| // wrapper function |
| bool QueryRegValueMULTISZ(ROOT_KEY root, |
| WOW64_OVERRIDE wow64_override, |
| const wchar_t* key_path, |
| const wchar_t* value_name, |
| std::vector<std::wstring>* out_multi_sz) { |
| HANDLE key = INVALID_HANDLE_VALUE; |
| |
| if (!OpenRegKey(root, key_path, KEY_QUERY_VALUE | wow64_override, &key, NULL)) |
| return false; |
| |
| if (!QueryRegValueMULTISZ(key, value_name, out_multi_sz)) { |
| CloseRegKey(key); |
| return false; |
| } |
| |
| CloseRegKey(key); |
| return true; |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Setter functions |
| //------------------------------------------------------------------------------ |
| |
| bool SetRegKeyValue(HANDLE key, |
| const wchar_t* value_name, |
| ULONG type, |
| const BYTE* data, |
| DWORD data_size) { |
| if (!g_initialized && !InitNativeRegApi()) |
| return false; |
| |
| NTSTATUS ntstatus = STATUS_UNSUCCESSFUL; |
| UNICODE_STRING value_uni = {}; |
| g_rtl_init_unicode_string(&value_uni, value_name); |
| |
| BYTE* non_const_data = const_cast<BYTE*>(data); |
| ntstatus = |
| g_nt_set_value_key(key, &value_uni, 0, type, non_const_data, data_size); |
| |
| if (NT_SUCCESS(ntstatus)) |
| return true; |
| |
| return false; |
| } |
| |
| // wrapper function |
| bool SetRegValueDWORD(HANDLE key, const wchar_t* value_name, DWORD value) { |
| return SetRegKeyValue(key, value_name, REG_DWORD, |
| reinterpret_cast<BYTE*>(&value), sizeof(value)); |
| } |
| |
| // wrapper function |
| bool SetRegValueDWORD(ROOT_KEY root, |
| WOW64_OVERRIDE wow64_override, |
| const wchar_t* key_path, |
| const wchar_t* value_name, |
| DWORD value) { |
| HANDLE key = INVALID_HANDLE_VALUE; |
| |
| if (!OpenRegKey(root, key_path, KEY_SET_VALUE | wow64_override, &key, NULL)) |
| return false; |
| |
| if (!SetRegValueDWORD(key, value_name, value)) { |
| CloseRegKey(key); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // wrapper function |
| bool SetRegValueSZ(HANDLE key, |
| const wchar_t* value_name, |
| const std::wstring& value) { |
| // Make sure the number of bytes in |value|, including EoS, fits in a DWORD. |
| if (std::numeric_limits<DWORD>::max() < |
| ((value.length() + 1) * sizeof(wchar_t))) |
| return false; |
| |
| DWORD size = (static_cast<DWORD>((value.length() + 1) * sizeof(wchar_t))); |
| return SetRegKeyValue(key, value_name, REG_SZ, |
| reinterpret_cast<const BYTE*>(value.c_str()), size); |
| } |
| |
| // wrapper function |
| bool SetRegValueSZ(ROOT_KEY root, |
| WOW64_OVERRIDE wow64_override, |
| const wchar_t* key_path, |
| const wchar_t* value_name, |
| const std::wstring& value) { |
| HANDLE key = INVALID_HANDLE_VALUE; |
| |
| if (!OpenRegKey(root, key_path, KEY_SET_VALUE | wow64_override, &key, NULL)) |
| return false; |
| |
| if (!SetRegValueSZ(key, value_name, value)) { |
| CloseRegKey(key); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // wrapper function |
| bool SetRegValueMULTISZ(HANDLE key, |
| const wchar_t* value_name, |
| const std::vector<std::wstring>& values) { |
| std::vector<wchar_t> builder; |
| |
| for (auto& string : values) { |
| // Just in case someone is passing in an illegal empty string |
| // (not allowed in REG_MULTI_SZ), ignore it. |
| if (!string.empty()) { |
| for (const wchar_t& w : string) { |
| builder.push_back(w); |
| } |
| builder.push_back(L'\0'); |
| } |
| } |
| // Add second null terminator to end REG_MULTI_SZ. |
| builder.push_back(L'\0'); |
| // Handle rare case where the vector passed in was empty, |
| // or only had an empty string. |
| if (builder.size() == 1) |
| builder.push_back(L'\0'); |
| |
| if (std::numeric_limits<DWORD>::max() < builder.size()) |
| return false; |
| |
| return SetRegKeyValue( |
| key, value_name, REG_MULTI_SZ, reinterpret_cast<BYTE*>(builder.data()), |
| (static_cast<DWORD>(builder.size()) + 1) * sizeof(wchar_t)); |
| } |
| |
| // wrapper function |
| bool SetRegValueMULTISZ(ROOT_KEY root, |
| WOW64_OVERRIDE wow64_override, |
| const wchar_t* key_path, |
| const wchar_t* value_name, |
| const std::vector<std::wstring>& values) { |
| HANDLE key = INVALID_HANDLE_VALUE; |
| |
| if (!OpenRegKey(root, key_path, KEY_SET_VALUE | wow64_override, &key, NULL)) |
| return false; |
| |
| if (!SetRegValueMULTISZ(key, value_name, values)) { |
| CloseRegKey(key); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Enumeration Support |
| //------------------------------------------------------------------------------ |
| |
| bool QueryRegEnumerationInfo(HANDLE key, ULONG* out_subkey_count) { |
| if (!g_initialized && !InitNativeRegApi()) |
| return false; |
| |
| // Use a loop here, to be a little more tolerant of concurrent registry |
| // changes. |
| NTSTATUS ntstatus = STATUS_UNSUCCESSFUL; |
| int tries = 0; |
| // Start with sizeof the structure. It's very common for the variable sized |
| // "Class" element to be of length 0. |
| KEY_FULL_INFORMATION* key_info = nullptr; |
| DWORD size_needed = sizeof(*key_info); |
| std::vector<BYTE> buffer(size_needed); |
| do { |
| buffer.resize(size_needed); |
| key_info = reinterpret_cast<KEY_FULL_INFORMATION*>(buffer.data()); |
| |
| ntstatus = g_nt_query_key(key, KeyFullInformation, key_info, size_needed, |
| &size_needed); |
| } while ((ntstatus == STATUS_BUFFER_OVERFLOW || |
| ntstatus == STATUS_BUFFER_TOO_SMALL) && |
| ++tries < kMaxTries); |
| |
| if (!NT_SUCCESS(ntstatus)) |
| return false; |
| |
| // Move desired information to out variables. |
| *out_subkey_count = key_info->SubKeys; |
| |
| return true; |
| } |
| |
| bool QueryRegSubkey(HANDLE key, |
| ULONG subkey_index, |
| std::wstring* out_subkey_name) { |
| if (!g_initialized && !InitNativeRegApi()) |
| return false; |
| |
| // Use a loop here, to be a little more tolerant of concurrent registry |
| // changes. |
| NTSTATUS ntstatus = STATUS_UNSUCCESSFUL; |
| int tries = 0; |
| // Start with sizeof the structure, plus 12 characters. It's very common for |
| // key names to be < 12 characters (without being inefficient as an initial |
| // allocation). |
| KEY_BASIC_INFORMATION* subkey_info = nullptr; |
| DWORD size_needed = sizeof(*subkey_info) + (12 * sizeof(wchar_t)); |
| std::vector<BYTE> buffer(size_needed); |
| do { |
| buffer.resize(size_needed); |
| subkey_info = reinterpret_cast<KEY_BASIC_INFORMATION*>(buffer.data()); |
| |
| ntstatus = g_nt_enumerate_key(key, subkey_index, KeyBasicInformation, |
| subkey_info, size_needed, &size_needed); |
| } while ((ntstatus == STATUS_BUFFER_OVERFLOW || |
| ntstatus == STATUS_BUFFER_TOO_SMALL) && |
| ++tries < kMaxTries); |
| |
| if (!NT_SUCCESS(ntstatus)) |
| return false; |
| |
| // Move desired information to out variables. |
| // NOTE: NameLength is size of Name array in bytes. Name array is also |
| // NOT null terminated! |
| BYTE* name = reinterpret_cast<BYTE*>(subkey_info->Name); |
| std::vector<BYTE> content(name, name + subkey_info->NameLength); |
| EnsureTerminatedSZ(&content, false); |
| out_subkey_name->assign(reinterpret_cast<wchar_t*>(content.data())); |
| |
| return true; |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Utils |
| //------------------------------------------------------------------------------ |
| |
| const wchar_t* GetCurrentUserSidString() { |
| if (!g_initialized && !InitNativeRegApi()) |
| return nullptr; |
| |
| return g_current_user_sid_string; |
| } |
| |
| bool IsCurrentProcWow64() { |
| if (!g_initialized && !InitNativeRegApi()) |
| return false; |
| |
| return g_wow64_proc; |
| } |
| |
| bool SetTestingOverride(ROOT_KEY root, const std::wstring& new_path) { |
| if (!g_initialized && !InitNativeRegApi()) |
| return false; |
| |
| std::wstring sani_new_path = new_path; |
| SanitizeSubkeyPath(&sani_new_path); |
| if (sani_new_path.length() > g_kRegMaxPathLen) |
| return false; |
| |
| if (root == HKCU || (root == AUTO && !g_system_install)) |
| ::wcsncpy(g_HKCU_override, sani_new_path.c_str(), nt::g_kRegMaxPathLen); |
| else |
| ::wcsncpy(g_HKLM_override, sani_new_path.c_str(), nt::g_kRegMaxPathLen); |
| |
| return true; |
| } |
| |
| std::wstring GetTestingOverride(ROOT_KEY root) { |
| if (!g_initialized && !InitNativeRegApi()) |
| return std::wstring(); |
| |
| if (root == HKCU || (root == AUTO && !g_system_install)) |
| return g_HKCU_override; |
| |
| return g_HKLM_override; |
| } |
| |
| } // namespace nt |