chromium/chrome/browser/prefs/tracked/pref_hash_browsertest.cc

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>
#include <optional>
#include <string>
#include <utility>

#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/json/json_file_value_serializer.h"
#include "base/json/json_reader.h"
#include "base/metrics/histogram_base.h"
#include "base/metrics/histogram_samples.h"
#include "base/metrics/statistics_recorder.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/values.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/prefs/chrome_pref_service_factory.h"
#include "chrome/browser/prefs/profile_pref_store_manager.h"
#include "chrome/browser/prefs/session_startup_pref.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engine_choice/search_engine_choice_service_factory.h"
#include "chrome/browser/sync/test/integration/sync_test.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_profile.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/search_engines/default_search_manager.h"
#include "components/search_engines/template_url_data.h"
#include "components/sync/base/features.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_launcher.h"
#include "extensions/browser/pref_names.h"
#include "extensions/common/extension.h"
#include "services/preferences/public/cpp/tracked/tracked_preference_histogram_names.h"

#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/constants/ash_switches.h"
#endif

#if BUILDFLAG(IS_WIN)
#include "base/win/registry.h"
#include "chrome/install_static/install_util.h"
#endif

namespace {

// Extension ID of chrome/test/data/extensions/good.crx
const char kGoodCrxId[] =;

// Explicit expectations from the caller of GetTrackedPrefHistogramCount(). This
// enables detailed reporting of the culprit on failure.
enum AllowedBuckets {};

#if BUILDFLAG(IS_WIN)
std::wstring GetRegistryPathForTestProfile() {
  // Cleanup follow-up to http://crbug.com/721245 for the previous location of
  // this test key which had similar problems (to a lesser extent). It's
  // redundant but harmless to have multiple callers hit this on the same
  // machine. TODO(gab): remove this mid-june 2017.
  base::win::RegKey key;
  if (key.Open(HKEY_CURRENT_USER, L"SOFTWARE\\Chromium\\PrefHashBrowserTest",
               KEY_SET_VALUE | KEY_WOW64_32KEY) == ERROR_SUCCESS) {
    LONG result = key.DeleteKey(L"");
    EXPECT_TRUE(result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND);
  }

  base::FilePath profile_dir;
  EXPECT_TRUE(base::PathService::Get(chrome::DIR_USER_DATA, &profile_dir));

  // |DIR_USER_DATA| usually has format %TMP%\12345_6789012345\user_data
  // (unless running with --single-process-tests, where the format is
  // %TMP%\scoped_dir12345_6789012345). Use the parent directory name instead of
  // the leaf directory name "user_data" to avoid conflicts in parallel tests,
  // which would try to modify the same registry key otherwise.
  if (profile_dir.BaseName().value() == L"user_data") {
    profile_dir = profile_dir.DirName();
  }
  // Try to detect regressions when |DIR_USER_DATA| test location changes, which
  // could cause this test to become flaky. See http://crbug/1091409 for more
  // details.
  DCHECK(profile_dir.BaseName().value().find_first_of(L"0123456789") !=
         std::string::npos);

  // Use a location under the real PreferenceMACs path so that the backup
  // cleanup logic in ChromeTestLauncherDelegate::PreSharding() for interrupted
  // tests covers this test key as well.
  return install_static::GetRegistryPath() +
         L"\\PreferenceMACs\\PrefHashBrowserTest\\" +
         profile_dir.BaseName().value();
}
#endif

// Returns the number of times |histogram_name| was reported so far; adding the
// results of the first 100 buckets (there are only ~19 reporting IDs as of this
// writing; varies depending on the platform). |allowed_buckets| hints at extra
// requirements verified in this method (see AllowedBuckets for details).
int GetTrackedPrefHistogramCount(const char* histogram_name,
                                 const char* histogram_suffix,
                                 int allowed_buckets) {}

// Helper function to call GetTrackedPrefHistogramCount with no external
// validation suffix.
int GetTrackedPrefHistogramCount(const char* histogram_name,
                                 int allowed_buckets) {}

#if !BUILDFLAG(IS_CHROMEOS_ASH)
std::optional<base::Value::Dict> ReadPrefsDictionary(
    const base::FilePath& pref_file) {}
#endif

// Returns whether external validation is supported on the platform through
// storing MACs in the registry.
bool SupportsRegistryValidation() {}

#define PREF_HASH_BROWSER_TEST(fixture, test_name)

// A base fixture designed such that implementations do two things:
//  1) Override all three pure-virtual methods below to setup, attack, and
//     verify preferences throughout the tests provided by this fixture.
//  2) Instantiate their test via the PREF_HASH_BROWSER_TEST macro above.
// Based on top of ExtensionBrowserTest to allow easy interaction with the
// ExtensionRegistry.
class PrefHashBrowserTestBase : public extensions::ExtensionBrowserTest {};

}  // namespace

// Verifies that nothing is reset when nothing is tampered with.
// Also sanity checks that the expected preferences files are in place.
class PrefHashBrowserTestUnchangedDefault : public PrefHashBrowserTestBase {};

PREF_HASH_BROWSER_TEST(PrefHashBrowserTestUnchangedDefault, UnchangedDefault);

// Augments PrefHashBrowserTestUnchangedDefault to confirm that nothing is reset
// when nothing is tampered with, even if Chrome itself wrote custom prefs in
// its last run.
class PrefHashBrowserTestUnchangedCustom
    : public PrefHashBrowserTestUnchangedDefault {};

PREF_HASH_BROWSER_TEST(PrefHashBrowserTestUnchangedCustom, UnchangedCustom);

// Verifies that cleared prefs are reported.
class PrefHashBrowserTestClearedAtomic : public PrefHashBrowserTestBase {};

PREF_HASH_BROWSER_TEST(PrefHashBrowserTestClearedAtomic, ClearedAtomic);

// Verifies that clearing the MACs results in untrusted Initialized pings for
// non-null protected prefs.
class PrefHashBrowserTestUntrustedInitialized : public PrefHashBrowserTestBase {};

PREF_HASH_BROWSER_TEST(PrefHashBrowserTestUntrustedInitialized,
                       UntrustedInitialized);

// Verifies that changing an atomic pref results in it being reported (and reset
// if the protection level allows it).
class PrefHashBrowserTestChangedAtomic : public PrefHashBrowserTestBase {};

PREF_HASH_BROWSER_TEST(PrefHashBrowserTestChangedAtomic, ChangedAtomic);

// Verifies that changing or adding an entry in a split pref results in both
// items being reported (and remove if the protection level allows it).
class PrefHashBrowserTestChangedSplitPref : public PrefHashBrowserTestBase {};

PREF_HASH_BROWSER_TEST(PrefHashBrowserTestChangedSplitPref, ChangedSplitPref);

// Verifies that adding a value to unprotected preferences for a key which is
// still using the default (i.e. has no value stored in protected preferences)
// doesn't allow that value to slip in with no valid MAC (regression test for
// http://crbug.com/414554)
class PrefHashBrowserTestUntrustedAdditionToPrefs
    : public PrefHashBrowserTestBase {};

PREF_HASH_BROWSER_TEST(PrefHashBrowserTestUntrustedAdditionToPrefs,
                       UntrustedAdditionToPrefs);

// Verifies that adding a value to unprotected preferences while wiping a
// user-selected value from protected preferences doesn't allow that value to
// slip in with no valid MAC (regression test for http://crbug.com/414554).
class PrefHashBrowserTestUntrustedAdditionToPrefsAfterWipe
    : public PrefHashBrowserTestBase {};

PREF_HASH_BROWSER_TEST(PrefHashBrowserTestUntrustedAdditionToPrefsAfterWipe,
                       UntrustedAdditionToPrefsAfterWipe);

#if BUILDFLAG(IS_WIN)
class PrefHashBrowserTestRegistryValidationFailure
    : public PrefHashBrowserTestBase {
 public:
  void SetupPreferences() override {
    profile()->GetPrefs()->SetString(prefs::kHomePage, "http://example.com");
  }

  void AttackPreferencesOnDisk(
      base::Value::Dict* unprotected_preferences,
      base::Value::Dict* protected_preferences) override {
    std::wstring registry_key =
        GetRegistryPathForTestProfile() + L"\\PreferenceMACs\\Default";
    base::win::RegKey key;
    ASSERT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, registry_key.c_str(),
                                      KEY_SET_VALUE | KEY_WOW64_32KEY));
    // An incorrect hash should still have the correct size.
    ASSERT_EQ(ERROR_SUCCESS,
              key.WriteValue(L"homepage", std::wstring(64, 'A').c_str()));
  }

  void VerifyReactionToPrefAttack() override {
    EXPECT_EQ(
        protection_level_ > PROTECTION_DISABLED_ON_PLATFORM
            ? num_tracked_prefs()
            : 0,
        GetTrackedPrefHistogramCount(
            user_prefs::tracked::kTrackedPrefHistogramUnchanged, ALLOW_ANY));

    if (SupportsRegistryValidation()) {
      // Expect that the registry validation caught the invalid MAC for pref #2
      // (homepage).
      EXPECT_EQ(protection_level_ > PROTECTION_DISABLED_ON_PLATFORM ? 1 : 0,
                GetTrackedPrefHistogramCount(
                    user_prefs::tracked::kTrackedPrefHistogramChanged,
                    user_prefs::tracked::kTrackedPrefRegistryValidationSuffix,
                    BEGIN_ALLOW_SINGLE_BUCKET + 2));
    }
  }
};

PREF_HASH_BROWSER_TEST(PrefHashBrowserTestRegistryValidationFailure,
                       RegistryValidationFailure);
#endif

// Verifies that all preferences related to choice of default search engine are
// protected.
class PrefHashBrowserTestDefaultSearch : public PrefHashBrowserTestBase {};

PREF_HASH_BROWSER_TEST(PrefHashBrowserTestDefaultSearch, SearchProtected);

// Verifies that we handle a protected Dict preference being changed to an
// unexpected type (int). See https://crbug.com/1512724.
class PrefHashBrowserTestExtensionDictTypeChanged
    : public PrefHashBrowserTestBase {};

PREF_HASH_BROWSER_TEST(PrefHashBrowserTestExtensionDictTypeChanged,
                       ExtensionDictTypeChanged);

// Verifies that changing the account value of a tracked pref results in it
// being reported under the same id as the local value (and reset if the
// protection level allows it).
class PrefHashBrowserTestAccountValueUntrustedAddition
    : public PrefHashBrowserTestBase {};

PREF_HASH_BROWSER_TEST(PrefHashBrowserTestAccountValueUntrustedAddition,
                       AccountValueUntrustedAddition);