[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[tor-commits] [tor] 08/77: hs: Setup service side PoW defenses



This is an automated email from the git hooks/post-receive script.

dgoulet pushed a commit to branch main
in repository tor.

commit ca74530b40aa893196de2f6cdde9bcaeec4d03c2
Author: David Goulet <dgoulet@xxxxxxxxxxxxxx>
AuthorDate: Tue Jun 28 13:43:35 2022 -0400

    hs: Setup service side PoW defenses
    
    Signed-off-by: David Goulet <dgoulet@xxxxxxxxxxxxxx>
---
 src/app/config/config.c       |   1 +
 src/feature/hs/hs_config.c    |   5 +
 src/feature/hs/hs_config.h    |   5 +
 src/feature/hs/hs_options.inc |   1 +
 src/feature/hs/hs_pow.c       |  12 +++
 src/feature/hs/hs_pow.h       |   1 +
 src/feature/hs/hs_service.c   | 229 ++++++++++++++++++++++++++++++++++++++++++
 src/feature/hs/hs_service.h   |  14 +++
 8 files changed, 268 insertions(+)

diff --git a/src/app/config/config.c b/src/app/config/config.c
index 66a8fe5853..e035c6d6f3 100644
--- a/src/app/config/config.c
+++ b/src/app/config/config.c
@@ -508,6 +508,7 @@ static const config_var_t option_vars_[] = {
       LINELIST_S, RendConfigLines, NULL),
   VAR("HiddenServiceOnionBalanceInstance",
       LINELIST_S, RendConfigLines, NULL),
+  VAR("HiddenServicePoWDefensesEnabled", LINELIST_S, RendConfigLines, NULL),
   VAR("HiddenServiceStatistics", BOOL, HiddenServiceStatistics_option, "1"),
   V(ClientOnionAuthDir,          FILENAME, NULL),
   OBSOLETE("CloseHSClientCircuitsImmediatelyOnTimeout"),
diff --git a/src/feature/hs/hs_config.c b/src/feature/hs/hs_config.c
index a76893fe1a..5600c40236 100644
--- a/src/feature/hs/hs_config.c
+++ b/src/feature/hs/hs_config.c
@@ -392,6 +392,11 @@ config_service_v3(const hs_opts_t *hs_opts,
     }
   }
 
+  /* Are the PoW anti-DoS defenses enabled? */
+  config->has_pow_defenses_enabled = hs_opts->HiddenServicePoWDefensesEnabled;
+  log_info(LD_REND, "Service PoW defenses are %s.",
+           config->has_pow_defenses_enabled ? "enabled" : "disabled");
+
   /* We do not load the key material for the service at this stage. This is
    * done later once tor can confirm that it is in a running state. */
 
diff --git a/src/feature/hs/hs_config.h b/src/feature/hs/hs_config.h
index b250c62c8b..578aa468e2 100644
--- a/src/feature/hs/hs_config.h
+++ b/src/feature/hs/hs_config.h
@@ -25,6 +25,11 @@
 #define HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_MIN 0
 #define HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_MAX INT32_MAX
 
+/* Default values for the HS anti-DoS PoW defenses. */
+#define HS_CONFIG_V3_POW_DEFENSES_DEFAULT 0
+#define HS_CONFIG_V3_POW_DEFENSES_MIN_EFFORT_DEFAULT 100
+#define HS_CONFIG_V3_POW_DEFENSES_SVC_BOTTOM_CAPACITY_DEFAULT 100
+
 /* API */
 
 int hs_config_service_all(const or_options_t *options, int validate_only);
diff --git a/src/feature/hs/hs_options.inc b/src/feature/hs/hs_options.inc
index d3ca688b46..2eb76db40f 100644
--- a/src/feature/hs/hs_options.inc
+++ b/src/feature/hs/hs_options.inc
@@ -31,5 +31,6 @@ CONF_VAR(HiddenServiceEnableIntroDoSDefense, BOOL, 0, "0")
 CONF_VAR(HiddenServiceEnableIntroDoSRatePerSec, POSINT, 0, "25")
 CONF_VAR(HiddenServiceEnableIntroDoSBurstPerSec, POSINT, 0, "200")
 CONF_VAR(HiddenServiceOnionBalanceInstance, BOOL, 0, "0")
+CONF_VAR(HiddenServicePoWDefensesEnabled, BOOL, 0, "0")
 
 END_CONF_STRUCT(hs_opts_t)
diff --git a/src/feature/hs/hs_pow.c b/src/feature/hs/hs_pow.c
index 2b36da93db..c24ea5e351 100644
--- a/src/feature/hs/hs_pow.c
+++ b/src/feature/hs/hs_pow.c
@@ -278,3 +278,15 @@ hs_pow_remove_seed_from_cache(uint32_t seed)
   HT_FOREACH_FN(nonce_cache_table_ht, &nonce_cache_table,
                 nonce_cache_entry_has_seed, &seed);
 }
+
+/** Free a given PoW service state. */
+void
+hs_pow_free_service_state(hs_pow_service_state_t *state)
+{
+  if (state == NULL) {
+    return;
+  }
+  smartlist_free(state->rend_request_pqueue);
+  mainloop_event_free(state->pop_pqueue_ev);
+  tor_free(state);
+}
diff --git a/src/feature/hs/hs_pow.h b/src/feature/hs/hs_pow.h
index 7f5e297470..679332e644 100644
--- a/src/feature/hs/hs_pow.h
+++ b/src/feature/hs/hs_pow.h
@@ -123,5 +123,6 @@ int hs_pow_verify(const hs_pow_service_state_t *pow_state,
                   const hs_pow_solution_t *pow_solution);
 
 void hs_pow_remove_seed_from_cache(uint32_t seed);
+void hs_pow_free_service_state(hs_pow_service_state_t *state);
 
 #endif /* !defined(TOR_HS_POW_H) */
diff --git a/src/feature/hs/hs_service.c b/src/feature/hs/hs_service.c
index 14cd9efec1..fe3860d05d 100644
--- a/src/feature/hs/hs_service.c
+++ b/src/feature/hs/hs_service.c
@@ -262,6 +262,46 @@ set_service_default_config(hs_service_config_t *c,
   c->has_dos_defense_enabled = HS_CONFIG_V3_DOS_DEFENSE_DEFAULT;
   c->intro_dos_rate_per_sec = HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_DEFAULT;
   c->intro_dos_burst_per_sec = HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_DEFAULT;
+  /* PoW default options. */
+  c->has_dos_defense_enabled = HS_CONFIG_V3_POW_DEFENSES_DEFAULT;
+  c->pow_min_effort = HS_CONFIG_V3_POW_DEFENSES_MIN_EFFORT_DEFAULT;
+  c->pow_svc_bottom_capacity =
+    HS_CONFIG_V3_POW_DEFENSES_SVC_BOTTOM_CAPACITY_DEFAULT;
+}
+
+/** Initialize PoW defenses */
+static void
+initialize_pow_defenses(hs_service_t *service)
+{
+  service->state.pow_state = tor_malloc_zero(sizeof(hs_pow_service_state_t));
+
+  /* Make life easier */
+  hs_pow_service_state_t *pow_state = service->state.pow_state;
+
+  pow_state->rend_request_pqueue = smartlist_new();
+  pow_state->pop_pqueue_ev = NULL;
+
+  pow_state->min_effort = service->config.pow_min_effort;
+
+  /* We recalculate and update the suggested effort every HS_UPDATE_PERIOD
+   * seconds. */
+  pow_state->suggested_effort = HS_POW_SUGGESTED_EFFORT_DEFAULT;
+  pow_state->svc_bottom_capacity = service->config.pow_svc_bottom_capacity;
+  pow_state->total_effort = 0;
+  pow_state->next_effort_update = (time(NULL) + HS_UPDATE_PERIOD);
+
+  /* Generate the random seeds. We generate both as we don't want the previous
+   * seed to be predictable even if it doesn't really exist yet, and it needs
+   * to be different to the current nonce for the replay cache scrubbing to
+   * function correctly. */
+  log_err(LD_REND, "Generating both PoW seeds...");
+  crypto_rand((char *)&pow_state->seed_current, HS_POW_SEED_LEN);
+  crypto_rand((char *)&pow_state->seed_previous, HS_POW_SEED_LEN);
+
+  pow_state->expiration_time =
+      (time(NULL) +
+       crypto_rand_int_range(HS_SERVICE_POW_SEED_ROTATE_TIME_MIN,
+                             HS_SERVICE_POW_SEED_ROTATE_TIME_MAX));
 }
 
 /** From a service configuration object config, clear everything from it
@@ -2366,6 +2406,89 @@ update_all_descriptors_intro_points(time_t now)
   } FOR_EACH_SERVICE_END;
 }
 
+/* XXX: Need to check with mikeperry. */
+/** Update or initialise PoW parameters in the descriptors if they do not
+ * reflect the current state of the PoW defenses. If the defenses have been
+ * disabled then remove the PoW parameters from the descriptors. */
+static void
+update_all_descriptors_pow_params(time_t now)
+{
+  FOR_EACH_SERVICE_BEGIN(service) {
+    int descs_updated = 0;
+    hs_pow_service_state_t *pow_state = service->state.pow_state;
+    hs_desc_encrypted_data_t *encrypted;
+    uint32_t previous_effort;
+
+    /* If PoW defenses have been disabled after previously being enabled, i.e
+     * via config change and SIGHUP, we need to remove the PoW parameters from
+     * the descriptors so clients stop attempting to solve the puzzle. */
+    FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+      if (!service->config.has_pow_defenses_enabled &&
+          desc->desc->encrypted_data.pow_params) {
+        log_info(LD_REND, "PoW defenses have been disabled, clearing "
+                         "pow_params from a descriptor.");
+        tor_free(desc->desc->encrypted_data.pow_params);
+        /* Schedule for upload here as we can skip the following checks as PoW
+         * defenses are disabled. */
+        service_desc_schedule_upload(desc, now, 1);
+      }
+    } FOR_EACH_DESCRIPTOR_END;
+
+    /* Skip remaining checks if this service does not have PoW defenses
+     * enabled. */
+    if (!service->config.has_pow_defenses_enabled) {
+      continue;
+    }
+
+    FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+      encrypted = &desc->desc->encrypted_data;
+      /* If this is a new service or PoW defenses were just enabled we need to
+       * initialise pow_params in the descriptors. If this runs the next if
+       * statement will run and set the correct values. */
+      if (!encrypted->pow_params) {
+        log_err(LD_REND, "Initializing pow_params in descriptor...");
+        encrypted->pow_params = tor_malloc_zero(sizeof(hs_pow_desc_params_t));
+      }
+
+      /* Update the descriptor if it doesn't reflect the current pow_state, for
+       * example if the defenses have just been enabled or refreshed due to a
+       * SIGHUP. HRPR TODO: Don't check using expiration time? */
+      if (encrypted->pow_params->expiration_time !=
+          pow_state->expiration_time) {
+        encrypted->pow_params->type = 0; /* use first version in the list */
+        memcpy(encrypted->pow_params->seed, &pow_state->seed_current,
+               HS_POW_SEED_LEN);
+        encrypted->pow_params->suggested_effort = pow_state->suggested_effort;
+        encrypted->pow_params->expiration_time = pow_state->expiration_time;
+        descs_updated = 1;
+      }
+
+      /* Services SHOULD NOT upload a new descriptor if the suggested
+       * effort value changes by less than 15 percent. */
+      previous_effort = encrypted->pow_params->suggested_effort;
+      if (pow_state->suggested_effort <= previous_effort * 0.85 ||
+          previous_effort * 1.15 <= pow_state->suggested_effort) {
+        log_info(LD_REND, "Suggested effort changed significantly, "
+                          "updating descriptors...");
+        encrypted->pow_params->suggested_effort = pow_state->suggested_effort;
+        descs_updated = 1;
+      } else if (previous_effort != pow_state->suggested_effort) {
+        /* The change in suggested effort was not significant enough to
+        warrant updating the descriptors, return 0 to reflect they are
+        unchanged. */
+        log_info(LD_REND, "Change in suggested effort didn't warrant "
+                          "updating descriptors.");
+      }
+    } FOR_EACH_DESCRIPTOR_END;
+
+    if (descs_updated) {
+      FOR_EACH_DESCRIPTOR_BEGIN(service, desc) {
+        service_desc_schedule_upload(desc, now, 1);
+      } FOR_EACH_DESCRIPTOR_END;
+    }
+  } FOR_EACH_SERVICE_END;
+}
+
 /** Return true iff the given intro point has expired that is it has been used
  * for too long or we've reached our max seen INTRODUCE2 cell. */
 STATIC int
@@ -2507,6 +2630,100 @@ cleanup_intro_points(hs_service_t *service, time_t now)
   smartlist_free(ips_to_free);
 }
 
+/** Rotate the seeds used in the proof-of-work defenses. */
+static void
+rotate_pow_seeds(hs_service_t *service, time_t now)
+{
+  /* Make life easier */
+  hs_pow_service_state_t *pow_state = service->state.pow_state;
+
+  log_info(LD_REND,
+          "Current seed expired. Scrubbing replay cache, rotating PoW "
+          "seeds, generating new seed and updating descriptors.");
+
+  /* Before we overwrite the previous seed lets scrub entries corresponding
+   * to it in the nonce replay cache. */
+  hs_pow_remove_seed_from_cache(get_uint32(pow_state->seed_previous));
+
+  /* Keep track of the current seed that we are now rotating. */
+  memcpy(pow_state->seed_previous, pow_state->seed_current, HS_POW_SEED_LEN);
+
+  /* Generate a new random seed to use from now on. Make sure the seed head
+   * is different to that of the previous seed. The following while loop
+   * will run at least once as the seeds will initially be equal. */
+  while (get_uint32(pow_state->seed_previous) ==
+         get_uint32(pow_state->seed_current)) {
+    crypto_rand((char *)pow_state->seed_current, HS_POW_SEED_LEN);
+  }
+
+  /* Update the expiration time for the new seed. */
+  pow_state->expiration_time =
+      (now +
+       crypto_rand_int_range(HS_SERVICE_POW_SEED_ROTATE_TIME_MIN,
+                             HS_SERVICE_POW_SEED_ROTATE_TIME_MAX));
+
+  {
+    char fmt_next_time[ISO_TIME_LEN + 1];
+    format_local_iso_time(fmt_next_time, pow_state->expiration_time);
+    log_debug(LD_REND, "PoW state expiration time set to: %s", fmt_next_time);
+  }
+}
+
+/** Every HS_UPDATE_PERIOD seconds, and while PoW defenses are enabled, the
+ * service updates its suggested effort for PoW solutions as SUGGESTED_EFFORT =
+ * TOTAL_EFFORT / (SVC_BOTTOM_CAPACITY * HS_UPDATE_PERIOD) where TOTAL_EFFORT
+ * is the sum of the effort of all valid requests that have been received since
+ * the suggested_effort was last updated. */
+static void
+update_suggested_effort(hs_service_t *service, time_t now)
+{
+  uint64_t denom;
+
+  /* Make life easier */
+  hs_pow_service_state_t *pow_state = service->state.pow_state;
+
+  /* Calculate the new suggested effort. */
+  /* TODO Check for overflow in denominator? */
+  denom = (pow_state->svc_bottom_capacity * HS_UPDATE_PERIOD);
+  pow_state->suggested_effort = (pow_state->total_effort / denom);
+
+  log_debug(LD_REND, "Recalculated suggested effort: %u",
+            pow_state->suggested_effort);
+
+  /* Set suggested effort to max(min_effort, suggested_effort) */
+  if (pow_state->suggested_effort < pow_state->min_effort) {
+    pow_state->suggested_effort = pow_state->min_effort;
+  }
+
+  /* Reset the total effort sum for this update period. */
+  pow_state->total_effort = 0;
+  pow_state->next_effort_update = now + HS_UPDATE_PERIOD;
+}
+
+/** Run PoW defenses housekeeping. This MUST be called if the defenses are
+ * actually enabled for the given service. */
+static void
+pow_housekeeping(hs_service_t *service, time_t now)
+{
+  /* If the service is starting off or just been reset we need to
+   * initialize the state of the defenses. */
+  if (!service->state.pow_state) {
+    initialize_pow_defenses(service);
+  }
+
+  /* If the current PoW seed has expired then generate a new current
+   * seed, storing the old one in seed_previous. */
+  if (now >= service->state.pow_state->expiration_time) {
+    rotate_pow_seeds(service, now);
+  }
+
+  /* Update the suggested effort if HS_UPDATE_PERIOD seconds have passed
+   * since we last did so. */
+  if (now >= service->state.pow_state->next_effort_update) {
+    update_suggested_effort(service, now);
+  }
+}
+
 /** Set the next rotation time of the descriptors for the given service for the
  * time now. */
 static void
@@ -2651,6 +2868,12 @@ run_housekeeping_event(time_t now)
       set_rotation_time(service);
     }
 
+    /* Check if we need to initialize or update PoW parameters, if the
+     * defenses are enabled. */
+    if (service->config.has_pow_defenses_enabled) {
+      pow_housekeeping(service, now);
+    }
+
     /* Cleanup invalid intro points from the service descriptor. */
     cleanup_intro_points(service, now);
 
@@ -2684,6 +2907,9 @@ run_build_descriptor_event(time_t now)
    * points. Missing introduction points will be picked in this function which
    * is useful for newly built descriptors. */
   update_all_descriptors_intro_points(now);
+
+  /* Update the PoW params if needed. */
+  update_all_descriptors_pow_params(now);
 }
 
 /** For the given service, launch any intro point circuits that could be
@@ -4365,6 +4591,9 @@ hs_service_free_(hs_service_t *service)
     service_descriptor_free(desc);
   } FOR_EACH_DESCRIPTOR_END;
 
+  /* Free the state of the PoW defenses. */
+  hs_pow_free_service_state(service->state.pow_state);
+
   /* Free service configuration. */
   service_clear_config(&service->config);
 
diff --git a/src/feature/hs/hs_service.h b/src/feature/hs/hs_service.h
index 95461289ce..817fa67718 100644
--- a/src/feature/hs/hs_service.h
+++ b/src/feature/hs/hs_service.h
@@ -35,6 +35,11 @@
 /** Maximum interval for uploading next descriptor (in seconds). */
 #define HS_SERVICE_NEXT_UPLOAD_TIME_MAX (120 * 60)
 
+/** PoW seed expiration time is set to RAND_TIME(now+7200, 900)
+ * seconds. */
+#define HS_SERVICE_POW_SEED_ROTATE_TIME_MIN (7200 - 900)
+#define HS_SERVICE_POW_SEED_ROTATE_TIME_MAX (7200)
+
 /** Collected metrics for a specific service. */
 typedef struct hs_service_metrics_t {
   /** Store containing the metrics values. */
@@ -257,6 +262,11 @@ typedef struct hs_service_config_t {
   uint32_t intro_dos_rate_per_sec;
   uint32_t intro_dos_burst_per_sec;
 
+  /** True iff PoW anti-DoS defenses are enabled. */
+  unsigned int has_pow_defenses_enabled : 1;
+  uint32_t pow_min_effort;
+  uint32_t pow_svc_bottom_capacity;
+
   /** If set, contains the Onion Balance master ed25519 public key (taken from
    * an .onion addresses) that this tor instance serves as backend. */
   smartlist_t *ob_master_pubkeys;
@@ -291,6 +301,10 @@ typedef struct hs_service_state_t {
   hs_subcredential_t *ob_subcreds;
   /* Number of OB subcredentials */
   size_t n_ob_subcreds;
+
+  /** State of the PoW defenses, which may be enabled dynamically. NULL if not
+   * defined for this service. */
+  hs_pow_service_state_t *pow_state;
 } hs_service_state_t;
 
 /** Representation of a service running on this tor instance. */

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits