commit 9f738be8937d675929b43a149d706160641a089d Author: David Goulet dgoulet@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@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; }
tor-commits@lists.torproject.org