diff options
-rw-r--r-- | src/or/connection_edge.c | 87 | ||||
-rw-r--r-- | src/or/hs_common.c | 106 | ||||
-rw-r--r-- | src/or/hs_common.h | 2 | ||||
-rw-r--r-- | src/or/hs_service.c | 85 | ||||
-rw-r--r-- | src/or/hs_service.h | 2 | ||||
-rw-r--r-- | src/or/rendservice.c | 95 |
6 files changed, 257 insertions, 120 deletions
diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 8e447131fd..41e5f88ab8 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -76,6 +76,7 @@ #include "dirserv.h" #include "hibernate.h" #include "hs_common.h" +#include "hs_circuit.h" #include "main.h" #include "nodelist.h" #include "policies.h" @@ -3066,58 +3067,88 @@ begin_cell_parse(const cell_t *cell, begin_cell_t *bcell, return 0; } -/** For the given <b>circ</b> and the edge connection <b>n_stream</b>, setup - * the the connection, attach it to the circ and connect it. Return 0 on - * success or END_CIRC_AT_ORIGIN if we can't find the requested hidden - * service port where the caller should close the circuit. */ +/** For the given <b>circ</b> and the edge connection <b>conn</b>, setup the + * connection, attach it to the circ and connect it. Return 0 on success + * or END_CIRC_AT_ORIGIN if we can't find the requested hidden service port + * where the caller should close the circuit. */ static int -handle_hs_exit_conn(circuit_t *circ, edge_connection_t *n_stream) +handle_hs_exit_conn(circuit_t *circ, edge_connection_t *conn) { - origin_circuit_t *origin_circ = TO_ORIGIN_CIRCUIT(circ); - log_info(LD_REND,"begin is for rendezvous. configuring stream."); - n_stream->base_.address = tor_strdup("(rendezvous)"); - n_stream->base_.state = EXIT_CONN_STATE_CONNECTING; - n_stream->rend_data = rend_data_dup(origin_circ->rend_data); - tor_assert(connection_edge_is_rendezvous_stream(n_stream)); + int ret; + origin_circuit_t *origin_circ; + assert_circuit_ok(circ); + tor_assert(circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED); + tor_assert(conn); - const int r = rend_service_set_connection_addr_port(n_stream, origin_circ); - if (r < 0) { - log_info(LD_REND,"Didn't find rendezvous service (port %d)", - n_stream->base_.port); + log_debug(LD_REND, "Connecting the hidden service rendezvous circuit " + "to the service destination."); + + origin_circ = TO_ORIGIN_CIRCUIT(circ); + conn->base_.address = tor_strdup("(rendezvous)"); + conn->base_.state = EXIT_CONN_STATE_CONNECTING; + + /* The circuit either has an hs identifier for v3+ or a rend_data for legacy + * service. */ + if (origin_circ->rend_data) { + conn->rend_data = rend_data_dup(origin_circ->rend_data); + tor_assert(connection_edge_is_rendezvous_stream(conn)); + ret = rend_service_set_connection_addr_port(conn, origin_circ); + } else if (origin_circ->hs_ident) { + /* Setup the identifier to be the one for the circuit service. */ + conn->hs_ident = + hs_ident_edge_conn_new(&origin_circ->hs_ident->identity_pk); + ret = hs_service_set_conn_addr_port(origin_circ, conn); + } else { + /* We should never get here if the circuit's purpose is rendezvous. */ + tor_assert(0); + } + if (ret < 0) { + log_info(LD_REND, "Didn't find rendezvous service (addr%s, port %d)", + fmt_addr(&TO_CONN(conn)->addr), TO_CONN(conn)->port); /* Send back reason DONE because we want to make hidden service port * scanning harder thus instead of returning that the exit policy * didn't match, which makes it obvious that the port is closed, * return DONE and kill the circuit. That way, a user (malicious or * not) needs one circuit per bad port unless it matches the policy of * the hidden service. */ - relay_send_end_cell_from_edge(n_stream->stream_id, circ, + relay_send_end_cell_from_edge(conn->stream_id, circ, END_STREAM_REASON_DONE, origin_circ->cpath->prev); - connection_free(TO_CONN(n_stream)); + connection_free(TO_CONN(conn)); /* Drop the circuit here since it might be someone deliberately * scanning the hidden service ports. Note that this mitigates port * scanning by adding more work on the attacker side to successfully * scan but does not fully solve it. */ - if (r < -1) + if (ret < -1) { return END_CIRC_AT_ORIGIN; - else + } else { return 0; + } } - assert_circuit_ok(circ); - log_debug(LD_REND,"Finished assigning addr/port"); - n_stream->cpath_layer = origin_circ->cpath->prev; /* link it */ - /* add it into the linked list of p_streams on this circuit */ - n_stream->next_stream = origin_circ->p_streams; - n_stream->on_circuit = circ; - origin_circ->p_streams = n_stream; + /* Link the circuit and the connection crypt path. */ + conn->cpath_layer = origin_circ->cpath->prev; + + /* Add it into the linked list of p_streams on this circuit */ + conn->next_stream = origin_circ->p_streams; + origin_circ->p_streams = conn; + conn->on_circuit = circ; assert_circuit_ok(circ); - origin_circ->rend_data->nr_streams++; + if (origin_circ->rend_data) { + origin_circ->rend_data->nr_streams++; + } else if (origin_circ->hs_ident) { + origin_circ->hs_ident->num_rdv_streams++; + } else { + /* The previous if/else at the start of the function guarantee that we'll + * never end up in a else situation unless it's freed in between. */ + tor_assert(0); + } - connection_exit_connect(n_stream); + /* Connect tor to the hidden service destination. */ + connection_exit_connect(conn); /* For path bias: This circuit was used successfully */ pathbias_mark_use_success(origin_circ); diff --git a/src/or/hs_common.c b/src/or/hs_common.c index 4d5417afae..20b53bb91c 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -32,6 +32,60 @@ static const char *str_ed25519_basepoint = "463168356949264781694283940034751631413" "07993866256225615783033603165251855960)"; +#ifdef HAVE_SYS_UN_H + +/** Given <b>ports</b>, a smarlist containing rend_service_port_config_t, + * add the given <b>p</b>, a AF_UNIX port to the list. Return 0 on success + * else return -ENOSYS if AF_UNIX is not supported (see function in the + * #else statement below). */ +static int +add_unix_port(smartlist_t *ports, rend_service_port_config_t *p) +{ + tor_assert(ports); + tor_assert(p); + tor_assert(p->is_unix_addr); + + smartlist_add(ports, p); + return 0; +} + +/** Given <b>conn</b> set it to use the given port <b>p</b> values. Return 0 + * on success else return -ENOSYS if AF_UNIX is not supported (see function + * in the #else statement below). */ +static int +set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p) +{ + tor_assert(conn); + tor_assert(p); + tor_assert(p->is_unix_addr); + + conn->base_.socket_family = AF_UNIX; + tor_addr_make_unspec(&conn->base_.addr); + conn->base_.port = 1; + conn->base_.address = tor_strdup(p->unix_addr); + return 0; +} + +#else /* defined(HAVE_SYS_UN_H) */ + +static int +set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p) +{ + (void) conn; + (void) p; + return -ENOSYS; +} + +static int +add_unix_port(smartlist_t *ports, rend_service_port_config_t *p) +{ + (void) ports; + (void) p; + return -ENOSYS; +} + +#endif /* HAVE_SYS_UN_H */ + /* Helper function: The key is a digest that we compare to a node_t object * current hsdir_index. */ static int @@ -602,6 +656,58 @@ hs_get_subcredential(const ed25519_public_key_t *identity_pk, crypto_digest_free(digest); } +/* From the given list of hidden service ports, find the matching one from the + * given edge connection conn and set the connection address from the service + * port object. Return 0 on success or -1 if none. */ +int +hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn) +{ + rend_service_port_config_t *chosen_port; + unsigned int warn_once = 0; + smartlist_t *matching_ports; + + tor_assert(ports); + tor_assert(conn); + + matching_ports = smartlist_new(); + SMARTLIST_FOREACH_BEGIN(ports, rend_service_port_config_t *, p) { + if (TO_CONN(conn)->port != p->virtual_port) { + continue; + } + if (!(p->is_unix_addr)) { + smartlist_add(matching_ports, p); + } else { + if (add_unix_port(matching_ports, p)) { + if (!warn_once) { + /* Unix port not supported so warn only once. */ + log_warn(LD_REND, "Saw AF_UNIX virtual port mapping for port %d " + "which is unsupported on this platform. " + "Ignoring it.", + TO_CONN(conn)->port); + } + warn_once++; + } + } + } SMARTLIST_FOREACH_END(p); + + chosen_port = smartlist_choose(matching_ports); + smartlist_free(matching_ports); + if (chosen_port) { + if (!(chosen_port->is_unix_addr)) { + /* Get a non-AF_UNIX connection ready for connection_exit_connect() */ + tor_addr_copy(&TO_CONN(conn)->addr, &chosen_port->real_addr); + TO_CONN(conn)->port = chosen_port->real_port; + } else { + if (set_unix_port(conn, chosen_port)) { + /* Simply impossible to end up here else we were able to add a Unix + * port without AF_UNIX support... ? */ + tor_assert(0); + } + } + } + return (chosen_port) ? 0 : -1; +} + /* Using a base32 representation of a service address, parse its content into * the key_out, checksum_out and version_out. Any out variable can be NULL in * case the caller would want only one field. checksum_out MUST at least be 2 diff --git a/src/or/hs_common.h b/src/or/hs_common.h index 695f0b8954..3670ff3790 100644 --- a/src/or/hs_common.h +++ b/src/or/hs_common.h @@ -218,6 +218,8 @@ void hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk, uint64_t time_period_num, int is_next_period, int is_client, smartlist_t *responsible_dirs); +int hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn); + #ifdef HS_COMMON_PRIVATE #ifdef TOR_UNIT_TESTS diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 760ba1bc3d..293c17f6f1 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -2386,6 +2386,91 @@ service_add_fnames_to_list(const hs_service_t *service, smartlist_t *list) /* Public API */ /* ========== */ +/* Given conn, a rendezvous edge connection acting as an exit stream, look up + * the hidden service for the circuit circ, and look up the port and address + * based on the connection port. Assign the actual connection address. + * + * Return 0 on success. Return -1 on failure and the caller should NOT close + * the circuit. Return -2 on failure and the caller MUST close the circuit for + * security reasons. */ +int +hs_service_set_conn_addr_port(const origin_circuit_t *circ, + edge_connection_t *conn) +{ + hs_service_t *service = NULL; + + tor_assert(circ); + tor_assert(conn); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_REND_JOINED); + tor_assert(circ->hs_ident); + + get_objects_from_ident(circ->hs_ident, &service, NULL, NULL); + + if (service == NULL) { + log_warn(LD_REND, "Unable to find any hidden service associated " + "identity key %s on rendezvous circuit %u.", + ed25519_fmt(&circ->hs_ident->identity_pk), + TO_CIRCUIT(circ)->n_circ_id); + /* We want the caller to close the circuit because it's not a valid + * service so no danger. Attempting to bruteforce the entire key space by + * opening circuits to learn which service is being hosted here is + * impractical. */ + goto err_close; + } + + /* Enforce the streams-per-circuit limit, and refuse to provide a mapping if + * this circuit will exceed the limit. */ + if (service->config.max_streams_per_rdv_circuit > 0 && + (circ->hs_ident->num_rdv_streams >= + service->config.max_streams_per_rdv_circuit)) { +#define MAX_STREAM_WARN_INTERVAL 600 + static struct ratelim_t stream_ratelim = + RATELIM_INIT(MAX_STREAM_WARN_INTERVAL); + log_fn_ratelim(&stream_ratelim, LOG_WARN, LD_REND, + "Maximum streams per circuit limit reached on " + "rendezvous circuit %u for service %s. Circuit has " + "%" PRIu64 " out of %" PRIu64 " streams. %s.", + TO_CIRCUIT(circ)->n_circ_id, + service->onion_address, + circ->hs_ident->num_rdv_streams, + service->config.max_streams_per_rdv_circuit, + service->config.max_streams_close_circuit ? + "Closing circuit" : "Ignoring open stream request"); + if (service->config.max_streams_close_circuit) { + /* Service explicitly configured to close immediately. */ + goto err_close; + } + /* Exceeding the limit makes tor silently ignore the stream creation + * request and keep the circuit open. */ + goto err_no_close; + } + + /* Find a virtual port of that service mathcing the one in the connection if + * succesful, set the address in the connection. */ + if (hs_set_conn_addr_port(service->config.ports, conn) < 0) { + log_info(LD_REND, "No virtual port mapping exists for port %d for " + "hidden service %s.", + TO_CONN(conn)->port, service->onion_address); + if (service->config.allow_unknown_ports) { + /* Service explicitly allow connection to unknown ports so close right + * away because we do not care about port mapping. */ + goto err_close; + } + /* If the service didn't explicitly allow it, we do NOT close the circuit + * here to raise the bar in terms of performance for port mapping. */ + goto err_no_close; + } + + /* Success. */ + return 0; + err_close: + /* Indicate the caller that the circuit should be closed. */ + return -2; + err_no_close: + /* Indicate the caller to NOT close the circuit. */ + return -1; +} + /* Add to file_list every filename used by a configured hidden service, and to * dir_list every directory path used by a configured hidden service. This is * used by the sandbox subsystem to whitelist those. */ diff --git a/src/or/hs_service.h b/src/or/hs_service.h index 7d026fb354..f678aa2c0e 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -256,6 +256,8 @@ void hs_service_stage_services(const smartlist_t *service_list); int hs_service_load_all_keys(void); void hs_service_lists_fnames_for_sandbox(smartlist_t *file_list, smartlist_t *dir_list); +int hs_service_set_conn_addr_port(const origin_circuit_t *circ, + edge_connection_t *conn); void hs_service_dir_info_changed(void); void hs_service_run_scheduled_events(time_t now); diff --git a/src/or/rendservice.c b/src/or/rendservice.c index 7353a4f99d..5f4a7c6a79 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -4246,60 +4246,6 @@ rend_service_dump_stats(int severity) } } -#ifdef HAVE_SYS_UN_H - -/** Given <b>ports</b>, a smarlist containing rend_service_port_config_t, - * add the given <b>p</b>, a AF_UNIX port to the list. Return 0 on success - * else return -ENOSYS if AF_UNIX is not supported (see function in the - * #else statement below). */ -static int -add_unix_port(smartlist_t *ports, rend_service_port_config_t *p) -{ - tor_assert(ports); - tor_assert(p); - tor_assert(p->is_unix_addr); - - smartlist_add(ports, p); - return 0; -} - -/** Given <b>conn</b> set it to use the given port <b>p</b> values. Return 0 - * on success else return -ENOSYS if AF_UNIX is not supported (see function - * in the #else statement below). */ -static int -set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p) -{ - tor_assert(conn); - tor_assert(p); - tor_assert(p->is_unix_addr); - - conn->base_.socket_family = AF_UNIX; - tor_addr_make_unspec(&conn->base_.addr); - conn->base_.port = 1; - conn->base_.address = tor_strdup(p->unix_addr); - return 0; -} - -#else /* defined(HAVE_SYS_UN_H) */ - -static int -set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p) -{ - (void) conn; - (void) p; - return -ENOSYS; -} - -static int -add_unix_port(smartlist_t *ports, rend_service_port_config_t *p) -{ - (void) ports; - (void) p; - return -ENOSYS; -} - -#endif /* HAVE_SYS_UN_H */ - /** Given <b>conn</b>, a rendezvous exit stream, look up the hidden service for * 'circ', and look up the port and address based on conn-\>port. * Assign the actual conn-\>addr and conn-\>port. Return -2 on failure @@ -4312,9 +4258,6 @@ rend_service_set_connection_addr_port(edge_connection_t *conn, { rend_service_t *service; char serviceid[REND_SERVICE_ID_LEN_BASE32+1]; - smartlist_t *matching_ports; - rend_service_port_config_t *chosen_port; - unsigned int warn_once = 0; const char *rend_pk_digest; tor_assert(circ->base_.purpose == CIRCUIT_PURPOSE_S_REND_JOINED); @@ -4350,41 +4293,9 @@ rend_service_set_connection_addr_port(edge_connection_t *conn, return service->max_streams_close_circuit ? -2 : -1; } } - matching_ports = smartlist_new(); - SMARTLIST_FOREACH(service->ports, rend_service_port_config_t *, p, - { - if (conn->base_.port != p->virtual_port) { - continue; - } - if (!(p->is_unix_addr)) { - smartlist_add(matching_ports, p); - } else { - if (add_unix_port(matching_ports, p)) { - if (!warn_once) { - /* Unix port not supported so warn only once. */ - log_warn(LD_REND, - "Saw AF_UNIX virtual port mapping for port %d on service " - "%s, which is unsupported on this platform. Ignoring it.", - conn->base_.port, serviceid); - } - warn_once++; - } - } - }); - chosen_port = smartlist_choose(matching_ports); - smartlist_free(matching_ports); - if (chosen_port) { - if (!(chosen_port->is_unix_addr)) { - /* Get a non-AF_UNIX connection ready for connection_exit_connect() */ - tor_addr_copy(&conn->base_.addr, &chosen_port->real_addr); - conn->base_.port = chosen_port->real_port; - } else { - if (set_unix_port(conn, chosen_port)) { - /* Simply impossible to end up here else we were able to add a Unix - * port without AF_UNIX support... ? */ - tor_assert(0); - } - } + + if (hs_set_conn_addr_port(service->ports, conn) == 0) { + /* Successfully set the port to the connection. We are done. */ return 0; } |