From 66d176ad38d72eabc969893b2b09ad6759109836 Mon Sep 17 00:00:00 2001
From: "blundell@chromium.org"
 <blundell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>
Date: Thu, 22 May 2014 13:49:39 +0000
Subject: Move ClonedInstallDetector and MachineIdProvider into the metrics
 component

cloned_install_detector_unittest is left in //chrome at the current time as it
still has //chrome-level dependencies.

BUG=374212
R=isherman@chromium.org
TBR=thakis

Review URL: https://codereview.chromium.org/290033003

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@272172 0039d316-1c4b-4281-b951-d872f2087c98
---
 components/components_tests.gyp                    |   1 +
 components/metrics.gypi                            |  10 ++
 components/metrics/cloned_install_detector.cc      |  98 +++++++++++++++++
 components/metrics/cloned_install_detector.h       |  59 +++++++++++
 components/metrics/machine_id_provider.h           |  46 ++++++++
 components/metrics/machine_id_provider_stub.cc     |  24 +++++
 components/metrics/machine_id_provider_win.cc      | 117 +++++++++++++++++++++
 .../metrics/machine_id_provider_win_unittest.cc    |  23 ++++
 components/metrics/metrics_pref_names.cc           |   8 ++
 components/metrics/metrics_pref_names.h            |   2 +
 10 files changed, 388 insertions(+)
 create mode 100644 components/metrics/cloned_install_detector.cc
 create mode 100644 components/metrics/cloned_install_detector.h
 create mode 100644 components/metrics/machine_id_provider.h
 create mode 100644 components/metrics/machine_id_provider_stub.cc
 create mode 100644 components/metrics/machine_id_provider_win.cc
 create mode 100644 components/metrics/machine_id_provider_win_unittest.cc

(limited to 'components')

diff --git a/components/components_tests.gyp b/components/components_tests.gyp
index 64cf12b..c6928eb 100644
--- a/components/components_tests.gyp
+++ b/components/components_tests.gyp
@@ -104,6 +104,7 @@
             'keyed_service/content/browser_context_dependency_manager_unittest.cc',
             'keyed_service/core/dependency_graph_unittest.cc',
             'language_usage_metrics/language_usage_metrics_unittest.cc',
+            'metrics/machine_id_provider_win_unittest.cc',
             'metrics/metrics_hashes_unittest.cc',
             'metrics/metrics_log_base_unittest.cc',
             'metrics/metrics_log_manager_unittest.cc',
diff --git a/components/metrics.gypi b/components/metrics.gypi
index 850ecd09..fd79d50 100644
--- a/components/metrics.gypi
+++ b/components/metrics.gypi
@@ -16,6 +16,11 @@
       ],
       'sources': [
         'metrics/metrics_provider.h',
+        'metrics/cloned_install_detector.cc',
+        'metrics/cloned_install_detector.h',
+        'metrics/machine_id_provider.h',
+        'metrics/machine_id_provider_stub.cc',
+        'metrics/machine_id_provider_win.cc',
         'metrics/metrics_hashes.cc',
         'metrics/metrics_hashes.h',
         'metrics/metrics_log_base.cc',
@@ -38,6 +43,11 @@
             'metrics_chromeos',
           ],
         }],
+        ['OS=="win"', {
+          'sources!': [
+            'metrics/machine_id_provider_stub.cc',
+          ],
+        }],
       ],
     },
     {
diff --git a/components/metrics/cloned_install_detector.cc b/components/metrics/cloned_install_detector.cc
new file mode 100644
index 0000000..b93e6e6
--- /dev/null
+++ b/components/metrics/cloned_install_detector.cc
@@ -0,0 +1,98 @@
+// 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 "components/metrics/cloned_install_detector.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/metrics/histogram.h"
+#include "base/prefs/pref_registry_simple.h"
+#include "base/prefs/pref_service.h"
+#include "base/single_thread_task_runner.h"
+#include "base/task_runner_util.h"
+#include "components/metrics/cloned_install_detector.h"
+#include "components/metrics/machine_id_provider.h"
+#include "components/metrics/metrics_hashes.h"
+#include "components/metrics/metrics_pref_names.h"
+
+namespace metrics {
+
+namespace {
+
+uint32 HashRawId(const std::string& value) {
+  uint64 hash = metrics::HashMetricName(value);
+
+  // Only use 24 bits from the 64-bit hash.
+  return hash & ((1 << 24) - 1);
+}
+
+// State of the generated machine id in relation to the previously stored value.
+// Note: UMA histogram enum - don't re-order or remove entries
+enum MachineIdState {
+  ID_GENERATION_FAILED,
+  ID_NO_STORED_VALUE,
+  ID_CHANGED,
+  ID_UNCHANGED,
+  ID_ENUM_SIZE
+};
+
+// Logs the state of generating a machine id and comparing it to a stored value.
+void LogMachineIdState(MachineIdState state) {
+  UMA_HISTOGRAM_ENUMERATION("UMA.MachineIdState", state, ID_ENUM_SIZE);
+}
+
+}  // namespace
+
+ClonedInstallDetector::ClonedInstallDetector(MachineIdProvider* raw_id_provider)
+    : raw_id_provider_(raw_id_provider), weak_ptr_factory_(this) {
+}
+
+ClonedInstallDetector::~ClonedInstallDetector() {
+}
+
+void ClonedInstallDetector::CheckForClonedInstall(
+    PrefService* local_state,
+    scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
+  base::PostTaskAndReplyWithResult(
+      task_runner.get(),
+      FROM_HERE,
+      base::Bind(&metrics::MachineIdProvider::GetMachineId, raw_id_provider_),
+      base::Bind(&metrics::ClonedInstallDetector::SaveMachineId,
+                 weak_ptr_factory_.GetWeakPtr(),
+                 local_state));
+}
+
+void ClonedInstallDetector::SaveMachineId(PrefService* local_state,
+                                          std::string raw_id) {
+  if (raw_id.empty()) {
+    LogMachineIdState(ID_GENERATION_FAILED);
+    local_state->ClearPref(prefs::kMetricsMachineId);
+    return;
+  }
+
+  int hashed_id = HashRawId(raw_id);
+
+  MachineIdState id_state = ID_NO_STORED_VALUE;
+  if (local_state->HasPrefPath(prefs::kMetricsMachineId)) {
+    if (local_state->GetInteger(prefs::kMetricsMachineId) != hashed_id) {
+      id_state = ID_CHANGED;
+      // TODO(jwd): Use a callback to set the reset pref. That way
+      // ClonedInstallDetector doesn't need to know about this pref.
+      local_state->SetBoolean(prefs::kMetricsResetIds, true);
+    } else {
+      id_state = ID_UNCHANGED;
+    }
+  }
+
+  LogMachineIdState(id_state);
+
+  local_state->SetInteger(prefs::kMetricsMachineId, hashed_id);
+}
+
+// static
+void ClonedInstallDetector::RegisterPrefs(PrefRegistrySimple* registry) {
+  registry->RegisterIntegerPref(prefs::kMetricsMachineId, 0);
+}
+
+}  // namespace metrics
diff --git a/components/metrics/cloned_install_detector.h b/components/metrics/cloned_install_detector.h
new file mode 100644
index 0000000..065cb49
--- /dev/null
+++ b/components/metrics/cloned_install_detector.h
@@ -0,0 +1,59 @@
+// 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 COMPONENTS_METRICS_CLONED_INSTALL_DETECTOR_H_
+#define COMPONENTS_METRICS_CLONED_INSTALL_DETECTOR_H_
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+
+class PrefRegistrySimple;
+class PrefService;
+
+namespace base {
+class SingleThreadTaskRunner;
+}
+
+namespace metrics {
+
+class MachineIdProvider;
+
+// A class for detecting if an install is cloned. It does this by detecting
+// when the hardware running Chrome changes.
+class ClonedInstallDetector {
+ public:
+  explicit ClonedInstallDetector(MachineIdProvider* raw_id_provider);
+  virtual ~ClonedInstallDetector();
+
+  // Posts a task to |task_runner| to generate a machine ID and store it to a
+  // local state pref. If the newly generated ID is different than the
+  // previously stored one, then the install is considered cloned. The ID is a
+  // 24-bit value based off of machine characteristics. This value should never
+  // be sent over the network.
+  // TODO(jwd): Implement change detection.
+  void CheckForClonedInstall(
+      PrefService* local_state,
+      scoped_refptr<base::SingleThreadTaskRunner> task_runner);
+
+  static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(ClonedInstallDetectorTest, SaveId);
+  FRIEND_TEST_ALL_PREFIXES(ClonedInstallDetectorTest, DetectClone);
+
+  // Converts raw_id into a 24-bit hash and stores the hash in |local_state|.
+  // |raw_id| is not a const ref because it's passed from a cross-thread post
+  // task.
+  void SaveMachineId(PrefService* local_state, std::string raw_id);
+
+  scoped_refptr<MachineIdProvider> raw_id_provider_;
+  base::WeakPtrFactory<ClonedInstallDetector> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(ClonedInstallDetector);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_CLONED_INSTALL_DETECTOR_H_
diff --git a/components/metrics/machine_id_provider.h b/components/metrics/machine_id_provider.h
new file mode 100644
index 0000000..d7fcc44
--- /dev/null
+++ b/components/metrics/machine_id_provider.h
@@ -0,0 +1,46 @@
+// 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 COMPONENTS_METRICS_MACHINE_ID_PROVIDER_H_
+#define COMPONENTS_METRICS_MACHINE_ID_PROVIDER_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+
+namespace metrics {
+
+// Provides machine characteristics used as a machine id. The implementation is
+// platform specific with a default implementation that gives an empty id. The
+// class is ref-counted thread safe so it can be used to post to the FILE thread
+// and communicate back to the UI thread.
+// This raw machine id should not be stored or transmitted over the network.
+// TODO(jwd): Simplify implementation to get rid of the need for
+// RefCountedThreadSafe (crbug.com/354882).
+class MachineIdProvider : public base::RefCountedThreadSafe<MachineIdProvider> {
+ public:
+  // Get a string containing machine characteristics, to be used as a machine
+  // id. The implementation is platform specific, with a default implementation
+  // returning an empty string.
+  // The return value should not be stored to disk or transmitted.
+  std::string GetMachineId();
+
+  // Returns a pointer to a new MachineIdProvider or NULL if there is no
+  // provider implemented on a given platform. This is done to avoid posting a
+  // task to the FILE thread on platforms with no implementation.
+  static MachineIdProvider* CreateInstance();
+
+ private:
+  friend class base::RefCountedThreadSafe<MachineIdProvider>;
+
+  MachineIdProvider();
+  virtual ~MachineIdProvider();
+
+  DISALLOW_COPY_AND_ASSIGN(MachineIdProvider);
+};
+
+}  //  namespace metrics
+
+#endif  // COMPONENTS_METRICS_MACHINE_ID_PROVIDER_H_
diff --git a/components/metrics/machine_id_provider_stub.cc b/components/metrics/machine_id_provider_stub.cc
new file mode 100644
index 0000000..626f2b7
--- /dev/null
+++ b/components/metrics/machine_id_provider_stub.cc
@@ -0,0 +1,24 @@
+// 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 "components/metrics/machine_id_provider.h"
+
+namespace metrics {
+
+MachineIdProvider::MachineIdProvider() {
+}
+
+MachineIdProvider::~MachineIdProvider() {
+}
+
+// static
+MachineIdProvider* MachineIdProvider::CreateInstance() {
+  return NULL;
+}
+
+std::string MachineIdProvider::GetMachineId() {
+  return std::string();
+}
+
+}  //  namespace metrics
diff --git a/components/metrics/machine_id_provider_win.cc b/components/metrics/machine_id_provider_win.cc
new file mode 100644
index 0000000..777151a
--- /dev/null
+++ b/components/metrics/machine_id_provider_win.cc
@@ -0,0 +1,117 @@
+// 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 "components/metrics/machine_id_provider.h"
+
+#include <windows.h>
+#include <winioctl.h>
+
+#include "base/base_paths.h"
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/win/scoped_handle.h"
+
+namespace metrics {
+
+MachineIdProvider::MachineIdProvider() {
+}
+
+MachineIdProvider::~MachineIdProvider() {
+}
+
+// On windows, the machine id is based on the serial number of the drive Chrome
+// is running from.
+std::string MachineIdProvider::GetMachineId() {
+  base::ThreadRestrictions::AssertIOAllowed();
+
+  // Use the program's path to get the drive used for the machine id. This means
+  // that whenever the underlying drive changes, it's considered a new machine.
+  // This is fine as we do not support migrating Chrome installs to new drives.
+  base::FilePath executable_path;
+
+  if (!PathService::Get(base::FILE_EXE, &executable_path)) {
+    NOTREACHED();
+    return std::string();
+  }
+
+  std::vector<base::FilePath::StringType> path_components;
+  executable_path.GetComponents(&path_components);
+  if (path_components.empty()) {
+    NOTREACHED();
+    return std::string();
+  }
+  base::FilePath::StringType drive_name = L"\\\\.\\" + path_components[0];
+
+  base::win::ScopedHandle drive_handle(
+      CreateFile(drive_name.c_str(),
+                 0,
+                 FILE_SHARE_READ | FILE_SHARE_WRITE,
+                 NULL,
+                 OPEN_EXISTING,
+                 0,
+                 NULL));
+
+  STORAGE_PROPERTY_QUERY query = {};
+  query.PropertyId = StorageDeviceProperty;
+  query.QueryType = PropertyStandardQuery;
+
+  // Perform an initial query to get the number of bytes being returned.
+  DWORD bytes_returned;
+  STORAGE_DESCRIPTOR_HEADER header = {};
+  BOOL status = DeviceIoControl(drive_handle,
+                                IOCTL_STORAGE_QUERY_PROPERTY,
+                                &query,
+                                sizeof(STORAGE_PROPERTY_QUERY),
+                                &header,
+                                sizeof(STORAGE_DESCRIPTOR_HEADER),
+                                &bytes_returned,
+                                NULL);
+
+  if (!status)
+    return std::string();
+
+  // Query for the actual serial number.
+  std::vector<int8> output_buf(header.Size);
+  status = DeviceIoControl(drive_handle,
+                           IOCTL_STORAGE_QUERY_PROPERTY,
+                           &query,
+                           sizeof(STORAGE_PROPERTY_QUERY),
+                           &output_buf[0],
+                           output_buf.size(),
+                           &bytes_returned,
+                           NULL);
+
+  if (!status)
+    return std::string();
+
+  const STORAGE_DEVICE_DESCRIPTOR* device_descriptor =
+      reinterpret_cast<STORAGE_DEVICE_DESCRIPTOR*>(&output_buf[0]);
+
+  // The serial number is stored in the |output_buf| as a null-terminated
+  // string starting at the specified offset.
+  const DWORD offset = device_descriptor->SerialNumberOffset;
+  if (offset >= output_buf.size())
+    return std::string();
+
+  // Make sure that the null-terminator exists.
+  const std::vector<int8>::iterator serial_number_begin =
+      output_buf.begin() + offset;
+  const std::vector<int8>::iterator null_location =
+      std::find(serial_number_begin, output_buf.end(), '\0');
+  if (null_location == output_buf.end())
+    return std::string();
+
+  const char* serial_number =
+      reinterpret_cast<const char*>(&output_buf[offset]);
+
+  return std::string(serial_number);
+}
+
+// static
+MachineIdProvider* MachineIdProvider::CreateInstance() {
+  return new MachineIdProvider();
+}
+
+}  //  namespace metrics
diff --git a/components/metrics/machine_id_provider_win_unittest.cc b/components/metrics/machine_id_provider_win_unittest.cc
new file mode 100644
index 0000000..44917d0
--- /dev/null
+++ b/components/metrics/machine_id_provider_win_unittest.cc
@@ -0,0 +1,23 @@
+// 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 "components/metrics/machine_id_provider.h"
+
+#include "base/memory/ref_counted.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+TEST(MachineIdProviderTest, GetId) {
+  scoped_refptr<MachineIdProvider> provider(
+      MachineIdProvider::CreateInstance());
+  std::string id1 = provider->GetMachineId();
+
+  EXPECT_NE(std::string(), id1);
+
+  std::string id2 = provider->GetMachineId();
+  EXPECT_EQ(id1, id2);
+}
+
+}  // namespace metrics
diff --git a/components/metrics/metrics_pref_names.cc b/components/metrics/metrics_pref_names.cc
index 92a1890..e0b358a1 100644
--- a/components/metrics/metrics_pref_names.cc
+++ b/components/metrics/metrics_pref_names.cc
@@ -13,6 +13,10 @@ namespace prefs {
 const char kMetricsInitialLogs[] =
     "user_experience_metrics.initial_logs_as_protobufs";
 
+// A machine ID used to detect when underlying hardware changes. It is only
+// stored locally and never transmitted in metrics reports.
+const char kMetricsMachineId[] = "user_experience_metrics.machine_id";
+
 // Array of strings that are each UMA logs that were not sent because the
 // browser terminated before these accumulated metrics could be sent.  These
 // logs typically include histograms and memory reports, as well as ongoing
@@ -20,5 +24,9 @@ const char kMetricsInitialLogs[] =
 const char kMetricsOngoingLogs[] =
     "user_experience_metrics.ongoing_logs_as_protobufs";
 
+// Boolean that indicates a cloned install has been detected and the metrics
+// client id and low entropy source should be reset.
+const char kMetricsResetIds[] = "user_experience_metrics.reset_metrics_ids";
+
 }  // namespace prefs
 }  // namespace metrics
diff --git a/components/metrics/metrics_pref_names.h b/components/metrics/metrics_pref_names.h
index 750d58d..4c799c7 100644
--- a/components/metrics/metrics_pref_names.h
+++ b/components/metrics/metrics_pref_names.h
@@ -11,7 +11,9 @@ namespace prefs {
 // Alphabetical list of preference names specific to the metrics
 // component. Keep alphabetized, and document each in the .cc file.
 extern const char kMetricsInitialLogs[];
+extern const char kMetricsMachineId[];
 extern const char kMetricsOngoingLogs[];
+extern const char kMetricsResetIds[];
 
 }  // namespace prefs
 }  // namespace metrics
-- 
cgit v1.1