chromium/chrome/browser/win/conflicts/module_blocklist_cache_updater.h

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

#ifndef CHROME_BROWSER_WIN_CONFLICTS_MODULE_BLOCKLIST_CACHE_UPDATER_H_
#define CHROME_BROWSER_WIN_CONFLICTS_MODULE_BLOCKLIST_CACHE_UPDATER_H_

#include <vector>

#include "base/containers/flat_map.h"
#include "base/files/file_path.h"
#include "base/functional/callback.h"
#include "base/hash/md5.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "chrome/browser/win/conflicts/module_database_observer.h"
#include "chrome/browser/win/conflicts/proto/module_list.pb.h"
#include "chrome/chrome_elf/third_party_dlls/packed_list_format.h"

class ModuleListFilter;
struct CertificateInfo;

namespace base {
class SequencedTaskRunner;
}

// This class is responsible for maintaining the module blocklist cache, which
// is used by chrome_elf.dll to determine which module to block from loading
// into the process.
//
// The cache is updated everytime the module database becomes idle and at least
// one module must be added to the cache.
//
//
// Additional implementation details about the module blocklist cache file:
//
// Because the file is written under the User Data directory, it is not possible
// for chrome_elf to find it by itself (see https://crbug.com/748949). So the
// path to the file is written into a registry key, which solves the problem by
// making the already expanded path available.
//
// A consequence of this solution is that in some circumstances, multiple
// browser process will race to write the path to their blocklist file into a
// single registry key because the same registry key is shared between all User
// Data directories.
//
// This means that a browser instance launch using one User Data directory can
// potentially use the cache from another User Data directory instead of their
// own.
//
// This is acceptable given that all the caches contains more or less the same
// information and are interchangeable. Also, updates to the cache file and to
// the registry are atomic, guaranteeing that chrome_elf always reads a valid
// blocklist.
class ModuleBlocklistCacheUpdater : public ModuleDatabaseObserver {
 public:
  // A decision that explains whether or not a module is to be blocked. This
  // decision is made with respect to the logic in the currently running
  // executable and the current version of the module list. It may not
  // correspond with the blocking decisions that were enforced by the blocking
  // logic in chrome_elf during early startup. Those decisions are cached from
  // the previous browser launch which may be with respect to (a) an entirely
  // different version of the browser and/or (b) an entirely different version
  // of the module list.
  //
  // Note that this enum is very similar to the ModuleWarningDecision in
  // IncompatibleApplicationsUpdater. This is done so that it is easier to keep
  // the 2 features separate, as they can be independently enabled/disabled.
  enum class ModuleBlockingDecision {
    // No decision was taken yet for the module.
    kUnknown,

    // Detailed reasons why modules will be allowed to load in subsequent
    // startups.

    // A shell extension or IME that is not loaded in the process yet.
    kNotLoaded,
    // A module that is loaded into a process type where third-party modules are
    // explicitly allowed.
    kAllowedInProcessType,
    // Input method editors are allowed.
    kAllowedIME,
    // Allowed because the certificate's subject of the module matches the
    // certificate's subject of the executable. The certificate is not
    // validated.
    kAllowedSameCertificate,
    // Allowed because the path of the executable is the parent of the path of
    // the module. Only used in non-official builds.
    kAllowedSameDirectory,
    // Allowed because it is signed by Microsoft. The certificate is not
    // validated.
    kAllowedMicrosoft,
    // Explicitly allowed by the Module List component.
    kAllowedAllowlisted,
    // Module analysis was interrupted using DisableModuleAnalysis(). This
    // module will not be added to the cache.
    kNotAnalyzed,
    // New "allowed" reasons should be added here!

    // Unwanted, but allowed to load by the Module List component. This is
    // usually because blocking the module would cause more stability issues
    // than allowing it. If the IncompatibleApplicationsWarning feature is
    // enabled, this module will cause a warning if it can be tied back to an
    // installed application.
    kTolerated,

    // Detailed reasons why modules will not be allowed to load in subsequent
    // startups.

    // The module will be blocked because it is explicitly listed in the module
    // blocklist.
    kDisallowedExplicit,
    // The module will be implicitly blocked because it is not otherwise
    // allowed.
    kDisallowedImplicit,
    // New "disallowed" reasons should be added here!
  };

  struct ModuleBlockingState {
    // Whether or not the module was in the blocklist cache that existed at
    // startup.
    bool was_in_blocklist_cache = false;

    // Whether or not the module was ever actively blocked from loading during
    // this session.
    bool was_blocked = false;

    // Whether or not the module ever loaded during this session. Usually this
    // means that the module is currently loaded, but it's possible for DLLs to
    // subsequently be unloaded at runtime.
    bool was_loaded = false;

    // The current blocking decision. This is synced to the cache and will be
    // applied at the next startup.
    ModuleBlockingDecision blocking_decision = ModuleBlockingDecision::kUnknown;
  };

  struct CacheUpdateResult {
    base::MD5Digest old_md5_digest;
    base::MD5Digest new_md5_digest;
  };
  using OnCacheUpdatedCallback =
      base::RepeatingCallback<void(const CacheUpdateResult&)>;

  // Creates an instance of the updater. The callback will be invoked every time
  // the cache is updated.
  // The parameters must outlive the lifetime of this class.
  // The ModuleListFilter is taken by scoped_refptr since it will be used in a
  // background sequence.
  ModuleBlocklistCacheUpdater(
      ModuleDatabaseEventSource* module_database_event_source,
      const CertificateInfo& exe_certificate_info,
      scoped_refptr<ModuleListFilter> module_list_filter,
      const std::vector<third_party_dlls::PackedListModule>&
          initial_blocklisted_modules,
      OnCacheUpdatedCallback on_cache_updated_callback,
      bool module_analysis_disabled);

  ModuleBlocklistCacheUpdater(const ModuleBlocklistCacheUpdater&) = delete;
  ModuleBlocklistCacheUpdater& operator=(const ModuleBlocklistCacheUpdater&) =
      delete;

  ~ModuleBlocklistCacheUpdater() override;

  // Returns true if the blocking of third-party modules is enabled. Can be
  // called on any thread. Notably does not check the ThirdPartyBlockingEnabled
  // group policy.
  static bool IsBlockingEnabled();

  // Returns the path to the module blocklist cache.
  static base::FilePath GetModuleBlocklistCachePath();

  // Deletes the module blocklist cache. This disables the blocking of third-
  // party modules for the next browser launch.
  static void DeleteModuleBlocklistCache();

  // ModuleDatabaseObserver:
  void OnNewModuleFound(const ModuleInfoKey& module_key,
                        const ModuleInfoData& module_data) override;
  void OnKnownModuleLoaded(const ModuleInfoKey& module_key,
                           const ModuleInfoData& module_data) override;
  void OnModuleDatabaseIdle() override;

  // Returns the blocking decision for a module.
  const ModuleBlockingState& GetModuleBlockingState(
      const ModuleInfoKey& module_key) const;

  // Disables the analysis of newly found modules. This is a one way switch that
  // will apply until Chrome is restarted.
  void DisableModuleAnalysis();

 private:
  // The state of the module with respect to the ModuleList.
  enum class ModuleListState {
    // The module is not in the module list at all.
    kUnlisted,
    // The module is in the module list and is explicitly allowlisted.
    kAllowlisted,
    // The module is in the module list and "blocklisted", but loading is
    // tolerated.
    kTolerated,
    // The module is explicitly blocklisted.
    kBlocklisted,
  };

  // Gets the state of a module with respect to the module list.
  ModuleListState DetermineModuleListState(const ModuleInfoKey& module_key,
                                           const ModuleInfoData& module_data);

  // Determines whether or not a module *should* be allowlisted or blocklisted
  // on the next startup. Returns a ModuleBlockingDecision.
  ModuleBlockingDecision DetermineModuleBlockingDecision(
      const ModuleInfoKey& module_key,
      const ModuleInfoData& module_data);

  // Posts the task to update the cache on |background_sequence_|.
  void StartModuleBlocklistCacheUpdate();

  // Invoked on the sequence that owns this instance when the cache is updated.
  void OnModuleBlocklistCacheUpdated(const CacheUpdateResult& result);

  const raw_ptr<ModuleDatabaseEventSource> module_database_event_source_;

  const raw_ref<const CertificateInfo> exe_certificate_info_;
  scoped_refptr<ModuleListFilter> module_list_filter_;
  const raw_ref<const std::vector<third_party_dlls::PackedListModule>>
      initial_blocklisted_modules_;

  OnCacheUpdatedCallback on_cache_updated_callback_;

  // The sequence on which the module blocklist cache file is updated.
  scoped_refptr<base::SequencedTaskRunner> background_sequence_;

  // Temporarily holds newly blocklisted modules before they are added to the
  // module blocklist cache.
  std::vector<third_party_dlls::PackedListModule> newly_blocklisted_modules_;

  // Temporarily holds modules that were blocked from loading into the browser
  // until they are used to update the cache.
  std::vector<third_party_dlls::PackedListModule> blocked_modules_;

  // Holds the blocking state for all known modules.
  base::flat_map<ModuleInfoKey, ModuleBlockingState> module_blocking_states_;

  // Indicates if the analysis of newly found modules is disabled. Used as a
  // workaround for https://crbug.com/892294.
  bool module_analysis_disabled_;

  SEQUENCE_CHECKER(sequence_checker_);

  base::WeakPtrFactory<ModuleBlocklistCacheUpdater> weak_ptr_factory_;
};

#endif  // CHROME_BROWSER_WIN_CONFLICTS_MODULE_BLOCKLIST_CACHE_UPDATER_H_