]> git.ipfire.org Git - thirdparty/tor.git/commitdiff
Add check of MaxHSDirCacheBytes every hsdesc upload
authorMike Perry <mikeperry-git@torproject.org>
Tue, 3 Jun 2025 19:44:21 +0000 (19:44 +0000)
committerMike Perry <mikeperry-git@torproject.org>
Thu, 5 Jun 2025 15:13:01 +0000 (15:13 +0000)
src/feature/hs/hs_cache.c

index 08afc373645398ee3fd72b8452764a5f8cc7dc47..66aab2a5060016d874562d53276869f6bf80537f 100644 (file)
 
 #include "feature/nodelist/networkstatus_st.h"
 
+/**
+ * Spare room for 1000 descriptors when pruning cache to avoid thrashing
+ * and memory fragmentation. */
+#define HSCACHE_PRUNE_SPARE_ROOM (1000 * HS_DESC_MAX_LEN)
+
 /* Total counter of the cache size. */
 static size_t hs_cache_total_allocation = 0;
 
@@ -148,6 +153,28 @@ cache_store_v3_as_dir(hs_cache_dir_descriptor_t *desc)
 
   tor_assert(desc);
 
+  /* Check if we've exceeded the MaxHSDirCacheBytes limit after adding
+   * this descriptor. If so, prune excess bytes leaving room for more. */
+  const size_t max_cache_bytes = get_options()->MaxHSDirCacheBytes;
+  const size_t current_cache_bytes = hs_cache_get_total_allocation();
+  if (max_cache_bytes > 0 && current_cache_bytes > max_cache_bytes) {
+    /* We prune only 1000 descriptors worth of memory here because
+     * pruning is an expensive O(n^2) option to keep finding lowest
+     * download count descs. */
+    size_t bytes_to_remove = current_cache_bytes/2;
+    /* Ensure user didn't set a really low max hsdir cache vlue */
+    if (HSCACHE_PRUNE_SPARE_ROOM < max_cache_bytes) {
+      bytes_to_remove = current_cache_bytes -
+                         (max_cache_bytes - HSCACHE_PRUNE_SPARE_ROOM);
+    }
+    size_t removed = hs_cache_handle_oom(bytes_to_remove);
+    static ratelim_t hs_cache_oom_ratelim = RATELIM_INIT(600);
+    log_fn_ratelim(&hs_cache_oom_ratelim, LOG_NOTICE, LD_REND,
+               "HSDir cache exceeded limit (%zu > %zu bytes). "
+               "Pruned %zu bytes during an HS descriptor upload.",
+               current_cache_bytes, max_cache_bytes, removed);
+  }
+
   /* Verify if we have an entry in the cache for that key and if yes, check
    * if we should replace it? */
   cache_entry = lookup_v3_desc_as_dir(desc->key);
@@ -170,9 +197,13 @@ cache_store_v3_as_dir(hs_cache_dir_descriptor_t *desc)
     hs_cache_decrement_allocation(cache_get_dir_entry_size(cache_entry));
     cache_dir_desc_free(cache_entry);
   }
+
   /* Store the descriptor we just got. We are sure here that either we
    * don't have the entry or we have a newer descriptor and the old one
-   * has been removed from the cache. */
+   * has been removed from the cache. We do this *after* pruning
+   * other descriptors so that this descriptor is not immediately pruned,
+   * if new. This prevents probing to detect OOM threshholds via its
+   * absence. */
   store_v3_desc_as_dir(desc);
 
   /* Update our total cache size with this entry for the OOM. This uses the