// Copyright 2015 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 "components/browser_watcher/crash_reporting_metrics_win.h" #include #include "base/atomicops.h" #include "base/guid.h" #include "base/logging.h" #include "base/strings/safe_sprintf.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/win/registry.h" namespace browser_watcher { namespace { // The prior implementation used 0 and 1. Its data collection is inconsistent // with ours and it's best to just ignore its records. So we start at 2. const DWORD kCrashDumpAttempt = 2; const DWORD kDumpWithoutCrashAttempt = 3; const DWORD kCrashDumpSuccess = 4; const DWORD kCrashDumpFailure = 5; const DWORD kDumpWithoutCrashSuccess = 6; const DWORD kDumpWithoutCrashFailure = 7; // A prefix intended to avoid registry value name collisions with other // processes (or modules within a single process). It is not necessary for the // reading and writing instances to share the prefix value. This array is sized // to hold a null-terminated string as generated by base::GenerateGUID. char g_unique_prefix[36 + 1] = {0}; // An atomic counter intended to make each registry value name unique within // this process and module. base::subtle::Atomic32 g_record_count = 0; // The length of a null-terminated string consisting of "{GUID}-{COUNT}". const size_t kValueNameSize = 36 + 1 + 8 + 1; void WriteValue(const base::string16& key_path, DWORD value) { // Generate the final value name we'll use (appends the crash number to the // base value name). char value_name_ascii[kValueNameSize] = ""; base::char16 value_name_utf16[kValueNameSize] = L""; if (base::strings::SafeSPrintf( value_name_ascii, "%s-%x", g_unique_prefix, base::subtle::NoBarrier_AtomicIncrement(&g_record_count, 1)) <= 0) { NOTREACHED(); } else { // Since it's an ASCII string, the UTF-16 form is identical with leading 0 // bytes. We're avoiding unnecessary heap operations since we're running in // a compromised process. std::copy(value_name_ascii, value_name_ascii + kValueNameSize, value_name_utf16); base::win::RegKey reg_key; if (reg_key.Create(HKEY_CURRENT_USER, key_path.c_str(), KEY_SET_VALUE) != ERROR_SUCCESS) { NOTREACHED(); } else { reg_key.WriteValue(value_name_utf16, value); } } } } // namespace CrashReportingMetrics::CrashReportingMetrics( const base::string16& registry_path) : registry_path_(registry_path) { if (g_unique_prefix[0] == 0) { std::string guid = base::GenerateGUID(); // It seems reasonable to assume that the worst possible outcome of two // separate threads trying to do the following would be to store a GUID // value that is a hybrid of the two intended values. Hence we can avoid any // thread-safety caveats in our public API. size_t copied_size = base::strlcpy(g_unique_prefix, guid.c_str(), arraysize(g_unique_prefix)); DCHECK_EQ(copied_size, guid.length()); } } void CrashReportingMetrics::RecordCrashDumpAttempt() { WriteValue(registry_path_, kCrashDumpAttempt); } void CrashReportingMetrics::RecordDumpWithoutCrashAttempt() { WriteValue(registry_path_, kDumpWithoutCrashAttempt); } void CrashReportingMetrics::RecordCrashDumpAttemptResult(bool succeeded) { WriteValue(registry_path_, succeeded ? kCrashDumpSuccess : kCrashDumpFailure); } void CrashReportingMetrics::RecordDumpWithoutCrashAttemptResult( bool succeeded) { WriteValue(registry_path_, succeeded ? kDumpWithoutCrashSuccess : kDumpWithoutCrashFailure); } CrashReportingMetrics::Values CrashReportingMetrics::RetrieveAndResetMetrics() { Values values = {0}; // Open the registry key for iteration. base::win::RegKey regkey; if (regkey.Open(HKEY_CURRENT_USER, registry_path_.c_str(), KEY_QUERY_VALUE | KEY_SET_VALUE) != ERROR_SUCCESS) { return values; } // Track a list of values to delete. We don't modify the registry key while // we're iterating over its values. typedef std::vector StringVector; StringVector to_delete; // Iterate over the values in the key counting dumps with and without crashes. // We directly walk the values instead of using RegistryValueIterator in order // to read all of the values as DWORDS instead of strings. base::string16 name; DWORD value = 0; for (int i = regkey.GetValueCount() - 1; i >= 0; --i) { if (regkey.GetValueNameAt(i, &name) == ERROR_SUCCESS && regkey.ReadValueDW(name.c_str(), &value) == ERROR_SUCCESS) { to_delete.push_back(name); switch (value) { case kCrashDumpAttempt: ++values.crash_dump_attempts; break; case kCrashDumpSuccess: ++values.successful_crash_dumps; break; case kCrashDumpFailure: ++values.failed_crash_dumps; break; case kDumpWithoutCrashAttempt: ++values.dump_without_crash_attempts; break; case kDumpWithoutCrashSuccess: ++values.successful_dumps_without_crash; break; case kDumpWithoutCrashFailure: ++values.failed_dumps_without_crash; break; default: // Presumably a pre-existing record from the previous implementation. // We will delete it. break; } } } // Delete the registry keys we've just counted. for (const auto& value_name : to_delete) regkey.DeleteValue(value_name.c_str()); return values; } } // namespace browser_watcher