]> git.ipfire.org Git - thirdparty/tor.git/commitdiff
add rate limit on BEGIN and RESOLVE cell per circuit
authortrinity-1686a <trinity@deuxfleurs.fr>
Sun, 10 Sep 2023 11:13:11 +0000 (13:13 +0200)
committerDavid Goulet <dgoulet@torproject.org>
Wed, 18 Oct 2023 17:06:04 +0000 (13:06 -0400)
src/core/or/circuitlist.c
src/core/or/connection_edge.c
src/core/or/dos.c
src/core/or/dos.h
src/core/or/or_circuit_st.h
src/core/or/relay.c
src/feature/relay/dns.c
src/feature/relay/dns.h
src/test/test_status.c

index b90c7ebb58965e6cd1976bee13a4d7ad41105b74..643d97b0644c93cc07b9787e80f45fc247ed1a22 100644 (file)
@@ -65,6 +65,7 @@
 #include "core/or/conflux.h"
 #include "core/or/conflux_pool.h"
 #include "core/or/crypt_path.h"
+#include "core/or/dos.h"
 #include "core/or/extendinfo.h"
 #include "core/or/status.h"
 #include "core/or/trace_probes_circuit.h"
@@ -1130,6 +1131,7 @@ or_circuit_new(circid_t p_circ_id, channel_t *p_chan)
   cell_queue_init(&circ->p_chan_cells);
 
   init_circuit_base(TO_CIRCUIT(circ));
+  dos_stream_init_circ_tbf(circ);
 
   tor_trace(TR_SUBSYS(circuit), TR_EV(new_or), circ);
   return circ;
index 900d639959139f79db1bc378db8c5656d9bfb0cf..764e1c886b4591a120e2651de786786d7524f313 100644 (file)
@@ -73,6 +73,7 @@
 #include "core/or/conflux_util.h"
 #include "core/or/circuitstats.h"
 #include "core/or/connection_or.h"
+#include "core/or/dos.h"
 #include "core/or/extendinfo.h"
 #include "core/or/policies.h"
 #include "core/or/reasons.h"
@@ -3990,6 +3991,7 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
   begin_cell_t bcell;
   int rv;
   uint8_t end_reason=0;
+  dos_stream_defense_type_t dos_defense_type;
 
   assert_circuit_ok(circ);
   if (!CIRCUIT_IS_ORIGIN(circ)) {
@@ -4148,6 +4150,35 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
 
   log_debug(LD_EXIT,"about to start the dns_resolve().");
 
+  /* TODO should this be moved higher to protect from a stream DoS on directory
+   * requests, and possibly against an onion service? (for OS, more changes
+   * would be required) */
+  dos_defense_type = dos_stream_new_begin_or_resolve_cell(or_circ);
+  switch (dos_defense_type) {
+    case DOS_STREAM_DEFENSE_NONE:
+      break;
+    case DOS_STREAM_DEFENSE_REFUSE_STREAM:
+      // we don't use END_STREAM_REASON_RESOURCELIMIT because it would make a
+      // client mark us as non-functional until they get a new consensus.
+      relay_send_end_cell_from_edge(rh.stream_id, circ, END_STREAM_REASON_MISC,
+                                    layer_hint);
+      connection_free_(TO_CONN(n_stream));
+      return 0;
+    case DOS_STREAM_DEFENSE_CLOSE_CIRCUIT:
+      connection_free_(TO_CONN(n_stream));
+      /* TODO we could return REASON_NONE or REASON_RESOURCELIMIT. When closing
+       * circuits, you either get:
+       * - END_CIRC_REASON_NONE: tons of notice level "We tried for 15
+       *   seconds to connect to 'target' using exit X. Retrying on a new
+       *   circuit."
+       * - END_CIRC_REASON_RESOURCELIMIT: warn level "Guard X is failing
+       *   to carry an extremely large amount of streams on its circuits"
+       *
+       * I'm not sure which one we want
+       */
+      return -END_CIRC_REASON_NONE;
+  }
+
   /* send it off to the gethostbyname farm */
   switch (dns_resolve(n_stream)) {
     case 1: /* resolve worked; now n_stream is attached to circ. */
@@ -4171,17 +4202,21 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
  * Called when we receive a RELAY_COMMAND_RESOLVE cell 'cell' along the
  * circuit <b>circ</b>;
  * begin resolving the hostname, and (eventually) reply with a RESOLVED cell.
+ *
+ * Return -(some circuit end reason) if we want to tear down <b>circ</b>.
+ * Else return 0.
  */
 int
 connection_exit_begin_resolve(cell_t *cell, or_circuit_t *circ)
 {
   edge_connection_t *dummy_conn;
   relay_header_t rh;
+  dos_stream_defense_type_t dos_defense_type;
 
   assert_circuit_ok(TO_CIRCUIT(circ));
   relay_header_unpack(&rh, cell->payload);
   if (rh.length > RELAY_PAYLOAD_SIZE)
-    return -1;
+    return 0;
 
   /* Note the RESOLVE stream as seen. */
   rep_hist_note_exit_stream(RELAY_COMMAND_RESOLVE);
@@ -4204,6 +4239,18 @@ connection_exit_begin_resolve(cell_t *cell, or_circuit_t *circ)
 
   dummy_conn->on_circuit = TO_CIRCUIT(circ);
 
+  dos_defense_type = dos_stream_new_begin_or_resolve_cell(circ);
+  switch (dos_defense_type) {
+    case DOS_STREAM_DEFENSE_NONE:
+      break;
+    case DOS_STREAM_DEFENSE_REFUSE_STREAM:
+      dns_send_resolved_error_cell(dummy_conn, RESOLVED_TYPE_ERROR_TRANSIENT);
+      return 0;
+    case DOS_STREAM_DEFENSE_CLOSE_CIRCUIT:
+      /* TODO maybe use REASON_RESOURCELIMIT? See connection_exit_begin_conn() */
+      return -END_CIRC_REASON_NONE;
+  }
+
   /* send it off to the gethostbyname farm */
   switch (dns_resolve(dummy_conn)) {
     case -1: /* Impossible to resolve; a resolved cell was sent. */
index a47738c9066e8be25805b206a7c5a837a7d146d3..63cac190fdf21befc236bb339daa8310eb92bd04 100644 (file)
@@ -325,7 +325,8 @@ get_param_stream_defense_type(const networkstatus_t *ns)
   }
   return networkstatus_get_param(ns, "DoSStreamCreationDefenseType",
                                  DOS_STREAM_DEFENSE_TYPE_DEFAULT,
-                                 DOS_STREAM_DEFENSE_NONE, DOS_STREAM_DEFENSE_MAX);
+                                 DOS_STREAM_DEFENSE_NONE,
+                                 DOS_STREAM_DEFENSE_MAX);
 }
 
 /* Set circuit creation parameters located in the consensus or their default
@@ -836,6 +837,41 @@ dos_conn_addr_get_defense_type(const tor_addr_t *addr)
   return DOS_CONN_DEFENSE_NONE;
 }
 
+/* Stream creation public API. */
+
+/* Return the action to take against a BEGIN or RESOLVE cell. Return
+ *  DOS_STREAM_DEFENSE_NONE when no action should be taken.
+ *  Increment the appropriate counter when the cell was found to go over a
+ *  limit. */
+dos_stream_defense_type_t
+dos_stream_new_begin_or_resolve_cell(or_circuit_t *circ)
+{
+  if (!dos_stream_enabled || circ == NULL)
+    return DOS_STREAM_DEFENSE_NONE;
+
+  token_bucket_ctr_refill(&circ->stream_limiter,
+                          (uint32_t) monotime_coarse_absolute_sec());
+
+  if (token_bucket_ctr_get(&circ->stream_limiter) > 0) {
+    token_bucket_ctr_dec(&circ->stream_limiter, 1);
+    return DOS_STREAM_DEFENSE_NONE;
+  }
+  /* if defense type is DOS_STREAM_DEFENSE_NONE but DoSStreamEnabled is true,
+   * we count offending cells as rejected, despite them being actually
+   * accepted. */
+  ++stream_num_rejected;
+  return dos_stream_defense_type;
+}
+
+/* Initialize the token bucket for stream rate limit on a circuit. */
+void
+dos_stream_init_circ_tbf(or_circuit_t *circ)
+{
+  token_bucket_ctr_init(&circ->stream_limiter, dos_stream_rate,
+                        dos_stream_burst,
+                        (uint32_t) monotime_coarse_absolute_sec());
+}
+
 /* General API */
 
 /* Take any appropriate actions for the given geoip entry that is about to get
index 8b36fe14155b9b0cd9292ad59b5d94f635575451..77dce333d118bbf239b8f84526133ec5680482a9 100644 (file)
@@ -186,6 +186,10 @@ typedef enum dos_stream_defense_type_t {
   DOS_STREAM_DEFENSE_MAX            = 3,
 } dos_stream_defense_type_t;
 
+dos_stream_defense_type_t dos_stream_new_begin_or_resolve_cell(
+                                                          or_circuit_t *circ);
+void dos_stream_init_circ_tbf(or_circuit_t *circ);
+
 #ifdef DOS_PRIVATE
 
 STATIC uint32_t get_param_conn_max_concurrent_count(
index d5a70079284333d2352f12cd0fe29cf3f3f5e013..28e357338ad6c7bf966dab3bd4478645aa2eaa64 100644 (file)
@@ -102,6 +102,10 @@ struct or_circuit_t {
    * used if this is a service introduction circuit at the intro point
    * (purpose = CIRCUIT_PURPOSE_INTRO_POINT). */
   token_bucket_ctr_t introduce2_bucket;
+
+  /** RELAY_BEGIN and RELAY_RESOLVE cell bucket controlling how much can go on
+   * this circuit. Only used if this is the end of a circuit on an exit node.*/
+  token_bucket_ctr_t stream_limiter;
 };
 
 #endif /* !defined(OR_CIRCUIT_ST_H) */
index 6abe802355e0f5ace42a74bb573d0479d138a022..0cda6e7bf7c95cba5c481380d8780341e54f20c4 100644 (file)
@@ -2015,8 +2015,7 @@ handle_relay_cell_command(cell_t *cell, circuit_t *circ,
                circ->purpose);
         return 0;
       }
-      connection_exit_begin_resolve(cell, TO_OR_CIRCUIT(circ));
-      return 0;
+      return connection_exit_begin_resolve(cell, TO_OR_CIRCUIT(circ));
     case RELAY_COMMAND_RESOLVED:
       if (conn) {
         log_fn(LOG_PROTOCOL_WARN, domain,
index f6a020d061d4ca6e9efe4d4996a5bd59da5ae49e..129f6209d7854ba80df692793458337218d4c378 100644 (file)
@@ -563,6 +563,12 @@ send_resolved_cell,(edge_connection_t *conn, uint8_t answer_type,
   connection_edge_send_command(conn, RELAY_COMMAND_RESOLVED, buf, buflen);
 }
 
+void
+dns_send_resolved_error_cell(edge_connection_t *conn, uint8_t answer_type)
+{
+  send_resolved_cell(conn, answer_type, NULL);
+}
+
 /** Send a response to the RESOLVE request of a connection for an in-addr.arpa
  * address on connection <b>conn</b> which yielded the result <b>hostname</b>.
  * The answer type will be RESOLVED_HOSTNAME.
index 3f8519bd9796d3963285cfaf07cb37f0e4d7e9d8..b43b42756e70910517aeec82ef7385ad3c41a553 100644 (file)
@@ -20,6 +20,8 @@ int dns_reset(void);
 void connection_dns_remove(edge_connection_t *conn);
 void assert_connection_edge_not_dns_pending(edge_connection_t *conn);
 int dns_resolve(edge_connection_t *exitconn);
+void dns_send_resolved_error_cell(edge_connection_t *conn,
+                                  uint8_t answer_type);
 int dns_seems_to_be_broken(void);
 int dns_seems_to_be_broken_for_ipv6(void);
 void dns_reset_correctness_checks(void);
index 5873d8e5c371c9b7d0d25d7aab8dc356529c7375..4ceb81f3a5270da244456cc86291a319dc7499b1 100644 (file)
@@ -365,6 +365,7 @@ test_status_hb_not_in_consensus(void *arg)
                  "with too many cells, [DoSCircuitCreationEnabled disabled], "
                  "[DoSConnectionEnabled disabled], "
                  "[DoSRefuseSingleHopClientRendezvous disabled], "
+                 "[DoSStreamCreationEnabled disabled], "
                  "0 INTRODUCE2 rejected.\n");
   tt_int_op(mock_saved_log_n_entries(), OP_EQ, 6);