diff --git a/changes/bug23820 b/changes/bug23820 new file mode 100644 index 000000000..4e920d049 --- /dev/null +++ b/changes/bug23820 @@ -0,0 +1,5 @@ + o Minor bugfixes (IPv6, v3 single onion services): + - Remove buggy code for IPv6-only v3 single onion services, and reject + attempts to configure them. This release supports IPv4, dual-stack, and + IPv6-only v3 hidden services; and IPv4 and dual-stack v3 single onion + services. Fixes bug 23820; bugfix on 0.3.2.1-alpha. diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c index e1e513c5f..ee952f4d6 100644 --- a/src/or/hs_circuit.c +++ b/src/or/hs_circuit.c @@ -343,6 +343,17 @@ send_establish_intro(const hs_service_t *service, memwipe(payload, 0, sizeof(payload)); } +/* Return a string constant describing the anonymity of service. */ +static const char * +get_service_anonymity_string(const hs_service_t *service) +{ + if (service->config.is_single_onion) { + return "single onion"; + } else { + return "hidden"; + } +} + /* For a given service, the ntor onion key and a rendezvous cookie, launch a * circuit to the rendezvous point specified by the link specifiers. On * success, a circuit identifier is attached to the circuit with the needed @@ -370,7 +381,15 @@ 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. */ + /* 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. + */ + log_fn(LOG_PROTOCOL_WARN, LD_REND, + "Not enough info to open a circuit to a rendezvous point for " + "%s service %s.", + get_service_anonymity_string(service), + safe_str_client(service->onion_address)); goto end; } @@ -392,17 +411,19 @@ launch_rendezvous_point_circuit(const hs_service_t *service, } } if (circ == NULL) { - log_warn(LD_REND, "Giving up on launching rendezvous circuit to %s " - "for service %s", + log_warn(LD_REND, "Giving up on launching a rendezvous circuit to %s " + "for %s service %s", safe_str_client(extend_info_describe(info)), + get_service_anonymity_string(service), safe_str_client(service->onion_address)); goto end; } log_info(LD_REND, "Rendezvous circuit launched to %s with cookie %s " - "for service %s", + "for %s service %s", safe_str_client(extend_info_describe(info)), safe_str_client(hex_str((const char *) data->rendezvous_cookie, REND_COOKIE_LEN)), + get_service_anonymity_string(service), safe_str_client(service->onion_address)); tor_assert(circ->build_state); /* Rendezvous circuit have a specific timeout for the time spent on trying @@ -533,7 +554,10 @@ retry_service_rendezvous_point(const origin_circuit_t *circ) } /* Using an extend info object ei, set all possible link specifiers in lspecs. - * IPv4, legacy ID and ed25519 ID are mandatory thus MUST be present in ei. */ + * legacy ID is mandatory thus MUST be present in ei. If IPv4 is not present, + * logs a BUG() warning, and returns an empty smartlist. Clients never make + * direct connections to rendezvous points, so they should always have an + * IPv4 address in ei. */ static void get_lspecs_from_extend_info(const extend_info_t *ei, smartlist_t *lspecs) { @@ -542,7 +566,11 @@ get_lspecs_from_extend_info(const extend_info_t *ei, smartlist_t *lspecs) tor_assert(ei); tor_assert(lspecs); - /* IPv4 is mandatory. */ + /* We require IPv4, we will add IPv6 support in a later tor version */ + if (BUG(!tor_addr_is_v4(&ei->addr))) { + return; + } + ls = link_specifier_new(); link_specifier_set_ls_type(ls, LS_IPV4); link_specifier_set_un_ipv4_addr(ls, tor_addr_to_ipv4h(&ei->addr)); @@ -560,15 +588,15 @@ get_lspecs_from_extend_info(const extend_info_t *ei, smartlist_t *lspecs) link_specifier_set_ls_len(ls, link_specifier_getlen_un_legacy_id(ls)); smartlist_add(lspecs, ls); - /* ed25519 ID is mandatory. */ - ls = link_specifier_new(); - link_specifier_set_ls_type(ls, LS_ED25519_ID); - memcpy(link_specifier_getarray_un_ed25519_id(ls), &ei->ed_identity, - link_specifier_getlen_un_ed25519_id(ls)); - link_specifier_set_ls_len(ls, link_specifier_getlen_un_ed25519_id(ls)); - smartlist_add(lspecs, ls); - - /* XXX: IPv6 is not clearly a thing in extend_info_t? */ + /* ed25519 ID is only included if the extend_info has it. */ + if (!ed25519_public_key_is_zero(&ei->ed_identity)) { + ls = link_specifier_new(); + link_specifier_set_ls_type(ls, LS_ED25519_ID); + memcpy(link_specifier_getarray_un_ed25519_id(ls), &ei->ed_identity, + link_specifier_getlen_un_ed25519_id(ls)); + link_specifier_set_ls_len(ls, link_specifier_getlen_un_ed25519_id(ls)); + smartlist_add(lspecs, ls); + } } /* Using the given descriptor intro point ip, the extend information of the @@ -1053,6 +1081,14 @@ hs_circ_send_introduce1(origin_circuit_t *intro_circ, * object which is used to build the content of the cell. */ setup_introduce1_data(ip, rend_circ->build_state->chosen_exit, subcredential, &intro1_data); + /* If we didn't get any link specifiers, it's because our extend info was + * bad. */ + if (BUG(!intro1_data.link_specifiers) || + !smartlist_len(intro1_data.link_specifiers)) { + log_warn(LD_REND, "Unable to get link specifiers for INTRODUCE1 cell on " + "circuit %u.", TO_CIRCUIT(intro_circ)->n_circ_id); + goto done; + } /* Final step before we encode a cell, we setup the circuit identifier which * will generate both the rendezvous cookie and client keypair for this diff --git a/src/or/hs_client.c b/src/or/hs_client.c index 18b76e8b3..9ac653c72 100644 --- a/src/or/hs_client.c +++ b/src/or/hs_client.c @@ -776,15 +776,24 @@ client_get_random_intro(const ed25519_public_key_t *service_pk) const hs_descriptor_t *desc; const hs_desc_encrypted_data_t *enc_data; const or_options_t *options = get_options(); + /* Calculate the onion address for logging purposes */ + char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1]; tor_assert(service_pk); desc = hs_cache_lookup_as_client(service_pk); + /* Assume the service is v3 if the descriptor is missing. This is ok, + * because we only use the address in log messages */ + hs_build_address(service_pk, + desc ? desc->plaintext_data.version : HS_VERSION_THREE, + onion_address); if (desc == NULL || !hs_client_any_intro_points_usable(service_pk, desc)) { log_info(LD_REND, "Unable to randomly select an introduction point " - "because descriptor %s.", - (desc) ? "doesn't have usable intro point" : "is missing"); + "for service %s because descriptor %s. We can't connect.", + safe_str_client(onion_address), + (desc) ? "doesn't have any usable intro points" + : "is missing (assuming v3 onion address)"); goto end; } @@ -812,6 +821,10 @@ client_get_random_intro(const ed25519_public_key_t *service_pk) if (ei == NULL) { /* We can get here for instance if the intro point is a private address * and we aren't allowed to extend to those. */ + log_info(LD_REND, "Unable to select introduction point with auth key %s " + "for service %s, because we could not extend to it.", + safe_str_client(ed25519_fmt(&ip->auth_key_cert->signed_key)), + safe_str_client(onion_address)); continue; } @@ -840,14 +853,20 @@ client_get_random_intro(const ed25519_public_key_t *service_pk) * set, we are forced to not use anything. */ ei = ei_excluded; if (options->StrictNodes) { - log_warn(LD_REND, "Every introduction points are in the ExcludeNodes set " - "and StrictNodes is set. We can't connect."); + log_warn(LD_REND, "Every introduction point for service %s is in the " + "ExcludeNodes set and StrictNodes is set. We can't connect.", + safe_str_client(onion_address)); extend_info_free(ei); ei = NULL; + } else { + log_fn(LOG_PROTOCOL_WARN, LD_REND, "Every introduction point for service " + "%s is unusable or we can't extend to it. We can't connect.", + safe_str_client(onion_address)); } end: smartlist_free(usable_ips); + memwipe(onion_address, 0, sizeof(onion_address)); return ei; } diff --git a/src/or/hs_common.c b/src/or/hs_common.c index 42c15c80d..f79aeb598 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -1638,24 +1638,22 @@ hs_pick_hsdir(smartlist_t *responsible_dirs, const char *req_key_str) /* From a list of link specifier, an onion key and if we are requesting a * direct connection (ex: single onion service), return a newly allocated - * extend_info_t object. This function checks the firewall policies and if we - * are allowed to extend to the chosen address. + * extend_info_t object. This function always returns an extend info with + * an IPv4 address, or NULL. * - * if either IPv4 or legacy ID is missing, error. - * if not direct_conn, IPv4 is prefered. - * if direct_conn, IPv6 is prefered if we have one available. - * if firewall does not allow the chosen address, error. - * - * Return NULL if we can fulfill the conditions. */ + * It performs the following checks: + * if either IPv4 or legacy ID is missing, return NULL. + * if direct_conn, and we can't reach the IPv4 address, return NULL. + */ extend_info_t * hs_get_extend_info_from_lspecs(const smartlist_t *lspecs, const curve25519_public_key_t *onion_key, int direct_conn) { - int have_v4 = 0, have_v6 = 0, have_legacy_id = 0, have_ed25519_id = 0; + int have_v4 = 0, have_legacy_id = 0, have_ed25519_id = 0; char legacy_id[DIGEST_LEN] = {0}; - uint16_t port_v4 = 0, port_v6 = 0, port = 0; - tor_addr_t addr_v4, addr_v6, *addr = NULL; + uint16_t port_v4 = 0; + tor_addr_t addr_v4; ed25519_public_key_t ed25519_pk; extend_info_t *info = NULL; @@ -1671,14 +1669,6 @@ hs_get_extend_info_from_lspecs(const smartlist_t *lspecs, port_v4 = link_specifier_get_un_ipv4_port(ls); have_v4 = 1; break; - case LS_IPV6: - /* Skip if we already seen a v6. */ - if (have_v6) continue; - tor_addr_from_ipv6_bytes(&addr_v6, - (const char *) link_specifier_getconstarray_un_ipv6_addr(ls)); - port_v6 = link_specifier_get_un_ipv6_port(ls); - have_v6 = 1; - break; case LS_LEGACY_ID: /* Make sure we do have enough bytes for the legacy ID. */ if (link_specifier_getlen_un_legacy_id(ls) < sizeof(legacy_id)) { @@ -1700,55 +1690,45 @@ hs_get_extend_info_from_lspecs(const smartlist_t *lspecs, } } SMARTLIST_FOREACH_END(ls); - /* IPv4 and legacy ID are mandatory. */ + /* Legacy ID is mandatory, and we require IPv4. */ if (!have_v4 || !have_legacy_id) { goto done; } - /* By default, we pick IPv4 but this might change to v6 if certain - * conditions are met. */ - addr = &addr_v4; port = port_v4; - /* If we are NOT in a direct connection, we'll use our Guard and a 3-hop - * circuit so we can't extend in IPv6. And at this point, we do have an IPv4 - * address available so go to validation. */ + /* We know we have IPv4, because we just checked. */ if (!direct_conn) { + /* All clients can extend to any IPv4 via a 3-hop path. */ goto validate; - } - - /* From this point on, we have a request for a direct connection to the - * rendezvous point so make sure we can actually connect through our - * firewall. We'll prefer IPv6. */ - - /* IPv6 test. */ - if (have_v6 && - fascist_firewall_allows_address_addr(&addr_v6, port_v6, - FIREWALL_OR_CONNECTION, 1, 1)) { - /* Direct connection and we can reach it in IPv6 so go for it. */ - addr = &addr_v6; port = port_v6; - goto validate; - } - /* IPv4 test and we are sure we have a v4 because of the check above. */ - if (fascist_firewall_allows_address_addr(&addr_v4, port_v4, - FIREWALL_OR_CONNECTION, 0, 0)) { + } else if (direct_conn && + fascist_firewall_allows_address_addr(&addr_v4, port_v4, + FIREWALL_OR_CONNECTION, + 0, 0)) { /* Direct connection and we can reach it in IPv4 so go for it. */ - addr = &addr_v4; port = port_v4; goto validate; + + /* We will add support for falling back to a 3-hop path in a later + * release. */ + } else { + /* If we can't reach IPv4, return NULL. */ + goto done; } + /* We will add support for IPv6 in a later release. */ + validate: /* We'll validate now that the address we've picked isn't a private one. If * it is, are we allowing to extend to private address? */ - if (!extend_info_addr_is_allowed(addr)) { - log_warn(LD_REND, "Requested address is private and it is not " - "allowed to extend to it: %s:%u", - fmt_addr(&addr_v4), port_v4); + if (!extend_info_addr_is_allowed(&addr_v4)) { + log_fn(LOG_PROTOCOL_WARN, LD_REND, + "Requested address is private and we are not allowed to extend to " + "it: %s:%u", fmt_addr(&addr_v4), port_v4); goto done; } /* We do have everything for which we think we can connect successfully. */ info = extend_info_new(NULL, legacy_id, (have_ed25519_id) ? &ed25519_pk : NULL, NULL, - onion_key, addr, port); + onion_key, &addr_v4, port_v4); done: return info; } diff --git a/src/or/hs_config.c b/src/or/hs_config.c index 5f9282ea7..fa5c1ab17 100644 --- a/src/or/hs_config.c +++ b/src/or/hs_config.c @@ -424,11 +424,19 @@ config_generic_service(const config_line_t *line_, } } - /* Check if we are configured in non anonymous mode and single hop mode - * meaning every service become single onion. */ - if (rend_service_allow_non_anonymous_connection(options) && - rend_service_non_anonymous_mode_enabled(options)) { + /* Check if we are configured in non anonymous mode meaning every service + * becomes a single onion service. */ + if (rend_service_non_anonymous_mode_enabled(options)) { config->is_single_onion = 1; + /* We will add support for IPv6-only v3 single onion services in a future + * Tor version. This won't catch "ReachableAddresses reject *4", but that + * option doesn't work anyway. */ + if (options->ClientUseIPv4 == 0 && config->version == HS_VERSION_THREE) { + log_warn(LD_CONFIG, "IPv6-only v3 single onion services are not " + "supported. Set HiddenServiceSingleHopMode 0 and " + "HiddenServiceNonAnonymousMode 0, or set ClientUseIPv4 1."); + goto err; + } } /* Success */ diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 4b67bb4d4..a2082b391 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -375,7 +375,14 @@ service_intro_point_free_(void *obj) /* Return a newly allocated service intro point and fully initialized from the * given extend_info_t ei if non NULL. If is_legacy is true, we also generate - * the legacy key. On error, NULL is returned. */ + * the legacy key. On error, NULL is returned. + * + * If ei is NULL, returns a hs_service_intro_point_t with an empty link + * specifier list and no onion key. (This is used for testing.) + * + * ei must be an extend_info_t containing an IPv4 address. (We will add supoort + * for IPv6 in a later release.) When calling extend_info_from_node(), pass + * 0 in for_direct_connection to make sure ei always has an IPv4 address. */ STATIC hs_service_intro_point_t * service_intro_point_new(const extend_info_t *ei, unsigned int is_legacy) { @@ -427,8 +434,8 @@ service_intro_point_new(const extend_info_t *ei, unsigned int is_legacy) goto done; } - /* We'll try to add all link specifier. Legacy, IPv4 and ed25519 are - * mandatory. */ + /* We'll try to add all link specifiers. Legacy is mandatory. + * IPv4 or IPv6 is required, and we always send IPv4. */ ls = hs_desc_link_specifier_new(ei, LS_IPV4); /* It is impossible to have an extend info object without a v4. */ if (BUG(!ls)) { @@ -450,11 +457,7 @@ service_intro_point_new(const extend_info_t *ei, unsigned int is_legacy) smartlist_add(ip->base.link_specifiers, ls); } - /* IPv6 is optional. */ - ls = hs_desc_link_specifier_new(ei, LS_IPV6); - if (ls) { - smartlist_add(ip->base.link_specifiers, ls); - } + /* IPv6 is not supported in this release. */ /* Finally, copy onion key from the extend_info_t object. */ memcpy(&ip->onion_key, &ei->curve25519_onion_key, sizeof(ip->onion_key)); @@ -1520,10 +1523,17 @@ build_all_descriptors(time_t now) /* Randomly pick a node to become an introduction point but not present in the * given exclude_nodes list. The chosen node is put in the exclude list * regardless of success or not because in case of failure, the node is simply - * unsusable from that point on. If direct_conn is set, try to pick a node - * that our local firewall/policy allows to directly connect to and if not, - * fallback to a normal 3-hop node. Return a newly allocated service intro - * point ready to be used for encoding. NULL on error. */ + * unsusable from that point on. + * + * If direct_conn is set, try to pick a node that our local firewall/policy + * allows us to connect to directly. If we can't find any, return NULL. + * This function supports selecting dual-stack nodes for direct single onion + * service IPv6 connections. But it does not send IPv6 addresses in link + * specifiers. (Current clients don't use IPv6 addresses to extend, and + * direct client connections to intro points are not supported.) + * + * Return a newly allocated service intro point ready to be used for encoding. + * Return NULL on error. */ static hs_service_intro_point_t * pick_intro_point(unsigned int direct_conn, smartlist_t *exclude_nodes) { @@ -1537,12 +1547,9 @@ pick_intro_point(unsigned int direct_conn, smartlist_t *exclude_nodes) node = router_choose_random_node(exclude_nodes, get_options()->ExcludeNodes, direct_conn ? direct_flags : flags); - if (node == NULL && direct_conn) { - /* Unable to find a node for direct connection, let's fall back to a - * normal 3-hop node. */ - node = router_choose_random_node(exclude_nodes, - get_options()->ExcludeNodes, flags); - } + /* Unable to find a node. When looking for a node for a direct connection, + * we could try a 3-hop path instead. We'll add support for this in a later + * release. */ if (!node) { goto err; } @@ -1553,8 +1560,11 @@ pick_intro_point(unsigned int direct_conn, smartlist_t *exclude_nodes) smartlist_add(exclude_nodes, (void *) node); /* We do this to ease our life but also this call makes appropriate checks - * of the node object such as validating ntor support for instance. */ - info = extend_info_from_node(node, direct_conn); + * of the node object such as validating ntor support for instance. + * + * We must provide an extend_info for clients to connect over a 3-hop path, + * so we don't pass direct_conn here. */ + info = extend_info_from_node(node, 0); if (BUG(info == NULL)) { goto err; } diff --git a/src/test/include.am b/src/test/include.am index 50cee693a..7300b8541 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -42,8 +42,10 @@ TESTS += src/test/test src/test/test-slow src/test/test-memwipe \ TEST_CHUTNEY_FLAVORS = basic-min bridges-min hs-v2-min hs-v3-min \ single-onion-v23 # only run if we can ping6 ::1 (localhost) +# IPv6-only v3 single onion services don't work yet, so we don't test the +# single-onion-v23-ipv6 flavor TEST_CHUTNEY_FLAVORS_IPV6 = bridges+ipv6-min ipv6-exit-min hs-v23-ipv6 \ - single-onion-v23-ipv6 + single-onion-ipv6 # only run if we can find a stable (or simply another) version of tor TEST_CHUTNEY_FLAVORS_MIXED = mixed+hs-v23