diff options
author | caitkp@chromium.org <caitkp@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-02-24 02:12:09 +0000 |
---|---|---|
committer | caitkp@chromium.org <caitkp@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-02-24 02:12:09 +0000 |
commit | 5a542073d52dd75e4489011e6696451b9281c95a (patch) | |
tree | 2c4a00295b08a34c9650fd3b0ed8a0c86593753b /chrome_elf | |
parent | 32f3c23a75143288e5c5eaa17fd53d2c224fb17c (diff) | |
download | chromium_src-5a542073d52dd75e4489011e6696451b9281c95a.zip chromium_src-5a542073d52dd75e4489011e6696451b9281c95a.tar.gz chromium_src-5a542073d52dd75e4489011e6696451b9281c95a.tar.bz2 |
Breakpad coverage for chrome_elf start up
Review URL: https://codereview.chromium.org/154653002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@252870 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome_elf')
-rw-r--r-- | chrome_elf/DEPS | 1 | ||||
-rw-r--r-- | chrome_elf/blacklist.gypi | 4 | ||||
-rw-r--r-- | chrome_elf/blacklist/blacklist_interceptions.cc | 65 | ||||
-rw-r--r-- | chrome_elf/breakpad.cc | 180 | ||||
-rw-r--r-- | chrome_elf/breakpad.h | 34 | ||||
-rw-r--r-- | chrome_elf/chrome_elf.gyp | 48 | ||||
-rw-r--r-- | chrome_elf/chrome_elf_main.cc | 10 | ||||
-rw-r--r-- | chrome_elf/chrome_elf_util.cc | 193 | ||||
-rw-r--r-- | chrome_elf/chrome_elf_util.h | 27 | ||||
-rw-r--r-- | chrome_elf/chrome_elf_util_unittest.cc | 186 | ||||
-rw-r--r-- | chrome_elf/create_file/chrome_create_file.cc | 6 | ||||
-rw-r--r-- | chrome_elf/create_file/chrome_create_file.h | 4 | ||||
-rw-r--r-- | chrome_elf/create_file/chrome_create_file_unittest.cc | 6 |
13 files changed, 724 insertions, 40 deletions
diff --git a/chrome_elf/DEPS b/chrome_elf/DEPS index ec69c8f..495665e 100644 --- a/chrome_elf/DEPS +++ b/chrome_elf/DEPS @@ -1,3 +1,4 @@ include_rules = [ "+sandbox", + "+breakpad/src/client", ] diff --git a/chrome_elf/blacklist.gypi b/chrome_elf/blacklist.gypi index 2db4941..24fcec6 100644 --- a/chrome_elf/blacklist.gypi +++ b/chrome_elf/blacklist.gypi @@ -7,6 +7,7 @@ 'target_name': 'blacklist', 'type': 'static_library', 'include_dirs': [ + '..', '<(SHARED_INTERMEDIATE_DIR)', ], 'sources': [ @@ -21,7 +22,8 @@ # chrome_elf cannot do. '../base/base.gyp:base_static', '../chrome/chrome.gyp:chrome_version_header', - '../sandbox/sandbox.gyp:sandbox', + '../chrome_elf/chrome_elf.gyp:chrome_elf_breakpad', + '../sandbox/sandbox.gyp:sandbox', ], }, { diff --git a/chrome_elf/blacklist/blacklist_interceptions.cc b/chrome_elf/blacklist/blacklist_interceptions.cc index 9a3bdab..b3f3d82 100644 --- a/chrome_elf/blacklist/blacklist_interceptions.cc +++ b/chrome_elf/blacklist/blacklist_interceptions.cc @@ -17,6 +17,7 @@ #include "base/strings/string16.h" #include "base/win/pe_image.h" #include "chrome_elf/blacklist/blacklist.h" +#include "chrome_elf/breakpad.h" #include "sandbox/win/src/internal_types.h" #include "sandbox/win/src/nt_internals.h" #include "sandbox/win/src/sandbox_nt_util.h" @@ -167,25 +168,7 @@ bool IsSameAsCurrentProcess(HANDLE process) { (::GetProcessId(process) == ::GetCurrentProcessId()); } -} // namespace - -namespace blacklist { - -bool InitializeInterceptImports() { - g_nt_query_section_func = reinterpret_cast<NtQuerySectionFunction>( - GetNtDllExportByName("NtQuerySection")); - g_nt_query_virtual_memory_func = - reinterpret_cast<NtQueryVirtualMemoryFunction>( - GetNtDllExportByName("NtQueryVirtualMemory")); - g_nt_unmap_view_of_section_func = - reinterpret_cast<NtUnmapViewOfSectionFunction>( - GetNtDllExportByName("NtUnmapViewOfSection")); - - return g_nt_query_section_func && g_nt_query_virtual_memory_func && - g_nt_unmap_view_of_section_func; -} - -SANDBOX_INTERCEPT NTSTATUS WINAPI BlNtMapViewOfSection( +NTSTATUS BlNtMapViewOfSectionImpl( NtMapViewOfSectionFunction orig_MapViewOfSection, HANDLE section, HANDLE process, @@ -230,6 +213,50 @@ SANDBOX_INTERCEPT NTSTATUS WINAPI BlNtMapViewOfSection( return ret; } +} // namespace + +namespace blacklist { + +bool InitializeInterceptImports() { + g_nt_query_section_func = + reinterpret_cast<NtQuerySectionFunction>( + GetNtDllExportByName("NtQuerySection")); + g_nt_query_virtual_memory_func = + reinterpret_cast<NtQueryVirtualMemoryFunction>( + GetNtDllExportByName("NtQueryVirtualMemory")); + g_nt_unmap_view_of_section_func = + reinterpret_cast<NtUnmapViewOfSectionFunction>( + GetNtDllExportByName("NtUnmapViewOfSection")); + + return (g_nt_query_section_func && g_nt_query_virtual_memory_func && + g_nt_unmap_view_of_section_func); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI BlNtMapViewOfSection( + NtMapViewOfSectionFunction orig_MapViewOfSection, + HANDLE section, + HANDLE process, + PVOID *base, + ULONG_PTR zero_bits, + SIZE_T commit_size, + PLARGE_INTEGER offset, + PSIZE_T view_size, + SECTION_INHERIT inherit, + ULONG allocation_type, + ULONG protect) { + NTSTATUS ret = STATUS_UNSUCCESSFUL; + + __try { + ret = BlNtMapViewOfSectionImpl(orig_MapViewOfSection, section, process, + base, zero_bits, commit_size, offset, + view_size, inherit, allocation_type, + protect); + } __except(GenerateCrashDump(GetExceptionInformation())) { + } + + return ret; +} + #if defined(_WIN64) NTSTATUS WINAPI BlNtMapViewOfSection64( HANDLE section, HANDLE process, PVOID *base, ULONG_PTR zero_bits, diff --git a/chrome_elf/breakpad.cc b/chrome_elf/breakpad.cc new file mode 100644 index 0000000..d2189b6 --- /dev/null +++ b/chrome_elf/breakpad.cc @@ -0,0 +1,180 @@ +// Copyright 2014 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. + +// This module contains the necessary code to register the Breakpad exception +// handler. This implementation is based on Chrome's crash reporting code. + +#include "chrome_elf/breakpad.h" + +#include <sddl.h> + +#include "base/macros.h" +#include "breakpad/src/client/windows/handler/exception_handler.h" +#include "chrome_elf/chrome_elf_util.h" +#include "version.h" // NOLINT + +google_breakpad::ExceptionHandler* g_elf_breakpad = NULL; + +namespace { + +const wchar_t kBreakpadProductName[] = L"ChromeElf"; +const wchar_t kBreakpadVersionEntry[] = L"ver"; +const wchar_t kBreakpadProdEntry[] = L"prod"; +const wchar_t kBreakpadPlatformEntry[] = L"plat"; +const wchar_t kBreakpadPlatformWin32[] = L"Win32"; + +// The protocol for connecting to the out-of-process Breakpad crash +// reporter is different for x86-32 and x86-64: the message sizes +// are different because the message struct contains a pointer. As +// a result, there are two different named pipes to connect to. The +// 64-bit one is distinguished with an "-x64" suffix. +const wchar_t kChromePipeName[] = L"\\\\.\\pipe\\ChromeCrashServices\\"; +const wchar_t kGoogleUpdatePipeName[] = L"\\\\.\\pipe\\GoogleCrashServices\\"; +const wchar_t kSystemPrincipalSid[] = L"S-1-5-18"; + +const wchar_t kNoErrorDialogs[] = L"noerrdialogs"; +const wchar_t kChromeHeadless[] = L"CHROME_HEADLESS"; + +google_breakpad::CustomClientInfo* GetCustomInfo() { + static google_breakpad::CustomInfoEntry ver_entry( + kBreakpadVersionEntry, TEXT(CHROME_VERSION_STRING)); + static google_breakpad::CustomInfoEntry prod_entry( + kBreakpadProdEntry, kBreakpadProductName); + static google_breakpad::CustomInfoEntry plat_entry( + kBreakpadPlatformEntry, kBreakpadPlatformWin32); + static google_breakpad::CustomInfoEntry entries[] = { + ver_entry, prod_entry, plat_entry }; + static google_breakpad::CustomClientInfo custom_info = { + entries, arraysize(entries) }; + return &custom_info; +} + +base::string16 GetUserSidString() { + // Get the current token. + HANDLE token = NULL; + base::string16 user_sid; + if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &token)) + return user_sid; + + DWORD size = sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE; + BYTE user_bytes[sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE] = {}; + TOKEN_USER* user = reinterpret_cast<TOKEN_USER*>(user_bytes); + + wchar_t* sid_string = NULL; + if (::GetTokenInformation(token, TokenUser, user, size, &size) && + user->User.Sid && + ::ConvertSidToStringSid(user->User.Sid, &sid_string)) { + user_sid = sid_string; + ::LocalFree(sid_string); + } + + CloseHandle(token); + return user_sid; +} + +bool IsHeadless() { + DWORD ret = ::GetEnvironmentVariable(L"CHROME_HEADLESS", NULL, 0); + if (ret != 0) + return true; + + wchar_t* command_line = ::GetCommandLine(); + + // Note: Since this is a pure substring search rather than a check for a + // switch, there is a small chance that this code will match things that the + // Chrome code (which executes a similar check) does not. However, as long as + // no other switches contain the string "noerrdialogs", it should not be an + // issue. + return (command_line && wcsstr(command_line, kNoErrorDialogs)); +} + +} // namespace + +int GenerateCrashDump(EXCEPTION_POINTERS* exinfo) { + DWORD code = exinfo->ExceptionRecord->ExceptionCode; + if (code == EXCEPTION_BREAKPOINT || code == EXCEPTION_SINGLE_STEP) + return EXCEPTION_CONTINUE_SEARCH; + + if (g_elf_breakpad != NULL) + g_elf_breakpad->WriteMinidumpForException(exinfo); + return EXCEPTION_CONTINUE_SEARCH; +} + +void InitializeCrashReporting() { + wchar_t exe_path[MAX_PATH] = {}; + if(!::GetModuleFileName(NULL, exe_path, arraysize(exe_path))) + return; + + // Disable the message box for assertions. + _CrtSetReportMode(_CRT_ASSERT, 0); + + // Get the alternate dump directory. We use the temp path. + // N.B. We don't use base::GetTempDir() here to avoid running more code then + // necessary before crashes can be properly reported. + wchar_t temp_directory[MAX_PATH + 1] = {}; + DWORD length = GetTempPath(MAX_PATH, temp_directory); + if (length == 0) + return; + + // Minidump with stacks, PEB, TEBs and unloaded module list. + MINIDUMP_TYPE dump_type = static_cast<MINIDUMP_TYPE>( + MiniDumpWithProcessThreadData | // Get PEB and TEB. + MiniDumpWithUnloadedModules | // Get unloaded modules when available. + MiniDumpWithIndirectlyReferencedMemory); // Get memory referenced by + // stack. + + // Convert #define to a variable so that we can use if() rather than + // #if below and so at least compile-test the Chrome code in + // Chromium builds. +#if defined(GOOGLE_CHROME_BUILD) + bool is_chrome_build = true; +#else + bool is_chrome_build = false; +#endif + + base::string16 pipe_name; + + bool enabled_by_policy = false; + bool use_policy = ReportingIsEnforcedByPolicy(&enabled_by_policy); + + if (!use_policy && IsHeadless()) { + pipe_name = kChromePipeName; + } else if (use_policy ? enabled_by_policy : + (is_chrome_build && AreUsageStatsEnabled(exe_path))) { + // Build the pipe name. It can be one of: + // 32-bit system: \\.\pipe\GoogleCrashServices\S-1-5-18 + // 32-bit user: \\.\pipe\GoogleCrashServices\<user SID> + // 64-bit system: \\.\pipe\GoogleCrashServices\S-1-5-18-x64 + // 64-bit user: \\.\pipe\GoogleCrashServices\<user SID>-x64 + base::string16 user_sid = IsSystemInstall(exe_path) ? kSystemPrincipalSid : + GetUserSidString(); + if (user_sid.empty()) + return; + + pipe_name = kGoogleUpdatePipeName; + pipe_name += user_sid; + +#if defined(_WIN64) + pipe_name += L"-x64"; +#endif + } else { + // Either this is a Chromium build, reporting is disabled by policy or the + // user has not given consent. + return; + } + + g_elf_breakpad = new google_breakpad::ExceptionHandler( + temp_directory, + NULL, + NULL, + NULL, + google_breakpad::ExceptionHandler::HANDLER_ALL, + dump_type, + pipe_name.c_str(), + GetCustomInfo()); + + if (g_elf_breakpad->IsOutOfProcess()) { + // Tells breakpad to handle breakpoint and single step exceptions. + g_elf_breakpad->set_handle_debug_exceptions(true); + } +} diff --git a/chrome_elf/breakpad.h b/chrome_elf/breakpad.h new file mode 100644 index 0000000..8067708 --- /dev/null +++ b/chrome_elf/breakpad.h @@ -0,0 +1,34 @@ +// Copyright 2014 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. + +#ifndef CHROME_ELF_BREAKPAD_H_ +#define CHROME_ELF_BREAKPAD_H_ + +#include <windows.h> + +namespace google_breakpad { +class ExceptionHandler; +} + +// Initializes collection and upload of crash reports. This will only be done if +// the user has agreed to crash dump reporting. +// +// Crash reporting has to be initialized as early as possible (e.g., the first +// thing in main()) to catch crashes occuring during process startup. +// Crashes which occur during the global static construction phase will not +// be caught and reported. This should not be a problem as static non-POD +// objects are not allowed by the style guide and exceptions to this rule are +// rare. +void InitializeCrashReporting(); + +// Generates a crashdump for the provided |exinfo|. This crashdump will be +// either be saved locally, or uploaded, depending on how the ExceptionHandler +// has been configured. +int GenerateCrashDump(EXCEPTION_POINTERS* exinfo); + +// Global pointer to the ExceptionHandler. This is initialized by +// InitializeCrashReporting() and used by GenerateCrashDump() to record dumps. +extern google_breakpad::ExceptionHandler* g_elf_breakpad; + +#endif // CHROME_ELF_BREAKPAD_H_ diff --git a/chrome_elf/chrome_elf.gyp b/chrome_elf/chrome_elf.gyp index acf84ce..00cee29 100644 --- a/chrome_elf/chrome_elf.gyp +++ b/chrome_elf/chrome_elf.gyp @@ -24,6 +24,7 @@ ], 'dependencies': [ 'blacklist', + 'chrome_elf_breakpad', 'chrome_elf_lib', ], 'msvs_settings': { @@ -46,6 +47,7 @@ 'type': 'executable', 'sources': [ 'blacklist/test/blacklist_test.cc', + 'chrome_elf_util_unittest.cc', 'create_file/chrome_create_file_unittest.cc', 'elf_imports_unittest.cc', 'ntdll_cache_unittest.cc', @@ -96,14 +98,14 @@ '..', ], 'sources': [ - 'chrome_elf_constants.cc', - 'chrome_elf_constants.h', - 'chrome_elf_types.h', 'create_file/chrome_create_file.cc', 'create_file/chrome_create_file.h', 'ntdll_cache.cc', 'ntdll_cache.h', ], + 'dependencies': [ + 'chrome_elf_common', + ], 'conditions': [ ['component=="shared_library"', { # In component builds, all targets depend on chrome_redirects by @@ -114,6 +116,46 @@ }], ], }, + { + 'target_name': 'chrome_elf_common', + 'type': 'static_library', + 'include_dirs': [ + '..', + ], + 'sources': [ + 'chrome_elf_constants.cc', + 'chrome_elf_constants.h', + 'chrome_elf_types.h', + 'chrome_elf_util.cc', + 'chrome_elf_util.h', + ], + 'conditions': [ + ['component=="shared_library"', { + # In component builds, all targets depend on chrome_redirects by + # default. Remove it here so we are able to test it. + 'dependencies!': [ + '../chrome_elf/chrome_elf.gyp:chrome_redirects', + ], + }], + ], + }, + { + 'target_name': 'chrome_elf_breakpad', + 'type': 'static_library', + 'include_dirs': [ + '..', + '<(SHARED_INTERMEDIATE_DIR)', + ], + 'sources': [ + 'breakpad.cc', + 'breakpad.h', + ], + 'dependencies': [ + 'chrome_elf_common', + '../breakpad/breakpad.gyp:breakpad_handler', + '../chrome/chrome.gyp:chrome_version_header', + ], + }, ], # targets 'conditions': [ ['component=="shared_library"', { diff --git a/chrome_elf/chrome_elf_main.cc b/chrome_elf/chrome_elf_main.cc index 772c906..de64375 100644 --- a/chrome_elf/chrome_elf_main.cc +++ b/chrome_elf/chrome_elf_main.cc @@ -7,6 +7,7 @@ #include "chrome_elf/chrome_elf_main.h" #include "chrome_elf/blacklist/blacklist.h" +#include "chrome_elf/breakpad.h" #include "chrome_elf/ntdll_cache.h" void SignalChromeElf() { @@ -15,8 +16,13 @@ void SignalChromeElf() { BOOL APIENTRY DllMain(HMODULE module, DWORD reason, LPVOID reserved) { if (reason == DLL_PROCESS_ATTACH) { - InitCache(); - blacklist::Initialize(false); // Don't force, abort if beacon is present. + InitializeCrashReporting(); + + __try { + InitCache(); + blacklist::Initialize(false); // Don't force, abort if beacon is present. + } __except(GenerateCrashDump(GetExceptionInformation())) { + } // TODO(csharp): Move additions to the DLL blacklist to a sane place. // blacklist::AddDllToBlacklist(L"foo.dll"); diff --git a/chrome_elf/chrome_elf_util.cc b/chrome_elf/chrome_elf_util.cc new file mode 100644 index 0000000..fc99ae4 --- /dev/null +++ b/chrome_elf/chrome_elf_util.cc @@ -0,0 +1,193 @@ +// Copyright 2014 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/chrome_elf_util.h" + +#include <windows.h> + +#include "base/macros.h" +#include "base/strings/string16.h" + +namespace { + +const wchar_t kRegPathClientState[] = L"Software\\Google\\Update\\ClientState"; +const wchar_t kRegPathClientStateMedium[] = + L"Software\\Google\\Update\\ClientStateMedium"; +#if defined(GOOGLE_CHROME_BUILD) +const wchar_t kRegPathChromePolicy[] = L"SOFTWARE\\Policies\\Google\\Chrome"; +#else +const wchar_t kRegPathChromePolicy[] = L"SOFTWARE\\Policies\\Chromium"; +#endif // defined(GOOGLE_CHROME_BUILD) + +const wchar_t kRegValueUsageStats[] = L"usagestats"; +const wchar_t kUninstallArgumentsField[] = L"UninstallArguments"; +const wchar_t kMetricsReportingEnabled[] =L"MetricsReportingEnabled"; + +const wchar_t kAppGuidCanary[] = + L"{4ea16ac7-fd5a-47c3-875b-dbf4a2008c20}"; +const wchar_t kAppGuidGoogleChrome[] = + L"{8A69D345-D564-463c-AFF1-A69D9E530F96}"; +const wchar_t kAppGuidGoogleBinaries[] = + L"{4DC8B4CA-1BDA-483e-B5FA-D3C12E15B62D}"; + +bool ReadKeyValueString(bool system_install, const wchar_t* key_path, + const wchar_t* guid, const wchar_t* value_to_read, + base::string16* value_out) { + HKEY key = NULL; + value_out->clear(); + + base::string16 full_key_path(key_path); + full_key_path.append(1, L'\\'); + full_key_path.append(guid); + + if (::RegOpenKeyEx(system_install ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, + full_key_path.c_str(), 0, + KEY_QUERY_VALUE | KEY_WOW64_32KEY, &key) != + ERROR_SUCCESS) { + return false; + } + + const size_t kMaxStringLength = 1024; + wchar_t raw_value[kMaxStringLength] = {}; + DWORD size = sizeof(raw_value); + DWORD type = REG_SZ; + LONG result = ::RegQueryValueEx(key, value_to_read, 0, &type, + reinterpret_cast<LPBYTE>(raw_value), &size); + + if (result == ERROR_SUCCESS) { + if (type != REG_SZ || (size & 1) != 0) { + result = ERROR_NOT_SUPPORTED; + } else if (size == 0) { + *raw_value = L'\0'; + } else if (raw_value[size / sizeof(wchar_t) - 1] != L'\0') { + if ((size / sizeof(wchar_t)) < kMaxStringLength) + raw_value[size / sizeof(wchar_t)] = L'\0'; + else + result = ERROR_MORE_DATA; + } + } + + if (result == ERROR_SUCCESS) + *value_out = raw_value; + + ::RegCloseKey(key); + + return result == ERROR_SUCCESS; +} + +bool ReadKeyValueDW(bool system_install, const wchar_t* key_path, + base::string16 guid, const wchar_t* value_to_read, + DWORD* value_out) { + HKEY key = NULL; + *value_out = 0; + + base::string16 full_key_path(key_path); + full_key_path.append(1, L'\\'); + full_key_path.append(guid); + + if (::RegOpenKeyEx(system_install ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, + full_key_path.c_str(), 0, + KEY_QUERY_VALUE | KEY_WOW64_32KEY, &key) != + ERROR_SUCCESS) { + return false; + } + + DWORD size = sizeof(*value_out); + DWORD type = REG_DWORD; + LONG result = ::RegQueryValueEx(key, value_to_read, 0, &type, + reinterpret_cast<BYTE*>(value_out), &size); + + ::RegCloseKey(key); + + return result == ERROR_SUCCESS && size == sizeof(*value_out); +} + +} // namespace + +bool IsCanary(const wchar_t* exe_path) { + return wcsstr(exe_path, L"Chrome SxS\\Application") != NULL; +} + +bool IsSystemInstall(const wchar_t* exe_path) { + wchar_t program_dir[MAX_PATH] = {}; + DWORD ret = ::GetEnvironmentVariable(L"PROGRAMFILES", program_dir, + arraysize(program_dir)); + if (ret && ret < MAX_PATH && !wcsncmp(exe_path, program_dir, ret)) + return true; + + ret = ::GetEnvironmentVariable(L"PROGRAMFILES(X86)", program_dir, + arraysize(program_dir)); + if (ret && ret < MAX_PATH && !wcsncmp(exe_path, program_dir, ret)) + return true; + + return false; +} + +bool IsMultiInstall(bool is_system_install) { + base::string16 args; + if (!ReadKeyValueString(is_system_install, kRegPathClientState, + kAppGuidGoogleChrome, kUninstallArgumentsField, + &args)) { + return false; + } + return args.find(L"--multi-install") != base::string16::npos; +} + +bool AreUsageStatsEnabled(const wchar_t* exe_path) { + bool enabled = true; + bool controlled_by_policy = ReportingIsEnforcedByPolicy(&enabled); + + if (controlled_by_policy && !enabled) + return false; + + bool system_install = IsSystemInstall(exe_path); + base::string16 app_guid; + + if (IsCanary(exe_path)) { + app_guid = kAppGuidCanary; + } else { + app_guid = IsMultiInstall(system_install) ? kAppGuidGoogleBinaries : + kAppGuidGoogleChrome; + } + + DWORD out_value = 0; + if (system_install && + ReadKeyValueDW(system_install, kRegPathClientStateMedium, app_guid, + kRegValueUsageStats, &out_value)) { + return out_value == 1; + } + + return ReadKeyValueDW(system_install, kRegPathClientState, app_guid, + kRegValueUsageStats, &out_value) && out_value == 1; +} + +bool ReportingIsEnforcedByPolicy(bool* breakpad_enabled) { + HKEY key = NULL; + DWORD value = 0; + BYTE* value_bytes = reinterpret_cast<BYTE*>(&value); + DWORD size = sizeof(value); + DWORD type = REG_DWORD; + + if (::RegOpenKeyEx(HKEY_LOCAL_MACHINE, kRegPathChromePolicy, 0, + KEY_QUERY_VALUE, &key) == ERROR_SUCCESS) { + if (::RegQueryValueEx(key, kMetricsReportingEnabled, 0, &type, + value_bytes, &size) == ERROR_SUCCESS) { + *breakpad_enabled = value != 0; + } + ::RegCloseKey(key); + return size == sizeof(value); + } + + if (::RegOpenKeyEx(HKEY_CURRENT_USER, kRegPathChromePolicy, 0, + KEY_QUERY_VALUE, &key) == ERROR_SUCCESS) { + if (::RegQueryValueEx(key, kMetricsReportingEnabled, 0, &type, + value_bytes, &size) == ERROR_SUCCESS) { + *breakpad_enabled = value != 0; + } + ::RegCloseKey(key); + return size == sizeof(value); + } + + return false; +} diff --git a/chrome_elf/chrome_elf_util.h b/chrome_elf/chrome_elf_util.h new file mode 100644 index 0000000..a81e54f --- /dev/null +++ b/chrome_elf/chrome_elf_util.h @@ -0,0 +1,27 @@ +// Copyright 2014 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. + +#ifndef CHROME_ELF_CHROME_ELF_UTIL_H_ +#define CHROME_ELF_CHROME_ELF_UTIL_H_ + +#include "base/strings/string16.h" + +// Returns true if |exe_path| points to a Chrome installed in an SxS +// installation. +bool IsCanary(const wchar_t* exe_path); + +// Returns true if |exe_path| points to a per-user level Chrome installation. +bool IsSystemInstall(const wchar_t* exe_path); + +// Returns true if current installation of Chrome is a multi-install. +bool IsMultiInstall(bool is_system_install); + +// Returns true if usage stats collecting is enabled for this user. +bool AreUsageStatsEnabled(const wchar_t* exe_path); + +// Returns true if a policy is in effect. |breakpad_enabled| will be set to true +// if stats collecting is permitted by this policy and false if not. +bool ReportingIsEnforcedByPolicy(bool* breakpad_enabled); + +#endif // CHROME_ELF_CHROME_ELF_UTIL_H_ diff --git a/chrome_elf/chrome_elf_util_unittest.cc b/chrome_elf/chrome_elf_util_unittest.cc new file mode 100644 index 0000000..1cb6488 --- /dev/null +++ b/chrome_elf/chrome_elf_util_unittest.cc @@ -0,0 +1,186 @@ +// Copyright 2014 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/chrome_elf_util.h" + +#include <tuple> + +#include "base/test/test_reg_util_win.h" +#include "base/win/registry.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +namespace { + +const wchar_t kRegPathClientState[] = L"Software\\Google\\Update\\ClientState"; +const wchar_t kRegPathClientStateMedium[] = + L"Software\\Google\\Update\\ClientStateMedium"; +const wchar_t kRegValueUsageStats[] = L"usagestats"; +const wchar_t kUninstallArgumentsField[] = L"UninstallArguments"; + +const wchar_t kAppGuidCanary[] = + L"{4ea16ac7-fd5a-47c3-875b-dbf4a2008c20}"; +const wchar_t kAppGuidGoogleChrome[] = + L"{8A69D345-D564-463c-AFF1-A69D9E530F96}"; +const wchar_t kAppGuidGoogleBinaries[] = + L"{4DC8B4CA-1BDA-483e-B5FA-D3C12E15B62D}"; + +const wchar_t kCanaryExePath[] = + L"C:\\Users\\user\\AppData\\Local\\Google\\Chrome SxS\\Application" + L"\\chrome.exe"; +const wchar_t kChromeSystemExePath[] = + L"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe"; +const wchar_t kChromeUserExePath[] = + L"C:\\Users\\user\\AppData\\Local\\Google\\Chrome\\Application\\chrome.exe"; +const wchar_t kChromiumExePath[] = + L"C:\\Users\\user\\AppData\\Local\\Chromium\\Application\\chrome.exe"; + + +TEST(ChromeElfUtilTest, CanaryTest) { + EXPECT_TRUE(IsCanary(kCanaryExePath)); + EXPECT_FALSE(IsCanary(kChromeUserExePath)); + EXPECT_FALSE(IsCanary(kChromiumExePath)); +} + +TEST(ChromeElfUtilTest, SystemInstallTest) { + EXPECT_TRUE(IsSystemInstall(kChromeSystemExePath)); + EXPECT_FALSE(IsSystemInstall(kChromeUserExePath)); +} + +// Parameterized test with paramters: +// 1: product: "canary" or "google" +// 2: install level: "user" or "system" +// 3: install mode: "single" or "multi" +class ChromeElfUtilTest : + public testing::TestWithParam<std::tuple<const char*, + const char*, + const char*> > { + protected: + virtual void SetUp() OVERRIDE { + override_manager_.OverrideRegistry(HKEY_LOCAL_MACHINE, + L"chrome_elf_test_local"); + override_manager_.OverrideRegistry(HKEY_CURRENT_USER, + L"chrome_elf_test_current"); + const char* app; + const char* level; + const char* mode; + std::tie(app, level, mode) = GetParam(); + is_canary_ = (std::string(app) == "canary"); + system_level_ = (std::string(level) != "user"); + multi_install_ = (std::string(mode) != "single"); + if (is_canary_) { + ASSERT_FALSE(system_level_); + ASSERT_FALSE(multi_install_); + app_guid_ = kAppGuidCanary; + chrome_path_ = kCanaryExePath; + } else { + app_guid_ = kAppGuidGoogleChrome; + chrome_path_ = (system_level_ ? kChromeSystemExePath : + kChromeUserExePath); + } + if (multi_install_) { + SetMultiInstallStateInRegistry(system_level_, true); + app_guid_ = kAppGuidGoogleBinaries; + } + } + + base::string16 BuildKey(const wchar_t* path, const wchar_t* guid) { + base::string16 full_key_path(path); + full_key_path.append(1, L'\\'); + full_key_path.append(guid); + return full_key_path; + } + + void SetUsageStat(DWORD value, bool state_medium) { + LONG result = base::win::RegKey( + system_level_ ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, + BuildKey(state_medium ? kRegPathClientStateMedium : kRegPathClientState, + app_guid_).c_str(), + KEY_SET_VALUE).WriteValue(kRegValueUsageStats, value); + ASSERT_EQ(ERROR_SUCCESS, result); + } + + void SetMultiInstallStateInRegistry(bool system_install, bool multi) { + base::win::RegKey key( + system_install ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, + BuildKey(kRegPathClientState, kAppGuidGoogleChrome).c_str(), + KEY_SET_VALUE); + LONG result; + if (multi) { + result = key.WriteValue(kUninstallArgumentsField, + L"yadda yadda --multi-install yadda yadda"); + } else { + result = key.DeleteValue(kUninstallArgumentsField); + } + ASSERT_EQ(ERROR_SUCCESS, result); + } + + const wchar_t* app_guid_; + const wchar_t* chrome_path_; + bool system_level_; + bool multi_install_; + bool is_canary_; + registry_util::RegistryOverrideManager override_manager_; +}; + +TEST_P(ChromeElfUtilTest, MultiInstallTest) { + if (is_canary_) + return; + SetMultiInstallStateInRegistry(system_level_, true); + EXPECT_TRUE(IsMultiInstall(system_level_)); + + SetMultiInstallStateInRegistry(system_level_, false); + EXPECT_FALSE(IsMultiInstall(system_level_)); +} + +TEST_P(ChromeElfUtilTest, UsageStatsAbsent) { + EXPECT_FALSE(AreUsageStatsEnabled(chrome_path_)); +} + +TEST_P(ChromeElfUtilTest, UsageStatsZero) { + SetUsageStat(0, false); + EXPECT_FALSE(AreUsageStatsEnabled(chrome_path_)); +} + +TEST_P(ChromeElfUtilTest, UsageStatsOne) { + SetUsageStat(1, false); + EXPECT_TRUE(AreUsageStatsEnabled(chrome_path_)); + if (is_canary_) { + EXPECT_FALSE(AreUsageStatsEnabled(kChromeUserExePath)); + EXPECT_FALSE(AreUsageStatsEnabled(kChromeSystemExePath)); + } else if (system_level_) { + EXPECT_FALSE(AreUsageStatsEnabled(kCanaryExePath)); + EXPECT_FALSE(AreUsageStatsEnabled(kChromeUserExePath)); + } else { + EXPECT_FALSE(AreUsageStatsEnabled(kCanaryExePath)); + EXPECT_FALSE(AreUsageStatsEnabled(kChromeSystemExePath)); + } +} + +TEST_P(ChromeElfUtilTest, UsageStatsZeroInStateMedium) { + if (!system_level_) + return; + SetUsageStat(0, true); + EXPECT_FALSE(AreUsageStatsEnabled(chrome_path_)); +} + +TEST_P(ChromeElfUtilTest, UsageStatsOneInStateMedium) { + if (!system_level_) + return; + SetUsageStat(1, true); + EXPECT_TRUE(AreUsageStatsEnabled(chrome_path_)); + EXPECT_FALSE(AreUsageStatsEnabled(kCanaryExePath)); + EXPECT_FALSE(AreUsageStatsEnabled(kChromeUserExePath)); +} + +INSTANTIATE_TEST_CASE_P(Canary, ChromeElfUtilTest, + testing::Combine(testing::Values("canary"), + testing::Values("user"), + testing::Values("single"))); +INSTANTIATE_TEST_CASE_P(GoogleChrome, ChromeElfUtilTest, + testing::Combine(testing::Values("google"), + testing::Values("user", "system"), + testing::Values("single", "multi"))); + +} // namespace diff --git a/chrome_elf/create_file/chrome_create_file.cc b/chrome_elf/create_file/chrome_create_file.cc index b6744f3..6bb2c78 100644 --- a/chrome_elf/create_file/chrome_create_file.cc +++ b/chrome_elf/create_file/chrome_create_file.cc @@ -8,6 +8,7 @@ #include "base/strings/string16.h" #include "chrome_elf/chrome_elf_constants.h" +#include "chrome_elf/chrome_elf_util.h" #include "chrome_elf/ntdll_cache.h" #include "sandbox/win/src/nt_internals.h" @@ -243,11 +244,6 @@ HANDLE CreateFileNTDLL( return file_handle; } -bool IsCanary(LPWSTR exe_path) { - wchar_t* found = wcsstr(exe_path, L"Google\\Chrome SxS"); - return !!found; -} - bool ShouldBypass(LPCWSTR file_path) { // Do not redirect in non-browser processes. wchar_t* command_line = ::GetCommandLine(); diff --git a/chrome_elf/create_file/chrome_create_file.h b/chrome_elf/create_file/chrome_create_file.h index df38986..dac93af 100644 --- a/chrome_elf/create_file/chrome_create_file.h +++ b/chrome_elf/create_file/chrome_create_file.h @@ -35,8 +35,4 @@ HANDLE CreateFileNTDLL( // system version (only uses ours if we're writing to the user data directory). bool ShouldBypass(LPCWSTR file_name); -// Returns true if |exe_path| points to a Chrome installed in a SxS -// installation. -bool IsCanary(LPWSTR exe_path); - #endif // CHROME_ELF_CREATE_FILE_CHROME_CREATE_FILE_H_ diff --git a/chrome_elf/create_file/chrome_create_file_unittest.cc b/chrome_elf/create_file/chrome_create_file_unittest.cc index 1d5800a..e25b159 100644 --- a/chrome_elf/create_file/chrome_create_file_unittest.cc +++ b/chrome_elf/create_file/chrome_create_file_unittest.cc @@ -343,12 +343,6 @@ TEST_F(ChromeCreateFileTest, CheckParams_FILE_FLAG_OPEN_NO_RECALL) { ResetNtCreateFileCalls(); } -TEST_F(ChromeCreateFileTest, CanaryTest) { - EXPECT_TRUE(IsCanary(L"C:\\Users\\user\\AppData\\Local\\Google\\Chrome SxS")); - EXPECT_FALSE(IsCanary(L"C:\\Users\\user\\AppData\\Local\\Google\\Chrome")); - EXPECT_FALSE(IsCanary(L"C:\\Users\\user\\AppData\\Local\\Chromium")); -} - TEST_F(ChromeCreateFileTest, BypassTest) { std::wstring UNC_filepath_file(L"\\\\.\\some_file.txt"); |