1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163

content / browser / interest_group / interest_group_permissions_cache.h [blame]

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

#ifndef CONTENT_BROWSER_INTEREST_GROUP_INTEREST_GROUP_PERMISSIONS_CACHE_H_
#define CONTENT_BROWSER_INTEREST_GROUP_INTEREST_GROUP_PERMISSIONS_CACHE_H_

#include "content/common/content_export.h"

#include <map>
#include <memory>
#include <tuple>

#include "base/containers/lru_cache.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "net/base/network_isolation_key.h"
#include "url/origin.h"

namespace content {

// Cache of per-origin join/leave interest group permissions, partitioned by
// NetworkIsolationKey and initiating frame origin. To minimize leaks across
// first party contexts, uses a separate LRU cache "shard" for each {frame
// origin, NetworkIsolationKey} pair. Each LRU cache has a maximum number of
// entries, but there's no global limit, to prevent leaks. The short entry
// lifetime and delay in creating entries, along with the LRU size cap, should
// keep memory usage low.
//
// In addition to the LRU-ness of the cache, individual entries are passively
// expired (i.e., no longer returned, but not proactively deleted) after
// `kCacheDuration` has passed since they were added to the cache. Cache shards
// are garbage collected on a timer, at some point after all their entries have
// expired. Expired entries and shards are also deleted on access, which isn't
// strictly necessary.
//
// Permissions are typically learned from .well-known fetches, and should be
// cached by the network process, depending on HTTP response headers, but
// caching it in the browser process results in much better performance for
// sites that want to join (or, more likely, leave) large numbers of
// cross-origin interest groups, due to the limit of outstanding joins/leaves in
// the renderer process, and overhead on accessing the network stack's HTTP
// cache.
class CONTENT_EXPORT InterestGroupPermissionsCache {
 public:
  // Cache duration for cache entries. Cache shards expire when all their
  // individual entries expire, so this is also the expiration duration of
  // cache shards, relative to last added entry.
  static constexpr base::TimeDelta kCacheDuration = base::Minutes(1);

  // How often expired cache shards are evicted. Expired but not-yet-evicted
  // entries are not returned.
  static constexpr base::TimeDelta kDeleteExpiredTimerDuration =
      2 * kCacheDuration;

  // The maximum number of cache entries for each cache shard. There is no
  // global maximum, to prevent that from becoming a sidechannel.
  static constexpr int kMaxCacheEntriesPerShard = 50;

  // Permissions associated with an interest group origin.
  struct Permissions {
    bool can_join = false;
    bool can_leave = false;

    // Comparison operators are only useful for testing.

    bool operator==(Permissions permissions) const {
      return can_join == permissions.can_join &&
             can_leave == permissions.can_leave;
    }

    bool operator!=(Permissions permissions) const {
      return !(*this == permissions);
    }
  };

  InterestGroupPermissionsCache();
  InterestGroupPermissionsCache(InterestGroupPermissionsCache&) = delete;
  ~InterestGroupPermissionsCache();

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

  // Retrieves unexpired cached Permissions that `interest_group_owner` has
  // granted to `frame_origin`, learned in the context of
  // `network_isolation_key`.
  Permissions* GetPermissions(
      const url::Origin& frame_origin,
      const url::Origin& interest_group_owner,
      const net::NetworkIsolationKey& network_isolation_key);

  // Adds `permissions` to the cache as the set of permissions
  // `interest_group_owner` has granted to `frame_origin`, learned in the
  // context of `network_isolation_key`.
  void CachePermissions(Permissions permissions,
                        const url::Origin& frame_origin,
                        const url::Origin& interest_group_owner,
                        const net::NetworkIsolationKey& network_isolation_key);

  // Deletes all cache entries.
  void Clear();

  // Returns the number of non-deleted cache shards, to test cleanup logic. All
  // values in a cache shard may be expired, since expired cache shards are only
  // deleted on access and on a timer.
  size_t cache_shards_for_testing() const;

 private:
  // An entry in the cache.
  struct CacheEntry {
    base::TimeTicks expiry;
    Permissions permissions;
  };

  // An independent shard of the cache. Each shard must have no impact on the
  // others, including when their entries are observably evicted.
  struct CacheShard {
    CacheShard();
    CacheShard(CacheShard&&);
    ~CacheShard();

    // Last expiration time of any of the shard's CacheEntry.
    base::TimeTicks expiry;

    // Map of interest group owner origins to CacheEntries. A std::unique_ptr
    // wrapper is needed to make these movable, so they can be put in a
    // std::map.
    std::unique_ptr<base::LRUCache<url::Origin, CacheEntry>> cache;
  };

  // Key indicating which cache shard to use.
  struct CacheShardKey {
    bool operator<(const CacheShardKey& other) const {
      return std::tie(frame_origin, network_isolation_key) <
             std::tie(other.frame_origin, other.network_isolation_key);
    }

    url::Origin frame_origin;
    net::NetworkIsolationKey network_isolation_key;
  };

  // Returns the specified cache shard, if it exists and is not expired. Deletes
  // the cache shard if it exists but is expired.
  CacheShard* FindShard(const url::Origin& frame_origin,
                        const net::NetworkIsolationKey& network_isolation_key,
                        base::TimeTicks now);

  // Starts a timer to invoke DeleteExpired(), if there are any cache shards and
  // the timer hasn't already been started.
  void MaybeStartDeleteExpiredTimer();

  // Walks through the cache shards, deleting any that have expired, and
  // restarts timer if there are any shards left. Expired LRU entries are not
  // deleted on an individual basis, except when accessed directly.
  void DeleteExpired();

  base::OneShotTimer delete_expired_timer_;
  std::map<CacheShardKey, CacheShard> cache_shards_;
};

}  // namespace content

#endif  // CONTENT_BROWSER_INTEREST_GROUP_INTEREST_GROUP_PERMISSIONS_CACHE_H_