[tor-commits] [tor/master] hs: Limit the amount of relayed INTRODUCE2

nickm at torproject.org nickm at torproject.org
Wed Aug 7 13:52:13 UTC 2019


commit 9f738be8937d675929b43a149d706160641a089d
Author: David Goulet <dgoulet at torproject.org>
Date:   Wed May 29 14:05:16 2019 -0400

    hs: Limit the amount of relayed INTRODUCE2
    
    This commit add the hs_dos.{c|h} file that has the purpose of having the
    anti-DoS code for onion services.
    
    At this commit, it only has one which is a function that decides if an
    INTRODUCE2 can be sent on the given introduction service circuit (S<->IP)
    using a simple token bucket.
    
    The rate per second is 25 and allowed burst to 200.
    
    Basic defenses on #15516.
    
    Signed-off-by: David Goulet <dgoulet at torproject.org>
---
 src/core/include.am            |  2 ++
 src/core/or/or_circuit_st.h    |  7 +++++
 src/feature/hs/hs_dos.c        | 60 ++++++++++++++++++++++++++++++++++++++++++
 src/feature/hs/hs_dos.h        | 44 +++++++++++++++++++++++++++++++
 src/feature/hs/hs_intropoint.c | 20 +++++++++++++-
 src/feature/rend/rendmid.c     |  9 +++++++
 src/test/test_hs_intropoint.c  |  2 ++
 7 files changed, 143 insertions(+), 1 deletion(-)

diff --git a/src/core/include.am b/src/core/include.am
index 1a4b9fb8a..ee275f172 100644
--- a/src/core/include.am
+++ b/src/core/include.am
@@ -117,6 +117,7 @@ LIBTOR_APP_A_SOURCES = 				\
 	src/feature/hs/hs_config.c		\
 	src/feature/hs/hs_control.c		\
 	src/feature/hs/hs_descriptor.c		\
+	src/feature/hs/hs_dos.c			\
 	src/feature/hs/hs_ident.c		\
 	src/feature/hs/hs_intropoint.c		\
 	src/feature/hs/hs_service.c		\
@@ -374,6 +375,7 @@ noinst_HEADERS +=					\
 	src/feature/hs/hs_config.h			\
 	src/feature/hs/hs_control.h			\
 	src/feature/hs/hs_descriptor.h			\
+	src/feature/hs/hs_dos.h				\
 	src/feature/hs/hs_ident.h			\
 	src/feature/hs/hs_intropoint.h			\
 	src/feature/hs/hs_service.h			\
diff --git a/src/core/or/or_circuit_st.h b/src/core/or/or_circuit_st.h
index 678966822..8f319585a 100644
--- a/src/core/or/or_circuit_st.h
+++ b/src/core/or/or_circuit_st.h
@@ -12,6 +12,8 @@
 #include "core/or/circuit_st.h"
 #include "core/or/crypt_path_st.h"
 
+#include "lib/evloop/token_bucket.h"
+
 struct onion_queue_t;
 
 /** An or_circuit_t holds information needed to implement a circuit at an
@@ -69,6 +71,11 @@ struct or_circuit_t {
    * exit-ward queues of this circuit; reset every time when writing
    * buffer stats to disk. */
   uint64_t total_cell_waiting_time;
+
+  /** INTRODUCE2 cell bucket controlling how much can go on this circuit. Only
+   * used if this is a service introduction circuit at the intro point
+   * (purpose = CIRCUIT_PURPOSE_INTRO_POINT). */
+  token_bucket_ctr_t introduce2_bucket;
 };
 
 #endif /* !defined(OR_CIRCUIT_ST_H) */
diff --git a/src/feature/hs/hs_dos.c b/src/feature/hs/hs_dos.c
new file mode 100644
index 000000000..ad9d044f4
--- /dev/null
+++ b/src/feature/hs/hs_dos.c
@@ -0,0 +1,60 @@
+/* Copyright (c) 2019, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_dos.c
+ * \brief Implement denial of service mitigation for the onion service
+ *        subsystem.
+ *
+ * This module defenses:
+ *
+ * - Introduction Rate Limiting: If enabled by the consensus, an introduction
+ *   point will rate limit client introduction towards the service (INTRODUCE2
+ *   cells). It uses a token bucket model with a rate and burst per second.
+ *
+ *   Proposal 305 will expand this module by allowing an operator to define
+ *   these values into the ESTABLISH_INTRO cell. Not yet implemented.
+ **/
+
+#define HS_DOS_PRIVATE
+
+#include "core/or/circuitlist.h"
+
+#include "hs_dos.h"
+
+/*
+ * Public API.
+ */
+
+/* Return true iff an INTRODUCE2 cell can be sent on the given service
+ * introduction circuit. */
+bool
+hs_dos_can_send_intro2(or_circuit_t *s_intro_circ)
+{
+  tor_assert(s_intro_circ);
+
+  /* Should not happen but if so, scream loudly. */
+  if (BUG(TO_CIRCUIT(s_intro_circ)->purpose != CIRCUIT_PURPOSE_INTRO_POINT)) {
+    return false;
+  }
+
+  /* This is called just after we got a valid and parsed INTRODUCE1 cell. The
+   * service has been found and we have its introduction circuit.
+   *
+   * First, the INTRODUCE2 bucket will be refilled (if any). Then, decremented
+   * because we are about to send or not the cell we just got. Finally,
+   * evaluate if we can send it based on our token bucket state. */
+
+  /* Refill INTRODUCE2 bucket. */
+  token_bucket_ctr_refill(&s_intro_circ->introduce2_bucket,
+                          (uint32_t) approx_time());
+
+  /* Decrement the bucket for this valid INTRODUCE1 cell we just got. Don't
+   * underflow else we end up with a too big of a bucket. */
+  if (token_bucket_ctr_get(&s_intro_circ->introduce2_bucket) > 0) {
+    token_bucket_ctr_dec(&s_intro_circ->introduce2_bucket, 1);
+  }
+
+  /* Finally, we can send a new INTRODUCE2 if there are still tokens. */
+  return token_bucket_ctr_get(&s_intro_circ->introduce2_bucket) > 0;
+}
diff --git a/src/feature/hs/hs_dos.h b/src/feature/hs/hs_dos.h
new file mode 100644
index 000000000..e3a83a103
--- /dev/null
+++ b/src/feature/hs/hs_dos.h
@@ -0,0 +1,44 @@
+/* Copyright (c) 2019, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_dos.h
+ * \brief Header file containing denial of service defenses for the HS
+ *        subsystem for all versions.
+ **/
+
+#ifndef TOR_HS_DOS_H
+#define TOR_HS_DOS_H
+
+#include "core/or/or_circuit_st.h"
+
+#include "lib/evloop/token_bucket.h"
+
+#define HS_DOS_INTRODUCE_CELL_RATE_PER_SEC 25
+#define HS_DOS_INTRODUCE_CELL_BURST_PER_SEC 200
+
+bool hs_dos_can_send_intro2(or_circuit_t *s_intro_circ);
+
+/* Return the INTRODUCE2 cell rate per second. */
+static inline
+uint32_t hs_dos_get_intro2_rate(void)
+{
+  return HS_DOS_INTRODUCE_CELL_RATE_PER_SEC;
+}
+
+/* Return the INTRODUCE2 cell burst per second. */
+static inline
+uint32_t hs_dos_get_intro2_burst(void)
+{
+  return HS_DOS_INTRODUCE_CELL_BURST_PER_SEC;
+}
+
+#ifdef HS_DOS_PRIVATE
+
+#ifdef TOR_UNIT_TESTS
+
+#endif /* define(TOR_UNIT_TESTS) */
+
+#endif /* defined(HS_DOS_PRIVATE) */
+
+#endif /* !defined(TOR_HS_DOS_H) */
diff --git a/src/feature/hs/hs_intropoint.c b/src/feature/hs/hs_intropoint.c
index 6383d3ed2..2c105f0b6 100644
--- a/src/feature/hs/hs_intropoint.c
+++ b/src/feature/hs/hs_intropoint.c
@@ -25,9 +25,10 @@
 #include "trunnel/hs/cell_introduce1.h"
 
 #include "feature/hs/hs_circuitmap.h"
+#include "feature/hs/hs_common.h"
 #include "feature/hs/hs_descriptor.h"
+#include "feature/hs/hs_dos.h"
 #include "feature/hs/hs_intropoint.h"
-#include "feature/hs/hs_common.h"
 
 #include "core/or/or_circuit_st.h"
 
@@ -203,6 +204,9 @@ handle_verified_establish_intro_cell(or_circuit_t *circ,
   hs_circuitmap_register_intro_circ_v3_relay_side(circ, &auth_key);
   /* Repurpose this circuit into an intro circuit. */
   circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_INTRO_POINT);
+  /* Initialize the INTRODUCE2 token bucket for the rate limiting. */
+  token_bucket_ctr_init(&circ->introduce2_bucket, hs_dos_get_intro2_rate(),
+                        hs_dos_get_intro2_burst(), (uint32_t) approx_time());
 
   return 0;
 }
@@ -481,6 +485,20 @@ handle_introduce1(or_circuit_t *client_circ, const uint8_t *request,
     }
   }
 
+  /* Before sending, lets make sure this cell can be sent on the service
+   * circuit asking the DoS defenses. */
+  if (!hs_dos_can_send_intro2(service_circ)) {
+    char *msg;
+    static ratelim_t rlimit = RATELIM_INIT(5 * 60);
+    if ((msg = rate_limit_log(&rlimit, approx_time()))) {
+      log_info(LD_PROTOCOL, "Can't relay INTRODUCE1 v3 cell due to DoS "
+                            "limitations. Sending NACK to client.");
+      tor_free(msg);
+    }
+    status = TRUNNEL_HS_INTRO_ACK_STATUS_UNKNOWN_ID;
+    goto send_ack;
+  }
+
   /* Relay the cell to the service on its intro circuit with an INTRODUCE2
    * cell which is the same exact payload. */
   if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(service_circ),
diff --git a/src/feature/rend/rendmid.c b/src/feature/rend/rendmid.c
index 849f35599..192da166e 100644
--- a/src/feature/rend/rendmid.c
+++ b/src/feature/rend/rendmid.c
@@ -18,6 +18,7 @@
 #include "feature/rend/rendmid.h"
 #include "feature/stats/rephist.h"
 #include "feature/hs/hs_circuitmap.h"
+#include "feature/hs/hs_dos.h"
 #include "feature/hs/hs_intropoint.h"
 
 #include "core/or/or_circuit_st.h"
@@ -180,6 +181,14 @@ rend_mid_introduce_legacy(or_circuit_t *circ, const uint8_t *request,
     goto err;
   }
 
+  /* Before sending, lets make sure this cell can be sent on the service
+   * circuit asking the DoS defenses. */
+  if (!hs_dos_can_send_intro2(intro_circ)) {
+    log_info(LD_PROTOCOL, "Can't relay INTRODUCE1 v2 cell due to DoS "
+                          "limitations. Sending NACK to client.");
+    goto err;
+  }
+
   log_info(LD_REND,
            "Sending introduction request for service %s "
            "from circ %u to circ %u",
diff --git a/src/test/test_hs_intropoint.c b/src/test/test_hs_intropoint.c
index 0cdb1fef2..87338b448 100644
--- a/src/test/test_hs_intropoint.c
+++ b/src/test/test_hs_intropoint.c
@@ -119,6 +119,8 @@ helper_create_intro_circuit(void)
   or_circuit_t *circ = or_circuit_new(0, NULL);
   tt_assert(circ);
   circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_OR);
+  token_bucket_ctr_init(&circ->introduce2_bucket, 100, 100,
+                        (uint32_t) approx_time());
  done:
   return circ;
 }





More information about the tor-commits mailing list