| // Copyright 2018 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/conflicts/problematic_programs_updater_win.h" | 
 |  | 
 | #include <string> | 
 | #include <utility> | 
 |  | 
 | #include "base/bind.h" | 
 | #include "base/strings/utf_string_conversions.h" | 
 | #include "base/values.h" | 
 | #include "base/win/registry.h" | 
 | #include "chrome/browser/browser_process.h" | 
 | #include "chrome/browser/conflicts/module_database_win.h" | 
 | #include "chrome/browser/conflicts/module_info_util_win.h" | 
 | #include "chrome/browser/conflicts/module_list_filter_win.h" | 
 | #include "chrome/browser/conflicts/third_party_metrics_recorder_win.h" | 
 | #include "chrome/common/pref_names.h" | 
 | #include "components/prefs/pref_registry_simple.h" | 
 | #include "components/prefs/pref_service.h" | 
 | #include "components/prefs/scoped_user_pref_update.h" | 
 | #include "content/public/browser/browser_thread.h" | 
 |  | 
 | namespace { | 
 |  | 
 | // Serializes a vector of ProblematicPrograms to JSON. | 
 | base::Value ConvertToDictionary( | 
 |     const std::vector<ProblematicProgramsUpdater::ProblematicProgram>& | 
 |         programs) { | 
 |   base::Value result(base::Value::Type::DICTIONARY); | 
 |  | 
 |   for (const auto& program : programs) { | 
 |     base::Value element(base::Value::Type::DICTIONARY); | 
 |  | 
 |     // The registry location is necessary to quickly figure out if that program | 
 |     // is still installed on the computer. | 
 |     element.SetKey("registry_is_hkcu", base::Value(program.info.registry_root == | 
 |                                                    HKEY_CURRENT_USER)); | 
 |     element.SetKey("registry_key_path", | 
 |                    base::Value(program.info.registry_key_path)); | 
 |     element.SetKey( | 
 |         "registry_wow64_access", | 
 |         base::Value(static_cast<int>(program.info.registry_wow64_access))); | 
 |  | 
 |     // And then the actual information needed to display a warning to the user. | 
 |     element.SetKey("allow_load", | 
 |                    base::Value(program.blacklist_action->allow_load())); | 
 |     element.SetKey("type", | 
 |                    base::Value(program.blacklist_action->message_type())); | 
 |     element.SetKey("message_url", | 
 |                    base::Value(program.blacklist_action->message_url())); | 
 |  | 
 |     result.SetKey(base::UTF16ToUTF8(program.info.name), std::move(element)); | 
 |   } | 
 |  | 
 |   return result; | 
 | } | 
 |  | 
 | // Deserializes a ProblematicProgram named |name| from |value|. Returns null if | 
 | // |value| is not a dict containing all required fields. | 
 | std::unique_ptr<ProblematicProgramsUpdater::ProblematicProgram> | 
 | ConvertToProblematicProgram(const std::string& name, const base::Value& value) { | 
 |   if (!value.is_dict()) | 
 |     return nullptr; | 
 |  | 
 |   const base::Value* registry_is_hkcu_value = | 
 |       value.FindKeyOfType("registry_is_hkcu", base::Value::Type::BOOLEAN); | 
 |   const base::Value* registry_key_path_value = | 
 |       value.FindKeyOfType("registry_key_path", base::Value::Type::STRING); | 
 |   const base::Value* registry_wow64_access_value = | 
 |       value.FindKeyOfType("registry_wow64_access", base::Value::Type::INTEGER); | 
 |   const base::Value* allow_load_value = | 
 |       value.FindKeyOfType("allow_load", base::Value::Type::BOOLEAN); | 
 |   const base::Value* type_value = | 
 |       value.FindKeyOfType("type", base::Value::Type::INTEGER); | 
 |   const base::Value* message_url_value = | 
 |       value.FindKeyOfType("message_url", base::Value::Type::STRING); | 
 |  | 
 |   // All of the above are required for a valid program. | 
 |   if (!registry_is_hkcu_value || !registry_key_path_value || | 
 |       !registry_wow64_access_value || !allow_load_value || !type_value || | 
 |       !message_url_value) { | 
 |     return nullptr; | 
 |   } | 
 |  | 
 |   InstalledPrograms::ProgramInfo program_info = { | 
 |       base::UTF8ToUTF16(name), | 
 |       registry_is_hkcu_value->GetBool() ? HKEY_CURRENT_USER | 
 |                                         : HKEY_LOCAL_MACHINE, | 
 |       base::UTF8ToUTF16(registry_key_path_value->GetString()), | 
 |       static_cast<REGSAM>(registry_wow64_access_value->GetInt())}; | 
 |  | 
 |   auto blacklist_action = | 
 |       std::make_unique<chrome::conflicts::BlacklistAction>(); | 
 |   blacklist_action->set_allow_load(allow_load_value->GetBool()); | 
 |   blacklist_action->set_message_type( | 
 |       static_cast<chrome::conflicts::BlacklistMessageType>( | 
 |           type_value->GetInt())); | 
 |   blacklist_action->set_message_url(message_url_value->GetString()); | 
 |  | 
 |   return std::make_unique<ProblematicProgramsUpdater::ProblematicProgram>( | 
 |       std::move(program_info), std::move(blacklist_action)); | 
 | } | 
 |  | 
 | // Returns true if |program| references an existing program in the registry. | 
 | // | 
 | // Used to filter out stale programs from the cache. This can happen if a | 
 | // program was uninstalled between the time it was found and Chrome was | 
 | // relaunched. | 
 | bool IsValidProgram( | 
 |     const ProblematicProgramsUpdater::ProblematicProgram& program) { | 
 |   return base::win::RegKey(program.info.registry_root, | 
 |                            program.info.registry_key_path.c_str(), | 
 |                            KEY_QUERY_VALUE | program.info.registry_wow64_access) | 
 |       .Valid(); | 
 | } | 
 |  | 
 | // Clears the cache of all the programs whose name is in |state_program_names|. | 
 | void RemoveStalePrograms(const std::vector<std::string>& stale_program_names) { | 
 |   // Early exit because DictionaryPrefUpdate will write to the pref even if it | 
 |   // doesn't contain a value. | 
 |   if (stale_program_names.empty()) | 
 |     return; | 
 |  | 
 |   DictionaryPrefUpdate update(g_browser_process->local_state(), | 
 |                               prefs::kProblematicPrograms); | 
 |   base::Value* existing_programs = update.Get(); | 
 |  | 
 |   for (const auto& program_name : stale_program_names) { | 
 |     bool removed = existing_programs->RemoveKey(program_name); | 
 |     DCHECK(removed); | 
 |   } | 
 | } | 
 |  | 
 | // Applies the given |function| object to each valid ProblematicProgram found | 
 | // in the kProblematicPrograms preference. | 
 | // | 
 | // The signature of the function must be equivalent to the following: | 
 | //   bool Function(std::unique_ptr<ProblematicProgram> program)); | 
 | // | 
 | // The return value of |function| indicates if the enumeration should continue | 
 | // (true) or be stopped (false). | 
 | // | 
 | // This function takes care of removing invalid entries that are found during | 
 | // the enumeration. | 
 | template <class UnaryFunction> | 
 | void EnumerateAndTrimProblematicPrograms(UnaryFunction function) { | 
 |   std::vector<std::string> stale_program_names; | 
 |   for (const auto& item : g_browser_process->local_state() | 
 |                               ->FindPreference(prefs::kProblematicPrograms) | 
 |                               ->GetValue() | 
 |                               ->DictItems()) { | 
 |     auto program = ConvertToProblematicProgram(item.first, item.second); | 
 |  | 
 |     if (!program || !IsValidProgram(*program)) { | 
 |       // Mark every invalid program as stale so they are removed from the cache. | 
 |       stale_program_names.push_back(item.first); | 
 |       continue; | 
 |     } | 
 |  | 
 |     // Notify the caller and stop the enumeration if requested by the function. | 
 |     if (!function(std::move(program))) | 
 |       break; | 
 |   } | 
 |  | 
 |   RemoveStalePrograms(stale_program_names); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | // ProblematicProgram ---------------------------------------------------------- | 
 |  | 
 | ProblematicProgramsUpdater::ProblematicProgram::ProblematicProgram( | 
 |     InstalledPrograms::ProgramInfo info, | 
 |     std::unique_ptr<chrome::conflicts::BlacklistAction> blacklist_action) | 
 |     : info(std::move(info)), blacklist_action(std::move(blacklist_action)) {} | 
 |  | 
 | ProblematicProgramsUpdater::ProblematicProgram::~ProblematicProgram() = default; | 
 |  | 
 | ProblematicProgramsUpdater::ProblematicProgram::ProblematicProgram( | 
 |     ProblematicProgram&& problematic_program) = default; | 
 |  | 
 | ProblematicProgramsUpdater::ProblematicProgram& | 
 | ProblematicProgramsUpdater::ProblematicProgram::operator=( | 
 |     ProblematicProgram&& problematic_program) = default; | 
 |  | 
 | // ProblematicProgramsUpdater -------------------------------------------------- | 
 |  | 
 | ProblematicProgramsUpdater::ProblematicProgramsUpdater( | 
 |     const CertificateInfo& exe_certificate_info, | 
 |     const ModuleListFilter& module_list_filter, | 
 |     const InstalledPrograms& installed_programs) | 
 |     : exe_certificate_info_(exe_certificate_info), | 
 |       module_list_filter_(module_list_filter), | 
 |       installed_programs_(installed_programs) { | 
 |   DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
 | } | 
 |  | 
 | ProblematicProgramsUpdater::~ProblematicProgramsUpdater() = default; | 
 |  | 
 | // static | 
 | void ProblematicProgramsUpdater::RegisterLocalStatePrefs( | 
 |     PrefRegistrySimple* registry) { | 
 |   registry->RegisterDictionaryPref(prefs::kProblematicPrograms); | 
 | } | 
 |  | 
 | // static | 
 | bool ProblematicProgramsUpdater::IsIncompatibleApplicationsWarningEnabled() { | 
 |   return ModuleDatabase::GetInstance() && | 
 |          ModuleDatabase::GetInstance()->third_party_conflicts_manager(); | 
 | } | 
 |  | 
 | // static | 
 | bool ProblematicProgramsUpdater::HasCachedPrograms() { | 
 |   DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
 |  | 
 |   bool found_valid_program = false; | 
 |  | 
 |   EnumerateAndTrimProblematicPrograms( | 
 |       [&found_valid_program](std::unique_ptr<ProblematicProgram> program) { | 
 |         found_valid_program = true; | 
 |  | 
 |         // Break the enumeration. | 
 |         return false; | 
 |       }); | 
 |  | 
 |   return found_valid_program; | 
 | } | 
 |  | 
 | // static | 
 | std::vector<ProblematicProgramsUpdater::ProblematicProgram> | 
 | ProblematicProgramsUpdater::GetCachedPrograms() { | 
 |   DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
 |  | 
 |   std::vector<ProblematicProgram> valid_programs; | 
 |  | 
 |   EnumerateAndTrimProblematicPrograms( | 
 |       [&valid_programs](std::unique_ptr<ProblematicProgram> program) { | 
 |         valid_programs.push_back(std::move(*program)); | 
 |  | 
 |         // Continue the enumeration. | 
 |         return true; | 
 |       }); | 
 |  | 
 |   return valid_programs; | 
 | } | 
 |  | 
 | void ProblematicProgramsUpdater::OnNewModuleFound( | 
 |     const ModuleInfoKey& module_key, | 
 |     const ModuleInfoData& module_data) { | 
 |   DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
 |  | 
 |   // Only consider loaded modules that are not shell extensions or IMEs. | 
 |   static constexpr uint32_t kModuleTypesBitmask = | 
 |       ModuleInfoData::kTypeLoadedModule | ModuleInfoData::kTypeShellExtension | | 
 |       ModuleInfoData::kTypeIme; | 
 |   if ((module_data.module_types & kModuleTypesBitmask) != | 
 |       ModuleInfoData::kTypeLoadedModule) { | 
 |     return; | 
 |   } | 
 |  | 
 |   // Explicitly whitelist modules whose signing cert's Subject field matches the | 
 |   // one in the current executable. No attempt is made to check the validity of | 
 |   // module signatures or of signing certs. | 
 |   if (exe_certificate_info_.type != CertificateType::NO_CERTIFICATE && | 
 |       exe_certificate_info_.subject == | 
 |           module_data.inspection_result->certificate_info.subject) { | 
 |     return; | 
 |   } | 
 |  | 
 |   // Skip modules whitelisted by the Module List component. | 
 |   if (module_list_filter_.IsWhitelisted(module_key, module_data)) | 
 |     return; | 
 |  | 
 |   // Also skip a module if it cannot be associated with an installed program | 
 |   // on the user's computer. | 
 |   std::vector<InstalledPrograms::ProgramInfo> associated_programs; | 
 |   if (!installed_programs_.GetInstalledPrograms(module_key.module_path, | 
 |                                                 &associated_programs)) { | 
 |     return; | 
 |   } | 
 |  | 
 |   std::unique_ptr<chrome::conflicts::BlacklistAction> blacklist_action = | 
 |       module_list_filter_.IsBlacklisted(module_key, module_data); | 
 |   if (!blacklist_action) { | 
 |     // The default behavior is to suggest to uninstall. | 
 |     blacklist_action = std::make_unique<chrome::conflicts::BlacklistAction>(); | 
 |     blacklist_action->set_allow_load(true); | 
 |     blacklist_action->set_message_type( | 
 |         chrome::conflicts::BlacklistMessageType::UNINSTALL); | 
 |     blacklist_action->set_message_url(std::string()); | 
 |   } | 
 |  | 
 |   for (auto&& associated_program : associated_programs) { | 
 |     problematic_programs_.emplace_back( | 
 |         std::move(associated_program), | 
 |         std::make_unique<chrome::conflicts::BlacklistAction>( | 
 |             *blacklist_action)); | 
 |   } | 
 | } | 
 |  | 
 | void ProblematicProgramsUpdater::OnModuleDatabaseIdle() { | 
 |   DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
 |  | 
 |   // On the first call to OnModuleDatabaseIdle(), the previous value must always | 
 |   // be overwritten. | 
 |   if (before_first_idle_) | 
 |     g_browser_process->local_state()->ClearPref(prefs::kProblematicPrograms); | 
 |   before_first_idle_ = false; | 
 |  | 
 |   // If there is no new problematic programs, there is nothing to do. | 
 |   if (problematic_programs_.empty()) | 
 |     return; | 
 |  | 
 |   // The conversion of the accumulated programs to a json dictionary takes care | 
 |   // of eliminating duplicates. | 
 |   base::Value new_programs = ConvertToDictionary(problematic_programs_); | 
 |   problematic_programs_.clear(); | 
 |  | 
 |   // Update the existing dictionary. | 
 |   DictionaryPrefUpdate update(g_browser_process->local_state(), | 
 |                               prefs::kProblematicPrograms); | 
 |   base::Value* existing_programs = update.Get(); | 
 |   for (auto&& element : new_programs.DictItems()) { | 
 |     existing_programs->SetKey(std::move(element.first), | 
 |                               std::move(element.second)); | 
 |   } | 
 | } |