diff options
Diffstat (limited to 'src/feature')
71 files changed, 1878 insertions, 560 deletions
diff --git a/src/feature/api/tor_api.c b/src/feature/api/tor_api.c index fd9d241353..e270c51ac9 100644 --- a/src/feature/api/tor_api.c +++ b/src/feature/api/tor_api.c @@ -40,7 +40,7 @@ #define raw_socketpair tor_ersatz_socketpair #define raw_closesocket closesocket #define snprintf _snprintf -#else /* !(defined(_WIN32)) */ +#else /* !defined(_WIN32) */ #define raw_socketpair socketpair #define raw_closesocket close #endif /* defined(_WIN32) */ diff --git a/src/feature/client/circpathbias.c b/src/feature/client/circpathbias.c index 3544dbb859..60a52664f1 100644 --- a/src/feature/client/circpathbias.c +++ b/src/feature/client/circpathbias.c @@ -303,7 +303,7 @@ pathbias_is_new_circ_attempt(origin_circuit_t *circ) return circ->cpath && circ->cpath->next != circ->cpath && circ->cpath->next->state == CPATH_STATE_AWAITING_KEYS; -#else /* !(defined(N2N_TAGGING_IS_POSSIBLE)) */ +#else /* !defined(N2N_TAGGING_IS_POSSIBLE) */ /* If tagging attacks are no longer possible, we probably want to * count bias from the first hop. However, one could argue that * timing-based tagging is still more useful than per-hop failure. diff --git a/src/feature/client/dnsserv.c b/src/feature/client/dnsserv.c index 7fb3fff6c1..f9e436e88f 100644 --- a/src/feature/client/dnsserv.c +++ b/src/feature/client/dnsserv.c @@ -238,7 +238,7 @@ dnsserv_launch_request(const char *name, int reverse, TO_CONN(conn)->port = control_conn->base_.port; TO_CONN(conn)->address = tor_addr_to_str_dup(&control_conn->base_.addr); } -#else /* !(defined(AF_UNIX)) */ +#else /* !defined(AF_UNIX) */ TO_CONN(conn)->port = control_conn->base_.port; TO_CONN(conn)->address = tor_addr_to_str_dup(&control_conn->base_.addr); #endif /* defined(AF_UNIX) */ diff --git a/src/feature/client/entrynodes.c b/src/feature/client/entrynodes.c index 5b6216f483..36b575ef20 100644 --- a/src/feature/client/entrynodes.c +++ b/src/feature/client/entrynodes.c @@ -114,7 +114,7 @@ #include "core/or/or.h" #include "app/config/config.h" -#include "app/config/confparse.h" +#include "lib/confmgt/confparse.h" #include "app/config/statefile.h" #include "core/mainloop/connection.h" #include "core/mainloop/mainloop.h" diff --git a/src/feature/control/btrack.c b/src/feature/control/btrack.c index d3d12cb2b7..3ce97dc855 100644 --- a/src/feature/control/btrack.c +++ b/src/feature/control/btrack.c @@ -24,6 +24,7 @@ #include "feature/control/btrack_circuit.h" #include "feature/control/btrack_orconn.h" #include "feature/control/btrack_sys.h" +#include "lib/pubsub/pubsub.h" #include "lib/subsys/subsys.h" static int @@ -31,8 +32,6 @@ btrack_init(void) { if (btrack_orconn_init()) return -1; - if (btrack_circ_init()) - return -1; return 0; } @@ -44,10 +43,22 @@ btrack_fini(void) btrack_circ_fini(); } +static int +btrack_add_pubsub(pubsub_connector_t *connector) +{ + if (btrack_orconn_add_pubsub(connector)) + return -1; + if (btrack_circ_add_pubsub(connector)) + return -1; + + return 0; +} + const subsys_fns_t sys_btrack = { .name = "btrack", .supported = true, .level = -30, .initialize = btrack_init, .shutdown = btrack_fini, + .add_pubsub = btrack_add_pubsub, }; diff --git a/src/feature/control/btrack_circuit.c b/src/feature/control/btrack_circuit.c index dcee9e460e..2980c77ddc 100644 --- a/src/feature/control/btrack_circuit.c +++ b/src/feature/control/btrack_circuit.c @@ -109,51 +109,53 @@ btc_update_evtype(const ocirc_cevent_msg_t *msg, btc_best_t *best, return false; } +DECLARE_SUBSCRIBE(ocirc_state, btc_state_rcvr); +DECLARE_SUBSCRIBE(ocirc_cevent, btc_cevent_rcvr); +DECLARE_SUBSCRIBE(ocirc_chan, btc_chan_rcvr); + static void -btc_state_rcvr(const ocirc_state_msg_t *msg) +btc_state_rcvr(const msg_t *msg, const ocirc_state_msg_t *arg) { + (void)msg; log_debug(LD_BTRACK, "CIRC gid=%"PRIu32" state=%d onehop=%d", - msg->gid, msg->state, msg->onehop); + arg->gid, arg->state, arg->onehop); - btc_update_state(msg, &best_any_state, "ANY"); - if (msg->onehop) + btc_update_state(arg, &best_any_state, "ANY"); + if (arg->onehop) return; - btc_update_state(msg, &best_ap_state, "AP"); + btc_update_state(arg, &best_ap_state, "AP"); } static void -btc_cevent_rcvr(const ocirc_cevent_msg_t *msg) +btc_cevent_rcvr(const msg_t *msg, const ocirc_cevent_msg_t *arg) { + (void)msg; log_debug(LD_BTRACK, "CIRC gid=%"PRIu32" evtype=%d reason=%d onehop=%d", - msg->gid, msg->evtype, msg->reason, msg->onehop); + arg->gid, arg->evtype, arg->reason, arg->onehop); - btc_update_evtype(msg, &best_any_evtype, "ANY"); - if (msg->onehop) + btc_update_evtype(arg, &best_any_evtype, "ANY"); + if (arg->onehop) return; - btc_update_evtype(msg, &best_ap_evtype, "AP"); + btc_update_evtype(arg, &best_ap_evtype, "AP"); } static void -btc_event_rcvr(const ocirc_event_msg_t *msg) +btc_chan_rcvr(const msg_t *msg, const ocirc_chan_msg_t *arg) { - switch (msg->type) { - case OCIRC_MSGTYPE_STATE: - return btc_state_rcvr(&msg->u.state); - case OCIRC_MSGTYPE_CHAN: - log_debug(LD_BTRACK, "CIRC gid=%"PRIu32" chan=%"PRIu64" onehop=%d", - msg->u.chan.gid, msg->u.chan.chan, msg->u.chan.onehop); - break; - case OCIRC_MSGTYPE_CEVENT: - return btc_cevent_rcvr(&msg->u.cevent); - default: - break; - } + (void)msg; + log_debug(LD_BTRACK, "CIRC gid=%"PRIu32" chan=%"PRIu64" onehop=%d", + arg->gid, arg->chan, arg->onehop); } int -btrack_circ_init(void) +btrack_circ_add_pubsub(pubsub_connector_t *connector) { - ocirc_event_subscribe(btc_event_rcvr); + if (DISPATCH_ADD_SUB(connector, ocirc, ocirc_chan)) + return -1; + if (DISPATCH_ADD_SUB(connector, ocirc, ocirc_cevent)) + return -1; + if (DISPATCH_ADD_SUB(connector, ocirc, ocirc_state)) + return -1; return 0; } diff --git a/src/feature/control/btrack_circuit.h b/src/feature/control/btrack_circuit.h index 9e06fefb07..b326c22ccf 100644 --- a/src/feature/control/btrack_circuit.h +++ b/src/feature/control/btrack_circuit.h @@ -9,7 +9,10 @@ #ifndef TOR_BTRACK_CIRCUIT_H #define TOR_BTRACK_CIRCUIT_H +#include "lib/pubsub/pubsub.h" + int btrack_circ_init(void); void btrack_circ_fini(void); +int btrack_circ_add_pubsub(pubsub_connector_t *); #endif /* !defined(TOR_BTRACK_CIRCUIT_H) */ diff --git a/src/feature/control/btrack_orconn.c b/src/feature/control/btrack_orconn.c index 93ebe8d9cc..922b542a0c 100644 --- a/src/feature/control/btrack_orconn.c +++ b/src/feature/control/btrack_orconn.c @@ -45,6 +45,11 @@ #include "feature/control/btrack_orconn_cevent.h" #include "feature/control/btrack_orconn_maps.h" #include "lib/log/log.h" +#include "lib/pubsub/pubsub.h" + +DECLARE_SUBSCRIBE(orconn_state, bto_state_rcvr); +DECLARE_SUBSCRIBE(orconn_status, bto_status_rcvr); +DECLARE_SUBSCRIBE(ocirc_chan, bto_chan_rcvr); /** Pair of a best ORCONN GID and with its state */ typedef struct bto_best_t { @@ -110,16 +115,17 @@ bto_reset_bests(void) * message comes from code in connection_or.c. **/ static void -bto_state_rcvr(const orconn_state_msg_t *msg) +bto_state_rcvr(const msg_t *msg, const orconn_state_msg_t *arg) { bt_orconn_t *bto; - bto = bto_find_or_new(msg->gid, msg->chan); + (void)msg; + bto = bto_find_or_new(arg->gid, arg->chan); log_debug(LD_BTRACK, "ORCONN gid=%"PRIu64" chan=%"PRIu64 " proxy_type=%d state=%d", - msg->gid, msg->chan, msg->proxy_type, msg->state); - bto->proxy_type = msg->proxy_type; - bto->state = msg->state; + arg->gid, arg->chan, arg->proxy_type, arg->state); + bto->proxy_type = arg->proxy_type; + bto->state = arg->state; if (bto->is_orig) bto_update_bests(bto); } @@ -130,54 +136,38 @@ bto_state_rcvr(const orconn_state_msg_t *msg) * control.c. **/ static void -bto_status_rcvr(const orconn_status_msg_t *msg) +bto_status_rcvr(const msg_t *msg, const orconn_status_msg_t *arg) { - switch (msg->status) { + (void)msg; + switch (arg->status) { case OR_CONN_EVENT_FAILED: case OR_CONN_EVENT_CLOSED: log_info(LD_BTRACK, "ORCONN DELETE gid=%"PRIu64" status=%d reason=%d", - msg->gid, msg->status, msg->reason); - return bto_delete(msg->gid); + arg->gid, arg->status, arg->reason); + return bto_delete(arg->gid); default: break; } } -/** Dispatch to individual ORCONN message handlers */ -static void -bto_event_rcvr(const orconn_event_msg_t *msg) -{ - switch (msg->type) { - case ORCONN_MSGTYPE_STATE: - return bto_state_rcvr(&msg->u.state); - case ORCONN_MSGTYPE_STATUS: - return bto_status_rcvr(&msg->u.status); - default: - tor_assert(false); - } -} - /** * Create or update a cached ORCONN state for a newly launched * connection, including whether it's launched by an origin circuit * and whether it's a one-hop circuit. **/ static void -bto_chan_rcvr(const ocirc_event_msg_t *msg) +bto_chan_rcvr(const msg_t *msg, const ocirc_chan_msg_t *arg) { bt_orconn_t *bto; - /* Ignore other kinds of origin circuit events; we don't need them */ - if (msg->type != OCIRC_MSGTYPE_CHAN) - return; - - bto = bto_find_or_new(0, msg->u.chan.chan); - if (!bto->is_orig || (bto->is_onehop && !msg->u.chan.onehop)) { + (void)msg; + bto = bto_find_or_new(0, arg->chan); + if (!bto->is_orig || (bto->is_onehop && !arg->onehop)) { log_debug(LD_BTRACK, "ORCONN LAUNCH chan=%"PRIu64" onehop=%d", - msg->u.chan.chan, msg->u.chan.onehop); + arg->chan, arg->onehop); } bto->is_orig = true; - if (!msg->u.chan.onehop) + if (!arg->onehop) bto->is_onehop = false; bto_update_bests(bto); } @@ -190,12 +180,22 @@ int btrack_orconn_init(void) { bto_init_maps(); - orconn_event_subscribe(bto_event_rcvr); - ocirc_event_subscribe(bto_chan_rcvr); return 0; } +int +btrack_orconn_add_pubsub(pubsub_connector_t *connector) +{ + if (DISPATCH_ADD_SUB(connector, orconn, orconn_state)) + return -1; + if (DISPATCH_ADD_SUB(connector, orconn, orconn_status)) + return -1; + if (DISPATCH_ADD_SUB(connector, ocirc, ocirc_chan)) + return -1; + return 0; +} + /** Clear the hash maps and reset the "best" states */ void btrack_orconn_fini(void) diff --git a/src/feature/control/btrack_orconn.h b/src/feature/control/btrack_orconn.h index f8f5c1096c..07b1b755f3 100644 --- a/src/feature/control/btrack_orconn.h +++ b/src/feature/control/btrack_orconn.h @@ -9,6 +9,8 @@ #ifndef TOR_BTRACK_ORCONN_H #define TOR_BTRACK_ORCONN_H +#include "lib/pubsub/pubsub.h" + #ifdef BTRACK_ORCONN_PRIVATE #include "ht.h" @@ -33,6 +35,7 @@ typedef struct bt_orconn_t { #endif /* defined(BTRACK_ORCONN_PRIVATE) */ int btrack_orconn_init(void); +int btrack_orconn_add_pubsub(pubsub_connector_t *); void btrack_orconn_fini(void); #endif /* !defined(TOR_BTRACK_ORCONN_H) */ diff --git a/src/feature/control/control.c b/src/feature/control/control.c index 436bf423cf..d6581808c0 100644 --- a/src/feature/control/control.c +++ b/src/feature/control/control.c @@ -339,6 +339,60 @@ static const char CONTROLPORT_IS_NOT_AN_HTTP_PROXY_MSG[] = "</body>\n" "</html>\n"; +/** Return an error on a control connection that tried to use the v0 protocol. + */ +static void +control_send_v0_reject(control_connection_t *conn) +{ + size_t body_len; + char buf[128]; + set_uint16(buf+2, htons(0x0000)); /* type == error */ + set_uint16(buf+4, htons(0x0001)); /* code == internal error */ + strlcpy(buf+6, "The v0 control protocol is not supported by Tor 0.1.2.17 " + "and later; upgrade your controller.", + sizeof(buf)-6); + body_len = 2+strlen(buf+6)+2; /* code, msg, nul. */ + set_uint16(buf+0, htons(body_len)); + connection_buf_add(buf, 4+body_len, TO_CONN(conn)); + + connection_mark_and_flush(TO_CONN(conn)); +} + +/** Return an error on a control connection that tried to use HTTP. + */ +static void +control_send_http_reject(control_connection_t *conn) +{ + connection_write_str_to_buf(CONTROLPORT_IS_NOT_AN_HTTP_PROXY_MSG, conn); + log_notice(LD_CONTROL, "Received HTTP request on ControlPort"); + connection_mark_and_flush(TO_CONN(conn)); +} + +/** Check if a control connection has tried to use a known invalid protocol. + * If it has, then: + * - send a reject response, + * - log a notice-level message, and + * - return false. */ +static bool +control_protocol_is_valid(control_connection_t *conn) +{ + /* Detect v0 commands and send a "no more v0" message. */ + if (conn->base_.state == CONTROL_CONN_STATE_NEEDAUTH && + peek_connection_has_control0_command(TO_CONN(conn))) { + control_send_v0_reject(conn); + return 0; + } + + /* If the user has the HTTP proxy port and the control port confused. */ + if (conn->base_.state == CONTROL_CONN_STATE_NEEDAUTH && + peek_connection_has_http_command(TO_CONN(conn))) { + control_send_http_reject(conn); + return 0; + } + + return 1; +} + /** Called when data has arrived on a v1 control connection: Try to fetch * commands from conn->inbuf, and execute them. */ @@ -359,30 +413,7 @@ connection_control_process_inbuf(control_connection_t *conn) conn->incoming_cmd_cur_len = 0; } - if (conn->base_.state == CONTROL_CONN_STATE_NEEDAUTH && - peek_connection_has_control0_command(TO_CONN(conn))) { - /* Detect v0 commands and send a "no more v0" message. */ - size_t body_len; - char buf[128]; - set_uint16(buf+2, htons(0x0000)); /* type == error */ - set_uint16(buf+4, htons(0x0001)); /* code == internal error */ - strlcpy(buf+6, "The v0 control protocol is not supported by Tor 0.1.2.17 " - "and later; upgrade your controller.", - sizeof(buf)-6); - body_len = 2+strlen(buf+6)+2; /* code, msg, nul. */ - set_uint16(buf+0, htons(body_len)); - connection_buf_add(buf, 4+body_len, TO_CONN(conn)); - - connection_mark_and_flush(TO_CONN(conn)); - return 0; - } - - /* If the user has the HTTP proxy port and the control port confused. */ - if (conn->base_.state == CONTROL_CONN_STATE_NEEDAUTH && - peek_connection_has_http_command(TO_CONN(conn))) { - connection_write_str_to_buf(CONTROLPORT_IS_NOT_AN_HTTP_PROXY_MSG, conn); - log_notice(LD_CONTROL, "Received HTTP request on ControlPort"); - connection_mark_and_flush(TO_CONN(conn)); + if (!control_protocol_is_valid(conn)) { return 0; } diff --git a/src/feature/control/control_auth.c b/src/feature/control/control_auth.c index 49d4d415c6..a574d07b33 100644 --- a/src/feature/control/control_auth.c +++ b/src/feature/control/control_auth.c @@ -151,12 +151,8 @@ handle_control_authchallenge(control_connection_t *conn, goto fail; } if (args->kwargs == NULL || args->kwargs->next != NULL) { - /* connection_write_str_to_buf("512 AUTHCHALLENGE requires exactly " - "2 arguments.\r\n", conn); - */ - control_printf_endreply(conn, 512, - "AUTHCHALLENGE dislikes argument list %s", - escaped(args->raw_body)); + control_write_endreply(conn, 512, + "Wrong number of arguments for AUTHCHALLENGE"); goto fail; } if (strcmp(args->kwargs->key, "")) { diff --git a/src/feature/control/control_cmd.c b/src/feature/control/control_cmd.c index e0706ee4c8..de1bef7e59 100644 --- a/src/feature/control/control_cmd.c +++ b/src/feature/control/control_cmd.c @@ -13,7 +13,7 @@ #include "core/or/or.h" #include "app/config/config.h" -#include "app/config/confparse.h" +#include "lib/confmgt/confparse.h" #include "app/main/main.h" #include "core/mainloop/connection.h" #include "core/or/circuitbuild.h" @@ -705,9 +705,8 @@ handle_control_mapaddress(control_connection_t *conn, connection_buf_add(r, sz, TO_CONN(conn)); tor_free(r); } else { - const char *response = - "512 syntax error: not enough arguments to mapaddress.\r\n"; - connection_buf_add(response, strlen(response), TO_CONN(conn)); + control_write_endreply(conn, 512, "syntax error: " + "not enough arguments to mapaddress."); } SMARTLIST_FOREACH(reply, char *, cp, tor_free(cp)); @@ -847,7 +846,7 @@ handle_control_extendcircuit(control_connection_t *conn, "addresses that are allowed by the firewall configuration; " "circuit marked for closing."); circuit_mark_for_close(TO_CIRCUIT(circ), -END_CIRC_REASON_CONNECTFAILED); - connection_write_str_to_buf("551 Couldn't start circuit\r\n", conn); + control_write_endreply(conn, 551, "Couldn't start circuit"); goto done; } circuit_append_new_exit(circ, info); @@ -1746,16 +1745,10 @@ handle_control_add_onion(control_connection_t *conn, goto out; } else if (!strcasecmp(arg->key, "ClientAuth")) { - char *err_msg = NULL; int created = 0; rend_authorized_client_t *client = - add_onion_helper_clientauth(arg->value, - &created, &err_msg); + add_onion_helper_clientauth(arg->value, &created, conn); if (!client) { - if (err_msg) { - connection_write_str_to_buf(err_msg, conn); - tor_free(err_msg); - } goto out; } @@ -1820,19 +1813,13 @@ handle_control_add_onion(control_connection_t *conn, add_onion_secret_key_t pk = { NULL }; const char *key_new_alg = NULL; char *key_new_blob = NULL; - char *err_msg = NULL; const char *onionkey = smartlist_get(args->args, 0); if (add_onion_helper_keyarg(onionkey, discard_pk, &key_new_alg, &key_new_blob, &pk, &hs_version, - &err_msg) < 0) { - if (err_msg) { - connection_write_str_to_buf(err_msg, conn); - tor_free(err_msg); - } + conn) < 0) { goto out; } - tor_assert(!err_msg); /* Hidden service version 3 don't have client authentication support so if * ClientAuth was given, send back an error. */ @@ -1878,8 +1865,8 @@ handle_control_add_onion(control_connection_t *conn, char *encoded = rend_auth_encode_cookie(ac->descriptor_cookie, auth_type); tor_assert(encoded); - connection_printf_to_buf(conn, "250-ClientAuth=%s:%s\r\n", - ac->client_name, encoded); + control_printf_midreply(conn, 250, "ClientAuth=%s:%s", + ac->client_name, encoded); memwipe(encoded, 0, strlen(encoded)); tor_free(encoded); }); @@ -1932,27 +1919,30 @@ handle_control_add_onion(control_connection_t *conn, * ADD_ONION command. Return a new crypto_pk_t and if a new key was generated * and the private key not discarded, the algorithm and serialized private key, * or NULL and an optional control protocol error message on failure. The - * caller is responsible for freeing the returned key_new_blob and err_msg. + * caller is responsible for freeing the returned key_new_blob. * * Note: The error messages returned are deliberately vague to avoid echoing * key material. + * + * Note: conn is only used for writing control replies. For testing + * purposes, it can be NULL if control_write_reply() is appropriately + * mocked. */ STATIC int add_onion_helper_keyarg(const char *arg, int discard_pk, const char **key_new_alg_out, char **key_new_blob_out, add_onion_secret_key_t *decoded_key, int *hs_version, - char **err_msg_out) + control_connection_t *conn) { smartlist_t *key_args = smartlist_new(); crypto_pk_t *pk = NULL; const char *key_new_alg = NULL; char *key_new_blob = NULL; - char *err_msg = NULL; int ret = -1; smartlist_split_string(key_args, arg, ":", SPLIT_IGNORE_BLANK, 0); if (smartlist_len(key_args) != 2) { - err_msg = tor_strdup("512 Invalid key type/blob\r\n"); + control_write_endreply(conn, 512, "Invalid key type/blob"); goto err; } @@ -1969,12 +1959,12 @@ add_onion_helper_keyarg(const char *arg, int discard_pk, /* "RSA:<Base64 Blob>" - Loading a pre-existing RSA1024 key. */ pk = crypto_pk_base64_decode_private(key_blob, strlen(key_blob)); if (!pk) { - err_msg = tor_strdup("512 Failed to decode RSA key\r\n"); + control_write_endreply(conn, 512, "Failed to decode RSA key"); goto err; } if (crypto_pk_num_bits(pk) != PK_BYTES*8) { crypto_pk_free(pk); - err_msg = tor_strdup("512 Invalid RSA key size\r\n"); + control_write_endreply(conn, 512, "Invalid RSA key size"); goto err; } decoded_key->v2 = pk; @@ -1985,39 +1975,40 @@ add_onion_helper_keyarg(const char *arg, int discard_pk, if (base64_decode((char *) sk->seckey, sizeof(sk->seckey), key_blob, strlen(key_blob)) != sizeof(sk->seckey)) { tor_free(sk); - err_msg = tor_strdup("512 Failed to decode ED25519-V3 key\r\n"); + control_write_endreply(conn, 512, "Failed to decode ED25519-V3 key"); goto err; } decoded_key->v3 = sk; *hs_version = HS_VERSION_THREE; } else if (!strcasecmp(key_type_new, key_type)) { /* "NEW:<Algorithm>" - Generating a new key, blob as algorithm. */ - if (!strcasecmp(key_type_rsa1024, key_blob) || - !strcasecmp(key_type_best, key_blob)) { + if (!strcasecmp(key_type_rsa1024, key_blob)) { /* "RSA1024", RSA 1024 bit, also currently "BEST" by default. */ pk = crypto_pk_new(); if (crypto_pk_generate_key(pk)) { - tor_asprintf(&err_msg, "551 Failed to generate %s key\r\n", - key_type_rsa1024); + control_printf_endreply(conn, 551, "Failed to generate %s key", + key_type_rsa1024); goto err; } if (!discard_pk) { if (crypto_pk_base64_encode_private(pk, &key_new_blob)) { crypto_pk_free(pk); - tor_asprintf(&err_msg, "551 Failed to encode %s key\r\n", - key_type_rsa1024); + control_printf_endreply(conn, 551, "Failed to encode %s key", + key_type_rsa1024); goto err; } key_new_alg = key_type_rsa1024; } decoded_key->v2 = pk; *hs_version = HS_VERSION_TWO; - } else if (!strcasecmp(key_type_ed25519_v3, key_blob)) { + } else if (!strcasecmp(key_type_ed25519_v3, key_blob) || + !strcasecmp(key_type_best, key_blob)) { + /* "ED25519-V3", ed25519 key, also currently "BEST" by default. */ ed25519_secret_key_t *sk = tor_malloc_zero(sizeof(*sk)); if (ed25519_secret_key_generate(sk, 1) < 0) { tor_free(sk); - tor_asprintf(&err_msg, "551 Failed to generate %s key\r\n", - key_type_ed25519_v3); + control_printf_endreply(conn, 551, "Failed to generate %s key", + key_type_ed25519_v3); goto err; } if (!discard_pk) { @@ -2027,8 +2018,8 @@ add_onion_helper_keyarg(const char *arg, int discard_pk, sizeof(sk->seckey), 0) != (len - 1)) { tor_free(sk); tor_free(key_new_blob); - tor_asprintf(&err_msg, "551 Failed to encode %s key\r\n", - key_type_ed25519_v3); + control_printf_endreply(conn, 551, "Failed to encode %s key", + key_type_ed25519_v3); goto err; } key_new_alg = key_type_ed25519_v3; @@ -2036,11 +2027,11 @@ add_onion_helper_keyarg(const char *arg, int discard_pk, decoded_key->v3 = sk; *hs_version = HS_VERSION_THREE; } else { - err_msg = tor_strdup("513 Invalid key type\r\n"); + control_write_endreply(conn, 513, "Invalid key type"); goto err; } } else { - err_msg = tor_strdup("513 Invalid key type\r\n"); + control_write_endreply(conn, 513, "Invalid key type"); goto err; } @@ -2054,11 +2045,6 @@ add_onion_helper_keyarg(const char *arg, int discard_pk, }); smartlist_free(key_args); - if (err_msg_out) { - *err_msg_out = err_msg; - } else { - tor_free(err_msg); - } *key_new_alg_out = key_new_alg; *key_new_blob_out = key_new_blob; @@ -2068,27 +2054,30 @@ add_onion_helper_keyarg(const char *arg, int discard_pk, /** Helper function to handle parsing a ClientAuth argument to the * ADD_ONION command. Return a new rend_authorized_client_t, or NULL * and an optional control protocol error message on failure. The - * caller is responsible for freeing the returned auth_client and err_msg. + * caller is responsible for freeing the returned auth_client. * * If 'created' is specified, it will be set to 1 when a new cookie has * been generated. + * + * Note: conn is only used for writing control replies. For testing + * purposes, it can be NULL if control_write_reply() is appropriately + * mocked. */ STATIC rend_authorized_client_t * -add_onion_helper_clientauth(const char *arg, int *created, char **err_msg) +add_onion_helper_clientauth(const char *arg, int *created, + control_connection_t *conn) { int ok = 0; tor_assert(arg); tor_assert(created); - tor_assert(err_msg); - *err_msg = NULL; smartlist_t *auth_args = smartlist_new(); rend_authorized_client_t *client = tor_malloc_zero(sizeof(rend_authorized_client_t)); smartlist_split_string(auth_args, arg, ":", 0, 0); if (smartlist_len(auth_args) < 1 || smartlist_len(auth_args) > 2) { - *err_msg = tor_strdup("512 Invalid ClientAuth syntax\r\n"); + control_write_endreply(conn, 512, "Invalid ClientAuth syntax"); goto err; } client->client_name = tor_strdup(smartlist_get(auth_args, 0)); @@ -2098,7 +2087,7 @@ add_onion_helper_clientauth(const char *arg, int *created, char **err_msg) client->descriptor_cookie, NULL, &decode_err_msg) < 0) { tor_assert(decode_err_msg); - tor_asprintf(err_msg, "512 %s\r\n", decode_err_msg); + control_write_endreply(conn, 512, decode_err_msg); tor_free(decode_err_msg); goto err; } @@ -2109,7 +2098,7 @@ add_onion_helper_clientauth(const char *arg, int *created, char **err_msg) } if (!rend_valid_client_name(client->client_name)) { - *err_msg = tor_strdup("512 Invalid name in ClientAuth\r\n"); + control_write_endreply(conn, 512, "Invalid name in ClientAuth"); goto err; } diff --git a/src/feature/control/control_cmd.h b/src/feature/control/control_cmd.h index 5c3d1a1cec..4b6d54abe7 100644 --- a/src/feature/control/control_cmd.h +++ b/src/feature/control/control_cmd.h @@ -91,10 +91,11 @@ STATIC int add_onion_helper_keyarg(const char *arg, int discard_pk, const char **key_new_alg_out, char **key_new_blob_out, add_onion_secret_key_t *decoded_key, - int *hs_version, char **err_msg_out); + int *hs_version, + control_connection_t *conn); STATIC rend_authorized_client_t *add_onion_helper_clientauth(const char *arg, - int *created, char **err_msg_out); + int *created, control_connection_t *conn); STATIC control_cmd_args_t *control_cmd_parse_args( const char *command, diff --git a/src/feature/control/control_events.c b/src/feature/control/control_events.c index 9e0966ca54..82ea943999 100644 --- a/src/feature/control/control_events.c +++ b/src/feature/control/control_events.c @@ -26,9 +26,9 @@ #include "feature/control/control_fmt.h" #include "feature/control/control_proto.h" #include "feature/dircommon/directory.h" +#include "feature/nodelist/describe.h" #include "feature/nodelist/networkstatus.h" #include "feature/nodelist/nodelist.h" -#include "feature/nodelist/routerinfo.h" #include "feature/control/control_connection_st.h" #include "core/or/entry_connection_st.h" diff --git a/src/feature/control/control_proto.c b/src/feature/control/control_proto.c index 1dd62da2be..5dec87491d 100644 --- a/src/feature/control/control_proto.c +++ b/src/feature/control/control_proto.c @@ -174,10 +174,11 @@ send_control_done(control_connection_t *conn) * @param conn control connection * @param code numeric result code * @param c separator character, usually ' ', '-', or '+' - * @param s string + * @param s string reply content */ -void -control_write_reply(control_connection_t *conn, int code, int c, const char *s) +MOCK_IMPL(void, +control_write_reply, (control_connection_t *conn, int code, int c, + const char *s)) { connection_printf_to_buf(conn, "%03d%c%s\r\n", code, c, s); } diff --git a/src/feature/control/control_proto.h b/src/feature/control/control_proto.h index 101b808d88..3182f3d415 100644 --- a/src/feature/control/control_proto.h +++ b/src/feature/control/control_proto.h @@ -21,8 +21,8 @@ size_t write_escaped_data(const char *data, size_t len, char **out); size_t read_escaped_data(const char *data, size_t len, char **out); void send_control_done(control_connection_t *conn); -void control_write_reply(control_connection_t *conn, int code, int c, - const char *s); +MOCK_DECL(void, control_write_reply, (control_connection_t *conn, int code, + int c, const char *s)); void control_vprintf_reply(control_connection_t *conn, int code, int c, const char *fmt, va_list ap) CHECK_PRINTF(4, 0); diff --git a/src/feature/control/fmt_serverstatus.c b/src/feature/control/fmt_serverstatus.c index a80bf50ad9..33c5ba1336 100644 --- a/src/feature/control/fmt_serverstatus.c +++ b/src/feature/control/fmt_serverstatus.c @@ -9,8 +9,8 @@ #include "app/config/config.h" #include "feature/dirauth/authmode.h" #include "feature/dirauth/voteflags.h"// XXXX remove +#include "feature/nodelist/describe.h" #include "feature/nodelist/nodelist.h" -#include "feature/nodelist/routerinfo.h" #include "feature/nodelist/node_st.h" #include "feature/nodelist/routerinfo_st.h" diff --git a/src/feature/dirauth/authmode.h b/src/feature/dirauth/authmode.h index 48afc3cdb4..bfd5f4dc04 100644 --- a/src/feature/dirauth/authmode.h +++ b/src/feature/dirauth/authmode.h @@ -29,7 +29,7 @@ authdir_mode_v3(const or_options_t *options) #define have_module_dirauth() (1) -#else /* !(defined(HAVE_MODULE_DIRAUTH)) */ +#else /* !defined(HAVE_MODULE_DIRAUTH) */ #define authdir_mode(options) (((void)(options)),0) #define authdir_mode_handles_descs(options,purpose) \ diff --git a/src/feature/dirauth/dirauth_periodic.h b/src/feature/dirauth/dirauth_periodic.h index 1124fae952..866fbd35de 100644 --- a/src/feature/dirauth/dirauth_periodic.h +++ b/src/feature/dirauth/dirauth_periodic.h @@ -12,7 +12,7 @@ void dirauth_register_periodic_events(void); void reschedule_dirvote(const or_options_t *options); -#else /* !(defined(HAVE_MODULE_DIRAUTH)) */ +#else /* !defined(HAVE_MODULE_DIRAUTH) */ static inline void reschedule_dirvote(const or_options_t *options) diff --git a/src/feature/dirauth/dirvote.c b/src/feature/dirauth/dirvote.c index cdbdf5a216..043bbfc227 100644 --- a/src/feature/dirauth/dirvote.c +++ b/src/feature/dirauth/dirvote.c @@ -220,7 +220,6 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, networkstatus_t *v3_ns) { smartlist_t *chunks = smartlist_new(); - char *packages = NULL; char fingerprint[FINGERPRINT_LEN+1]; char digest[DIGEST_LEN]; uint32_t addr; @@ -246,19 +245,6 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, v3_ns->server_versions); protocols_lines = format_protocols_lines_for_vote(v3_ns); - if (v3_ns->package_lines) { - smartlist_t *tmp = smartlist_new(); - SMARTLIST_FOREACH(v3_ns->package_lines, const char *, p, - if (validate_recommended_package_line(p)) - smartlist_add_asprintf(tmp, "package %s\n", p)); - smartlist_sort_strings(tmp); - packages = smartlist_join_strings(tmp, "", 0, NULL); - SMARTLIST_FOREACH(tmp, char *, cp, tor_free(cp)); - smartlist_free(tmp); - } else { - packages = tor_strdup(""); - } - /* Get shared random commitments/reveals line(s). */ shared_random_vote_str = sr_get_string_for_vote(); @@ -344,7 +330,6 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, "voting-delay %d %d\n" "%s%s" /* versions */ "%s" /* protocols */ - "%s" /* packages */ "known-flags %s\n" "flag-thresholds %s\n" "params %s\n" @@ -361,7 +346,6 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, client_versions_line, server_versions_line, protocols_lines, - packages, flags, flag_thresholds, params, @@ -460,7 +444,6 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, tor_free(client_versions_line); tor_free(server_versions_line); tor_free(protocols_lines); - tor_free(packages); SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp)); smartlist_free(chunks); @@ -4668,15 +4651,6 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, tor_assert_nonfatal(protover_all_supported( v3_out->recommended_client_protocols, NULL)); - v3_out->package_lines = smartlist_new(); - { - config_line_t *cl; - for (cl = get_options()->RecommendedPackages; cl; cl = cl->next) { - if (validate_recommended_package_line(cl->value)) - smartlist_add_strdup(v3_out->package_lines, cl->value); - } - } - v3_out->known_flags = smartlist_new(); smartlist_split_string(v3_out->known_flags, DIRVOTE_UNIVERSAL_FLAGS, diff --git a/src/feature/dirauth/dirvote.h b/src/feature/dirauth/dirvote.h index a0cfe0a34c..b7df33a3a9 100644 --- a/src/feature/dirauth/dirvote.h +++ b/src/feature/dirauth/dirvote.h @@ -128,7 +128,7 @@ struct config_line_t; char *format_recommended_version_list(const struct config_line_t *line, int warn); -#else /* !(defined(HAVE_MODULE_DIRAUTH)) */ +#else /* !defined(HAVE_MODULE_DIRAUTH) */ static inline time_t dirvote_act(const or_options_t *options, time_t now) diff --git a/src/feature/dirauth/keypin.c b/src/feature/dirauth/keypin.c index 667feb2c03..3ca2c3ef91 100644 --- a/src/feature/dirauth/keypin.c +++ b/src/feature/dirauth/keypin.c @@ -438,7 +438,7 @@ keypin_load_journal_impl(const char *data, size_t size) tor_log(severity, LD_DIRSERV, "Loaded %d entries from keypin journal. " "Found %d corrupt lines (ignored), %d duplicates (harmless), " - "and %d conflicts (resolved in favor or more recent entry).", + "and %d conflicts (resolved in favor of more recent entry).", n_entries, n_corrupt_lines, n_duplicates, n_conflicts); return 0; diff --git a/src/feature/dirauth/keypin.h b/src/feature/dirauth/keypin.h index ab2362b3f8..1de84f6d4a 100644 --- a/src/feature/dirauth/keypin.h +++ b/src/feature/dirauth/keypin.h @@ -29,7 +29,7 @@ keypin_load_journal(const char *fname) (void)fname; return 0; } -#endif +#endif /* defined(HAVE_MODULE_DIRAUTH) */ void keypin_clear(void); int keypin_check_lone_rsa(const uint8_t *rsa_id_digest); diff --git a/src/feature/dirauth/process_descs.c b/src/feature/dirauth/process_descs.c index 760560a5d9..71e3195c01 100644 --- a/src/feature/dirauth/process_descs.c +++ b/src/feature/dirauth/process_descs.c @@ -216,9 +216,14 @@ dirserv_load_fingerprint_file(void) #define DISABLE_DISABLING_ED25519 -/** Check whether <b>router</b> has a nickname/identity key combination that - * we recognize from the fingerprint list, or an IP we automatically act on - * according to our configuration. Return the appropriate router status. +/** Check whether <b>router</b> has: + * - a nickname/identity key combination that we recognize from the fingerprint + * list, + * - an IP we automatically act on according to our configuration, + * - an appropriate version, and + * - matching pinned keys. + * + * Return the appropriate router status. * * If the status is 'FP_REJECT' and <b>msg</b> is provided, set * *<b>msg</b> to an explanation of why. */ @@ -236,7 +241,7 @@ dirserv_router_get_status(const routerinfo_t *router, const char **msg, return FP_REJECT; } - /* Check for the more usual versions to reject a router first. */ + /* Check for the more common reasons to reject a router first. */ const uint32_t r = dirserv_get_status_impl(d, router->nickname, router->addr, router->or_port, router->platform, msg, severity); @@ -316,7 +321,7 @@ dirserv_would_reject_router(const routerstatus_t *rs) * true, and set *<b>msg</b> (if present) to a rejection message. Otherwise * return false. */ -static bool +STATIC bool dirserv_rejects_tor_version(const char *platform, const char **msg) { @@ -450,20 +455,32 @@ dirserv_free_fingerprint_list(void) /** Return -1 if <b>ri</b> has a private or otherwise bad address, * unless we're configured to not care. Return 0 if all ok. */ -static int +STATIC int dirserv_router_has_valid_address(routerinfo_t *ri) { tor_addr_t addr; + if (get_options()->DirAllowPrivateAddresses) return 0; /* whatever it is, we're fine with it */ + tor_addr_from_ipv4h(&addr, ri->addr); + if (tor_addr_is_null(&addr) || tor_addr_is_internal(&addr, 0)) { + log_info(LD_DIRSERV, + "Router %s published internal IPv4 address. Refusing.", + router_describe(ri)); + return -1; /* it's a private IP, we should reject it */ + } - if (tor_addr_is_internal(&addr, 0)) { + /* We only check internal v6 on non-null addresses because we do not require + * IPv6 and null IPv6 is normal. */ + if (!tor_addr_is_null(&ri->ipv6_addr) && + tor_addr_is_internal(&ri->ipv6_addr, 0)) { log_info(LD_DIRSERV, - "Router %s published internal IP address. Refusing.", + "Router %s published internal IPv6 address. Refusing.", router_describe(ri)); return -1; /* it's a private IP, we should reject it */ } + return 0; } @@ -562,7 +579,7 @@ dirserv_add_multiple_descriptors(const char *desc, size_t desclen, int general = purpose == ROUTER_PURPOSE_GENERAL; tor_assert(msg); - r=ROUTER_ADDED_SUCCESSFULLY; /*Least severe return value. */ + r=ROUTER_ADDED_SUCCESSFULLY; /* Least severe return value. */ if (!string_is_utf8_no_bom(desc, desclen)) { *msg = "descriptor(s) or extrainfo(s) not valid UTF-8 or had BOM."; @@ -578,9 +595,7 @@ dirserv_add_multiple_descriptors(const char *desc, size_t desclen, !general ? router_purpose_to_string(purpose) : "", !general ? "\n" : "")<0) { *msg = "Couldn't format annotations"; - /* XXX Not cool: we return -1 below, but (was_router_added_t)-1 is - * ROUTER_BAD_EI, which isn't what's gone wrong here. :( */ - return -1; + return ROUTER_AUTHDIR_BUG_ANNOTATIONS; } s = desc; diff --git a/src/feature/dirauth/process_descs.h b/src/feature/dirauth/process_descs.h index a8a1dcca1e..e504daa7b7 100644 --- a/src/feature/dirauth/process_descs.h +++ b/src/feature/dirauth/process_descs.h @@ -38,7 +38,7 @@ uint32_t dirserv_router_get_status(const routerinfo_t *router, int severity); void dirserv_set_node_flags_from_authoritative_status(node_t *node, uint32_t authstatus); -#else +#else /* !defined(HAVE_MODULE_DIRAUTH) */ static inline int dirserv_load_fingerprint_file(void) { @@ -107,6 +107,12 @@ dirserv_set_node_flags_from_authoritative_status(node_t *node, (void)node; (void)authstatus; } -#endif +#endif /* defined(HAVE_MODULE_DIRAUTH) */ + +#ifdef TOR_UNIT_TESTS +STATIC int dirserv_router_has_valid_address(routerinfo_t *ri); +STATIC bool dirserv_rejects_tor_version(const char *platform, + const char **msg); +#endif /* defined(TOR_UNIT_TESTS) */ #endif /* !defined(TOR_RECV_UPLOADS_H) */ diff --git a/src/feature/dirauth/reachability.h b/src/feature/dirauth/reachability.h index 8a83f0c493..46d0e7ee2e 100644 --- a/src/feature/dirauth/reachability.h +++ b/src/feature/dirauth/reachability.h @@ -34,7 +34,7 @@ void dirserv_orconn_tls_done(const tor_addr_t *addr, uint16_t or_port, const char *digest_rcvd, const struct ed25519_public_key_t *ed_id_rcvd); -#else +#else /* !defined(HAVE_MODULE_DIRAUTH) */ static inline int dirserv_should_launch_reachability_test(const routerinfo_t *ri, const routerinfo_t *ri_old) @@ -54,6 +54,6 @@ dirserv_orconn_tls_done(const tor_addr_t *addr, (void)digest_rcvd; (void)ed_id_rcvd; } -#endif +#endif /* defined(HAVE_MODULE_DIRAUTH) */ #endif /* !defined(TOR_REACHABILITY_H) */ diff --git a/src/feature/dirauth/shared_random.c b/src/feature/dirauth/shared_random.c index 5ccf1a95e5..a45f0a29c3 100644 --- a/src/feature/dirauth/shared_random.c +++ b/src/feature/dirauth/shared_random.c @@ -90,7 +90,7 @@ #include "core/or/or.h" #include "feature/dirauth/shared_random.h" #include "app/config/config.h" -#include "app/config/confparse.h" +#include "lib/confmgt/confparse.h" #include "lib/crypt_ops/crypto_rand.h" #include "lib/crypt_ops/crypto_util.h" #include "feature/nodelist/networkstatus.h" diff --git a/src/feature/dirauth/shared_random.h b/src/feature/dirauth/shared_random.h index 1d8fa89b0f..7ff9f15512 100644 --- a/src/feature/dirauth/shared_random.h +++ b/src/feature/dirauth/shared_random.h @@ -110,7 +110,7 @@ int sr_init(int save_to_disk); void sr_save_and_cleanup(void); void sr_act_post_consensus(const networkstatus_t *consensus); -#else /* !(defined(HAVE_MODULE_DIRAUTH)) */ +#else /* !defined(HAVE_MODULE_DIRAUTH) */ static inline int sr_init(int save_to_disk) diff --git a/src/feature/dirauth/shared_random_state.c b/src/feature/dirauth/shared_random_state.c index b669e3836e..76befb0f5f 100644 --- a/src/feature/dirauth/shared_random_state.c +++ b/src/feature/dirauth/shared_random_state.c @@ -12,7 +12,7 @@ #include "core/or/or.h" #include "app/config/config.h" -#include "app/config/confparse.h" +#include "lib/confmgt/confparse.h" #include "lib/crypt_ops/crypto_util.h" #include "feature/dirauth/dirvote.h" #include "feature/nodelist/networkstatus.h" @@ -51,24 +51,21 @@ static const char dstate_cur_srv_key[] = "SharedRandCurrentValue"; * members with CONF_CHECK_VAR_TYPE. */ DUMMY_TYPECHECK_INSTANCE(sr_disk_state_t); -/* These next two are duplicates or near-duplicates from config.c */ -#define VAR(name, conftype, member, initvalue) \ - { name, CONFIG_TYPE_ ## conftype, offsetof(sr_disk_state_t, member), \ - initvalue CONF_TEST_MEMBERS(sr_disk_state_t, conftype, member) } -/* As VAR, but the option name and member name are the same. */ -#define V(member, conftype, initvalue) \ +#define VAR(varname,conftype,member,initvalue) \ + CONFIG_VAR_ETYPE(sr_disk_state_t, varname, conftype, member, 0, initvalue) +#define V(member,conftype,initvalue) \ VAR(#member, conftype, member, initvalue) + /* Our persistent state magic number. */ #define SR_DISK_STATE_MAGIC 0x98AB1254 static int disk_state_validate_cb(void *old_state, void *state, void *default_state, int from_setconf, char **msg); -static void disk_state_free_cb(void *); /* Array of variables that are saved to disk as a persistent state. */ -static config_var_t state_vars[] = { - V(Version, UINT, "0"), +static const config_var_t state_vars[] = { + V(Version, POSINT, "0"), V(TorVersion, STRING, NULL), V(ValidAfter, ISOTIME, NULL), V(ValidUntil, ISOTIME, NULL), @@ -83,25 +80,43 @@ static config_var_t state_vars[] = { /* "Extra" variable in the state that receives lines we can't parse. This * lets us preserve options from versions of Tor newer than us. */ -static config_var_t state_extra_var = { - "__extra", CONFIG_TYPE_LINELIST, - offsetof(sr_disk_state_t, ExtraLines), NULL - CONF_TEST_MEMBERS(sr_disk_state_t, LINELIST, ExtraLines) +static const struct_member_t state_extra_var = { + .name = "__extra", + .type = CONFIG_TYPE_LINELIST, + .offset = offsetof(sr_disk_state_t, ExtraLines), }; /* Configuration format of sr_disk_state_t. */ static const config_format_t state_format = { sizeof(sr_disk_state_t), - SR_DISK_STATE_MAGIC, - offsetof(sr_disk_state_t, magic_), + { + "sr_disk_state_t", + SR_DISK_STATE_MAGIC, + offsetof(sr_disk_state_t, magic_), + }, NULL, NULL, state_vars, disk_state_validate_cb, - disk_state_free_cb, + NULL, &state_extra_var, + -1, }; +/* Global configuration manager for the shared-random state file */ +static config_mgr_t *shared_random_state_mgr = NULL; + +/** Return the configuration manager for the shared-random state file. */ +static const config_mgr_t * +get_srs_mgr(void) +{ + if (PREDICT_UNLIKELY(shared_random_state_mgr == NULL)) { + shared_random_state_mgr = config_mgr_new(&state_format); + config_mgr_freeze(shared_random_state_mgr); + } + return shared_random_state_mgr; +} + static void state_query_del_(sr_state_object_t obj_type, void *data); /* Return a string representation of a protocol phase. */ @@ -263,23 +278,22 @@ disk_state_free_(sr_disk_state_t *state) if (state == NULL) { return; } - config_free(&state_format, state); + config_free(get_srs_mgr(), state); } /* Allocate a new disk state, initialize it and return it. */ static sr_disk_state_t * disk_state_new(time_t now) { - sr_disk_state_t *new_state = tor_malloc_zero(sizeof(*new_state)); + sr_disk_state_t *new_state = config_new(get_srs_mgr()); - new_state->magic_ = SR_DISK_STATE_MAGIC; new_state->Version = SR_PROTO_VERSION; new_state->TorVersion = tor_strdup(get_version()); new_state->ValidUntil = get_state_valid_until_time(now); new_state->ValidAfter = now; /* Init config format. */ - config_init(&state_format, new_state); + config_init(get_srs_mgr(), new_state); return new_state; } @@ -347,12 +361,6 @@ disk_state_validate_cb(void *old_state, void *state, void *default_state, return 0; } -static void -disk_state_free_cb(void *state) -{ - disk_state_free_(state); -} - /* Parse the Commit line(s) in the disk state and translate them to the * the memory state. Return 0 on success else -1 on error. */ static int @@ -583,11 +591,12 @@ disk_state_reset(void) config_free_lines(sr_disk_state->ExtraLines); tor_free(sr_disk_state->TorVersion); - /* Clean up the struct */ - memset(sr_disk_state, 0, sizeof(*sr_disk_state)); + /* Clear other fields. */ + sr_disk_state->ValidAfter = 0; + sr_disk_state->ValidUntil = 0; + sr_disk_state->Version = 0; /* Reset it with useful data */ - sr_disk_state->magic_ = SR_DISK_STATE_MAGIC; sr_disk_state->TorVersion = tor_strdup(get_version()); } @@ -682,7 +691,7 @@ disk_state_load_from_disk_impl(const char *fname) } disk_state = disk_state_new(time(NULL)); - config_assign(&state_format, disk_state, lines, 0, &errmsg); + config_assign(get_srs_mgr(), disk_state, lines, 0, &errmsg); config_free_lines(lines); if (errmsg) { log_warn(LD_DIR, "SR: Reading state error: %s", errmsg); @@ -735,7 +744,7 @@ disk_state_save_to_disk(void) /* Make sure that our disk state is up to date with our memory state * before saving it to disk. */ disk_state_update(); - state = config_dump(&state_format, NULL, sr_disk_state, 0, 0); + state = config_dump(get_srs_mgr(), NULL, sr_disk_state, 0, 0); format_local_iso_time(tbuf, now); tor_asprintf(&content, "# Tor shared random state file last generated on %s " @@ -1277,6 +1286,7 @@ sr_state_free_all(void) /* Nullify our global state. */ sr_state = NULL; sr_disk_state = NULL; + config_mgr_free(shared_random_state_mgr); } /* Save our current state in memory to disk. */ diff --git a/src/feature/dircache/conscache.c b/src/feature/dircache/conscache.c index cf4fe8701d..2ec9981c03 100644 --- a/src/feature/dircache/conscache.c +++ b/src/feature/dircache/conscache.c @@ -92,7 +92,7 @@ consensus_cache_open(const char *subdir, int max_entries) */ #define VERY_LARGE_STORAGEDIR_LIMIT (1000*1000) storagedir_max_entries = VERY_LARGE_STORAGEDIR_LIMIT; -#else /* !(defined(MUST_UNMAP_TO_UNLINK)) */ +#else /* !defined(MUST_UNMAP_TO_UNLINK) */ /* Otherwise, we can just tell the storagedir to use the same limits * as this cache. */ storagedir_max_entries = max_entries; diff --git a/src/feature/dircache/dircache.c b/src/feature/dircache/dircache.c index 1b36f716f4..d4d0ad9939 100644 --- a/src/feature/dircache/dircache.c +++ b/src/feature/dircache/dircache.c @@ -1390,9 +1390,11 @@ handle_get_hs_descriptor_v3(dir_connection_t *conn, const char *pubkey_str = NULL; const char *url = args->url; - /* Reject unencrypted dir connections */ - if (!connection_dir_is_encrypted(conn)) { - write_short_http_response(conn, 404, "Not found"); + /* Reject non anonymous dir connections (which also tests if encrypted). We + * do not allow single hop clients to query an HSDir. */ + if (!connection_dir_is_anonymous(conn)) { + write_short_http_response(conn, 503, + "Rejecting single hop HS v3 descriptor request"); goto done; } @@ -1632,10 +1634,15 @@ directory_handle_command_post,(dir_connection_t *conn, const char *headers, goto done; } - /* Handle HS descriptor publish request. */ - /* XXX: This should be disabled with a consensus param until we want to - * the prop224 be deployed and thus use. */ - if (connection_dir_is_encrypted(conn) && !strcmpstart(url, "/tor/hs/")) { + /* Handle HS descriptor publish request. We force an anonymous connection + * (which also tests for encrypted). We do not allow single-hop client to + * post a descriptor onto an HSDir. */ + if (!strcmpstart(url, "/tor/hs/")) { + if (!connection_dir_is_anonymous(conn)) { + write_short_http_response(conn, 503, + "Rejecting single hop HS descriptor post"); + goto done; + } const char *msg = "HS descriptor stored successfully."; /* We most probably have a publish request for an HS descriptor. */ diff --git a/src/feature/dircommon/directory.c b/src/feature/dircommon/directory.c index 9e6f72e9ac..8e5b413326 100644 --- a/src/feature/dircommon/directory.c +++ b/src/feature/dircommon/directory.c @@ -7,6 +7,10 @@ #include "app/config/config.h" #include "core/mainloop/connection.h" +#include "core/or/circuitlist.h" +#include "core/or/connection_edge.h" +#include "core/or/connection_or.h" +#include "core/or/channeltls.h" #include "feature/dircache/dircache.h" #include "feature/dircache/dirserv.h" #include "feature/dirclient/dirclient.h" @@ -15,6 +19,10 @@ #include "feature/stats/geoip_stats.h" #include "lib/compress/compress.h" +#include "core/or/circuit_st.h" +#include "core/or/or_circuit_st.h" +#include "core/or/edge_connection_st.h" +#include "core/or/or_connection_st.h" #include "feature/dircommon/dir_connection_st.h" #include "feature/nodelist/routerinfo_st.h" @@ -167,6 +175,82 @@ connection_dir_is_encrypted(const dir_connection_t *conn) return TO_CONN(conn)->linked; } +/** Return true iff the given directory connection <b>dir_conn</b> is + * anonymous, that is, it is on a circuit via a public relay and not directly + * from a client or bridge. + * + * For client circuits via relays: true for 2-hop+ paths. + * For client circuits via bridges: true for 3-hop+ paths. + * + * This first test if the connection is encrypted since it is a strong + * requirement for anonymity. */ +bool +connection_dir_is_anonymous(const dir_connection_t *dir_conn) +{ + const connection_t *conn, *linked_conn; + const edge_connection_t *edge_conn; + const circuit_t *circ; + + tor_assert(dir_conn); + + if (!connection_dir_is_encrypted(dir_conn)) { + return false; + } + + /* + * Buckle up, we'll do a deep dive into the connection in order to get the + * final connection channel of that connection in order to figure out if + * this is a client or relay link. + * + * We go: dir_conn -> linked_conn -> edge_conn -> on_circuit -> p_chan. + */ + + conn = TO_CONN(dir_conn); + linked_conn = conn->linked_conn; + + /* The dir connection should be connected to an edge connection. It can not + * be closed or marked for close. */ + if (linked_conn == NULL || linked_conn->magic != EDGE_CONNECTION_MAGIC || + conn->linked_conn_is_closed || conn->linked_conn->marked_for_close) { + log_debug(LD_DIR, "Directory connection is not anonymous: " + "not linked to edge"); + return false; + } + + edge_conn = TO_EDGE_CONN((connection_t *) linked_conn); + circ = edge_conn->on_circuit; + + /* Can't be a circuit we initiated and without a circuit, no channel. */ + if (circ == NULL || CIRCUIT_IS_ORIGIN(circ)) { + log_debug(LD_DIR, "Directory connection is not anonymous: " + "not on OR circuit"); + return false; + } + + /* It is possible that the circuit was closed because one of the channel was + * closed or a DESTROY cell was received. Either way, this connection can + * not continue so return that it is not anonymous since we can not know for + * sure if it is. */ + if (circ->marked_for_close) { + log_debug(LD_DIR, "Directory connection is not anonymous: " + "circuit marked for close"); + return false; + } + + /* Get the previous channel to learn if it is a client or relay link. We + * BUG() because if the circuit is not mark for close, we ought to have a + * p_chan else we have a code flow issue. */ + if (BUG(CONST_TO_OR_CIRCUIT(circ)->p_chan == NULL)) { + log_debug(LD_DIR, "Directory connection is not anonymous: " + "no p_chan on circuit"); + return false; + } + + /* Will be true if the channel is an unauthenticated peer which is only true + * for clients and bridges. */ + return !channel_is_client(CONST_TO_OR_CIRCUIT(circ)->p_chan); +} + /** Parse an HTTP request line at the start of a headers string. On failure, * return -1. On success, set *<b>command_out</b> to a copy of the HTTP * command ("get", "post", etc), set *<b>url_out</b> to a copy of the URL, and diff --git a/src/feature/dircommon/directory.h b/src/feature/dircommon/directory.h index ba3f8c1b0e..4fc743ad3d 100644 --- a/src/feature/dircommon/directory.h +++ b/src/feature/dircommon/directory.h @@ -94,6 +94,7 @@ int parse_http_command(const char *headers, char *http_get_header(const char *headers, const char *which); int connection_dir_is_encrypted(const dir_connection_t *conn); +bool connection_dir_is_anonymous(const dir_connection_t *conn); int connection_dir_reached_eof(dir_connection_t *conn); int connection_dir_process_inbuf(dir_connection_t *conn); int connection_dir_finished_flushing(dir_connection_t *conn); diff --git a/src/feature/dirparse/microdesc_parse.c b/src/feature/dirparse/microdesc_parse.c index 22cc1e272e..4bb4db7821 100644 --- a/src/feature/dirparse/microdesc_parse.c +++ b/src/feature/dirparse/microdesc_parse.c @@ -92,6 +92,190 @@ find_start_of_next_microdesc(const char *s, const char *eos) #undef NEXT_LINE } +static inline int +policy_is_reject_star_or_null(struct short_policy_t *policy) +{ + return !policy || short_policy_is_reject_star(policy); +} + +/** + * Return a human-readable description of a given saved_location_t. + * Never returns NULL. + **/ +static const char * +saved_location_to_string(saved_location_t where) +{ + const char *location; + switch (where) { + case SAVED_NOWHERE: + location = "download or generated string"; + break; + case SAVED_IN_CACHE: + location = "cache"; + break; + case SAVED_IN_JOURNAL: + location = "journal"; + break; + default: + location = "unknown location"; + break; + } + return location; +} + +/** + * Given a microdescriptor stored in <b>where</b> which starts at <b>s</b>, + * which ends at <b>start_of_next_microdescriptor</b>, and which is located + * within a larger document beginning at <b>start</b>: Fill in the body, + * bodylen, bodylen, saved_location, off, and digest fields of <b>md</b> as + * appropriate. + * + * The body field will be an alias within <b>s</b> if <b>saved_location</b> + * is SAVED_IN_CACHE, and will be copied into body and nul-terminated + * otherwise. + **/ +static int +microdesc_extract_body(microdesc_t *md, + const char *start, + const char *s, const char *start_of_next_microdesc, + saved_location_t where) +{ + const bool copy_body = (where != SAVED_IN_CACHE); + + const char *cp = tor_memstr(s, start_of_next_microdesc-s, "onion-key"); + + const bool no_onion_key = (cp == NULL); + if (no_onion_key) { + cp = s; /* So that we have *some* junk to put in the body */ + } + + md->bodylen = start_of_next_microdesc - cp; + md->saved_location = where; + if (copy_body) + md->body = tor_memdup_nulterm(cp, md->bodylen); + else + md->body = (char*)cp; + md->off = cp - start; + + crypto_digest256(md->digest, md->body, md->bodylen, DIGEST_SHA256); + + return no_onion_key ? -1 : 0; +} + +/** + * Parse a microdescriptor which begins at <b>s</b> and ends at + * <b>start_of_next_microdesc. Store its fields into <b>md</b>. Use + * <b>where</b> for generating log information. If <b>allow_annotations</b> + * is true, then one or more annotations may precede the microdescriptor body + * proper. Use <b>area</b> for memory management, clearing it when done. + * + * On success, return 0; otherwise return -1. + **/ +static int +microdesc_parse_fields(microdesc_t *md, + memarea_t *area, + const char *s, const char *start_of_next_microdesc, + int allow_annotations, + saved_location_t where) +{ + smartlist_t *tokens = smartlist_new(); + int rv = -1; + int flags = allow_annotations ? TS_ANNOTATIONS_OK : 0; + directory_token_t *tok; + + if (tokenize_string(area, s, start_of_next_microdesc, tokens, + microdesc_token_table, flags)) { + log_warn(LD_DIR, "Unparseable microdescriptor found in %s", + saved_location_to_string(where)); + goto err; + } + + if ((tok = find_opt_by_keyword(tokens, A_LAST_LISTED))) { + if (parse_iso_time(tok->args[0], &md->last_listed)) { + log_warn(LD_DIR, "Bad last-listed time in microdescriptor"); + goto err; + } + } + + tok = find_by_keyword(tokens, K_ONION_KEY); + if (!crypto_pk_public_exponent_ok(tok->key)) { + log_warn(LD_DIR, + "Relay's onion key had invalid exponent."); + goto err; + } + md->onion_pkey = tor_memdup(tok->object_body, tok->object_size); + md->onion_pkey_len = tok->object_size; + crypto_pk_free(tok->key); + + if ((tok = find_opt_by_keyword(tokens, K_ONION_KEY_NTOR))) { + curve25519_public_key_t k; + tor_assert(tok->n_args >= 1); + if (curve25519_public_from_base64(&k, tok->args[0]) < 0) { + log_warn(LD_DIR, "Bogus ntor-onion-key in microdesc"); + goto err; + } + md->onion_curve25519_pkey = + tor_memdup(&k, sizeof(curve25519_public_key_t)); + } + + smartlist_t *id_lines = find_all_by_keyword(tokens, K_ID); + if (id_lines) { + SMARTLIST_FOREACH_BEGIN(id_lines, directory_token_t *, t) { + tor_assert(t->n_args >= 2); + if (!strcmp(t->args[0], "ed25519")) { + if (md->ed25519_identity_pkey) { + log_warn(LD_DIR, "Extra ed25519 key in microdesc"); + smartlist_free(id_lines); + goto err; + } + ed25519_public_key_t k; + if (ed25519_public_from_base64(&k, t->args[1])<0) { + log_warn(LD_DIR, "Bogus ed25519 key in microdesc"); + smartlist_free(id_lines); + goto err; + } + md->ed25519_identity_pkey = tor_memdup(&k, sizeof(k)); + } + } SMARTLIST_FOREACH_END(t); + smartlist_free(id_lines); + } + + { + smartlist_t *a_lines = find_all_by_keyword(tokens, K_A); + if (a_lines) { + find_single_ipv6_orport(a_lines, &md->ipv6_addr, &md->ipv6_orport); + smartlist_free(a_lines); + } + } + + if ((tok = find_opt_by_keyword(tokens, K_FAMILY))) { + md->family = nodefamily_parse(tok->args[0], + NULL, + NF_WARN_MALFORMED); + } + + if ((tok = find_opt_by_keyword(tokens, K_P))) { + md->exit_policy = parse_short_policy(tok->args[0]); + } + if ((tok = find_opt_by_keyword(tokens, K_P6))) { + md->ipv6_exit_policy = parse_short_policy(tok->args[0]); + } + + if (policy_is_reject_star_or_null(md->exit_policy) && + policy_is_reject_star_or_null(md->ipv6_exit_policy)) { + md->policy_is_reject_star = 1; + } + + rv = 0; + err: + + SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t)); + memarea_clear(area); + smartlist_free(tokens); + + return rv; +} + /** Parse as many microdescriptors as are found from the string starting at * <b>s</b> and ending at <b>eos</b>. If allow_annotations is set, read any * annotations we recognize and ignore ones we don't. @@ -109,16 +293,11 @@ microdescs_parse_from_string(const char *s, const char *eos, saved_location_t where, smartlist_t *invalid_digests_out) { - smartlist_t *tokens; smartlist_t *result; microdesc_t *md = NULL; memarea_t *area; const char *start = s; const char *start_of_next_microdesc; - int flags = allow_annotations ? TS_ANNOTATIONS_OK : 0; - const int copy_body = (where != SAVED_IN_CACHE); - - directory_token_t *tok; if (!eos) eos = s + strlen(s); @@ -126,151 +305,47 @@ microdescs_parse_from_string(const char *s, const char *eos, s = eat_whitespace_eos(s, eos); area = memarea_new(); result = smartlist_new(); - tokens = smartlist_new(); while (s < eos) { - int okay = 0; + bool okay = false; start_of_next_microdesc = find_start_of_next_microdesc(s, eos); if (!start_of_next_microdesc) start_of_next_microdesc = eos; md = tor_malloc_zero(sizeof(microdesc_t)); + uint8_t md_digest[DIGEST256_LEN]; { - const char *cp = tor_memstr(s, start_of_next_microdesc-s, - "onion-key"); - const int no_onion_key = (cp == NULL); - if (no_onion_key) { - cp = s; /* So that we have *some* junk to put in the body */ - } + const bool body_not_found = + microdesc_extract_body(md, start, s, + start_of_next_microdesc, + where) < 0; - md->bodylen = start_of_next_microdesc - cp; - md->saved_location = where; - if (copy_body) - md->body = tor_memdup_nulterm(cp, md->bodylen); - else - md->body = (char*)cp; - md->off = cp - start; - crypto_digest256(md->digest, md->body, md->bodylen, DIGEST_SHA256); - if (no_onion_key) { + memcpy(md_digest, md->digest, DIGEST256_LEN); + if (body_not_found) { log_fn(LOG_PROTOCOL_WARN, LD_DIR, "Malformed or truncated descriptor"); goto next; } } - if (tokenize_string(area, s, start_of_next_microdesc, tokens, - microdesc_token_table, flags)) { - const char *location; - switch (where) { - case SAVED_NOWHERE: - location = "download or generated string"; - break; - case SAVED_IN_CACHE: - location = "cache"; - break; - case SAVED_IN_JOURNAL: - location = "journal"; - break; - default: - location = "unknown location"; - break; - } - log_warn(LD_DIR, "Unparseable microdescriptor found in %s", location); - goto next; - } - - if ((tok = find_opt_by_keyword(tokens, A_LAST_LISTED))) { - if (parse_iso_time(tok->args[0], &md->last_listed)) { - log_warn(LD_DIR, "Bad last-listed time in microdescriptor"); - goto next; - } + if (microdesc_parse_fields(md, area, s, start_of_next_microdesc, + allow_annotations, where) == 0) { + smartlist_add(result, md); + md = NULL; // prevent free + okay = true; } - tok = find_by_keyword(tokens, K_ONION_KEY); - if (!crypto_pk_public_exponent_ok(tok->key)) { - log_warn(LD_DIR, - "Relay's onion key had invalid exponent."); - goto next; - } - md->onion_pkey = tor_memdup(tok->object_body, tok->object_size); - md->onion_pkey_len = tok->object_size; - crypto_pk_free(tok->key); - - if ((tok = find_opt_by_keyword(tokens, K_ONION_KEY_NTOR))) { - curve25519_public_key_t k; - tor_assert(tok->n_args >= 1); - if (curve25519_public_from_base64(&k, tok->args[0]) < 0) { - log_warn(LD_DIR, "Bogus ntor-onion-key in microdesc"); - goto next; - } - md->onion_curve25519_pkey = - tor_memdup(&k, sizeof(curve25519_public_key_t)); - } - - smartlist_t *id_lines = find_all_by_keyword(tokens, K_ID); - if (id_lines) { - SMARTLIST_FOREACH_BEGIN(id_lines, directory_token_t *, t) { - tor_assert(t->n_args >= 2); - if (!strcmp(t->args[0], "ed25519")) { - if (md->ed25519_identity_pkey) { - log_warn(LD_DIR, "Extra ed25519 key in microdesc"); - smartlist_free(id_lines); - goto next; - } - ed25519_public_key_t k; - if (ed25519_public_from_base64(&k, t->args[1])<0) { - log_warn(LD_DIR, "Bogus ed25519 key in microdesc"); - smartlist_free(id_lines); - goto next; - } - md->ed25519_identity_pkey = tor_memdup(&k, sizeof(k)); - } - } SMARTLIST_FOREACH_END(t); - smartlist_free(id_lines); - } - - { - smartlist_t *a_lines = find_all_by_keyword(tokens, K_A); - if (a_lines) { - find_single_ipv6_orport(a_lines, &md->ipv6_addr, &md->ipv6_orport); - smartlist_free(a_lines); - } - } - - if ((tok = find_opt_by_keyword(tokens, K_FAMILY))) { - md->family = nodefamily_parse(tok->args[0], - NULL, - NF_WARN_MALFORMED); - } - - if ((tok = find_opt_by_keyword(tokens, K_P))) { - md->exit_policy = parse_short_policy(tok->args[0]); - } - if ((tok = find_opt_by_keyword(tokens, K_P6))) { - md->ipv6_exit_policy = parse_short_policy(tok->args[0]); - } - - smartlist_add(result, md); - okay = 1; - - md = NULL; next: if (! okay && invalid_digests_out) { smartlist_add(invalid_digests_out, - tor_memdup(md->digest, DIGEST256_LEN)); + tor_memdup(md_digest, DIGEST256_LEN)); } microdesc_free(md); md = NULL; - - SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t)); - memarea_clear(area); - smartlist_clear(tokens); s = start_of_next_microdesc; } - SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t)); memarea_drop_all(area); - smartlist_free(tokens); return result; } diff --git a/src/feature/dirparse/unparseable.h b/src/feature/dirparse/unparseable.h index 49e047961f..36c6b5a1ec 100644 --- a/src/feature/dirparse/unparseable.h +++ b/src/feature/dirparse/unparseable.h @@ -26,7 +26,7 @@ void dump_desc_init(void); log_debug(LD_MM, "Area for %s has %lu allocated; using %lu.", \ name, (unsigned long)alloc, (unsigned long)used); \ STMT_END -#else /* !(defined(DEBUG_AREA_ALLOC)) */ +#else /* !defined(DEBUG_AREA_ALLOC) */ #define DUMP_AREA(a,name) STMT_NIL #endif /* defined(DEBUG_AREA_ALLOC) */ diff --git a/src/feature/hibernate/hibernate.c b/src/feature/hibernate/hibernate.c index 674fe3c813..f7847d9a16 100644 --- a/src/feature/hibernate/hibernate.c +++ b/src/feature/hibernate/hibernate.c @@ -67,7 +67,7 @@ static hibernate_state_t hibernate_state = HIBERNATE_STATE_INITIAL; /** If are hibernating, when do we plan to wake up? Set to 0 if we * aren't hibernating. */ static time_t hibernate_end_time = 0; -/** If we are shutting down, when do we plan finally exit? Set to 0 if we +/** If we are shutting down, when do we plan to finally exit? Set to 0 if we * aren't shutting down. (This is obsolete; scheduled shutdowns are supposed * to happen from mainloop_schedule_shutdown() now.) */ static time_t shutdown_time = 0; @@ -562,7 +562,7 @@ time_to_record_bandwidth_usage(time_t now) /* Note every 600 sec */ #define NOTE_INTERVAL (600) /* Or every 20 megabytes */ -#define NOTE_BYTES 20*(1024*1024) +#define NOTE_BYTES (20*1024*1024) static uint64_t last_read_bytes_noted = 0; static uint64_t last_written_bytes_noted = 0; static time_t last_time_noted = 0; @@ -815,7 +815,7 @@ hibernate_soft_limit_reached(void) * We want to stop accepting connections when ALL of the following are true: * - We expect to use up the remaining bytes in under 3 hours * - We have used up 95% of our bytes. - * - We have less than 500MB of bytes left. + * - We have less than 500MBytes left. */ uint64_t soft_limit = (uint64_t) (acct_max * SOFT_LIM_PCT); if (acct_max > SOFT_LIM_BYTES && acct_max - SOFT_LIM_BYTES > soft_limit) { diff --git a/src/feature/hs/hs_cache.c b/src/feature/hs/hs_cache.c index 05f9940ae6..9817113b23 100644 --- a/src/feature/hs/hs_cache.c +++ b/src/feature/hs/hs_cache.c @@ -710,6 +710,11 @@ cache_clean_v3_as_client(time_t now) MAP_DEL_CURRENT(key); entry_size = cache_get_client_entry_size(entry); bytes_removed += entry_size; + /* We just removed an old descriptor. We need to close all intro circuits + * so we don't have leftovers that can be selected while lacking a + * descriptor. We leave the rendezvous circuits opened because they could + * be in use. */ + hs_client_close_intro_circuits_from_desc(entry->desc); /* Entry is not in the cache anymore, destroy it. */ cache_client_desc_free(entry); /* Update our OOM. We didn't use the remove() function because we are in diff --git a/src/feature/hs/hs_cell.c b/src/feature/hs/hs_cell.c index 69f1ccbef4..df59f73c1b 100644 --- a/src/feature/hs/hs_cell.c +++ b/src/feature/hs/hs_cell.c @@ -473,10 +473,132 @@ introduce1_set_legacy_id(trn_cell_introduce1_t *cell, } } +/* Build and add to the given DoS cell extension the given parameter type and + * value. */ +static void +build_establish_intro_dos_param(trn_cell_extension_dos_t *dos_ext, + uint8_t param_type, uint64_t param_value) +{ + trn_cell_extension_dos_param_t *dos_param = + trn_cell_extension_dos_param_new(); + + /* Extra safety. We should never send an unknown parameter type. */ + tor_assert(param_type == TRUNNEL_DOS_PARAM_TYPE_INTRO2_RATE_PER_SEC || + param_type == TRUNNEL_DOS_PARAM_TYPE_INTRO2_BURST_PER_SEC); + + trn_cell_extension_dos_param_set_type(dos_param, param_type); + trn_cell_extension_dos_param_set_value(dos_param, param_value); + trn_cell_extension_dos_add_params(dos_ext, dos_param); + + /* Not freeing the trunnel object because it is now owned by dos_ext. */ +} + +/* Build the DoS defense cell extension and put it in the given extensions + * object. Return 0 on success, -1 on failure. (Right now, failure is only + * possible if there is a bug.) */ +static int +build_establish_intro_dos_extension(const hs_service_config_t *service_config, + trn_cell_extension_t *extensions) +{ + ssize_t ret; + size_t dos_ext_encoded_len; + uint8_t *field_array; + trn_cell_extension_field_t *field = NULL; + trn_cell_extension_dos_t *dos_ext = NULL; + + tor_assert(service_config); + tor_assert(extensions); + + /* We are creating a cell extension field of the type DoS. */ + field = trn_cell_extension_field_new(); + trn_cell_extension_field_set_field_type(field, + TRUNNEL_CELL_EXTENSION_TYPE_DOS); + + /* Build DoS extension field. We will put in two parameters. */ + dos_ext = trn_cell_extension_dos_new(); + trn_cell_extension_dos_set_n_params(dos_ext, 2); + + /* Build DoS parameter INTRO2 rate per second. */ + build_establish_intro_dos_param(dos_ext, + TRUNNEL_DOS_PARAM_TYPE_INTRO2_RATE_PER_SEC, + service_config->intro_dos_rate_per_sec); + /* Build DoS parameter INTRO2 burst per second. */ + build_establish_intro_dos_param(dos_ext, + TRUNNEL_DOS_PARAM_TYPE_INTRO2_BURST_PER_SEC, + service_config->intro_dos_burst_per_sec); + + /* Set the field with the encoded DoS extension. */ + ret = trn_cell_extension_dos_encoded_len(dos_ext); + if (BUG(ret <= 0)) { + goto err; + } + dos_ext_encoded_len = ret; + /* Set length field and the field array size length. */ + trn_cell_extension_field_set_field_len(field, dos_ext_encoded_len); + trn_cell_extension_field_setlen_field(field, dos_ext_encoded_len); + /* Encode the DoS extension into the cell extension field. */ + field_array = trn_cell_extension_field_getarray_field(field); + ret = trn_cell_extension_dos_encode(field_array, + trn_cell_extension_field_getlen_field(field), dos_ext); + if (BUG(ret <= 0)) { + goto err; + } + tor_assert(ret == (ssize_t) dos_ext_encoded_len); + + /* Finally, encode field into the cell extension. */ + trn_cell_extension_add_fields(extensions, field); + + /* We've just add an extension field to the cell extensions so increment the + * total number. */ + trn_cell_extension_set_num(extensions, + trn_cell_extension_get_num(extensions) + 1); + + /* Cleanup. DoS extension has been encoded at this point. */ + trn_cell_extension_dos_free(dos_ext); + + return 0; + + err: + trn_cell_extension_field_free(field); + trn_cell_extension_dos_free(dos_ext); + return -1; +} + /* ========== */ /* Public API */ /* ========== */ +/* Allocate and build all the ESTABLISH_INTRO cell extension. The given + * extensions pointer is always set to a valid cell extension object. */ +STATIC trn_cell_extension_t * +build_establish_intro_extensions(const hs_service_config_t *service_config, + const hs_service_intro_point_t *ip) +{ + int ret; + trn_cell_extension_t *extensions; + + tor_assert(service_config); + tor_assert(ip); + + extensions = trn_cell_extension_new(); + trn_cell_extension_set_num(extensions, 0); + + /* If the defense has been enabled service side (by the operator with a + * torrc option) and the intro point does support it. */ + if (service_config->has_dos_defense_enabled && + ip->support_intro2_dos_defense) { + /* This function takes care to increment the number of extensions. */ + ret = build_establish_intro_dos_extension(service_config, extensions); + if (ret < 0) { + /* Return no extensions on error. */ + goto end; + } + } + + end: + return extensions; +} + /* Build an ESTABLISH_INTRO cell with the given circuit nonce and intro point * object. The encoded cell is put in cell_out that MUST at least be of the * size of RELAY_PAYLOAD_SIZE. Return the encoded cell length on success else @@ -484,15 +606,17 @@ introduce1_set_legacy_id(trn_cell_introduce1_t *cell, * legacy cell creation. */ ssize_t hs_cell_build_establish_intro(const char *circ_nonce, + const hs_service_config_t *service_config, const hs_service_intro_point_t *ip, uint8_t *cell_out) { ssize_t cell_len = -1; uint16_t sig_len = ED25519_SIG_LEN; - trn_cell_extension_t *ext; trn_cell_establish_intro_t *cell = NULL; + trn_cell_extension_t *extensions; tor_assert(circ_nonce); + tor_assert(service_config); tor_assert(ip); /* Quickly handle the legacy IP. */ @@ -505,11 +629,12 @@ hs_cell_build_establish_intro(const char *circ_nonce, goto done; } + /* Build the extensions, if any. */ + extensions = build_establish_intro_extensions(service_config, ip); + /* Set extension data. None used here. */ - ext = trn_cell_extension_new(); - trn_cell_extension_set_num(ext, 0); cell = trn_cell_establish_intro_new(); - trn_cell_establish_intro_set_extensions(cell, ext); + trn_cell_establish_intro_set_extensions(cell, extensions); /* Set signature size. Array is then allocated in the cell. We need to do * this early so we can use trunnel API to get the signature length. */ trn_cell_establish_intro_set_sig_len(cell, sig_len); @@ -956,4 +1081,3 @@ hs_cell_introduce1_data_clear(hs_cell_introduce1_data_t *data) /* The data object has no ownership of any members. */ memwipe(data, 0, sizeof(hs_cell_introduce1_data_t)); } - diff --git a/src/feature/hs/hs_cell.h b/src/feature/hs/hs_cell.h index 9569de535e..864b6fda5f 100644 --- a/src/feature/hs/hs_cell.h +++ b/src/feature/hs/hs_cell.h @@ -79,6 +79,7 @@ typedef struct hs_cell_introduce2_data_t { /* Build cell API. */ ssize_t hs_cell_build_establish_intro(const char *circ_nonce, + const hs_service_config_t *config, const hs_service_intro_point_t *ip, uint8_t *cell_out); ssize_t hs_cell_build_rendezvous1(const uint8_t *rendezvous_cookie, @@ -105,5 +106,15 @@ int hs_cell_parse_rendezvous2(const uint8_t *payload, size_t payload_len, /* Util API. */ void hs_cell_introduce1_data_clear(hs_cell_introduce1_data_t *data); +#ifdef TOR_UNIT_TESTS + +#include "trunnel/hs/cell_common.h" + +STATIC trn_cell_extension_t * +build_establish_intro_extensions(const hs_service_config_t *service_config, + const hs_service_intro_point_t *ip); + +#endif /* defined(TOR_UNIT_TESTS) */ + #endif /* !defined(TOR_HS_CELL_H) */ diff --git a/src/feature/hs/hs_circuit.c b/src/feature/hs/hs_circuit.c index 716c4b1f17..5e213b5aba 100644 --- a/src/feature/hs/hs_circuit.c +++ b/src/feature/hs/hs_circuit.c @@ -259,8 +259,7 @@ create_rp_circuit_identifier(const hs_service_t *service, tor_assert(server_pk); tor_assert(keys); - ident = hs_ident_circuit_new(&service->keys.identity_pk, - HS_IDENT_CIRCUIT_RENDEZVOUS); + ident = hs_ident_circuit_new(&service->keys.identity_pk); /* Copy the RENDEZVOUS_COOKIE which is the unique identifier. */ memcpy(ident->rendezvous_cookie, rendezvous_cookie, sizeof(ident->rendezvous_cookie)); @@ -294,8 +293,7 @@ create_intro_circuit_identifier(const hs_service_t *service, tor_assert(service); tor_assert(ip); - ident = hs_ident_circuit_new(&service->keys.identity_pk, - HS_IDENT_CIRCUIT_INTRO); + ident = hs_ident_circuit_new(&service->keys.identity_pk); ed25519_pubkey_copy(&ident->intro_auth_pk, &ip->auth_key_kp.pubkey); return ident; @@ -319,7 +317,7 @@ send_establish_intro(const hs_service_t *service, /* Encode establish intro cell. */ cell_len = hs_cell_build_establish_intro(circ->cpath->prev->rend_circ_nonce, - ip, payload); + &service->config, ip, payload); if (cell_len < 0) { log_warn(LD_REND, "Unable to encode ESTABLISH_INTRO cell for service %s " "on circuit %u. Closing circuit.", @@ -389,10 +387,7 @@ launch_rendezvous_point_circuit(const hs_service_t *service, &data->onion_pk, service->config.is_single_onion); if (info == NULL) { - /* We are done here, we can't extend to the rendezvous point. - * If you're running an IPv6-only v3 single onion service on 0.3.2 or with - * 0.3.2 clients, and somehow disable the option check, it will fail here. - */ + /* We are done here, we can't extend to the rendezvous point. */ log_fn(LOG_PROTOCOL_WARN, LD_REND, "Not enough info to open a circuit to a rendezvous point for " "%s service %s.", diff --git a/src/feature/hs/hs_circuitmap.c b/src/feature/hs/hs_circuitmap.c index 5480d5eb84..e34f564fb4 100644 --- a/src/feature/hs/hs_circuitmap.c +++ b/src/feature/hs/hs_circuitmap.c @@ -272,6 +272,33 @@ hs_circuitmap_get_or_circuit(hs_token_type_t type, /**** Public relay-side getters: */ +/* Public function: Return v2 and v3 introduction circuit to this relay. + * Always return a newly allocated list for which it is the caller's + * responsability to free it. */ +smartlist_t * +hs_circuitmap_get_all_intro_circ_relay_side(void) +{ + circuit_t **iter; + smartlist_t *circuit_list = smartlist_new(); + + HT_FOREACH(iter, hs_circuitmap_ht, the_hs_circuitmap) { + circuit_t *circ = *iter; + + /* An origin circuit or purpose is wrong or the hs token is not set to be + * a v2 or v3 intro relay side type, we ignore the circuit. Else, we have + * a match so add it to our list. */ + if (CIRCUIT_IS_ORIGIN(circ) || + circ->purpose != CIRCUIT_PURPOSE_INTRO_POINT || + (circ->hs_token->type != HS_TOKEN_INTRO_V3_RELAY_SIDE && + circ->hs_token->type != HS_TOKEN_INTRO_V2_RELAY_SIDE)) { + continue; + } + smartlist_add(circuit_list, circ); + } + + return circuit_list; +} + /* Public function: Return a v3 introduction circuit to this relay with * <b>auth_key</b>. Return NULL if no such circuit is found in the * circuitmap. */ diff --git a/src/feature/hs/hs_circuitmap.h b/src/feature/hs/hs_circuitmap.h index c1bbb1ff1c..eac8230bbf 100644 --- a/src/feature/hs/hs_circuitmap.h +++ b/src/feature/hs/hs_circuitmap.h @@ -34,6 +34,8 @@ void hs_circuitmap_register_intro_circ_v2_relay_side(struct or_circuit_t *circ, void hs_circuitmap_register_intro_circ_v3_relay_side(struct or_circuit_t *circ, const ed25519_public_key_t *auth_key); +smartlist_t *hs_circuitmap_get_all_intro_circ_relay_side(void); + /** Public service-side API: */ struct origin_circuit_t * diff --git a/src/feature/hs/hs_common.c b/src/feature/hs/hs_common.c index a5747fe170..036d23a6b0 100644 --- a/src/feature/hs/hs_common.c +++ b/src/feature/hs/hs_common.c @@ -21,6 +21,7 @@ #include "feature/hs/hs_circuitmap.h" #include "feature/hs/hs_client.h" #include "feature/hs/hs_common.h" +#include "feature/hs/hs_dos.h" #include "feature/hs/hs_ident.h" #include "feature/hs/hs_service.h" #include "feature/hs_common/shared_random_client.h" @@ -30,6 +31,7 @@ #include "feature/nodelist/routerset.h" #include "feature/rend/rendcommon.h" #include "feature/rend/rendservice.h" +#include "feature/relay/routermode.h" #include "lib/crypt_ops/crypto_rand.h" #include "lib/crypt_ops/crypto_util.h" @@ -84,7 +86,7 @@ set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p) return 0; } -#else /* !(defined(HAVE_SYS_UN_H)) */ +#else /* !defined(HAVE_SYS_UN_H) */ static int set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p) diff --git a/src/feature/hs/hs_config.c b/src/feature/hs/hs_config.c index 87f6257591..3b6caaec6a 100644 --- a/src/feature/hs/hs_config.c +++ b/src/feature/hs/hs_config.c @@ -218,6 +218,9 @@ config_has_invalid_options(const config_line_t *line_, const char *opts_exclude_v2[] = { "HiddenServiceExportCircuitID", + "HiddenServiceEnableIntroDoSDefense", + "HiddenServiceEnableIntroDoSRatePerSec", + "HiddenServiceEnableIntroDoSBurstPerSec", NULL /* End marker. */ }; @@ -250,6 +253,16 @@ config_has_invalid_options(const config_line_t *line_, "version %" PRIu32 " of service in %s", opt, service->config.version, service->config.directory_path); + + if (!strcasecmp(line->key, "HiddenServiceAuthorizeClient")) { + /* Special case this v2 option so that we can offer alternatives. + * If more such special cases appear, it would be good to + * generalize the exception mechanism here. */ + log_warn(LD_CONFIG, "For v3 onion service client authorization, " + "please read the 'CLIENT AUTHORIZATION' section in the " + "manual."); + } + ret = 1; /* Continue the loop so we can find all possible options. */ continue; @@ -276,6 +289,15 @@ config_validate_service(const hs_service_config_t *config) goto invalid; } + /* DoS validation values. */ + if (config->has_dos_defense_enabled && + (config->intro_dos_burst_per_sec < config->intro_dos_rate_per_sec)) { + log_warn(LD_CONFIG, "Hidden service DoS defenses burst (%" PRIu32 ") can " + "not be smaller than the rate value (%" PRIu32 ").", + config->intro_dos_burst_per_sec, config->intro_dos_rate_per_sec); + goto invalid; + } + /* Valid. */ return 0; invalid: @@ -296,6 +318,8 @@ config_service_v3(const config_line_t *line_, { int have_num_ip = 0; bool export_circuit_id = false; /* just to detect duplicate options */ + bool dos_enabled = false, dos_rate_per_sec = false; + bool dos_burst_per_sec = false; const char *dup_opt_seen = NULL; const config_line_t *line; @@ -334,6 +358,52 @@ config_service_v3(const config_line_t *line_, export_circuit_id = true; continue; } + if (!strcasecmp(line->key, "HiddenServiceEnableIntroDoSDefense")) { + config->has_dos_defense_enabled = + (unsigned int) helper_parse_uint64(line->key, line->value, + HS_CONFIG_V3_DOS_DEFENSE_DEFAULT, + 1, &ok); + if (!ok || dos_enabled) { + if (dos_enabled) { + dup_opt_seen = line->key; + } + goto err; + } + dos_enabled = true; + continue; + } + if (!strcasecmp(line->key, "HiddenServiceEnableIntroDoSRatePerSec")) { + config->intro_dos_rate_per_sec = + (unsigned int) helper_parse_uint64(line->key, line->value, + HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_MIN, + HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_MAX, &ok); + if (!ok || dos_rate_per_sec) { + if (dos_rate_per_sec) { + dup_opt_seen = line->key; + } + goto err; + } + dos_rate_per_sec = true; + log_info(LD_REND, "Service INTRO2 DoS defenses rate set to: %" PRIu32, + config->intro_dos_rate_per_sec); + continue; + } + if (!strcasecmp(line->key, "HiddenServiceEnableIntroDoSBurstPerSec")) { + config->intro_dos_burst_per_sec = + (unsigned int) helper_parse_uint64(line->key, line->value, + HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_MIN, + HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_MAX, &ok); + if (!ok || dos_burst_per_sec) { + if (dos_burst_per_sec) { + dup_opt_seen = line->key; + } + goto err; + } + dos_burst_per_sec = true; + log_info(LD_REND, "Service INTRO2 DoS defenses burst set to: %" PRIu32, + config->intro_dos_burst_per_sec); + continue; + } } /* We do not load the key material for the service at this stage. This is diff --git a/src/feature/hs/hs_config.h b/src/feature/hs/hs_config.h index 040e451f13..beefc7a613 100644 --- a/src/feature/hs/hs_config.h +++ b/src/feature/hs/hs_config.h @@ -15,6 +15,15 @@ #define HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT 65535 /* Maximum number of intro points per version 3 services. */ #define HS_CONFIG_V3_MAX_INTRO_POINTS 20 +/* Default value for the introduction DoS defenses. The MIN/MAX are inclusive + * meaning they can be used as valid values. */ +#define HS_CONFIG_V3_DOS_DEFENSE_DEFAULT 0 +#define HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_DEFAULT 25 +#define HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_MIN 0 +#define HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_MAX INT32_MAX +#define HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_DEFAULT 200 +#define HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_MIN 0 +#define HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_MAX INT32_MAX /* API */ diff --git a/src/feature/hs/hs_descriptor.c b/src/feature/hs/hs_descriptor.c index a8796c0029..924ab3115e 100644 --- a/src/feature/hs/hs_descriptor.c +++ b/src/feature/hs/hs_descriptor.c @@ -1477,10 +1477,8 @@ decrypt_descriptor_cookie(const hs_descriptor_t *desc, */ MOCK_IMPL(STATIC size_t, decrypt_desc_layer,(const hs_descriptor_t *desc, - const uint8_t *encrypted_blob, - size_t encrypted_blob_size, const uint8_t *descriptor_cookie, - int is_superencrypted_layer, + bool is_superencrypted_layer, char **decrypted_out)) { uint8_t *decrypted = NULL; @@ -1490,6 +1488,12 @@ decrypt_desc_layer,(const hs_descriptor_t *desc, uint8_t mac_key[DIGEST256_LEN], our_mac[DIGEST256_LEN]; const uint8_t *salt, *encrypted, *desc_mac; size_t encrypted_len, result_len = 0; + const uint8_t *encrypted_blob = (is_superencrypted_layer) + ? desc->plaintext_data.superencrypted_blob + : desc->superencrypted_data.encrypted_blob; + size_t encrypted_blob_size = (is_superencrypted_layer) + ? desc->plaintext_data.superencrypted_blob_size + : desc->superencrypted_data.encrypted_blob_size; tor_assert(decrypted_out); tor_assert(desc); @@ -1603,9 +1607,8 @@ desc_decrypt_superencrypted(const hs_descriptor_t *desc, char **decrypted_out) tor_assert(decrypted_out); superencrypted_len = decrypt_desc_layer(desc, - desc->plaintext_data.superencrypted_blob, - desc->plaintext_data.superencrypted_blob_size, - NULL, 1, &superencrypted_plaintext); + NULL, + true, &superencrypted_plaintext); if (!superencrypted_len) { log_warn(LD_REND, "Decrypting superencrypted desc failed."); @@ -1654,9 +1657,9 @@ desc_decrypt_encrypted(const hs_descriptor_t *desc, } encrypted_len = decrypt_desc_layer(desc, - desc->superencrypted_data.encrypted_blob, - desc->superencrypted_data.encrypted_blob_size, - descriptor_cookie, 0, &encrypted_plaintext); + descriptor_cookie, + false, &encrypted_plaintext); + if (!encrypted_len) { goto err; } diff --git a/src/feature/hs/hs_descriptor.h b/src/feature/hs/hs_descriptor.h index dbe0cb1c94..0a843f4f3c 100644 --- a/src/feature/hs/hs_descriptor.h +++ b/src/feature/hs/hs_descriptor.h @@ -276,6 +276,7 @@ void hs_desc_authorized_client_free_(hs_desc_authorized_client_t *client); hs_desc_authorized_client_free_, (client)) hs_desc_authorized_client_t *hs_desc_build_fake_authorized_client(void); + void hs_desc_build_authorized_client(const uint8_t *subcredential, const curve25519_public_key_t * client_auth_pk, @@ -308,10 +309,8 @@ STATIC int desc_sig_is_valid(const char *b64_sig, const char *encoded_desc, size_t encoded_len); MOCK_DECL(STATIC size_t, decrypt_desc_layer,(const hs_descriptor_t *desc, - const uint8_t *encrypted_blob, - size_t encrypted_blob_size, const uint8_t *descriptor_cookie, - int is_superencrypted_layer, + bool is_superencrypted_layer, char **decrypted_out)); #endif /* defined(HS_DESCRIPTOR_PRIVATE) */ diff --git a/src/feature/hs/hs_dos.c b/src/feature/hs/hs_dos.c new file mode 100644 index 0000000000..19794e09d3 --- /dev/null +++ b/src/feature/hs/hs_dos.c @@ -0,0 +1,200 @@ +/* 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/or.h" +#include "app/config/config.h" + +#include "core/or/circuitlist.h" + +#include "feature/hs/hs_circuitmap.h" +#include "feature/nodelist/networkstatus.h" +#include "feature/relay/routermode.h" + +#include "lib/evloop/token_bucket.h" + +#include "feature/hs/hs_dos.h" + +/* Default value of the allowed INTRODUCE2 cell rate per second. Above that + * value per second, the introduction is denied. */ +#define HS_DOS_INTRODUCE_DEFAULT_CELL_RATE_PER_SEC 25 + +/* Default value of the allowed INTRODUCE2 cell burst per second. This is the + * maximum value a token bucket has per second. We thus allow up to this value + * of INTRODUCE2 cell per second but the bucket is refilled by the rate value + * but never goes above that burst value. */ +#define HS_DOS_INTRODUCE_DEFAULT_CELL_BURST_PER_SEC 200 + +/* Default value of the consensus parameter enabling or disabling the + * introduction DoS defense. Disabled by default. */ +#define HS_DOS_INTRODUCE_ENABLED_DEFAULT 0 + +/* Consensus parameters. The ESTABLISH_INTRO DoS cell extension have higher + * priority than these values. If no extension is sent, these are used only by + * the introduction point. */ +static uint32_t consensus_param_introduce_rate_per_sec = + HS_DOS_INTRODUCE_DEFAULT_CELL_RATE_PER_SEC; +static uint32_t consensus_param_introduce_burst_per_sec = + HS_DOS_INTRODUCE_DEFAULT_CELL_BURST_PER_SEC; +static uint32_t consensus_param_introduce_defense_enabled = + HS_DOS_INTRODUCE_ENABLED_DEFAULT; + +STATIC uint32_t +get_intro2_enable_consensus_param(const networkstatus_t *ns) +{ + return networkstatus_get_param(ns, "HiddenServiceEnableIntroDoSDefense", + HS_DOS_INTRODUCE_ENABLED_DEFAULT, 0, 1); +} + +/* Return the parameter for the introduction rate per sec. */ +STATIC uint32_t +get_intro2_rate_consensus_param(const networkstatus_t *ns) +{ + return networkstatus_get_param(ns, "HiddenServiceEnableIntroDoSRatePerSec", + HS_DOS_INTRODUCE_DEFAULT_CELL_RATE_PER_SEC, + 0, INT32_MAX); +} + +/* Return the parameter for the introduction burst per sec. */ +STATIC uint32_t +get_intro2_burst_consensus_param(const networkstatus_t *ns) +{ + return networkstatus_get_param(ns, "HiddenServiceEnableIntroDoSBurstPerSec", + HS_DOS_INTRODUCE_DEFAULT_CELL_BURST_PER_SEC, + 0, INT32_MAX); +} + +/* Go over all introduction circuit relay side and adjust their rate/burst + * values using the global parameters. This is called right after the + * consensus parameters might have changed. */ +static void +update_intro_circuits(void) +{ + /* Returns all HS version intro circuits. */ + smartlist_t *intro_circs = hs_circuitmap_get_all_intro_circ_relay_side(); + + SMARTLIST_FOREACH_BEGIN(intro_circs, circuit_t *, circ) { + /* Defenses might have been enabled or disabled. */ + TO_OR_CIRCUIT(circ)->introduce2_dos_defense_enabled = + consensus_param_introduce_defense_enabled; + /* Adjust the rate/burst value that might have changed. */ + token_bucket_ctr_adjust(&TO_OR_CIRCUIT(circ)->introduce2_bucket, + consensus_param_introduce_rate_per_sec, + consensus_param_introduce_burst_per_sec); + } SMARTLIST_FOREACH_END(circ); + + smartlist_free(intro_circs); +} + +/* Set consensus parameters. */ +static void +set_consensus_parameters(const networkstatus_t *ns) +{ + consensus_param_introduce_rate_per_sec = + get_intro2_rate_consensus_param(ns); + consensus_param_introduce_burst_per_sec = + get_intro2_burst_consensus_param(ns); + consensus_param_introduce_defense_enabled = + get_intro2_enable_consensus_param(ns); + + /* The above might have changed which means we need to go through all + * introduction circuits (relay side) and update the token buckets. */ + update_intro_circuits(); +} + +/* + * Public API. + */ + +/* Initialize the INTRODUCE2 token bucket for the DoS defenses using the + * consensus/default values. We might get a cell extension that changes those + * later but if we don't, the default or consensus parameters are used. */ +void +hs_dos_setup_default_intro2_defenses(or_circuit_t *circ) +{ + tor_assert(circ); + + circ->introduce2_dos_defense_enabled = + consensus_param_introduce_defense_enabled; + token_bucket_ctr_init(&circ->introduce2_bucket, + consensus_param_introduce_rate_per_sec, + consensus_param_introduce_burst_per_sec, + (uint32_t) approx_time()); +} + +/* Called when the consensus has changed. We might have new consensus + * parameters to look at. */ +void +hs_dos_consensus_has_changed(const networkstatus_t *ns) +{ + /* No point on updating these values if we are not a public relay that can + * be picked to be an introduction point. */ + if (!public_server_mode(get_options())) { + return; + } + + set_consensus_parameters(ns); +} + +/* 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); + + /* Allow to send the cell if the DoS defenses are disabled on the circuit. + * This can be set by the consensus, the ESTABLISH_INTRO cell extension or + * the hardcoded values in tor code. */ + if (!s_intro_circ->introduce2_dos_defense_enabled) { + return true; + } + + /* 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; +} + +/* Initialize the onion service Denial of Service subsystem. */ +void +hs_dos_init(void) +{ + set_consensus_parameters(NULL); +} diff --git a/src/feature/hs/hs_dos.h b/src/feature/hs/hs_dos.h new file mode 100644 index 0000000000..ccf4e27179 --- /dev/null +++ b/src/feature/hs/hs_dos.h @@ -0,0 +1,39 @@ +/* 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 "feature/nodelist/networkstatus_st.h" + +/* Init */ +void hs_dos_init(void); + +/* Consensus. */ +void hs_dos_consensus_has_changed(const networkstatus_t *ns); + +/* Introduction Point. */ +bool hs_dos_can_send_intro2(or_circuit_t *s_intro_circ); +void hs_dos_setup_default_intro2_defenses(or_circuit_t *circ); + +#ifdef HS_DOS_PRIVATE + +#ifdef TOR_UNIT_TESTS + +STATIC uint32_t get_intro2_enable_consensus_param(const networkstatus_t *ns); +STATIC uint32_t get_intro2_rate_consensus_param(const networkstatus_t *ns); +STATIC uint32_t get_intro2_burst_consensus_param(const networkstatus_t *ns); + +#endif /* defined(TOR_UNIT_TESTS) */ + +#endif /* defined(HS_DOS_PRIVATE) */ + +#endif /* !defined(TOR_HS_DOS_H) */ diff --git a/src/feature/hs/hs_ident.c b/src/feature/hs/hs_ident.c index 8fd0013941..a00e55ec23 100644 --- a/src/feature/hs/hs_ident.c +++ b/src/feature/hs/hs_ident.c @@ -13,14 +13,10 @@ /* Return a newly allocated circuit identifier. The given public key is copied * identity_pk into the identifier. */ hs_ident_circuit_t * -hs_ident_circuit_new(const ed25519_public_key_t *identity_pk, - hs_ident_circuit_type_t circuit_type) +hs_ident_circuit_new(const ed25519_public_key_t *identity_pk) { - tor_assert(circuit_type == HS_IDENT_CIRCUIT_INTRO || - circuit_type == HS_IDENT_CIRCUIT_RENDEZVOUS); hs_ident_circuit_t *ident = tor_malloc_zero(sizeof(*ident)); ed25519_pubkey_copy(&ident->identity_pk, identity_pk); - ident->circuit_type = circuit_type; return ident; } diff --git a/src/feature/hs/hs_ident.h b/src/feature/hs/hs_ident.h index 8c46936a1e..82ca50f6b5 100644 --- a/src/feature/hs/hs_ident.h +++ b/src/feature/hs/hs_ident.h @@ -44,13 +44,6 @@ typedef struct hs_ident_circuit_t { * the one found in the onion address. */ ed25519_public_key_t identity_pk; - /* (All circuit) The type of circuit this identifier is attached to. - * Accessors of the fields in this object assert non fatal on this circuit - * type. In other words, if a rendezvous field is being accessed, the - * circuit type MUST BE of type HS_IDENT_CIRCUIT_RENDEZVOUS. This value is - * set when an object is initialized in its constructor. */ - hs_ident_circuit_type_t circuit_type; - /* (All circuit) Introduction point authentication key. It's also needed on * the rendezvous circuit for the ntor handshake. It's used as the unique key * of the introduction point so it should not be shared between multiple @@ -120,8 +113,7 @@ typedef struct hs_ident_edge_conn_t { /* Circuit identifier API. */ hs_ident_circuit_t *hs_ident_circuit_new( - const ed25519_public_key_t *identity_pk, - hs_ident_circuit_type_t circuit_type); + const ed25519_public_key_t *identity_pk); void hs_ident_circuit_free_(hs_ident_circuit_t *ident); #define hs_ident_circuit_free(id) \ FREE_AND_NULL(hs_ident_circuit_t, hs_ident_circuit_free_, (id)) diff --git a/src/feature/hs/hs_intropoint.c b/src/feature/hs/hs_intropoint.c index 9333060e7e..fe8486b1a6 100644 --- a/src/feature/hs/hs_intropoint.c +++ b/src/feature/hs/hs_intropoint.c @@ -10,6 +10,7 @@ #include "core/or/or.h" #include "app/config/config.h" +#include "core/or/channel.h" #include "core/or/circuitlist.h" #include "core/or/circuituse.h" #include "core/or/relay.h" @@ -24,9 +25,11 @@ #include "trunnel/hs/cell_introduce1.h" #include "feature/hs/hs_circuitmap.h" +#include "feature/hs/hs_common.h" +#include "feature/hs/hs_config.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" @@ -179,6 +182,185 @@ hs_intro_send_intro_established_cell,(or_circuit_t *circ)) return ret; } +/* Validate the cell DoS extension parameters. Return true iff they've been + * bound check and can be used. Else return false. See proposal 305 for + * details and reasons about this validation. */ +STATIC bool +cell_dos_extension_parameters_are_valid(uint64_t intro2_rate_per_sec, + uint64_t intro2_burst_per_sec) +{ + bool ret = false; + + /* Check that received value is not below the minimum. Don't check if minimum + is set to 0, since the param is a positive value and gcc will complain. */ +#if HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_MIN > 0 + if (intro2_rate_per_sec < HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_MIN) { + log_fn(LOG_PROTOCOL_WARN, LD_REND, + "Intro point DoS defenses rate per second is " + "too small. Received value: %" PRIu64, intro2_rate_per_sec); + goto end; + } +#endif /* HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_MIN > 0 */ + + /* Check that received value is not above maximum */ + if (intro2_rate_per_sec > HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_MAX) { + log_fn(LOG_PROTOCOL_WARN, LD_REND, + "Intro point DoS defenses rate per second is " + "too big. Received value: %" PRIu64, intro2_rate_per_sec); + goto end; + } + + /* Check that received value is not below the minimum */ +#if HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_MIN > 0 + if (intro2_burst_per_sec < HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_MIN) { + log_fn(LOG_PROTOCOL_WARN, LD_REND, + "Intro point DoS defenses burst per second is " + "too small. Received value: %" PRIu64, intro2_burst_per_sec); + goto end; + } +#endif /* HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_MIN > 0 */ + + /* Check that received value is not above maximum */ + if (intro2_burst_per_sec > HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_MAX) { + log_fn(LOG_PROTOCOL_WARN, LD_REND, + "Intro point DoS defenses burst per second is " + "too big. Received value: %" PRIu64, intro2_burst_per_sec); + goto end; + } + + /* In a rate limiting scenario, burst can never be smaller than the rate. At + * best it can be equal. */ + if (intro2_burst_per_sec < intro2_rate_per_sec) { + log_info(LD_REND, "Intro point DoS defenses burst is smaller than rate. " + "Rate: %" PRIu64 " vs Burst: %" PRIu64, + intro2_rate_per_sec, intro2_burst_per_sec); + goto end; + } + + /* Passing validation. */ + ret = true; + + end: + return ret; +} + +/* Parse the cell DoS extension and apply defenses on the given circuit if + * validation passes. If the cell extension is malformed or contains unusable + * values, the DoS defenses is disabled on the circuit. */ +static void +handle_establish_intro_cell_dos_extension( + const trn_cell_extension_field_t *field, + or_circuit_t *circ) +{ + ssize_t ret; + uint64_t intro2_rate_per_sec = 0, intro2_burst_per_sec = 0; + trn_cell_extension_dos_t *dos = NULL; + + tor_assert(field); + tor_assert(circ); + + ret = trn_cell_extension_dos_parse(&dos, + trn_cell_extension_field_getconstarray_field(field), + trn_cell_extension_field_getlen_field(field)); + if (ret < 0) { + goto end; + } + + for (size_t i = 0; i < trn_cell_extension_dos_get_n_params(dos); i++) { + const trn_cell_extension_dos_param_t *param = + trn_cell_extension_dos_getconst_params(dos, i); + if (BUG(param == NULL)) { + goto end; + } + + switch (trn_cell_extension_dos_param_get_type(param)) { + case TRUNNEL_DOS_PARAM_TYPE_INTRO2_RATE_PER_SEC: + intro2_rate_per_sec = trn_cell_extension_dos_param_get_value(param); + break; + case TRUNNEL_DOS_PARAM_TYPE_INTRO2_BURST_PER_SEC: + intro2_burst_per_sec = trn_cell_extension_dos_param_get_value(param); + break; + default: + goto end; + } + } + + /* A value of 0 is valid in the sense that we accept it but we still disable + * the defenses so return false. */ + if (intro2_rate_per_sec == 0 || intro2_burst_per_sec == 0) { + log_info(LD_REND, "Intro point DoS defenses parameter set to 0. " + "Disabling INTRO2 DoS defenses on circuit id %u", + circ->p_circ_id); + circ->introduce2_dos_defense_enabled = 0; + goto end; + } + + /* If invalid, we disable the defense on the circuit. */ + if (!cell_dos_extension_parameters_are_valid(intro2_rate_per_sec, + intro2_burst_per_sec)) { + circ->introduce2_dos_defense_enabled = 0; + log_info(LD_REND, "Disabling INTRO2 DoS defenses on circuit id %u", + circ->p_circ_id); + goto end; + } + + /* We passed validation, enable defenses and apply rate/burst. */ + circ->introduce2_dos_defense_enabled = 1; + + /* Initialize the INTRODUCE2 token bucket for the rate limiting. */ + token_bucket_ctr_init(&circ->introduce2_bucket, + (uint32_t) intro2_rate_per_sec, + (uint32_t) intro2_burst_per_sec, + (uint32_t) approx_time()); + log_info(LD_REND, "Intro point DoS defenses enabled. Rate is %" PRIu64 + " and Burst is %" PRIu64, + intro2_rate_per_sec, intro2_burst_per_sec); + + end: + trn_cell_extension_dos_free(dos); + return; +} + +/* Parse every cell extension in the given ESTABLISH_INTRO cell. */ +static void +handle_establish_intro_cell_extensions( + const trn_cell_establish_intro_t *parsed_cell, + or_circuit_t *circ) +{ + const trn_cell_extension_t *extensions; + + tor_assert(parsed_cell); + tor_assert(circ); + + extensions = trn_cell_establish_intro_getconst_extensions(parsed_cell); + if (extensions == NULL) { + goto end; + } + + /* Go over all extensions. */ + for (size_t idx = 0; idx < trn_cell_extension_get_num(extensions); idx++) { + const trn_cell_extension_field_t *field = + trn_cell_extension_getconst_fields(extensions, idx); + if (BUG(field == NULL)) { + /* The number of extensions should match the number of fields. */ + break; + } + + switch (trn_cell_extension_field_get_field_type(field)) { + case TRUNNEL_CELL_EXTENSION_TYPE_DOS: + /* After this, the circuit should be set for DoS defenses. */ + handle_establish_intro_cell_dos_extension(field, circ); + break; + default: + /* Unknown extension. Skip over. */ + break; + } + } + + end: + return; +} + /** We received an ESTABLISH_INTRO <b>parsed_cell</b> on <b>circ</b>. It's * well-formed and passed our verifications. Perform appropriate actions to * establish an intro point. */ @@ -191,6 +373,13 @@ handle_verified_establish_intro_cell(or_circuit_t *circ, get_auth_key_from_cell(&auth_key, RELAY_COMMAND_ESTABLISH_INTRO, parsed_cell); + /* Setup INTRODUCE2 defenses on the circuit. Must be done before parsing the + * cell extension that can possibly change the defenses' values. */ + hs_dos_setup_default_intro2_defenses(circ); + + /* Handle cell extension if any. */ + handle_establish_intro_cell_extensions(parsed_cell, circ); + /* Then notify the hidden service that the intro point is established by sending an INTRO_ESTABLISHED cell */ if (hs_intro_send_intro_established_cell(circ)) { @@ -480,6 +669,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), @@ -546,6 +749,14 @@ circuit_is_suitable_for_introduce1(const or_circuit_t *circ) return 0; } + /* Disallow single hop client circuit. */ + if (circ->p_chan && channel_is_client(circ->p_chan)) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Single hop client was rejected while trying to introduce. " + "Closing circuit."); + return 0; + } + return 1; } diff --git a/src/feature/hs/hs_intropoint.h b/src/feature/hs/hs_intropoint.h index e82575f052..94ebf021e4 100644 --- a/src/feature/hs/hs_intropoint.h +++ b/src/feature/hs/hs_intropoint.h @@ -57,6 +57,9 @@ STATIC int handle_introduce1(or_circuit_t *client_circ, const uint8_t *request, size_t request_len); STATIC int validate_introduce1_parsed_cell(const trn_cell_introduce1_t *cell); STATIC int circuit_is_suitable_for_introduce1(const or_circuit_t *circ); +STATIC bool cell_dos_extension_parameters_are_valid( + uint64_t intro2_rate_per_sec, + uint64_t intro2_burst_per_sec); #endif /* defined(HS_INTROPOINT_PRIVATE) */ diff --git a/src/feature/hs/hs_service.c b/src/feature/hs/hs_service.c index d1ca33b12e..18c38ebc0a 100644 --- a/src/feature/hs/hs_service.c +++ b/src/feature/hs/hs_service.c @@ -242,6 +242,9 @@ set_service_default_config(hs_service_config_t *c, c->is_single_onion = 0; c->dir_group_readable = 0; c->is_ephemeral = 0; + 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; } /* From a service configuration object config, clear everything from it @@ -489,6 +492,10 @@ service_intro_point_new(const node_t *node) } } + /* Flag if this intro point supports the INTRO2 dos defenses. */ + ip->support_intro2_dos_defense = + node_supports_establish_intro_dos_extension(node); + /* Finally, copy onion key from the node. */ memcpy(&ip->onion_key, node_get_curve25519_onion_key(node), sizeof(ip->onion_key)); @@ -2326,15 +2333,70 @@ intro_point_should_expire(const hs_service_intro_point_t *ip, return 1; } -/* Go over the given set of intro points for each service and remove any - * invalid ones. The conditions for removal are: +/* Return true iff we should remove the intro point ip from its service. + * + * We remove an intro point from the service descriptor list if one of + * these criteria is met: + * - It has expired (either in INTRO2 count or in time). + * - No node was found (fell off the consensus). + * - We are over the maximum amount of retries. * - * - The node doesn't exists anymore (not in consensus) - * OR - * - The intro point maximum circuit retry count has been reached and no - * circuit can be found associated with it. - * OR - * - The intro point has expired and we should pick a new one. + * If an established or pending circuit is found for the given ip object, this + * return false indicating it should not be removed. */ +static bool +should_remove_intro_point(hs_service_intro_point_t *ip, time_t now) +{ + bool ret = false; + + tor_assert(ip); + + /* Any one of the following needs to be True to furfill the criteria to + * remove an intro point. */ + bool has_no_retries = (ip->circuit_retries > + MAX_INTRO_POINT_CIRCUIT_RETRIES); + bool has_no_node = (get_node_from_intro_point(ip) == NULL); + bool has_expired = intro_point_should_expire(ip, now); + + /* If the node fell off the consensus or the IP has expired, we have to + * remove it now. */ + if (has_no_node || has_expired) { + ret = true; + goto end; + } + + /* Pass this point, even though we might be over the retry limit, we check + * if a circuit (established or pending) exists. In that case, we should not + * remove it because it might simply be valid and opened at the previous + * scheduled event for the last retry. */ + + /* Did we established already? */ + if (ip->circuit_established) { + goto end; + } + /* Do we simply have an existing circuit regardless of its state? */ + if (hs_circ_service_get_intro_circ(ip)) { + goto end; + } + + /* Getting here means we have _no_ circuits so then return if we have any + * remaining retries. */ + ret = has_no_retries; + + end: + /* Meaningful log in case we are about to remove the IP. */ + if (ret) { + log_info(LD_REND, "Intro point %s%s (retried: %u times). " + "Removing it.", + describe_intro_point(ip), + has_expired ? " has expired" : + (has_no_node) ? " fell off the consensus" : "", + ip->circuit_retries); + } + return ret; +} + +/* Go over the given set of intro points for each service and remove any + * invalid ones. * * If an intro point is removed, the circuit (if any) is immediately close. * If a circuit can't be found, the intro point is kept if it hasn't reached @@ -2359,21 +2421,7 @@ cleanup_intro_points(hs_service_t *service, time_t now) * valid and remove any of them that aren't. */ DIGEST256MAP_FOREACH_MODIFY(desc->intro_points.map, key, hs_service_intro_point_t *, ip) { - const node_t *node = get_node_from_intro_point(ip); - int has_expired = intro_point_should_expire(ip, now); - - /* We cleanup an intro point if it has expired or if we do not know the - * node_t anymore (removed from our latest consensus) or if we've - * reached the maximum number of retry with a non existing circuit. */ - if (has_expired || node == NULL || - ip->circuit_retries > MAX_INTRO_POINT_CIRCUIT_RETRIES) { - log_info(LD_REND, "Intro point %s%s (retried: %u times). " - "Removing it.", - describe_intro_point(ip), - has_expired ? " has expired" : - (node == NULL) ? " fell off the consensus" : "", - ip->circuit_retries); - + if (should_remove_intro_point(ip, now)) { /* We've retried too many times, remember it as a failed intro point * so we don't pick it up again for INTRO_CIRC_RETRY_PERIOD sec. */ if (ip->circuit_retries > MAX_INTRO_POINT_CIRCUIT_RETRIES) { diff --git a/src/feature/hs/hs_service.h b/src/feature/hs/hs_service.h index 22aa00b2d7..c4bbb293bb 100644 --- a/src/feature/hs/hs_service.h +++ b/src/feature/hs/hs_service.h @@ -76,6 +76,10 @@ typedef struct hs_service_intro_point_t { * circuit associated with this intro point has received. This is used to * prevent replay attacks. */ replaycache_t *replay_cache; + + /* Support the INTRO2 DoS defense. If set, the DoS extension described by + * proposal 305 is sent. */ + unsigned int support_intro2_dos_defense : 1; } hs_service_intro_point_t; /* Object handling introduction points of a service. */ @@ -241,6 +245,11 @@ typedef struct hs_service_config_t { /* Does this service export the circuit ID of its clients? */ hs_circuit_id_protocol_t circuit_id_protocol; + + /* DoS defenses. For the ESTABLISH_INTRO cell extension. */ + unsigned int has_dos_defense_enabled : 1; + uint32_t intro_dos_rate_per_sec; + uint32_t intro_dos_burst_per_sec; } hs_service_config_t; /* Service state. */ diff --git a/src/feature/nodelist/describe.c b/src/feature/nodelist/describe.c index 5c376408c0..1e46837685 100644 --- a/src/feature/nodelist/describe.c +++ b/src/feature/nodelist/describe.c @@ -9,66 +9,108 @@ * \brief Format short descriptions of relays. */ +#define DESCRIBE_PRIVATE + #include "core/or/or.h" #include "feature/nodelist/describe.h" -#include "feature/nodelist/routerinfo.h" #include "core/or/extend_info_st.h" #include "feature/nodelist/node_st.h" #include "feature/nodelist/routerinfo_st.h" #include "feature/nodelist/routerstatus_st.h" - -/** - * Longest allowed output of format_node_description, plus 1 character for - * NUL. This allows space for: - * "$FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF~xxxxxxxxxxxxxxxxxxx at" - * " [ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255]" - * plus a terminating NUL. - */ -#define NODE_DESC_BUF_LEN (MAX_VERBOSE_NICKNAME_LEN+4+TOR_ADDR_BUF_LEN) +#include "feature/nodelist/microdesc_st.h" /** Use <b>buf</b> (which must be at least NODE_DESC_BUF_LEN bytes long) to * hold a human-readable description of a node with identity digest - * <b>id_digest</b>, named-status <b>is_named</b>, nickname <b>nickname</b>, - * and address <b>addr</b> or <b>addr32h</b>. + * <b>id_digest</b>, nickname <b>nickname</b>, and addresses <b>addr32h</b> and + * <b>addr</b>. * * The <b>nickname</b> and <b>addr</b> fields are optional and may be set to - * NULL. The <b>addr32h</b> field is optional and may be set to 0. + * NULL or the null address. The <b>addr32h</b> field is optional and may be + * set to 0. * * Return a pointer to the front of <b>buf</b>. + * If buf is NULL, return a string constant describing the error. */ -static const char * +STATIC const char * format_node_description(char *buf, const char *id_digest, - int is_named, const char *nickname, const tor_addr_t *addr, uint32_t addr32h) { - char *cp; + size_t rv = 0; + bool has_addr = addr && !tor_addr_is_null(addr); if (!buf) return "<NULL BUFFER>"; - buf[0] = '$'; - base16_encode(buf+1, HEX_DIGEST_LEN+1, id_digest, DIGEST_LEN); - cp = buf+1+HEX_DIGEST_LEN; + memset(buf, 0, NODE_DESC_BUF_LEN); + + if (!id_digest) { + /* strlcpy() returns the length of the source string it attempted to copy, + * ignoring any required truncation due to the buffer length. */ + rv = strlcpy(buf, "<NULL ID DIGEST>", NODE_DESC_BUF_LEN); + tor_assert_nonfatal(rv < NODE_DESC_BUF_LEN); + return buf; + } + + /* strlcat() returns the length of the concatenated string it attempted to + * create, ignoring any required truncation due to the buffer length. */ + rv = strlcat(buf, "$", NODE_DESC_BUF_LEN); + tor_assert_nonfatal(rv < NODE_DESC_BUF_LEN); + + { + char hex_digest[HEX_DIGEST_LEN+1]; + memset(hex_digest, 0, sizeof(hex_digest)); + + base16_encode(hex_digest, sizeof(hex_digest), + id_digest, DIGEST_LEN); + rv = strlcat(buf, hex_digest, NODE_DESC_BUF_LEN); + tor_assert_nonfatal(rv < NODE_DESC_BUF_LEN); + } + if (nickname) { - buf[1+HEX_DIGEST_LEN] = is_named ? '=' : '~'; - strlcpy(buf+1+HEX_DIGEST_LEN+1, nickname, MAX_NICKNAME_LEN+1); - cp += strlen(cp); + rv = strlcat(buf, "~", NODE_DESC_BUF_LEN); + tor_assert_nonfatal(rv < NODE_DESC_BUF_LEN); + rv = strlcat(buf, nickname, NODE_DESC_BUF_LEN); + tor_assert_nonfatal(rv < NODE_DESC_BUF_LEN); } - if (addr32h || addr) { - memcpy(cp, " at ", 4); - cp += 4; - if (addr) { - tor_addr_to_str(cp, addr, TOR_ADDR_BUF_LEN, 0); - } else { - struct in_addr in; - in.s_addr = htonl(addr32h); - tor_inet_ntoa(&in, cp, INET_NTOA_BUF_LEN); - } + if (addr32h || has_addr) { + rv = strlcat(buf, " at ", NODE_DESC_BUF_LEN); + tor_assert_nonfatal(rv < NODE_DESC_BUF_LEN); } + if (addr32h) { + int ntoa_rv = 0; + char ipv4_addr_str[INET_NTOA_BUF_LEN]; + memset(ipv4_addr_str, 0, sizeof(ipv4_addr_str)); + struct in_addr in; + memset(&in, 0, sizeof(in)); + + in.s_addr = htonl(addr32h); + ntoa_rv = tor_inet_ntoa(&in, ipv4_addr_str, sizeof(ipv4_addr_str)); + tor_assert_nonfatal(ntoa_rv >= 0); + + rv = strlcat(buf, ipv4_addr_str, NODE_DESC_BUF_LEN); + tor_assert_nonfatal(rv < NODE_DESC_BUF_LEN); + } + /* Both addresses are valid */ + if (addr32h && has_addr) { + rv = strlcat(buf, " and ", NODE_DESC_BUF_LEN); + tor_assert_nonfatal(rv < NODE_DESC_BUF_LEN); + } + if (has_addr) { + const char *str_rv = NULL; + char addr_str[TOR_ADDR_BUF_LEN]; + memset(addr_str, 0, sizeof(addr_str)); + + str_rv = tor_addr_to_str(addr_str, addr, sizeof(addr_str), 1); + tor_assert_nonfatal(str_rv == addr_str); + + rv = strlcat(buf, addr_str, NODE_DESC_BUF_LEN); + tor_assert_nonfatal(rv < NODE_DESC_BUF_LEN); + } + return buf; } @@ -84,11 +126,11 @@ router_describe(const routerinfo_t *ri) if (!ri) return "<null>"; + return format_node_description(buf, ri->cache_info.identity_digest, - 0, ri->nickname, - NULL, + &ri->ipv6_addr, ri->addr); } @@ -103,25 +145,33 @@ node_describe(const node_t *node) static char buf[NODE_DESC_BUF_LEN]; const char *nickname = NULL; uint32_t addr32h = 0; - int is_named = 0; + const tor_addr_t *ipv6_addr = NULL; if (!node) return "<null>"; if (node->rs) { nickname = node->rs->nickname; - is_named = node->rs->is_named; addr32h = node->rs->addr; + ipv6_addr = &node->rs->ipv6_addr; + /* Support consensus versions less than 28, when IPv6 addresses were in + * microdescs. This code can be removed when 0.2.9 is no longer supported, + * and the MIN_METHOD_FOR_NO_A_LINES_IN_MICRODESC macro is removed. */ + if (node->md && tor_addr_is_null(ipv6_addr)) { + ipv6_addr = &node->md->ipv6_addr; + } } else if (node->ri) { nickname = node->ri->nickname; addr32h = node->ri->addr; + ipv6_addr = &node->ri->ipv6_addr; + } else { + return "<null rs and ri>"; } return format_node_description(buf, node->identity, - is_named, nickname, - NULL, + ipv6_addr, addr32h); } @@ -137,11 +187,11 @@ routerstatus_describe(const routerstatus_t *rs) if (!rs) return "<null>"; + return format_node_description(buf, rs->identity_digest, - rs->is_named, rs->nickname, - NULL, + &rs->ipv6_addr, rs->addr); } @@ -157,9 +207,9 @@ extend_info_describe(const extend_info_t *ei) if (!ei) return "<null>"; + return format_node_description(buf, ei->identity_digest, - 0, ei->nickname, &ei->addr, 0); @@ -175,9 +225,39 @@ extend_info_describe(const extend_info_t *ei) void router_get_verbose_nickname(char *buf, const routerinfo_t *router) { - buf[0] = '$'; - base16_encode(buf+1, HEX_DIGEST_LEN+1, router->cache_info.identity_digest, - DIGEST_LEN); - buf[1+HEX_DIGEST_LEN] = '~'; - strlcpy(buf+1+HEX_DIGEST_LEN+1, router->nickname, MAX_NICKNAME_LEN+1); + size_t rv = 0; + + if (!buf) + return; + + memset(buf, 0, MAX_VERBOSE_NICKNAME_LEN+1); + + if (!router) { + /* strlcpy() returns the length of the source string it attempted to copy, + * ignoring any required truncation due to the buffer length. */ + rv = strlcpy(buf, "<null>", MAX_VERBOSE_NICKNAME_LEN+1); + tor_assert_nonfatal(rv < MAX_VERBOSE_NICKNAME_LEN+1); + return; + } + + /* strlcat() returns the length of the concatenated string it attempted to + * create, ignoring any required truncation due to the buffer length. */ + rv = strlcat(buf, "$", MAX_VERBOSE_NICKNAME_LEN+1); + tor_assert_nonfatal(rv < MAX_VERBOSE_NICKNAME_LEN+1); + + { + char hex_digest[HEX_DIGEST_LEN+1]; + memset(hex_digest, 0, sizeof(hex_digest)); + + base16_encode(hex_digest, sizeof(hex_digest), + router->cache_info.identity_digest, DIGEST_LEN); + rv = strlcat(buf, hex_digest, MAX_VERBOSE_NICKNAME_LEN+1); + tor_assert_nonfatal(rv < MAX_VERBOSE_NICKNAME_LEN+1); + } + + rv = strlcat(buf, "~", MAX_VERBOSE_NICKNAME_LEN+1); + tor_assert_nonfatal(rv < MAX_VERBOSE_NICKNAME_LEN+1); + + rv = strlcat(buf, router->nickname, MAX_VERBOSE_NICKNAME_LEN+1); + tor_assert_nonfatal(rv < MAX_VERBOSE_NICKNAME_LEN+1); } diff --git a/src/feature/nodelist/describe.h b/src/feature/nodelist/describe.h index d29192200e..6c0d6dc48d 100644 --- a/src/feature/nodelist/describe.h +++ b/src/feature/nodelist/describe.h @@ -22,4 +22,36 @@ const char *node_describe(const struct node_t *node); const char *router_describe(const struct routerinfo_t *ri); const char *routerstatus_describe(const struct routerstatus_t *ri); +void router_get_verbose_nickname(char *buf, const routerinfo_t *router); + +#if defined(DESCRIBE_PRIVATE) || defined(TOR_UNIT_TESTS) + +/** + * Longest allowed output for an IPv4 address "255.255.255.255", with NO + * terminating NUL. + */ +#define IPV4_BUF_LEN_NO_NUL 15 + +/** + * Longest allowed output of format_node_description, plus 1 character for + * NUL. This allows space for: + * "$FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF~xxxxxxxxxxxxxxxxxxx at" + * " 255.255.255.255 and [ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255]" + * plus a terminating NUL. + */ +#define NODE_DESC_BUF_LEN \ + (MAX_VERBOSE_NICKNAME_LEN+4+IPV4_BUF_LEN_NO_NUL+5+TOR_ADDR_BUF_LEN) + +#endif /* defined(DESCRIBE_PRIVATE) || defined(TOR_UNIT_TESTS) */ + +#ifdef TOR_UNIT_TESTS + +STATIC const char *format_node_description(char *buf, + const char *id_digest, + const char *nickname, + const tor_addr_t *addr, + uint32_t addr32h); + +#endif /* defined(TOR_UNIT_TESTS) */ + #endif /* !defined(TOR_DESCRIBE_H) */ diff --git a/src/feature/nodelist/microdesc_st.h b/src/feature/nodelist/microdesc_st.h index c8265cb778..e017c46c79 100644 --- a/src/feature/nodelist/microdesc_st.h +++ b/src/feature/nodelist/microdesc_st.h @@ -33,6 +33,8 @@ struct microdesc_t { unsigned int no_save : 1; /** If true, this microdesc has an entry in the microdesc_map */ unsigned int held_in_map : 1; + /** True iff the exit policy for this router rejects everything. */ + unsigned int policy_is_reject_star : 1; /** Reference count: how many node_ts have a reference to this microdesc? */ unsigned int held_by_nodes; diff --git a/src/feature/nodelist/networkstatus.c b/src/feature/nodelist/networkstatus.c index 2db293a8af..496bafb865 100644 --- a/src/feature/nodelist/networkstatus.c +++ b/src/feature/nodelist/networkstatus.c @@ -68,6 +68,7 @@ #include "feature/dircommon/voting_schedule.h" #include "feature/dirparse/ns_parse.h" #include "feature/hibernate/hibernate.h" +#include "feature/hs/hs_dos.h" #include "feature/nodelist/authcert.h" #include "feature/nodelist/dirlist.h" #include "feature/nodelist/fmt_routerstatus.h" @@ -1674,6 +1675,7 @@ notify_before_networkstatus_changes(const networkstatus_t *old_c, notify_control_networkstatus_changed(old_c, new_c); dos_consensus_has_changed(new_c); relay_consensus_has_changed(new_c); + hs_dos_consensus_has_changed(new_c); } /* Called after a new consensus has been put in the global state. It is safe diff --git a/src/feature/nodelist/nodelist.c b/src/feature/nodelist/nodelist.c index 21914c6c6d..bd80fc1b4b 100644 --- a/src/feature/nodelist/nodelist.c +++ b/src/feature/nodelist/nodelist.c @@ -1106,7 +1106,7 @@ node_ed25519_id_matches(const node_t *node, const ed25519_public_key_t *id) /** Dummy object that should be unreturnable. Used to ensure that * node_get_protover_summary_flags() always returns non-NULL. */ static const protover_summary_flags_t zero_protover_flags = { - 0,0,0,0,0,0,0,0 + 0,0,0,0,0,0,0,0,0 }; /** Return the protover_summary_flags for a given node. */ @@ -1166,6 +1166,17 @@ node_supports_ed25519_hs_intro(const node_t *node) return node_get_protover_summary_flags(node)->supports_ed25519_hs_intro; } +/** Return true iff <b>node</b> supports the DoS ESTABLISH_INTRO cell + * extenstion. */ +int +node_supports_establish_intro_dos_extension(const node_t *node) +{ + tor_assert(node); + + return node_get_protover_summary_flags(node)-> + supports_establish_intro_dos_extension; +} + /** Return true iff <b>node</b> supports to be a rendezvous point for hidden * service version 3 (HSRend=2). */ int @@ -1424,8 +1435,7 @@ node_exit_policy_rejects_all(const node_t *node) if (node->ri) return node->ri->policy_is_reject_star; else if (node->md) - return node->md->exit_policy == NULL || - short_policy_is_reject_star(node->md->exit_policy); + return node->md->policy_is_reject_star; else return 1; } diff --git a/src/feature/nodelist/nodelist.h b/src/feature/nodelist/nodelist.h index 84ab5f7a54..af144c197f 100644 --- a/src/feature/nodelist/nodelist.h +++ b/src/feature/nodelist/nodelist.h @@ -76,6 +76,7 @@ int node_supports_ed25519_link_authentication(const node_t *node, int node_supports_v3_hsdir(const node_t *node); int node_supports_ed25519_hs_intro(const node_t *node); int node_supports_v3_rendezvous_point(const node_t *node); +int node_supports_establish_intro_dos_extension(const node_t *node); const uint8_t *node_get_rsa_id_digest(const node_t *node); smartlist_t *node_get_link_specifier_smartlist(const node_t *node, bool direct_conn); diff --git a/src/feature/nodelist/routerinfo.h b/src/feature/nodelist/routerinfo.h index ca66e660b3..8465060f93 100644 --- a/src/feature/nodelist/routerinfo.h +++ b/src/feature/nodelist/routerinfo.h @@ -17,8 +17,6 @@ void router_get_prim_orport(const routerinfo_t *router, int router_has_orport(const routerinfo_t *router, const tor_addr_port_t *orport); -void router_get_verbose_nickname(char *buf, const routerinfo_t *router); - smartlist_t *router_get_all_orports(const routerinfo_t *ri); const char *router_purpose_to_string(uint8_t p); diff --git a/src/feature/nodelist/routerlist.c b/src/feature/nodelist/routerlist.c index c56b714cb0..0cd7a76a9a 100644 --- a/src/feature/nodelist/routerlist.c +++ b/src/feature/nodelist/routerlist.c @@ -1459,12 +1459,13 @@ router_descriptor_is_older_than,(const routerinfo_t *router, int seconds)) } /** Add <b>router</b> to the routerlist, if we don't already have it. Replace - * older entries (if any) with the same key. Note: Callers should not hold - * their pointers to <b>router</b> if this function fails; <b>router</b> - * will either be inserted into the routerlist or freed. Similarly, even - * if this call succeeds, they should not hold their pointers to - * <b>router</b> after subsequent calls with other routerinfo's -- they - * might cause the original routerinfo to get freed. + * older entries (if any) with the same key. + * + * Note: Callers should not hold their pointers to <b>router</b> if this + * function fails; <b>router</b> will either be inserted into the routerlist or + * freed. Similarly, even if this call succeeds, they should not hold their + * pointers to <b>router</b> after subsequent calls with other routerinfo's -- + * they might cause the original routerinfo to get freed. * * Returns the status for the operation. Might set *<b>msg</b> if it wants * the poster of the router to know something. diff --git a/src/feature/nodelist/routerlist.h b/src/feature/nodelist/routerlist.h index 5771ebb1ab..dc9203e015 100644 --- a/src/feature/nodelist/routerlist.h +++ b/src/feature/nodelist/routerlist.h @@ -37,9 +37,12 @@ typedef enum was_router_added_t { ROUTER_WAS_NOT_WANTED = -6, /* Router descriptor was rejected because it was older than * OLD_ROUTER_DESC_MAX_AGE. */ - ROUTER_WAS_TOO_OLD = -7, /* note contrast with 'NOT_NEW' */ - /* DOCDOC */ - ROUTER_CERTS_EXPIRED = -8 + ROUTER_WAS_TOO_OLD = -7, /* note contrast with 'ROUTER_IS_ALREADY_KNOWN' */ + /* Some certs on this router are expired. */ + ROUTER_CERTS_EXPIRED = -8, + /* We couldn't format the annotations for this router. This is a directory + * authority bug. */ + ROUTER_AUTHDIR_BUG_ANNOTATIONS = -10 } was_router_added_t; /** How long do we avoid using a directory server after it's given us a 503? */ diff --git a/src/feature/nodelist/routerset.c b/src/feature/nodelist/routerset.c index e801fd81b1..9a205d39b7 100644 --- a/src/feature/nodelist/routerset.c +++ b/src/feature/nodelist/routerset.c @@ -1,5 +1,5 @@ /* Copyright (c) 2001 Matej Pfajfar. -n * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. * Copyright (c) 2007-2019, The Tor Project, Inc. */ /* See LICENSE for licensing information */ @@ -34,6 +34,9 @@ n * Copyright (c) 2001-2004, Roger Dingledine. #include "feature/nodelist/nickname.h" #include "feature/nodelist/nodelist.h" #include "feature/nodelist/routerset.h" +#include "lib/conf/conftypes.h" +#include "lib/confmgt/typedvar.h" +#include "lib/encoding/confline.h" #include "lib/geoip/geoip.h" #include "core/or/addr_policy_st.h" @@ -41,6 +44,7 @@ n * Copyright (c) 2001-2004, Roger Dingledine. #include "feature/nodelist/node_st.h" #include "feature/nodelist/routerinfo_st.h" #include "feature/nodelist/routerstatus_st.h" +#include "lib/confmgt/var_type_def_st.h" /** Return a new empty routerset. */ routerset_t * @@ -461,3 +465,111 @@ routerset_free_(routerset_t *routerset) bitarray_free(routerset->countries); tor_free(routerset); } + +/** + * config helper: parse a routerset-typed variable. + * + * Takes as input as a single line in <b>line</b>; writes its results into a + * routerset_t** passed as <b>target</b>. On success return 0; on failure + * return -1 and store an error message into *<b>errmsg</b>. + **/ +/* + * Warning: For this type, the default value (NULL) and "" are sometimes + * considered different values. That is generally risky, and best avoided for + * other types in the future. For cases where we want the default to be "all + * routers" (like EntryNodes) we should add a new routerset value indicating + * "all routers" (see #31908) + */ +static int +routerset_kv_parse(void *target, const config_line_t *line, char **errmsg, + const void *params) +{ + (void)params; + routerset_t **p = (routerset_t**)target; + routerset_free(*p); // clear the old value, if any. + routerset_t *rs = routerset_new(); + if (routerset_parse(rs, line->value, line->key) < 0) { + routerset_free(rs); + *errmsg = tor_strdup("Invalid router list."); + return -1; + } else { + if (routerset_is_empty(rs)) { + /* Represent empty sets as NULL. */ + routerset_free(rs); + } + *p = rs; + return 0; + } +} + +/** + * config helper: encode a routerset-typed variable. + * + * Return a newly allocated string containing the value of the + * routerset_t** passed as <b>value</b>. + */ +static char * +routerset_encode(const void *value, const void *params) +{ + (void)params; + const routerset_t **p = (const routerset_t**)value; + return routerset_to_string(*p); +} + +/** + * config helper: free and clear a routerset-typed variable. + * + * Clear the routerset_t** passed as <b>value</b>. + */ +static void +routerset_clear(void *value, const void *params) +{ + (void)params; + routerset_t **p = (routerset_t**)value; + routerset_free(*p); // sets *p to NULL. +} + +/** + * config helper: copy a routerset-typed variable. + * + * Takes it input from a routerset_t** in <b>src</b>; writes its output to a + * routerset_t** in <b>dest</b>. Returns 0 on success, -1 on (impossible) + * failure. + **/ +static int +routerset_copy(void *dest, const void *src, const void *params) +{ + (void)params; + routerset_t **output = (routerset_t**)dest; + const routerset_t *input = *(routerset_t**)src; + routerset_free(*output); // sets *output to NULL + if (! routerset_is_empty(input)) { + *output = routerset_new(); + routerset_union(*output, input); + } + return 0; +} + +/** + * Function table to implement a routerset_t-based configuration type. + **/ +static const var_type_fns_t routerset_type_fns = { + .kv_parse = routerset_kv_parse, + .encode = routerset_encode, + .clear = routerset_clear, + .copy = routerset_copy +}; + +/** + * Definition of a routerset_t-based configuration type. + * + * Values are mapped to and from strings using the format defined in + * routerset_parse(): nicknames, IP address patterns, and fingerprints--with + * optional space, separated by commas. + * + * Empty sets are represented as NULL. + **/ +const var_type_def_t ROUTERSET_type_defn = { + .name = "RouterList", + .fns = &routerset_type_fns +}; diff --git a/src/feature/nodelist/routerset.h b/src/feature/nodelist/routerset.h index ca8b6fed93..f3bf4a1f7c 100644 --- a/src/feature/nodelist/routerset.h +++ b/src/feature/nodelist/routerset.h @@ -44,6 +44,9 @@ void routerset_free_(routerset_t *routerset); #define routerset_free(rs) FREE_AND_NULL(routerset_t, routerset_free_, (rs)) int routerset_len(const routerset_t *set); +struct var_type_def_t; +extern const struct var_type_def_t ROUTERSET_type_defn; + #ifdef ROUTERSET_PRIVATE #include "lib/container/bitarray.h" diff --git a/src/feature/relay/router.c b/src/feature/relay/router.c index 29a8ffaa39..ab0762e17e 100644 --- a/src/feature/relay/router.c +++ b/src/feature/relay/router.c @@ -3111,33 +3111,22 @@ load_stats_file(const char *filename, const char *end_line, time_t now, return r; } -/** Write the contents of <b>extrainfo</b>, to * *<b>s_out</b>, signing them - * with <b>ident_key</b>. - * - * If ExtraInfoStatistics is 1, also write aggregated statistics and related - * configuration data before signing. Most statistics also have an option that - * enables or disables that particular statistic. - * - * Return 0 on success, negative on failure. */ -int -extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, - crypto_pk_t *ident_key, - const ed25519_keypair_t *signing_keypair) +/** Add header strings to chunks, based on the extrainfo object extrainfo, + * and ed25519 keypair signing_keypair, if emit_ed_sigs is true. + * Helper for extrainfo_dump_to_string(). + * Returns 0 on success, negative on failure. */ +static int +extrainfo_dump_to_string_header_helper( + smartlist_t *chunks, + const extrainfo_t *extrainfo, + const ed25519_keypair_t *signing_keypair, + int emit_ed_sigs) { - const or_options_t *options = get_options(); char identity[HEX_DIGEST_LEN+1]; char published[ISO_TIME_LEN+1]; - char digest[DIGEST_LEN]; - int result; - static int write_stats_to_extrainfo = 1; - char sig[DIROBJ_MAX_SIG_LEN+1]; - char *s = NULL, *pre, *contents, *cp, *s_dup = NULL; - time_t now = time(NULL); - smartlist_t *chunks = smartlist_new(); - extrainfo_t *ei_tmp = NULL; - const int emit_ed_sigs = signing_keypair && - extrainfo->cache_info.signing_key_cert; char *ed_cert_line = NULL; + char *pre = NULL; + int rv = -1; base16_encode(identity, sizeof(identity), extrainfo->cache_info.identity_digest, DIGEST_LEN); @@ -3167,12 +3156,41 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, ed_cert_line = tor_strdup(""); } + /* This is the first chunk in the file. If the file is too big, other chunks + * are removed. So we must only add one chunk here. */ tor_asprintf(&pre, "extra-info %s %s\n%spublished %s\n", extrainfo->nickname, identity, ed_cert_line, published); smartlist_add(chunks, pre); + rv = 0; + goto done; + + err: + rv = -1; + + done: + tor_free(ed_cert_line); + return rv; +} + +/** Add pluggable transport and statistics strings to chunks, skipping + * statistics if write_stats_to_extrainfo is false. + * Helper for extrainfo_dump_to_string(). + * Can not fail. */ +static void +extrainfo_dump_to_string_stats_helper(smartlist_t *chunks, + int write_stats_to_extrainfo) +{ + const or_options_t *options = get_options(); + char *contents = NULL; + time_t now = time(NULL); + + /* If the file is too big, these chunks are removed, starting with the last + * chunk. So each chunk must be a complete line, and the file must be valid + * after each chunk. */ + /* Add information about the pluggable transports we support, even if we * are not publishing statistics. This information is needed by BridgeDB * to distribute bridges. */ @@ -3239,34 +3257,132 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, } } } +} + +/** Add an ed25519 signature of chunks to chunks, using the ed25519 keypair + * signing_keypair. + * Helper for extrainfo_dump_to_string(). + * Returns 0 on success, negative on failure. */ +static int +extrainfo_dump_to_string_ed_sig_helper( + smartlist_t *chunks, + const ed25519_keypair_t *signing_keypair) +{ + char sha256_digest[DIGEST256_LEN]; + ed25519_signature_t ed_sig; + char buf[ED25519_SIG_BASE64_LEN+1]; + int rv = -1; + + /* These are two of the three final chunks in the file. If the file is too + * big, other chunks are removed. So we must only add two chunks here. */ + smartlist_add_strdup(chunks, "router-sig-ed25519 "); + crypto_digest_smartlist_prefix(sha256_digest, DIGEST256_LEN, + ED_DESC_SIGNATURE_PREFIX, + chunks, "", DIGEST_SHA256); + if (ed25519_sign(&ed_sig, (const uint8_t*)sha256_digest, DIGEST256_LEN, + signing_keypair) < 0) + goto err; + ed25519_signature_to_base64(buf, &ed_sig); + + smartlist_add_asprintf(chunks, "%s\n", buf); + + rv = 0; + goto done; + + err: + rv = -1; + + done: + return rv; +} + +/** Add an RSA signature of extrainfo_string to chunks, using the RSA key + * ident_key. + * Helper for extrainfo_dump_to_string(). + * Returns 0 on success, negative on failure. */ +static int +extrainfo_dump_to_string_rsa_sig_helper(smartlist_t *chunks, + crypto_pk_t *ident_key, + const char *extrainfo_string) +{ + char sig[DIROBJ_MAX_SIG_LEN+1]; + char digest[DIGEST_LEN]; + int rv = -1; + + memset(sig, 0, sizeof(sig)); + if (router_get_extrainfo_hash(extrainfo_string, strlen(extrainfo_string), + digest) < 0 || + router_append_dirobj_signature(sig, sizeof(sig), digest, DIGEST_LEN, + ident_key) < 0) { + log_warn(LD_BUG, "Could not append signature to extra-info " + "descriptor."); + goto err; + } + smartlist_add_strdup(chunks, sig); + + rv = 0; + goto done; + + err: + rv = -1; + + done: + return rv; +} + +/** Write the contents of <b>extrainfo</b>, to * *<b>s_out</b>, signing them + * with <b>ident_key</b>. + * + * If ExtraInfoStatistics is 1, also write aggregated statistics and related + * configuration data before signing. Most statistics also have an option that + * enables or disables that particular statistic. + * + * Always write pluggable transport lines. + * + * Return 0 on success, negative on failure. */ +int +extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, + crypto_pk_t *ident_key, + const ed25519_keypair_t *signing_keypair) +{ + int result; + static int write_stats_to_extrainfo = 1; + char *s = NULL, *cp, *s_dup = NULL; + smartlist_t *chunks = smartlist_new(); + extrainfo_t *ei_tmp = NULL; + const int emit_ed_sigs = signing_keypair && + extrainfo->cache_info.signing_key_cert; + int rv = 0; + + rv = extrainfo_dump_to_string_header_helper(chunks, extrainfo, + signing_keypair, + emit_ed_sigs); + if (rv < 0) + goto err; + + extrainfo_dump_to_string_stats_helper(chunks, write_stats_to_extrainfo); if (emit_ed_sigs) { - char sha256_digest[DIGEST256_LEN]; - smartlist_add_strdup(chunks, "router-sig-ed25519 "); - crypto_digest_smartlist_prefix(sha256_digest, DIGEST256_LEN, - ED_DESC_SIGNATURE_PREFIX, - chunks, "", DIGEST_SHA256); - ed25519_signature_t ed_sig; - char buf[ED25519_SIG_BASE64_LEN+1]; - if (ed25519_sign(&ed_sig, (const uint8_t*)sha256_digest, DIGEST256_LEN, - signing_keypair) < 0) + rv = extrainfo_dump_to_string_ed_sig_helper(chunks, signing_keypair); + if (rv < 0) goto err; - ed25519_signature_to_base64(buf, &ed_sig); - - smartlist_add_asprintf(chunks, "%s\n", buf); } + /* This is one of the three final chunks in the file. If the file is too big, + * other chunks are removed. So we must only add one chunk here. */ smartlist_add_strdup(chunks, "router-signature\n"); s = smartlist_join_strings(chunks, "", 0, NULL); while (strlen(s) > MAX_EXTRAINFO_UPLOAD_SIZE - DIROBJ_MAX_SIG_LEN) { /* So long as there are at least two chunks (one for the initial * extra-info line and one for the router-signature), we can keep removing - * things. */ - if (smartlist_len(chunks) > 2) { - /* We remove the next-to-last element (remember, len-1 is the last - element), since we need to keep the router-signature element. */ - int idx = smartlist_len(chunks) - 2; + * things. If emit_ed_sigs is true, we also keep 2 additional chunks at the + * end for the ed25519 signature. */ + const int required_chunks = emit_ed_sigs ? 4 : 2; + if (smartlist_len(chunks) > required_chunks) { + /* We remove the next-to-last or 4th-last element (remember, len-1 is the + * last element), since we need to keep the router-signature elements. */ + int idx = smartlist_len(chunks) - required_chunks; char *e = smartlist_get(chunks, idx); smartlist_del_keeporder(chunks, idx); log_warn(LD_GENERAL, "We just generated an extra-info descriptor " @@ -3283,15 +3399,10 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, } } - memset(sig, 0, sizeof(sig)); - if (router_get_extrainfo_hash(s, strlen(s), digest) < 0 || - router_append_dirobj_signature(sig, sizeof(sig), digest, DIGEST_LEN, - ident_key) < 0) { - log_warn(LD_BUG, "Could not append signature to extra-info " - "descriptor."); + rv = extrainfo_dump_to_string_rsa_sig_helper(chunks, ident_key, s); + if (rv < 0) goto err; - } - smartlist_add_strdup(chunks, sig); + tor_free(s); s = smartlist_join_strings(chunks, "", 0, NULL); @@ -3327,7 +3438,6 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, SMARTLIST_FOREACH(chunks, char *, chunk, tor_free(chunk)); smartlist_free(chunks); tor_free(s_dup); - tor_free(ed_cert_line); extrainfo_free(ei_tmp); return result; diff --git a/src/feature/rend/rendclient.c b/src/feature/rend/rendclient.c index 5bdd4d453e..2540066dfc 100644 --- a/src/feature/rend/rendclient.c +++ b/src/feature/rend/rendclient.c @@ -119,7 +119,7 @@ rend_client_send_introduction(origin_circuit_t *introcirc, char tmp[RELAY_PAYLOAD_SIZE]; rend_cache_entry_t *entry = NULL; crypt_path_t *cpath; - off_t dh_offset; + ptrdiff_t dh_offset; crypto_pk_t *intro_key = NULL; int status = 0; const char *onion_address; diff --git a/src/feature/rend/rendcommon.c b/src/feature/rend/rendcommon.c index 777de2984c..0a606a9f02 100644 --- a/src/feature/rend/rendcommon.c +++ b/src/feature/rend/rendcommon.c @@ -786,39 +786,39 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint, switch (command) { case RELAY_COMMAND_ESTABLISH_INTRO: if (or_circ) - r = hs_intro_received_establish_intro(or_circ,payload,length); + r = hs_intro_received_establish_intro(or_circ, payload, length); break; case RELAY_COMMAND_ESTABLISH_RENDEZVOUS: if (or_circ) - r = rend_mid_establish_rendezvous(or_circ,payload,length); + r = rend_mid_establish_rendezvous(or_circ, payload, length); break; case RELAY_COMMAND_INTRODUCE1: if (or_circ) - r = hs_intro_received_introduce1(or_circ,payload,length); + r = hs_intro_received_introduce1(or_circ, payload, length); break; case RELAY_COMMAND_INTRODUCE2: if (origin_circ) - r = hs_service_receive_introduce2(origin_circ,payload,length); + r = hs_service_receive_introduce2(origin_circ, payload, length); break; case RELAY_COMMAND_INTRODUCE_ACK: if (origin_circ) - r = hs_client_receive_introduce_ack(origin_circ,payload,length); + r = hs_client_receive_introduce_ack(origin_circ, payload, length); break; case RELAY_COMMAND_RENDEZVOUS1: if (or_circ) - r = rend_mid_rendezvous(or_circ,payload,length); + r = rend_mid_rendezvous(or_circ, payload, length); break; case RELAY_COMMAND_RENDEZVOUS2: if (origin_circ) - r = hs_client_receive_rendezvous2(origin_circ,payload,length); + r = hs_client_receive_rendezvous2(origin_circ, payload, length); break; case RELAY_COMMAND_INTRO_ESTABLISHED: if (origin_circ) - r = hs_service_receive_intro_established(origin_circ,payload,length); + r = hs_service_receive_intro_established(origin_circ, payload, length); break; case RELAY_COMMAND_RENDEZVOUS_ESTABLISHED: if (origin_circ) - r = hs_client_receive_rendezvous_acked(origin_circ,payload,length); + r = hs_client_receive_rendezvous_acked(origin_circ, payload, length); break; default: tor_fragile_assert(); diff --git a/src/feature/rend/rendmid.c b/src/feature/rend/rendmid.c index 421e0f2139..06471b2a7f 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" @@ -117,6 +118,7 @@ rend_mid_establish_intro_legacy(or_circuit_t *circ, const uint8_t *request, /* Now, set up this circuit. */ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_INTRO_POINT); hs_circuitmap_register_intro_circ_v2_relay_side(circ, (uint8_t *)pk_digest); + hs_dos_setup_default_intro2_defenses(circ); log_info(LD_REND, "Established introduction point on circuit %u for service %s", @@ -181,6 +183,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/feature/rend/rendservice.c b/src/feature/rend/rendservice.c index 119a6f9c89..e0cf06b9df 100644 --- a/src/feature/rend/rendservice.c +++ b/src/feature/rend/rendservice.c @@ -2127,7 +2127,7 @@ rend_service_receive_introduction(origin_circuit_t *circuit, * * We only use a one-hop path on the first attempt. If the first attempt * fails, we use a 3-hop path for reachability / reliability. - * See the comment in rend_service_relauch_rendezvous() for details. */ + * See the comment in rend_service_relaunch_rendezvous() for details. */ if (rend_service_use_direct_connection(options, rp) && i == 0) { flags = flags | CIRCLAUNCH_ONEHOP_TUNNEL; } @@ -3999,7 +3999,7 @@ remove_invalid_intro_points(rend_service_t *service, * accounted for when considiring uploading a descriptor. */ intro->circuit_established = 0; - /* Node is gone or we've reached our maximum circuit creationg retry + /* Node is gone or we've reached our maximum circuit creation retry * count, clean up everything, we'll find a new one. */ if (node == NULL || intro->circuit_retries >= MAX_INTRO_POINT_CIRCUIT_RETRIES) { |