tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

LocalStorageCache.h (11841B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #ifndef mozilla_dom_LocalStorageCache_h
      8 #define mozilla_dom_LocalStorageCache_h
      9 
     10 #include "mozilla/Atomics.h"
     11 #include "mozilla/Monitor.h"
     12 #include "nsHashKeys.h"
     13 #include "nsIPrincipal.h"
     14 #include "nsString.h"
     15 #include "nsTHashMap.h"
     16 
     17 namespace mozilla::dom {
     18 
     19 class LocalStorage;
     20 class LocalStorageCacheChild;
     21 class LocalStorageManager;
     22 class StorageUsage;
     23 class StorageDBBridge;
     24 
     25 // Interface class on which only the database or IPC may call.
     26 // Used to populate the cache with DB data.
     27 class LocalStorageCacheBridge {
     28 public:
     29  NS_IMETHOD_(MozExternalRefCountType) AddRef(void);
     30  NS_IMETHOD_(void) Release(void);
     31 
     32  // The origin of the cache, result is concatenation of OriginNoSuffix() and
     33  // OriginSuffix(), see below.
     34  virtual const nsCString Origin() const = 0;
     35 
     36  // The origin attributes suffix alone, this is usually passed as an
     37  // |aOriginSuffix| argument to various methods
     38  virtual const nsCString& OriginSuffix() const = 0;
     39 
     40  // The origin in the database usage format (reversed) and without the suffix
     41  virtual const nsCString& OriginNoSuffix() const = 0;
     42 
     43  // Whether the cache is already fully loaded
     44  virtual bool Loaded() = 0;
     45 
     46  // How many items has so far been loaded into the cache, used
     47  // for optimization purposes
     48  virtual uint32_t LoadedCount() = 0;
     49 
     50  // Called by the database to load a key and its value to the cache
     51  virtual bool LoadItem(const nsAString& aKey, const nsAString& aValue) = 0;
     52 
     53  // Called by the database after all keys and values has been loaded
     54  // to this cache
     55  virtual void LoadDone(nsresult aRv) = 0;
     56 
     57  // Use to synchronously wait until the cache gets fully loaded with data,
     58  // this method exits after LoadDone has been called
     59  virtual void LoadWait() = 0;
     60 
     61 protected:
     62  virtual ~LocalStorageCacheBridge() = default;
     63 
     64  ThreadSafeAutoRefCnt mRefCnt;
     65  NS_DECL_OWNINGTHREAD
     66 };
     67 
     68 // Implementation of scope cache that is responsible for preloading data
     69 // for persistent storage (localStorage) and hold data for non-private,
     70 // private and session-only cookie modes.  It is also responsible for
     71 // persisting data changes using the database, works as a write-back cache.
     72 class LocalStorageCache : public LocalStorageCacheBridge {
     73 public:
     74  void AssertIsOnOwningThread() const { NS_ASSERT_OWNINGTHREAD(LocalStorage); }
     75 
     76  void SetActor(LocalStorageCacheChild* aActor);
     77 
     78  void ClearActor() {
     79    AssertIsOnOwningThread();
     80 
     81    mActor = nullptr;
     82  }
     83 
     84  NS_IMETHOD_(void) Release(void) override;
     85 
     86  enum MutationSource {
     87    // The mutation is a result of an explicit JS mutation in this process.
     88    // The mutation should be sent to the sDatabase. Quota will be checked and
     89    // QuotaExceededError may be returned without the mutation being applied.
     90    ContentMutation,
     91    // The mutation initially was triggered in a different process and is being
     92    // propagated to this cache via LocalStorage::ApplyEvent.  The mutation
     93    // should
     94    // not be sent to the sDatabase because the originating process is already
     95    // doing that.  (In addition to the redundant writes being wasteful, there
     96    // is the potential for other processes to see inconsistent state from the
     97    // database while preloading.)  Quota will be updated but not checked
     98    // because it's assumed it was checked in another process and data-coherency
     99    // is more important than slightly exceeding quota.
    100    E10sPropagated
    101  };
    102 
    103  // Note: We pass aOriginNoSuffix through the ctor here, because
    104  // LocalStorageCacheHashKey's ctor is creating this class and
    105  // accepts reversed-origin-no-suffix as an argument - the hashing key.
    106  explicit LocalStorageCache(const nsACString* aOriginNoSuffix);
    107 
    108 protected:
    109  virtual ~LocalStorageCache();
    110 
    111 public:
    112  void Init(LocalStorageManager* aManager, bool aPersistent,
    113            nsIPrincipal* aPrincipal, const nsACString& aQuotaOriginScope);
    114 
    115  // Get size of per-origin data.
    116  int64_t GetOriginQuotaUsage(const LocalStorage* aStorage) const;
    117 
    118  // Starts async preload of this cache if it persistent and not loaded.
    119  void Preload();
    120 
    121  // The set of methods that are invoked by DOM storage web API.
    122  // We are passing the LocalStorage object just to let the cache
    123  // read properties like mPrivate and mSessionOnly.
    124  // Get* methods return error when load from the database has failed.
    125  nsresult GetLength(const LocalStorage* aStorage, uint32_t* aRetval);
    126  nsresult GetKey(const LocalStorage* aStorage, uint32_t index,
    127                  nsAString& aRetval);
    128  nsresult GetItem(const LocalStorage* aStorage, const nsAString& aKey,
    129                   nsAString& aRetval);
    130  nsresult SetItem(const LocalStorage* aStorage, const nsAString& aKey,
    131                   const nsAString& aValue, nsString& aOld,
    132                   const MutationSource aSource = ContentMutation);
    133  nsresult RemoveItem(const LocalStorage* aStorage, const nsAString& aKey,
    134                      nsString& aOld,
    135                      const MutationSource aSource = ContentMutation);
    136  nsresult Clear(const LocalStorage* aStorage,
    137                 const MutationSource aSource = ContentMutation);
    138 
    139  void GetKeys(const LocalStorage* aStorage, nsTArray<nsString>& aKeys);
    140 
    141  // LocalStorageCacheBridge
    142 
    143  const nsCString Origin() const override;
    144  const nsCString& OriginNoSuffix() const override { return mOriginNoSuffix; }
    145  const nsCString& OriginSuffix() const override { return mOriginSuffix; }
    146  bool Loaded() override { return mLoaded; }
    147  uint32_t LoadedCount() override;
    148  bool LoadItem(const nsAString& aKey, const nsAString& aValue) override;
    149  void LoadDone(nsresult aRv) override;
    150  void LoadWait() override;
    151 
    152  // Cache keeps 3 sets of data: regular, private and session-only.
    153  // This class keeps keys and values for a set and also caches
    154  // size of the data for quick per-origin quota checking.
    155  class Data {
    156   public:
    157    Data() : mOriginQuotaUsage(0) {}
    158    int64_t mOriginQuotaUsage;
    159    nsTHashMap<nsStringHashKey, nsString> mKeys;
    160  };
    161 
    162 public:
    163  // Number of data sets we keep: default, session
    164  static const uint32_t kDataSetCount = 2;
    165 
    166 private:
    167  // API to clear the cache data, this is invoked by chrome operations
    168  // like cookie deletion.
    169  friend class LocalStorageManager;
    170 
    171  static const uint32_t kUnloadDefault = 1 << 0;
    172  static const uint32_t kUnloadSession = 1 << 1;
    173  static const uint32_t kUnloadComplete = kUnloadDefault | kUnloadSession;
    174 
    175 #ifdef DOM_STORAGE_TESTS
    176  static const uint32_t kTestReload = 1 << 15;
    177 #endif
    178 
    179  void UnloadItems(uint32_t aUnloadFlags);
    180 
    181 private:
    182  // Synchronously blocks until the cache is fully loaded from the database
    183  void WaitForPreload();
    184 
    185  // Helper to get one of the 3 data sets (regular, private, session)
    186  Data& DataSet(const LocalStorage* aStorage);
    187 
    188  // Used for firing storage events and synchronization of caches in other
    189  // content processes.
    190  void NotifyObservers(const LocalStorage* aStorage, const nsAString& aKey,
    191                       const nsAString& aOldValue, const nsAString& aNewValue);
    192 
    193  // Whether the storage change is about to persist
    194  bool Persist(const LocalStorage* aStorage) const;
    195 
    196  // Changes the quota usage on the given data set if it fits the quota.
    197  // If not, then false is returned and no change to the set must be done.
    198  // A special case is if aSource==E10sPropagated, then we will return true even
    199  // if the change would put us over quota.  This is done to ensure coherency of
    200  // caches between processes in the face of races.  It does allow an attacker
    201  // to potentially use N multiples of the quota storage limit if they can
    202  // arrange for their origin to execute code in N processes.  However, this is
    203  // not considered a particularly concerning threat model because it's already
    204  // very possible for a rogue page to attempt to intentionally fill up the
    205  // user's storage through the use of multiple domains.
    206  bool ProcessUsageDelta(uint32_t aGetDataSetIndex, const int64_t aDelta,
    207                         const MutationSource aSource = ContentMutation);
    208  bool ProcessUsageDelta(const LocalStorage* aStorage, const int64_t aDelta,
    209                         const MutationSource aSource = ContentMutation);
    210 
    211 private:
    212  // When a cache is reponsible for its life time (in case of localStorage data
    213  // cache) we need to refer our manager since removal of the cache from the
    214  // hash table is handled in the destructor by call to the manager.  Cache
    215  // could potentially overlive the manager, hence the hard ref.
    216  RefPtr<LocalStorageManager> mManager;
    217 
    218  // Reference to the usage counter object we check on for eTLD+1 quota limit.
    219  // Obtained from the manager during initialization (Init method).
    220  RefPtr<StorageUsage> mUsage;
    221 
    222  // The LocalStorageCacheChild is created at the same time of this class.
    223  // In normal operation, the actor will be synchronously cleared in our
    224  // destructor when we tell it to delete itself.  In a shutdown-related edge
    225  // case in the parent process for JSM's, it is possible for the actor to be
    226  // destroyed while this class remains alive, in which case it will be nulled
    227  // out.
    228  LocalStorageCacheChild* mActor;
    229 
    230  // The origin this cache belongs to in the "DB format", i.e. reversed
    231  nsCString mOriginNoSuffix;
    232 
    233  // The origin attributes suffix
    234  nsCString mOriginSuffix;
    235 
    236  // The eTLD+1 scope used to count quota usage.  It is in the reversed format
    237  // and contains the origin attributes suffix.
    238  nsCString mQuotaOriginScope;
    239 
    240  // Non-private Browsing, Private Browsing and Session Only sets.
    241  Data mData[kDataSetCount];
    242 
    243  // This monitor is used to wait for full load of data.
    244  mozilla::Monitor mMonitor MOZ_UNANNOTATED;
    245 
    246  // Flag that is initially false.  When the cache is about to work with
    247  // the database (i.e. it is persistent) this flags is set to true after
    248  // all keys and coresponding values are loaded from the database.
    249  // This flag never goes from true back to false.  Since this flag is
    250  // critical for mData hashtable synchronization, it's made atomic.
    251  Atomic<bool, ReleaseAcquire> mLoaded;
    252 
    253  // Result of load from the database.  Valid after mLoaded flag has been set.
    254  nsresult mLoadResult;
    255 
    256  // Expected to be only 0 or 1.
    257  uint32_t mPrivateBrowsingId;
    258 
    259  // Init() method has been called
    260  bool mInitialized : 1;
    261 
    262  // This cache is about to be bound with the database (i.e. it has
    263  // to load from the DB first and has to persist when modifying the
    264  // default data set.)
    265  bool mPersistent : 1;
    266 
    267  // Whether we have already captured state of the cache preload on our first
    268  // access.
    269  bool mPreloadTelemetryRecorded : 1;
    270 };
    271 
    272 // StorageUsage
    273 // Infrastructure to manage and check eTLD+1 quota
    274 class StorageUsageBridge {
    275 public:
    276  NS_INLINE_DECL_THREADSAFE_VIRTUAL_REFCOUNTING(StorageUsageBridge)
    277 
    278  virtual const nsCString& OriginScope() = 0;
    279  virtual void LoadUsage(const int64_t aUsage) = 0;
    280 
    281 protected:
    282  // Protected destructor, to discourage deletion outside of Release():
    283  virtual ~StorageUsageBridge() = default;
    284 };
    285 
    286 class StorageUsage : public StorageUsageBridge {
    287 public:
    288  explicit StorageUsage(const nsACString& aOriginScope);
    289 
    290  bool CheckAndSetETLD1UsageDelta(
    291      uint32_t aDataSetIndex, int64_t aUsageDelta,
    292      const LocalStorageCache::MutationSource aSource);
    293 
    294 private:
    295  const nsCString& OriginScope() override { return mOriginScope; }
    296  void LoadUsage(const int64_t aUsage) override;
    297 
    298  nsCString mOriginScope;
    299  int64_t mUsage[LocalStorageCache::kDataSetCount];
    300 };
    301 
    302 }  // namespace mozilla::dom
    303 
    304 #endif  // mozilla_dom_LocalStorageCache_h