// Copyright (c) 2012 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/browser/protector/protected_prefs_watcher.h" #include "base/base64.h" #include "base/logging.h" #include "base/metrics/histogram.h" #include "base/stringprintf.h" #include "base/values.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/prefs/pref_set_observer.h" #include "chrome/browser/prefs/scoped_user_pref_update.h" #include "chrome/browser/prefs/session_startup_pref.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/protector/histograms.h" #include "chrome/browser/protector/protector_utils.h" #include "chrome/common/chrome_notification_types.h" #include "chrome/common/pref_names.h" #include "content/public/browser/notification_service.h" using extensions::ExtensionPrefs; namespace protector { namespace { // Prefix added to names of backup entries. const char kBackupPrefsPrefix[] = "backup."; // Names of prefs that are backed up. const char* const kProtectedPrefNames[] = { prefs::kHomePage, prefs::kHomePageIsNewTabPage, prefs::kShowHomeButton, prefs::kRestoreOnStartup, prefs::kURLsToRestoreOnStartup, prefs::kPinnedTabs }; // Backup pref names. const char kBackupHomePage[] = "backup.homepage"; const char kBackupHomePageIsNewTabPage[] = "backup.homepage_is_newtabpage"; const char kBackupShowHomeButton[] = "backup.browser.show_home_button"; const char kBackupRestoreOnStartup[] = "backup.session.restore_on_startup"; const char kBackupURLsToRestoreOnStartup[] = "backup.session.urls_to_restore_on_startup"; const char kBackupPinnedTabs[] = "backup.pinned_tabs"; // Special backup entries. const char kBackupExtensionsIDs[] = "backup.extensions.ids"; const char kBackupSignature[] = "backup._signature"; const char kBackupVersion[] = "backup._version"; // Returns name of the backup entry for pref |pref_name|. std::string GetBackupNameFor(const std::string& pref_name) { return kBackupPrefsPrefix + pref_name; } // Appends a list of strings to |out|. void StringAppendStringList(const base::ListValue* list, std::string* out) { for (base::ListValue::const_iterator it = list->begin(); it != list->end(); ++it) { std::string item; if (!(*it)->GetAsString(&item)) NOTREACHED(); base::StringAppendF(out, "|%s", item.c_str()); } } // Appends a dictionary with string values to |out|. void StringAppendStringDictionary(const base::DictionaryValue* dict, std::string* out) { for (base::DictionaryValue::Iterator it(*dict); it.HasNext(); it.Advance()) { std::string value; if (!it.value().GetAsString(&value)) NOTREACHED(); base::StringAppendF(out, "|%s|%s", it.key().c_str(), value.c_str()); } } void StringAppendBoolean(PrefService* prefs, const char* path, std::string* out) { if (prefs->HasPrefPath(path)) base::StringAppendF(out, "|%d", prefs->GetBoolean(path) ? 1 : 0); else base::StringAppendF(out, "|"); } void StringAppendInteger(PrefService* prefs, const char* path, std::string* out) { if (prefs->HasPrefPath(path)) base::StringAppendF(out, "|%d", prefs->GetInteger(path)); else base::StringAppendF(out, "|"); } } // namespace // static const int ProtectedPrefsWatcher::kCurrentVersionNumber = 4; ProtectedPrefsWatcher::ProtectedPrefsWatcher(Profile* profile) : is_backup_valid_(true), profile_(profile) { // Perform necessary pref migrations before actually starting to observe // pref changes, otherwise the migration would affect the backup data as well. EnsurePrefsMigration(); pref_observer_.reset(PrefSetObserver::CreateProtectedPrefSetObserver( profile->GetPrefs(), this)); UpdateCachedPrefs(); ValidateBackup(); VLOG(1) << "Initialized pref watcher"; } ProtectedPrefsWatcher::~ProtectedPrefsWatcher() { } // static void ProtectedPrefsWatcher::RegisterUserPrefs(PrefService* prefs) { prefs->RegisterStringPref(kBackupHomePage, "", PrefService::UNSYNCABLE_PREF); prefs->RegisterBooleanPref(kBackupHomePageIsNewTabPage, false, PrefService::UNSYNCABLE_PREF); prefs->RegisterBooleanPref(kBackupShowHomeButton, false, PrefService::UNSYNCABLE_PREF); prefs->RegisterIntegerPref(kBackupRestoreOnStartup, 0, PrefService::UNSYNCABLE_PREF); prefs->RegisterListPref(kBackupURLsToRestoreOnStartup, PrefService::UNSYNCABLE_PREF); prefs->RegisterListPref(kBackupPinnedTabs, PrefService::UNSYNCABLE_PREF); prefs->RegisterListPref(kBackupExtensionsIDs, PrefService::UNSYNCABLE_PREF); prefs->RegisterStringPref(kBackupSignature, "", PrefService::UNSYNCABLE_PREF); prefs->RegisterIntegerPref(kBackupVersion, 1, PrefService::UNSYNCABLE_PREF); } bool ProtectedPrefsWatcher::DidPrefChange(const std::string& path) const { std::string backup_path = GetBackupNameFor(path); PrefService* prefs = profile_->GetPrefs(); const PrefService::Preference* new_pref = prefs->FindPreference(path.c_str()); DCHECK(new_pref); const PrefService::Preference* backup_pref = profile_->GetPrefs()->FindPreference(backup_path.c_str()); DCHECK(backup_pref); if (new_pref->IsDefaultValue()) return !backup_pref->IsDefaultValue(); if (!new_pref->IsUserControlled()) return false; return !backup_pref->GetValue()->Equals(new_pref->GetValue()); } const base::Value* ProtectedPrefsWatcher::GetBackupForPref( const std::string& path) const { if (!is_backup_valid_) return NULL; std::string backup_path = GetBackupNameFor(path); // These do not directly correspond to any real preference. DCHECK(backup_path != kBackupExtensionsIDs && backup_path != kBackupSignature); PrefService* prefs = profile_->GetPrefs(); // If backup is not set, return the default value of the actual pref. // TODO(ivankr): return NULL instead and handle appropriately in SettingChange // classes. if (!prefs->HasPrefPath(backup_path.c_str())) return prefs->GetDefaultPrefValue(path.c_str()); const PrefService::Preference* backup_pref = profile_->GetPrefs()->FindPreference(backup_path.c_str()); DCHECK(backup_pref); return backup_pref->GetValue(); } void ProtectedPrefsWatcher::ForceUpdateBackup() { UMA_HISTOGRAM_ENUMERATION( kProtectorHistogramPrefs, kProtectorErrorForcedUpdate, kProtectorErrorCount); InitBackup(); } void ProtectedPrefsWatcher::Observe( int type, const content::NotificationSource& source, const content::NotificationDetails& details) { DCHECK(type == chrome::NOTIFICATION_PREF_CHANGED); const std::string* pref_name = content::Details<std::string>(details).ptr(); DCHECK(pref_name && pref_observer_->IsObserved(*pref_name)); if (UpdateBackupEntry(*pref_name)) UpdateBackupSignature(); } void ProtectedPrefsWatcher::EnsurePrefsMigration() { SessionStartupPref::MigrateIfNecessary(profile_->GetPrefs()); } bool ProtectedPrefsWatcher::UpdateCachedPrefs() { // ExtensionService may not yet have been initialized, so using static method // exposed for this purpose. ExtensionPrefs::ExtensionIds extension_ids = ExtensionPrefs::GetExtensionsFrom(profile_->GetPrefs()); if (extension_ids == cached_extension_ids_) return false; cached_extension_ids_.swap(extension_ids); return true; } bool ProtectedPrefsWatcher::HasBackup() const { // TODO(ivankr): as soon as some irreversible change to Preferences happens, // add a condition that this change has occured as well (otherwise it's // possible to simply clear the "backup" dictionary to make settings // unprotected). return profile_->GetPrefs()->HasPrefPath(kBackupSignature); } void ProtectedPrefsWatcher::InitBackup() { PrefService* prefs = profile_->GetPrefs(); for (size_t i = 0; i < arraysize(kProtectedPrefNames); ++i) { const base::Value* user_value = prefs->GetUserPrefValue(kProtectedPrefNames[i]); if (user_value) prefs->Set(GetBackupNameFor(kProtectedPrefNames[i]).c_str(), *user_value); } ListPrefUpdate extension_ids_update(prefs, kBackupExtensionsIDs); base::ListValue* extension_ids = extension_ids_update.Get(); extension_ids->Clear(); for (ExtensionPrefs::ExtensionIds::const_iterator it = cached_extension_ids_.begin(); it != cached_extension_ids_.end(); ++it) { extension_ids->Append(base::Value::CreateStringValue(*it)); } prefs->SetInteger(kBackupVersion, kCurrentVersionNumber); UpdateBackupSignature(); } void ProtectedPrefsWatcher::MigrateOldBackupIfNeeded() { PrefService* prefs = profile_->GetPrefs(); int current_version = prefs->GetInteger(kBackupVersion); VLOG(1) << "Backup version: " << current_version; if (current_version == kCurrentVersionNumber) return; switch (current_version) { case 1: { // Add pinned tabs. const base::Value* pinned_tabs = prefs->GetUserPrefValue(prefs::kPinnedTabs); if (pinned_tabs) prefs->Set(kBackupPinnedTabs, *pinned_tabs); } // FALL THROUGH case 2: // SessionStartupPref migration. DCHECK(prefs->GetBoolean(prefs::kRestoreOnStartupMigrated)); prefs->SetInteger(kBackupRestoreOnStartup, prefs->GetInteger(prefs::kRestoreOnStartup)); prefs->Set(kBackupURLsToRestoreOnStartup, *prefs->GetList(prefs::kURLsToRestoreOnStartup)); // FALL THROUGH case 3: // Reset to default values backup prefs whose actual prefs are not set. for (size_t i = 0; i < arraysize(kProtectedPrefNames); ++i) { if (!prefs->HasPrefPath(kProtectedPrefNames[i])) prefs->ClearPref(GetBackupNameFor(kProtectedPrefNames[i]).c_str()); } // FALL THROUGH } prefs->SetInteger(kBackupVersion, kCurrentVersionNumber); UpdateBackupSignature(); } bool ProtectedPrefsWatcher::UpdateBackupEntry(const std::string& path) { std::string backup_path = GetBackupNameFor(path); PrefService* prefs = profile_->GetPrefs(); const PrefService::Preference* pref = prefs->FindPreference(path.c_str()); if (path == ExtensionPrefs::kExtensionsPref) { // For changes in extension dictionary, do nothing if the IDs list remained // the same. if (!UpdateCachedPrefs()) return false; ListPrefUpdate extension_ids_update(prefs, kBackupExtensionsIDs); base::ListValue* extension_ids = extension_ids_update.Get(); extension_ids->Clear(); for (ExtensionPrefs::ExtensionIds::const_iterator it = cached_extension_ids_.begin(); it != cached_extension_ids_.end(); ++it) { extension_ids->Append(base::Value::CreateStringValue(*it)); } } else if (!prefs->HasPrefPath(path.c_str())) { // Preference has been removed, remove the backup as well. prefs->ClearPref(backup_path.c_str()); } else if (!pref->IsUserControlled()) { return false; } else { prefs->Set(backup_path.c_str(), *pref->GetValue()); } VLOG(1) << "Updated backup entry for: " << path; return true; } void ProtectedPrefsWatcher::UpdateBackupSignature() { PrefService* prefs = profile_->GetPrefs(); std::string signed_data = GetSignatureData(prefs); DCHECK(!signed_data.empty()); std::string signature = SignSetting(signed_data); DCHECK(!signature.empty()); std::string signature_base64; if (!base::Base64Encode(signature, &signature_base64)) NOTREACHED(); prefs->SetString(kBackupSignature, signature_base64); // Schedule disk write on FILE thread as soon as possible. prefs->CommitPendingWrite(); VLOG(1) << "Updated backup signature"; } bool ProtectedPrefsWatcher::IsSignatureValid() const { DCHECK(HasBackup()); PrefService* prefs = profile_->GetPrefs(); std::string signed_data = GetSignatureData(prefs); DCHECK(!signed_data.empty()); std::string signature; if (!base::Base64Decode(prefs->GetString(kBackupSignature), &signature)) return false; return IsSettingValid(signed_data, signature); } void ProtectedPrefsWatcher::ValidateBackup() { if (!HasBackup()) { // Create initial backup entries and sign them. InitBackup(); UMA_HISTOGRAM_ENUMERATION( kProtectorHistogramPrefs, kProtectorErrorValueValidZero, kProtectorErrorCount); } else if (IsSignatureValid()) { MigrateOldBackupIfNeeded(); UMA_HISTOGRAM_ENUMERATION( kProtectorHistogramPrefs, kProtectorErrorValueValid, kProtectorErrorCount); } else { LOG(WARNING) << "Invalid backup signature"; is_backup_valid_ = false; // The whole backup has been compromised, overwrite it. InitBackup(); UMA_HISTOGRAM_ENUMERATION( kProtectorHistogramPrefs, kProtectorErrorBackupInvalid, kProtectorErrorCount); } } std::string ProtectedPrefsWatcher::GetSignatureData(PrefService* prefs) const { int current_version = prefs->GetInteger(kBackupVersion); // TODO(ivankr): replace this with some existing reliable serializer. // JSONWriter isn't a good choice because JSON formatting may change suddenly. std::string data = prefs->GetString(kBackupHomePage); StringAppendBoolean(prefs, kBackupHomePageIsNewTabPage, &data); StringAppendBoolean(prefs, kBackupShowHomeButton, &data); StringAppendInteger(prefs, kBackupRestoreOnStartup, &data); StringAppendStringList(prefs->GetList(kBackupURLsToRestoreOnStartup), &data); StringAppendStringList(prefs->GetList(kBackupExtensionsIDs), &data); if (current_version >= 2) { // Version itself is included only since version 2 since it wasn't there // in version 1. base::StringAppendF(&data, "|v%d", current_version); const base::ListValue* pinned_tabs = prefs->GetList(kBackupPinnedTabs); for (base::ListValue::const_iterator it = pinned_tabs->begin(); it != pinned_tabs->end(); ++it) { const base::DictionaryValue* tab = NULL; if (!(*it)->GetAsDictionary(&tab)) { NOTREACHED(); continue; } StringAppendStringDictionary(tab, &data); } } return data; } } // namespace protector