diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/app/config/config.h | 2 | ||||
-rw-r--r-- | src/core/mainloop/cpuworker.c | 2 | ||||
-rw-r--r-- | src/core/or/channel.c | 37 | ||||
-rw-r--r-- | src/core/or/channel.h | 7 | ||||
-rw-r--r-- | src/core/or/circuitbuild.c | 273 | ||||
-rw-r--r-- | src/core/or/circuitbuild.h | 20 | ||||
-rw-r--r-- | src/core/or/command.c | 1 | ||||
-rw-r--r-- | src/core/or/onion.c | 8 | ||||
-rw-r--r-- | src/core/or/onion.h | 6 | ||||
-rw-r--r-- | src/core/or/or.h | 2 | ||||
-rw-r--r-- | src/core/or/relay.c | 1 | ||||
-rw-r--r-- | src/feature/nodelist/nodelist.c | 10 | ||||
-rw-r--r-- | src/feature/nodelist/nodelist.h | 8 | ||||
-rw-r--r-- | src/feature/relay/circuitbuild_relay.c | 399 | ||||
-rw-r--r-- | src/feature/relay/circuitbuild_relay.h | 84 | ||||
-rw-r--r-- | src/feature/relay/include.am | 2 | ||||
-rw-r--r-- | src/feature/relay/relay_config.c | 2 | ||||
-rw-r--r-- | src/feature/relay/selftest.c | 34 | ||||
-rw-r--r-- | src/feature/relay/selftest.h | 27 | ||||
-rw-r--r-- | src/test/test_circuitbuild.c | 1085 |
20 files changed, 1686 insertions, 324 deletions
diff --git a/src/app/config/config.h b/src/app/config/config.h index 311e27917a..460b5ef0ee 100644 --- a/src/app/config/config.h +++ b/src/app/config/config.h @@ -42,6 +42,8 @@ const char *escaped_safe_str(const char *address); void init_protocol_warning_severity_level(void); int get_protocol_warning_severity_level(void); +#define LOG_PROTOCOL_WARN (get_protocol_warning_severity_level()) + /** An error from options_trial_assign() or options_init_from_string(). */ typedef enum setopt_err_t { SETOPT_OK = 0, diff --git a/src/core/mainloop/cpuworker.c b/src/core/mainloop/cpuworker.c index abd48f886c..485ddb9741 100644 --- a/src/core/mainloop/cpuworker.c +++ b/src/core/mainloop/cpuworker.c @@ -19,7 +19,6 @@ **/ #include "core/or/or.h" #include "core/or/channel.h" -#include "core/or/circuitbuild.h" #include "core/or/circuitlist.h" #include "core/or/connection_or.h" #include "app/config/config.h" @@ -27,6 +26,7 @@ #include "lib/crypt_ops/crypto_rand.h" #include "lib/crypt_ops/crypto_util.h" #include "core/or/onion.h" +#include "feature/relay/circuitbuild_relay.h" #include "feature/relay/onion_queue.h" #include "feature/stats/rephist.h" #include "feature/relay/router.h" diff --git a/src/core/or/channel.c b/src/core/or/channel.c index 6fd33feda7..75054aa0c4 100644 --- a/src/core/or/channel.c +++ b/src/core/or/channel.c @@ -2359,19 +2359,22 @@ channel_is_better(channel_t *a, channel_t *b) /** * Get a channel to extend a circuit. * - * Pick a suitable channel to extend a circuit to given the desired digest - * the address we believe is correct for that digest; this tries to see - * if we already have one for the requested endpoint, but if there is no good - * channel, set *msg_out to a message describing the channel's state - * and our next action, and set *launch_out to a boolean indicated whether - * the caller should try to launch a new channel with channel_connect(). - */ -channel_t * -channel_get_for_extend(const char *rsa_id_digest, - const ed25519_public_key_t *ed_id, - const tor_addr_t *target_addr, - const char **msg_out, - int *launch_out) + * Given the desired relay identity, pick a suitable channel to extend a + * circuit to the target address requsted by the client. Search for an + * existing channel for the requested endpoint. Make sure the channel is + * usable for new circuits, and matches the target address. + * + * Try to return the best channel. But if there is no good channel, set + * *msg_out to a message describing the channel's state and our next action, + * and set *launch_out to a boolean indicated whether the caller should try to + * launch a new channel with channel_connect(). + */ +MOCK_IMPL(channel_t *, +channel_get_for_extend,(const char *rsa_id_digest, + const ed25519_public_key_t *ed_id, + const tor_addr_t *target_addr, + const char **msg_out, + int *launch_out)) { channel_t *chan, *best = NULL; int n_inprogress_goodaddr = 0, n_old = 0; @@ -2382,9 +2385,7 @@ channel_get_for_extend(const char *rsa_id_digest, chan = channel_find_by_remote_identity(rsa_id_digest, ed_id); - /* Walk the list, unrefing the old one and refing the new at each - * iteration. - */ + /* Walk the list of channels */ for (; chan; chan = channel_next_with_rsa_identity(chan)) { tor_assert(tor_memeq(chan->identity_digest, rsa_id_digest, DIGEST_LEN)); @@ -2819,8 +2820,8 @@ channel_get_actual_remote_address(channel_t *chan) * Subsequent calls to channel_get_{actual,canonical}_remote_{address,descr} * may invalidate the return value from this function. */ -const char * -channel_get_canonical_remote_descr(channel_t *chan) +MOCK_IMPL(const char *, +channel_get_canonical_remote_descr,(channel_t *chan)) { tor_assert(chan); tor_assert(chan->get_remote_descr); diff --git a/src/core/or/channel.h b/src/core/or/channel.h index 2e2936a69a..49331d5d58 100644 --- a/src/core/or/channel.h +++ b/src/core/or/channel.h @@ -658,11 +658,12 @@ channel_t * channel_connect(const tor_addr_t *addr, uint16_t port, const char *rsa_id_digest, const struct ed25519_public_key_t *ed_id); -channel_t * channel_get_for_extend(const char *rsa_id_digest, +MOCK_DECL(channel_t *, channel_get_for_extend,( + const char *rsa_id_digest, const struct ed25519_public_key_t *ed_id, const tor_addr_t *target_addr, const char **msg_out, - int *launch_out); + int *launch_out)); /* Ask which of two channels is better for circuit-extension purposes */ int channel_is_better(channel_t *a, channel_t *b); @@ -723,7 +724,7 @@ const char * channel_get_actual_remote_descr(channel_t *chan); const char * channel_get_actual_remote_address(channel_t *chan); MOCK_DECL(int, channel_get_addr_if_possible, (channel_t *chan, tor_addr_t *addr_out)); -const char * channel_get_canonical_remote_descr(channel_t *chan); +MOCK_DECL(const char *, channel_get_canonical_remote_descr,(channel_t *chan)); int channel_has_queued_writes(channel_t *chan); int channel_is_bad_for_new_circs(channel_t *chan); void channel_mark_bad_for_new_circs(channel_t *chan); diff --git a/src/core/or/circuitbuild.c b/src/core/or/circuitbuild.c index 003b91af8d..ce0f9618fe 100644 --- a/src/core/or/circuitbuild.c +++ b/src/core/or/circuitbuild.c @@ -21,8 +21,7 @@ * cells arrive, the client will invoke circuit_send_next_onion_skin() to send * CREATE or RELAY_EXTEND cells. * - * On the server side, this module also handles the logic of responding to - * RELAY_EXTEND requests, using circuit_extend(). + * The server side is handled in feature/relay/circuitbuild_relay.c. **/ #define CIRCUITBUILD_PRIVATE @@ -35,7 +34,6 @@ #include "core/crypto/onion_crypto.h" #include "core/crypto/onion_fast.h" #include "core/crypto/onion_tap.h" -#include "core/crypto/relay_crypto.h" #include "core/mainloop/connection.h" #include "core/mainloop/mainloop.h" #include "core/or/channel.h" @@ -84,13 +82,6 @@ #include "feature/nodelist/routerinfo_st.h" #include "feature/nodelist/routerstatus_st.h" -static channel_t * channel_connect_for_circuit(const tor_addr_t *addr, - uint16_t port, - const char *id_digest, - const ed25519_public_key_t *ed_id); -static int circuit_deliver_create_cell(circuit_t *circ, - const create_cell_t *create_cell, - int relayed); static int circuit_send_first_onion_skin(origin_circuit_t *circ); static int circuit_build_no_more_hops(origin_circuit_t *circ); static int circuit_send_intermediate_onion_skin(origin_circuit_t *circ, @@ -104,10 +95,10 @@ static const node_t *choose_good_middle_server(uint8_t purpose, * and then calls command_setup_channel() to give it the right * callbacks. */ -static channel_t * -channel_connect_for_circuit(const tor_addr_t *addr, uint16_t port, - const char *id_digest, - const ed25519_public_key_t *ed_id) +MOCK_IMPL(channel_t *, +channel_connect_for_circuit,(const tor_addr_t *addr, uint16_t port, + const char *id_digest, + const struct ed25519_public_key_t *ed_id)) { channel_t *chan; @@ -707,9 +698,10 @@ circuit_n_chan_done(channel_t *chan, int status, int close_origin_circuits) * gave us via an EXTEND cell, so we shouldn't worry if we don't understand * it. Return -1 if we failed to find a suitable circid, else return 0. */ -static int -circuit_deliver_create_cell(circuit_t *circ, const create_cell_t *create_cell, - int relayed) +MOCK_IMPL(int, +circuit_deliver_create_cell,(circuit_t *circ, + const struct create_cell_t *create_cell, + int relayed)) { cell_t cell; circid_t id; @@ -767,40 +759,6 @@ circuit_deliver_create_cell(circuit_t *circ, const create_cell_t *create_cell, return -1; } -/** We've decided to start our reachability testing. If all - * is set, log this to the user. Return 1 if we did, or 0 if - * we chose not to log anything. */ -int -inform_testing_reachability(void) -{ - char dirbuf[128]; - char *address; - const routerinfo_t *me = router_get_my_routerinfo(); - if (!me) - return 0; - address = tor_dup_ip(me->addr); - control_event_server_status(LOG_NOTICE, - "CHECKING_REACHABILITY ORADDRESS=%s:%d", - address, me->or_port); - if (me->dir_port) { - tor_snprintf(dirbuf, sizeof(dirbuf), " and DirPort %s:%d", - address, me->dir_port); - control_event_server_status(LOG_NOTICE, - "CHECKING_REACHABILITY DIRADDRESS=%s:%d", - address, me->dir_port); - } - log_notice(LD_OR, "Now checking whether ORPort %s:%d%s %s reachable... " - "(this may take up to %d minutes -- look for log " - "messages indicating success)", - address, me->or_port, - me->dir_port ? dirbuf : "", - me->dir_port ? "are" : "is", - TIMEOUT_UNTIL_UNREACHABILITY_COMPLAINT/60); - - tor_free(address); - return 1; -} - /** Return true iff we should send a create_fast cell to start building a given * circuit */ static inline int @@ -1200,164 +1158,6 @@ circuit_note_clock_jumped(int64_t seconds_elapsed, bool was_idle) } } -/** Take the 'extend' <b>cell</b>, pull out addr/port plus the onion - * skin and identity digest for the next hop. If we're already connected, - * pass the onion skin to the next hop using a create cell; otherwise - * launch a new OR connection, and <b>circ</b> will notice when the - * connection succeeds or fails. - * - * Return -1 if we want to warn and tear down the circuit, else return 0. - */ -int -circuit_extend(cell_t *cell, circuit_t *circ) -{ - channel_t *n_chan; - relay_header_t rh; - extend_cell_t ec; - const char *msg = NULL; - int should_launch = 0; - - if (circ->n_chan) { - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, - "n_chan already set. Bug/attack. Closing."); - return -1; - } - if (circ->n_hop) { - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, - "conn to next hop already launched. Bug/attack. Closing."); - return -1; - } - - if (!server_mode(get_options())) { - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, - "Got an extend cell, but running as a client. Closing."); - return -1; - } - - relay_header_unpack(&rh, cell->payload); - - if (extend_cell_parse(&ec, rh.command, - cell->payload+RELAY_HEADER_SIZE, - rh.length) < 0) { - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, - "Can't parse extend cell. Closing circuit."); - return -1; - } - - if (!ec.orport_ipv4.port || tor_addr_is_null(&ec.orport_ipv4.addr)) { - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, - "Client asked me to extend to zero destination port or addr."); - return -1; - } - - if (tor_addr_is_internal(&ec.orport_ipv4.addr, 0) && - !get_options()->ExtendAllowPrivateAddresses) { - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, - "Client asked me to extend to a private address"); - return -1; - } - - /* Check if they asked us for 0000..0000. We support using - * an empty fingerprint for the first hop (e.g. for a bridge relay), - * but we don't want to let clients send us extend cells for empty - * fingerprints -- a) because it opens the user up to a mitm attack, - * and b) because it lets an attacker force the relay to hold open a - * new TLS connection for each extend request. */ - if (tor_digest_is_zero((const char*)ec.node_id)) { - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, - "Client asked me to extend without specifying an id_digest."); - return -1; - } - - /* Fill in ed_pubkey if it was not provided and we can infer it from - * our networkstatus */ - if (ed25519_public_key_is_zero(&ec.ed_pubkey)) { - const node_t *node = node_get_by_id((const char*)ec.node_id); - const ed25519_public_key_t *node_ed_id = NULL; - if (node && - node_supports_ed25519_link_authentication(node, 1) && - (node_ed_id = node_get_ed25519_id(node))) { - ed25519_pubkey_copy(&ec.ed_pubkey, node_ed_id); - } - } - - /* Next, check if we're being asked to connect to the hop that the - * extend cell came from. There isn't any reason for that, and it can - * assist circular-path attacks. */ - if (tor_memeq(ec.node_id, - TO_OR_CIRCUIT(circ)->p_chan->identity_digest, - DIGEST_LEN)) { - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, - "Client asked me to extend back to the previous hop."); - return -1; - } - - /* Check the previous hop Ed25519 ID too */ - if (! ed25519_public_key_is_zero(&ec.ed_pubkey) && - ed25519_pubkey_eq(&ec.ed_pubkey, - &TO_OR_CIRCUIT(circ)->p_chan->ed25519_identity)) { - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, - "Client asked me to extend back to the previous hop " - "(by Ed25519 ID)."); - return -1; - } - - n_chan = channel_get_for_extend((const char*)ec.node_id, - &ec.ed_pubkey, - &ec.orport_ipv4.addr, - &msg, - &should_launch); - - if (!n_chan) { - log_debug(LD_CIRC|LD_OR,"Next router (%s): %s", - fmt_addrport(&ec.orport_ipv4.addr,ec.orport_ipv4.port), - msg?msg:"????"); - - circ->n_hop = extend_info_new(NULL /*nickname*/, - (const char*)ec.node_id, - &ec.ed_pubkey, - NULL, /*onion_key*/ - NULL, /*curve25519_key*/ - &ec.orport_ipv4.addr, - ec.orport_ipv4.port); - - circ->n_chan_create_cell = tor_memdup(&ec.create_cell, - sizeof(ec.create_cell)); - - circuit_set_state(circ, CIRCUIT_STATE_CHAN_WAIT); - - if (should_launch) { - /* we should try to open a connection */ - n_chan = channel_connect_for_circuit(&ec.orport_ipv4.addr, - ec.orport_ipv4.port, - (const char*)ec.node_id, - &ec.ed_pubkey); - if (!n_chan) { - log_info(LD_CIRC,"Launching n_chan failed. Closing circuit."); - circuit_mark_for_close(circ, END_CIRC_REASON_CONNECTFAILED); - return 0; - } - log_debug(LD_CIRC,"connecting in progress (or finished). Good."); - } - /* return success. The onion/circuit/etc will be taken care of - * automatically (may already have been) whenever n_chan reaches - * OR_CONN_STATE_OPEN. - */ - return 0; - } - - tor_assert(!circ->n_hop); /* Connection is already established. */ - circ->n_chan = n_chan; - log_debug(LD_CIRC, - "n_chan is %s", - channel_get_canonical_remote_descr(n_chan)); - - if (circuit_deliver_create_cell(circ, &ec.create_cell, 1) < 0) - return -1; - - return 0; -} - /** A "created" cell <b>reply</b> came back to us on circuit <b>circ</b>. * (The body of <b>reply</b> varies depending on what sort of handshake * this is.) @@ -1467,61 +1267,6 @@ circuit_truncated(origin_circuit_t *circ, int reason) #endif /* 0 */ } -/** Given a response payload and keys, initialize, then send a created - * cell back. - */ -int -onionskin_answer(or_circuit_t *circ, - const created_cell_t *created_cell, - const char *keys, size_t keys_len, - const uint8_t *rend_circ_nonce) -{ - cell_t cell; - - tor_assert(keys_len == CPATH_KEY_MATERIAL_LEN); - - if (created_cell_format(&cell, created_cell) < 0) { - log_warn(LD_BUG,"couldn't format created cell (type=%d, len=%d)", - (int)created_cell->cell_type, (int)created_cell->handshake_len); - return -1; - } - cell.circ_id = circ->p_circ_id; - - circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_OPEN); - - log_debug(LD_CIRC,"init digest forward 0x%.8x, backward 0x%.8x.", - (unsigned int)get_uint32(keys), - (unsigned int)get_uint32(keys+20)); - if (relay_crypto_init(&circ->crypto, keys, keys_len, 0, 0)<0) { - log_warn(LD_BUG,"Circuit initialization failed"); - return -1; - } - - memcpy(circ->rend_circ_nonce, rend_circ_nonce, DIGEST_LEN); - - int used_create_fast = (created_cell->cell_type == CELL_CREATED_FAST); - - append_cell_to_circuit_queue(TO_CIRCUIT(circ), - circ->p_chan, &cell, CELL_DIRECTION_IN, 0); - log_debug(LD_CIRC,"Finished sending '%s' cell.", - used_create_fast ? "created_fast" : "created"); - - /* Ignore the local bit when ExtendAllowPrivateAddresses is set: - * it violates the assumption that private addresses are local. - * Also, many test networks run on local addresses, and - * TestingTorNetwork sets ExtendAllowPrivateAddresses. */ - if ((!channel_is_local(circ->p_chan) - || get_options()->ExtendAllowPrivateAddresses) - && !channel_is_outgoing(circ->p_chan)) { - /* record that we could process create cells from a non-local conn - * that we didn't initiate; presumably this means that create cells - * can reach us too. */ - router_orport_found_reachable(); - } - - return 0; -} - /** Helper for new_route_len(). Choose a circuit length for purpose * <b>purpose</b>: DEFAULT_ROUTE_LEN (+ 1 if someone else chose the * exit). If someone else chose the exit, they could be colluding diff --git a/src/core/or/circuitbuild.h b/src/core/or/circuitbuild.h index 48592dd346..e62bb41de9 100644 --- a/src/core/or/circuitbuild.h +++ b/src/core/or/circuitbuild.h @@ -29,19 +29,13 @@ struct circuit_guard_state_t *origin_circuit_get_guard_state( int circuit_handle_first_hop(origin_circuit_t *circ); void circuit_n_chan_done(channel_t *chan, int status, int close_origin_circuits); -int inform_testing_reachability(void); int circuit_timeout_want_to_count_circ(const origin_circuit_t *circ); int circuit_send_next_onion_skin(origin_circuit_t *circ); void circuit_note_clock_jumped(int64_t seconds_elapsed, bool was_idle); -int circuit_extend(cell_t *cell, circuit_t *circ); struct created_cell_t; int circuit_finish_handshake(origin_circuit_t *circ, const struct created_cell_t *created_cell); int circuit_truncated(origin_circuit_t *circ, int reason); -int onionskin_answer(or_circuit_t *circ, - const struct created_cell_t *created_cell, - const char *keys, size_t keys_len, - const uint8_t *rend_circ_nonce); MOCK_DECL(int, circuit_all_predicted_ports_handled, (time_t now, int *need_uptime, int *need_capacity)); @@ -77,6 +71,20 @@ const node_t *choose_good_entry_server(uint8_t purpose, struct circuit_guard_state_t **guard_state_out); void circuit_upgrade_circuits_from_guard_wait(void); +struct ed25519_public_key_t; + +MOCK_DECL(channel_t *, +channel_connect_for_circuit,(const tor_addr_t *addr, + uint16_t port, + const char *id_digest, + const struct ed25519_public_key_t *ed_id)); + +struct create_cell_t; +MOCK_DECL(int, +circuit_deliver_create_cell,(circuit_t *circ, + const struct create_cell_t *create_cell, + int relayed)); + #ifdef CIRCUITBUILD_PRIVATE STATIC circid_t get_unique_circ_id_by_chan(channel_t *chan); STATIC int new_route_len(uint8_t purpose, extend_info_t *exit_ei, diff --git a/src/core/or/command.c b/src/core/or/command.c index 9d946974bc..8a1d2066cc 100644 --- a/src/core/or/command.c +++ b/src/core/or/command.c @@ -54,6 +54,7 @@ #include "feature/nodelist/describe.h" #include "feature/nodelist/nodelist.h" #include "feature/nodelist/routerlist.h" +#include "feature/relay/circuitbuild_relay.h" #include "feature/relay/routermode.h" #include "feature/stats/rephist.h" #include "lib/crypt_ops/crypto_util.h" diff --git a/src/core/or/onion.c b/src/core/or/onion.c index 4a5b296b9e..45144b5e6c 100644 --- a/src/core/or/onion.c +++ b/src/core/or/onion.c @@ -374,9 +374,11 @@ extend_cell_from_extend2_cell_body(extend_cell_t *cell_out, /** Parse an EXTEND or EXTEND2 cell (according to <b>command</b>) from the * <b>payload_length</b> bytes of <b>payload</b> into <b>cell_out</b>. Return * 0 on success, -1 on failure. */ -int -extend_cell_parse(extend_cell_t *cell_out, const uint8_t command, - const uint8_t *payload, size_t payload_length) +MOCK_IMPL(int, +extend_cell_parse,(extend_cell_t *cell_out, + const uint8_t command, + const uint8_t *payload, + size_t payload_length)) { tor_assert(cell_out); diff --git a/src/core/or/onion.h b/src/core/or/onion.h index 717854e639..256f0a3f31 100644 --- a/src/core/or/onion.h +++ b/src/core/or/onion.h @@ -74,8 +74,10 @@ void create_cell_init(create_cell_t *cell_out, uint8_t cell_type, const uint8_t *onionskin); int create_cell_parse(create_cell_t *cell_out, const cell_t *cell_in); int created_cell_parse(created_cell_t *cell_out, const cell_t *cell_in); -int extend_cell_parse(extend_cell_t *cell_out, const uint8_t command, - const uint8_t *payload_in, size_t payload_len); +MOCK_DECL(int,extend_cell_parse,(extend_cell_t *cell_out, + const uint8_t command, + const uint8_t *payload_in, + size_t payload_len)); int extended_cell_parse(extended_cell_t *cell_out, const uint8_t command, const uint8_t *payload_in, size_t payload_len); diff --git a/src/core/or/or.h b/src/core/or/or.h index 488a0fb09c..5b35cbe7f1 100644 --- a/src/core/or/or.h +++ b/src/core/or/or.h @@ -995,8 +995,6 @@ typedef struct routerset_t routerset_t; typedef struct or_options_t or_options_t; -#define LOG_PROTOCOL_WARN (get_protocol_warning_severity_level()) - typedef struct or_state_t or_state_t; #define MAX_SOCKS_ADDR_LEN 256 diff --git a/src/core/or/relay.c b/src/core/or/relay.c index 5ddabf3474..00bbf77b99 100644 --- a/src/core/or/relay.c +++ b/src/core/or/relay.c @@ -66,6 +66,7 @@ #include "lib/crypt_ops/crypto_util.h" #include "feature/dircommon/directory.h" #include "feature/relay/dns.h" +#include "feature/relay/circuitbuild_relay.h" #include "feature/stats/geoip_stats.h" #include "feature/hs/hs_cache.h" #include "core/mainloop/mainloop.h" diff --git a/src/feature/nodelist/nodelist.c b/src/feature/nodelist/nodelist.c index fcf27b1a3a..7454f342f9 100644 --- a/src/feature/nodelist/nodelist.c +++ b/src/feature/nodelist/nodelist.c @@ -1074,8 +1074,8 @@ node_get_by_nickname,(const char *nickname, unsigned flags)) /** Return the Ed25519 identity key for the provided node, or NULL if it * doesn't have one. */ -const ed25519_public_key_t * -node_get_ed25519_id(const node_t *node) +MOCK_IMPL(const ed25519_public_key_t *, +node_get_ed25519_id,(const node_t *node)) { const ed25519_public_key_t *ri_pk = NULL; const ed25519_public_key_t *md_pk = NULL; @@ -1158,9 +1158,9 @@ node_get_protover_summary_flags(const node_t *node) * by ed25519 ID during the link handshake. If <b>compatible_with_us</b>, * it needs to be using a link authentication method that we understand. * If not, any plausible link authentication method will do. */ -int -node_supports_ed25519_link_authentication(const node_t *node, - int compatible_with_us) +MOCK_IMPL(int, +node_supports_ed25519_link_authentication,(const node_t *node, + int compatible_with_us)) { if (! node_get_ed25519_id(node)) return 0; diff --git a/src/feature/nodelist/nodelist.h b/src/feature/nodelist/nodelist.h index 6e854ec879..57ab2d5913 100644 --- a/src/feature/nodelist/nodelist.h +++ b/src/feature/nodelist/nodelist.h @@ -70,11 +70,13 @@ const char *node_get_platform(const node_t *node); uint32_t node_get_prim_addr_ipv4h(const node_t *node); void node_get_address_string(const node_t *node, char *cp, size_t len); long node_get_declared_uptime(const node_t *node); -const struct ed25519_public_key_t *node_get_ed25519_id(const node_t *node); +MOCK_DECL(const struct ed25519_public_key_t *,node_get_ed25519_id, + (const node_t *node)); int node_ed25519_id_matches(const node_t *node, const struct ed25519_public_key_t *id); -int node_supports_ed25519_link_authentication(const node_t *node, - int compatible_with_us); +MOCK_DECL(int,node_supports_ed25519_link_authentication, + (const node_t *node, + int compatible_with_us)); 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); diff --git a/src/feature/relay/circuitbuild_relay.c b/src/feature/relay/circuitbuild_relay.c new file mode 100644 index 0000000000..2fa92eeac1 --- /dev/null +++ b/src/feature/relay/circuitbuild_relay.c @@ -0,0 +1,399 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file circuitbuild_relay.c + * @brief Implements the details of exteding circuits (by relaying extend + * cells as create cells, and answering create cells). + * + * On the server side, this module handles the logic of responding to + * RELAY_EXTEND requests, using circuit_extend() and onionskin_answer(). + * + * The shared client and server code is in core/or/circuitbuild.c. + **/ + +#include "orconfig.h" +#include "feature/relay/circuitbuild_relay.h" + +#include "core/or/or.h" +#include "app/config/config.h" + +#include "core/crypto/relay_crypto.h" + +#include "core/or/cell_st.h" +#include "core/or/circuit_st.h" +#include "core/or/extend_info_st.h" +#include "core/or/or_circuit_st.h" + +#include "core/or/channel.h" +#include "core/or/circuitbuild.h" +#include "core/or/circuitlist.h" +#include "core/or/onion.h" +#include "core/or/relay.h" + +#include "feature/nodelist/nodelist.h" + +#include "feature/relay/routermode.h" +#include "feature/relay/selftest.h" + +/* Before replying to an extend cell, check the state of the circuit + * <b>circ</b>, and the configured tor mode. + * + * <b>circ</b> must not be NULL. + * + * If the state and mode are valid, return 0. + * Otherwise, if they are invalid, log a protocol warning, and return -1. + */ +STATIC int +circuit_extend_state_valid_helper(const struct circuit_t *circ) +{ + if (!server_mode(get_options())) { + circuitbuild_warn_client_extend(); + return -1; + } + + IF_BUG_ONCE(!circ) { + return -1; + } + + if (circ->n_chan) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "n_chan already set. Bug/attack. Closing."); + return -1; + } + + if (circ->n_hop) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "conn to next hop already launched. Bug/attack. Closing."); + return -1; + } + + return 0; +} + +/* Make sure the extend cell <b>ec</b> has an ed25519 link specifier. + * + * First, check that the RSA node id is valid. + * If the node id is valid, add the ed25519 link specifier (if required), + * and return 0. + * + * Otherwise, if the node id is invalid, log a protocol warning, + * and return -1.(And do not modify the extend cell.) + * + * Must be called before circuit_extend_lspec_valid_helper(). + */ +STATIC int +circuit_extend_add_ed25519_helper(struct extend_cell_t *ec) +{ + IF_BUG_ONCE(!ec) { + return -1; + } + + /* Check if they asked us for 0000..0000. We support using + * an empty fingerprint for the first hop (e.g. for a bridge relay), + * but we don't want to let clients send us extend cells for empty + * fingerprints -- a) because it opens the user up to a mitm attack, + * and b) because it lets an attacker force the relay to hold open a + * new TLS connection for each extend request. */ + if (tor_digest_is_zero((const char*)ec->node_id)) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Client asked me to extend without specifying an id_digest."); + return -1; + } + + /* Fill in ed_pubkey if it was not provided and we can infer it from + * our networkstatus */ + if (ed25519_public_key_is_zero(&ec->ed_pubkey)) { + const node_t *node = node_get_by_id((const char*)ec->node_id); + const ed25519_public_key_t *node_ed_id = NULL; + if (node && + node_supports_ed25519_link_authentication(node, 1) && + (node_ed_id = node_get_ed25519_id(node))) { + ed25519_pubkey_copy(&ec->ed_pubkey, node_ed_id); + } + } + + return 0; +} + +/* Before replying to an extend cell, check the link specifiers in the extend + * cell <b>ec</b>, which was received on the circuit <b>circ</b>. + * + * If they are valid, return 0. + * Otherwise, if they are invalid, log a protocol warning, and return -1. + * + * Must be called after circuit_extend_add_ed25519_helper(). + */ +STATIC int +circuit_extend_lspec_valid_helper(const struct extend_cell_t *ec, + const struct circuit_t *circ) +{ + IF_BUG_ONCE(!ec) { + return -1; + } + + IF_BUG_ONCE(!circ) { + return -1; + } + + if (!ec->orport_ipv4.port || tor_addr_is_null(&ec->orport_ipv4.addr)) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Client asked me to extend to zero destination port or addr."); + return -1; + } + + if (tor_addr_is_internal(&ec->orport_ipv4.addr, 0) && + !get_options()->ExtendAllowPrivateAddresses) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Client asked me to extend to a private address."); + return -1; + } + + IF_BUG_ONCE(circ->magic != OR_CIRCUIT_MAGIC) { + return -1; + } + + const channel_t *p_chan = CONST_TO_OR_CIRCUIT(circ)->p_chan; + + IF_BUG_ONCE(!p_chan) { + return -1; + } + + /* Next, check if we're being asked to connect to the hop that the + * extend cell came from. There isn't any reason for that, and it can + * assist circular-path attacks. */ + if (tor_memeq(ec->node_id, p_chan->identity_digest, DIGEST_LEN)) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Client asked me to extend back to the previous hop."); + return -1; + } + + /* Check the previous hop Ed25519 ID too */ + if (! ed25519_public_key_is_zero(&ec->ed_pubkey) && + ed25519_pubkey_eq(&ec->ed_pubkey, &p_chan->ed25519_identity)) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Client asked me to extend back to the previous hop " + "(by Ed25519 ID)."); + return -1; + } + + return 0; +} + +/* When there is no open channel for an extend cell <b>ec</b>, set up the + * circuit <b>circ</b> to wait for a new connection. If <b>should_launch</b> + * is true, open a new connection. (Otherwise, we are already waiting for a + * new connection to the same relay.) + */ +STATIC void +circuit_open_connection_for_extend(const struct extend_cell_t *ec, + struct circuit_t *circ, + int should_launch) +{ + /* We have to check circ first, so we can close it on all other failures */ + IF_BUG_ONCE(!circ) { + /* We can't mark a NULL circuit for close. */ + return; + } + + /* Now we know that circ is not NULL */ + IF_BUG_ONCE(!ec) { + circuit_mark_for_close(circ, END_CIRC_REASON_CONNECTFAILED); + return; + } + + circ->n_hop = extend_info_new(NULL /*nickname*/, + (const char*)ec->node_id, + &ec->ed_pubkey, + NULL, /*onion_key*/ + NULL, /*curve25519_key*/ + &ec->orport_ipv4.addr, + ec->orport_ipv4.port); + + circ->n_chan_create_cell = tor_memdup(&ec->create_cell, + sizeof(ec->create_cell)); + + circuit_set_state(circ, CIRCUIT_STATE_CHAN_WAIT); + + if (should_launch) { + /* we should try to open a connection */ + channel_t *n_chan = channel_connect_for_circuit(&ec->orport_ipv4.addr, + ec->orport_ipv4.port, + (const char*)ec->node_id, + &ec->ed_pubkey); + if (!n_chan) { + log_info(LD_CIRC,"Launching n_chan failed. Closing circuit."); + circuit_mark_for_close(circ, END_CIRC_REASON_CONNECTFAILED); + return; + } + log_debug(LD_CIRC,"connecting in progress (or finished). Good."); + } +} + +/** Take the 'extend' <b>cell</b>, pull out addr/port plus the onion + * skin and identity digest for the next hop. If we're already connected, + * pass the onion skin to the next hop using a create cell; otherwise + * launch a new OR connection, and <b>circ</b> will notice when the + * connection succeeds or fails. + * + * Return -1 if we want to warn and tear down the circuit, else return 0. + */ +int +circuit_extend(struct cell_t *cell, struct circuit_t *circ) +{ + channel_t *n_chan; + relay_header_t rh; + extend_cell_t ec; + const char *msg = NULL; + int should_launch = 0; + + IF_BUG_ONCE(!cell) { + return -1; + } + + IF_BUG_ONCE(!circ) { + return -1; + } + + if (circuit_extend_state_valid_helper(circ) < 0) + return -1; + + relay_header_unpack(&rh, cell->payload); + + if (extend_cell_parse(&ec, rh.command, + cell->payload+RELAY_HEADER_SIZE, + rh.length) < 0) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Can't parse extend cell. Closing circuit."); + return -1; + } + + if (circuit_extend_add_ed25519_helper(&ec) < 0) + return -1; + + if (circuit_extend_lspec_valid_helper(&ec, circ) < 0) + return -1; + + n_chan = channel_get_for_extend((const char*)ec.node_id, + &ec.ed_pubkey, + &ec.orport_ipv4.addr, + &msg, + &should_launch); + + if (!n_chan) { + log_debug(LD_CIRC|LD_OR,"Next router (%s): %s.", + fmt_addrport(&ec.orport_ipv4.addr,ec.orport_ipv4.port), + msg?msg:"????"); + + circuit_open_connection_for_extend(&ec, circ, should_launch); + + /* return success. The onion/circuit/etc will be taken care of + * automatically (may already have been) whenever n_chan reaches + * OR_CONN_STATE_OPEN. + */ + return 0; + } else { + /* Connection is already established. + * So we need to extend the circuit to the next hop. */ + tor_assert(!circ->n_hop); + circ->n_chan = n_chan; + log_debug(LD_CIRC, + "n_chan is %s.", + channel_get_canonical_remote_descr(n_chan)); + + if (circuit_deliver_create_cell(circ, &ec.create_cell, 1) < 0) + return -1; + + return 0; + } +} + +/** On a relay, accept a create cell, initialise a circuit, and send a + * created cell back. + * + * Given: + * - a response payload consisting of: + * - the <b>created_cell</b> and + * - an optional <b>rend_circ_nonce</b>, and + * - <b>keys</b> of length <b>keys_len</b>, which must be + * CPATH_KEY_MATERIAL_LEN; + * then: + * - initialize the circuit <b>circ</b>'s cryptographic material, + * - set the circuit's state to open, and + * - send a created cell back on that circuit. + * + * If we haven't found our ORPorts reachable yet, and the channel meets the + * necessary conditions, mark the relevant ORPorts as reachable. + * + * Returns -1 if cell or circuit initialisation fails. + */ +int +onionskin_answer(struct or_circuit_t *circ, + const created_cell_t *created_cell, + const char *keys, size_t keys_len, + const uint8_t *rend_circ_nonce) +{ + cell_t cell; + + IF_BUG_ONCE(!circ) { + return -1; + } + + IF_BUG_ONCE(!created_cell) { + return -1; + } + + IF_BUG_ONCE(!keys) { + return -1; + } + + IF_BUG_ONCE(!rend_circ_nonce) { + return -1; + } + + tor_assert(keys_len == CPATH_KEY_MATERIAL_LEN); + + if (created_cell_format(&cell, created_cell) < 0) { + log_warn(LD_BUG,"couldn't format created cell (type=%d, len=%d).", + (int)created_cell->cell_type, (int)created_cell->handshake_len); + return -1; + } + cell.circ_id = circ->p_circ_id; + + circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_OPEN); + + log_debug(LD_CIRC,"init digest forward 0x%.8x, backward 0x%.8x.", + (unsigned int)get_uint32(keys), + (unsigned int)get_uint32(keys+20)); + if (relay_crypto_init(&circ->crypto, keys, keys_len, 0, 0)<0) { + log_warn(LD_BUG,"Circuit initialization failed."); + return -1; + } + + memcpy(circ->rend_circ_nonce, rend_circ_nonce, DIGEST_LEN); + + int used_create_fast = (created_cell->cell_type == CELL_CREATED_FAST); + + append_cell_to_circuit_queue(TO_CIRCUIT(circ), + circ->p_chan, &cell, CELL_DIRECTION_IN, 0); + log_debug(LD_CIRC,"Finished sending '%s' cell.", + used_create_fast ? "created_fast" : "created"); + + /* Ignore the local bit when ExtendAllowPrivateAddresses is set: + * it violates the assumption that private addresses are local. + * Also, many test networks run on local addresses, and + * TestingTorNetwork sets ExtendAllowPrivateAddresses. */ + if ((!channel_is_local(circ->p_chan) + || get_options()->ExtendAllowPrivateAddresses) + && !channel_is_outgoing(circ->p_chan)) { + /* record that we could process create cells from a non-local conn + * that we didn't initiate; presumably this means that create cells + * can reach us too. */ + router_orport_found_reachable(); + } + + return 0; +} diff --git a/src/feature/relay/circuitbuild_relay.h b/src/feature/relay/circuitbuild_relay.h new file mode 100644 index 0000000000..d14f304f1c --- /dev/null +++ b/src/feature/relay/circuitbuild_relay.h @@ -0,0 +1,84 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file circuitbuild_relay.h + * @brief Header for feature/relay/circuitbuild_relay.c + **/ + +#ifndef TOR_FEATURE_RELAY_CIRCUITBUILD_RELAY_H +#define TOR_FEATURE_RELAY_CIRCUITBUILD_RELAY_H + +#include "lib/cc/torint.h" +#include "lib/log/log.h" + +#include "app/config/config.h" + +struct cell_t; +struct created_cell_t; + +struct circuit_t; +struct or_circuit_t; +struct extend_cell_t; + +/* Log a protocol warning about getting an extend cell on a client. */ +static inline void +circuitbuild_warn_client_extend(void) +{ + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Got an extend cell, but running as a client. Closing."); +} + +#ifdef HAVE_MODULE_RELAY + +int circuit_extend(struct cell_t *cell, struct circuit_t *circ); + +int onionskin_answer(struct or_circuit_t *circ, + const struct created_cell_t *created_cell, + const char *keys, size_t keys_len, + const uint8_t *rend_circ_nonce); + +#else /* !defined(HAVE_MODULE_RELAY) */ + +static inline int +circuit_extend(struct cell_t *cell, struct circuit_t *circ) +{ + (void)cell; + (void)circ; + circuitbuild_warn_client_extend(); + return -1; +} + +static inline int +onionskin_answer(struct or_circuit_t *circ, + const struct created_cell_t *created_cell, + const char *keys, size_t keys_len, + const uint8_t *rend_circ_nonce) +{ + (void)circ; + (void)created_cell; + (void)keys; + (void)keys_len; + (void)rend_circ_nonce; + tor_assert_nonfatal_unreached(); + return -1; +} + +#endif /* defined(HAVE_MODULE_RELAY) */ + +#ifdef TOR_UNIT_TESTS + +STATIC int circuit_extend_state_valid_helper(const struct circuit_t *circ); +STATIC int circuit_extend_add_ed25519_helper(struct extend_cell_t *ec); +STATIC int circuit_extend_lspec_valid_helper(const struct extend_cell_t *ec, + const struct circuit_t *circ); +STATIC void circuit_open_connection_for_extend(const struct extend_cell_t *ec, + struct circuit_t *circ, + int should_launch); + +#endif /* defined(TOR_UNIT_TESTS) */ + +#endif /* !defined(TOR_FEATURE_RELAY_CIRCUITBUILD_RELAY_H) */ diff --git a/src/feature/relay/include.am b/src/feature/relay/include.am index 813ddb8fb1..654432c34b 100644 --- a/src/feature/relay/include.am +++ b/src/feature/relay/include.am @@ -8,6 +8,7 @@ LIBTOR_APP_A_SOURCES += \ # ADD_C_FILE: INSERT SOURCES HERE. MODULE_RELAY_SOURCES = \ + src/feature/relay/circuitbuild_relay.c \ src/feature/relay/dns.c \ src/feature/relay/ext_orport.c \ src/feature/relay/routermode.c \ @@ -21,6 +22,7 @@ MODULE_RELAY_SOURCES = \ # ADD_C_FILE: INSERT HEADERS HERE. noinst_HEADERS += \ + src/feature/relay/circuitbuild_relay.h \ src/feature/relay/dns.h \ src/feature/relay/dns_structs.h \ src/feature/relay/ext_orport.h \ diff --git a/src/feature/relay/relay_config.c b/src/feature/relay/relay_config.c index 3e9961f47e..fac6a2f577 100644 --- a/src/feature/relay/relay_config.c +++ b/src/feature/relay/relay_config.c @@ -29,7 +29,6 @@ #include "core/mainloop/connection.h" #include "core/mainloop/cpuworker.h" #include "core/mainloop/mainloop.h" -#include "core/or/circuitbuild.h" #include "core/or/connection_or.h" #include "core/or/port_cfg_st.h" @@ -44,6 +43,7 @@ #include "feature/dircache/consdiffmgr.h" #include "feature/relay/dns.h" #include "feature/relay/routermode.h" +#include "feature/relay/selftest.h" /** Contents of most recently read DirPortFrontPage file. */ static char *global_dirfrontpagecontents = NULL; diff --git a/src/feature/relay/selftest.c b/src/feature/relay/selftest.c index 29febdee82..8aaf068f9c 100644 --- a/src/feature/relay/selftest.c +++ b/src/feature/relay/selftest.c @@ -213,6 +213,40 @@ router_do_reachability_checks(int test_or, int test_dir) } } +/** We've decided to start our reachability testing. If all + * is set, log this to the user. Return 1 if we did, or 0 if + * we chose not to log anything. */ +int +inform_testing_reachability(void) +{ + char dirbuf[128]; + char *address; + const routerinfo_t *me = router_get_my_routerinfo(); + if (!me) + return 0; + address = tor_dup_ip(me->addr); + control_event_server_status(LOG_NOTICE, + "CHECKING_REACHABILITY ORADDRESS=%s:%d", + address, me->or_port); + if (me->dir_port) { + tor_snprintf(dirbuf, sizeof(dirbuf), " and DirPort %s:%d", + address, me->dir_port); + control_event_server_status(LOG_NOTICE, + "CHECKING_REACHABILITY DIRADDRESS=%s:%d", + address, me->dir_port); + } + log_notice(LD_OR, "Now checking whether ORPort %s:%d%s %s reachable... " + "(this may take up to %d minutes -- look for log " + "messages indicating success)", + address, me->or_port, + me->dir_port ? dirbuf : "", + me->dir_port ? "are" : "is", + TIMEOUT_UNTIL_UNREACHABILITY_COMPLAINT/60); + + tor_free(address); + return 1; +} + /** Annotate that we found our ORPort reachable. */ void router_orport_found_reachable(void) diff --git a/src/feature/relay/selftest.h b/src/feature/relay/selftest.h index f3dd698bb7..f5babc95da 100644 --- a/src/feature/relay/selftest.h +++ b/src/feature/relay/selftest.h @@ -13,14 +13,18 @@ #define TOR_SELFTEST_H #ifdef HAVE_MODULE_RELAY + struct or_options_t; int check_whether_orport_reachable(const struct or_options_t *options); int check_whether_dirport_reachable(const struct or_options_t *options); void router_do_reachability_checks(int test_or, int test_dir); +void router_perform_bandwidth_test(int num_circs, time_t now); +int inform_testing_reachability(void); + void router_orport_found_reachable(void); void router_dirport_found_reachable(void); -void router_perform_bandwidth_test(int num_circs, time_t now); + void router_reset_reachability(void); #else /* !defined(HAVE_MODULE_RELAY) */ @@ -30,13 +34,6 @@ void router_reset_reachability(void); #define check_whether_dirport_reachable(opts) \ ((void)(opts), 0) -#define router_orport_found_reachable() \ - STMT_NIL -#define router_dirport_found_reachable() \ - STMT_NIL -#define router_reset_reachability() \ - STMT_NIL - static inline void router_do_reachability_checks(int test_or, int test_dir) { @@ -51,6 +48,20 @@ router_perform_bandwidth_test(int num_circs, time_t now) (void)now; tor_assert_nonfatal_unreached(); } +static inline int +inform_testing_reachability(void) +{ + tor_assert_nonfatal_unreached(); + return 0; +} + +#define router_orport_found_reachable() \ + STMT_NIL +#define router_dirport_found_reachable() \ + STMT_NIL + +#define router_reset_reachability() \ + STMT_NIL #endif /* defined(HAVE_MODULE_RELAY) */ diff --git a/src/test/test_circuitbuild.c b/src/test/test_circuitbuild.c index 10d78abc35..6934cf3d78 100644 --- a/src/test/test_circuitbuild.c +++ b/src/test/test_circuitbuild.c @@ -8,18 +8,31 @@ #define ENTRYNODES_PRIVATE #include "core/or/or.h" + #include "test/test.h" #include "test/test_helpers.h" #include "test/log_test_helpers.h" + +#define CONFIG_PRIVATE #include "app/config/config.h" + +#include "core/or/channel.h" #include "core/or/circuitbuild.h" #include "core/or/circuitlist.h" +#include "core/or/onion.h" +#include "core/or/cell_st.h" #include "core/or/cpath_build_state_st.h" #include "core/or/extend_info_st.h" #include "core/or/origin_circuit_st.h" +#include "core/or/or_circuit_st.h" #include "feature/client/entrynodes.h" +#include "feature/nodelist/nodelist.h" +#include "feature/relay/circuitbuild_relay.h" +#include "feature/relay/routermode.h" + +#include "feature/nodelist/node_st.h" /* Dummy nodes smartlist for testing */ static smartlist_t dummy_nodes; @@ -133,10 +146,10 @@ test_new_route_len_unhandled_exit(void *arg) "!(exit_ei && !known_purpose)"); expect_single_log_msg_containing("Unhandled purpose"); expect_single_log_msg_containing("with a chosen exit; assuming routelen"); - teardown_capture_of_logs(); - tor_end_capture_bugs_(); done: + teardown_capture_of_logs(); + tor_end_capture_bugs_(); UNMOCK(count_acceptable_nodes); } @@ -180,12 +193,1068 @@ test_upgrade_from_guard_wait(void *arg) entry_guard_free_(guard); } +static int server = 0; +static int +mock_server_mode(const or_options_t *options) +{ + (void)options; + return server; +} + +/* Test the different cases in circuit_extend_state_valid_helper(). */ +static void +test_circuit_extend_state_valid(void *arg) +{ + (void)arg; + circuit_t *circ = tor_malloc_zero(sizeof(circuit_t)); + + server = 0; + MOCK(server_mode, mock_server_mode); + + setup_full_capture_of_logs(LOG_INFO); + + /* Clients can't extend */ + server = 0; + tt_int_op(circuit_extend_state_valid_helper(NULL), OP_EQ, -1); + expect_log_msg("Got an extend cell, but running as a client. Closing.\n"); + mock_clean_saved_logs(); + + /* Circuit must be non-NULL */ + tor_capture_bugs_(1); + server = 1; + tt_int_op(circuit_extend_state_valid_helper(NULL), OP_EQ, -1); + tt_int_op(smartlist_len(tor_get_captured_bug_log_()), OP_EQ, 1); + tt_str_op(smartlist_get(tor_get_captured_bug_log_(), 0), OP_EQ, + "!(ASSERT_PREDICT_UNLIKELY_(!circ))"); + tor_end_capture_bugs_(); + mock_clean_saved_logs(); + + /* n_chan and n_hop are NULL, this should succeed */ + server = 1; + tt_int_op(circuit_extend_state_valid_helper(circ), OP_EQ, 0); + mock_clean_saved_logs(); + + /* But clients still can't extend */ + server = 0; + tt_int_op(circuit_extend_state_valid_helper(circ), OP_EQ, -1); + expect_log_msg("Got an extend cell, but running as a client. Closing.\n"); + mock_clean_saved_logs(); + + /* n_chan must be NULL */ + circ->n_chan = tor_malloc_zero(sizeof(channel_t)); + server = 1; + tt_int_op(circuit_extend_state_valid_helper(circ), OP_EQ, -1); + expect_log_msg("n_chan already set. Bug/attack. Closing.\n"); + mock_clean_saved_logs(); + tor_free(circ->n_chan); + + /* n_hop must be NULL */ + circ->n_hop = tor_malloc_zero(sizeof(extend_info_t)); + server = 1; + tt_int_op(circuit_extend_state_valid_helper(circ), OP_EQ, -1); + expect_log_msg("conn to next hop already launched. Bug/attack. Closing.\n"); + mock_clean_saved_logs(); + tor_free(circ->n_hop); + + done: + tor_end_capture_bugs_(); + teardown_capture_of_logs(); + + UNMOCK(server_mode); + server = 0; + + tor_free(circ->n_chan); + tor_free(circ->n_hop); + tor_free(circ); +} + +static node_t *mocked_node = NULL; +static const node_t * +mock_node_get_by_id(const char *identity_digest) +{ + (void)identity_digest; + return mocked_node; +} + +static int mocked_supports_ed25519_link_authentication = 0; +static int +mock_node_supports_ed25519_link_authentication(const node_t *node, + int compatible_with_us) +{ + (void)node; + (void)compatible_with_us; + return mocked_supports_ed25519_link_authentication; +} + +static ed25519_public_key_t * mocked_ed25519_id = NULL; +static const ed25519_public_key_t * +mock_node_get_ed25519_id(const node_t *node) +{ + (void)node; + return mocked_ed25519_id; +} + +/* Test the different cases in circuit_extend_add_ed25519_helper(). */ +static void +test_circuit_extend_add_ed25519(void *arg) +{ + (void)arg; + extend_cell_t *ec = tor_malloc_zero(sizeof(extend_cell_t)); + extend_cell_t *old_ec = tor_malloc_zero(sizeof(extend_cell_t)); + extend_cell_t *zero_ec = tor_malloc_zero(sizeof(extend_cell_t)); + + node_t *fake_node = tor_malloc_zero(sizeof(node_t)); + ed25519_public_key_t *fake_ed25519_id = NULL; + fake_ed25519_id = tor_malloc_zero(sizeof(ed25519_public_key_t)); + + MOCK(node_get_by_id, mock_node_get_by_id); + MOCK(node_supports_ed25519_link_authentication, + mock_node_supports_ed25519_link_authentication); + MOCK(node_get_ed25519_id, mock_node_get_ed25519_id); + + setup_full_capture_of_logs(LOG_INFO); + + /* The extend cell must be non-NULL */ + tor_capture_bugs_(1); + tt_int_op(circuit_extend_add_ed25519_helper(NULL), OP_EQ, -1); + tt_int_op(smartlist_len(tor_get_captured_bug_log_()), OP_EQ, 1); + tt_str_op(smartlist_get(tor_get_captured_bug_log_(), 0), OP_EQ, + "!(ASSERT_PREDICT_UNLIKELY_(!ec))"); + tor_end_capture_bugs_(); + mock_clean_saved_logs(); + + /* The node id must be non-zero */ + memcpy(old_ec, ec, sizeof(extend_cell_t)); + tt_int_op(circuit_extend_add_ed25519_helper(ec), OP_EQ, -1); + expect_log_msg( + "Client asked me to extend without specifying an id_digest.\n"); + /* And nothing should have changed */ + tt_mem_op(ec, OP_EQ, old_ec, sizeof(extend_cell_t)); + mock_clean_saved_logs(); + + /* Fill in fake node_id, and try again */ + memset(ec->node_id, 0xAA, sizeof(ec->node_id)); + memcpy(old_ec, ec, sizeof(extend_cell_t)); + tt_int_op(circuit_extend_add_ed25519_helper(ec), OP_EQ, 0); + /* There's no node with that id, so the ed pubkey should still be zeroed */ + tt_mem_op(&ec->ed_pubkey, OP_EQ, &zero_ec->ed_pubkey, sizeof(ec->ed_pubkey)); + /* In fact, nothing should have changed */ + tt_mem_op(ec, OP_EQ, old_ec, sizeof(extend_cell_t)); + mock_clean_saved_logs(); + + /* Provide 2 out of 3 of node, supports link auth, and ed_id. + * The ed_id should remain zeroed. */ + + /* Provide node and supports link auth */ + memset(ec->node_id, 0xAA, sizeof(ec->node_id)); + memcpy(old_ec, ec, sizeof(extend_cell_t)); + /* Set up the fake variables */ + mocked_node = fake_node; + mocked_supports_ed25519_link_authentication = 1; + /* Do the test */ + tt_int_op(circuit_extend_add_ed25519_helper(ec), OP_EQ, 0); + /* The ed pubkey should still be zeroed */ + tt_mem_op(&ec->ed_pubkey, OP_EQ, &zero_ec->ed_pubkey, sizeof(ec->ed_pubkey)); + /* In fact, nothing should have changed */ + tt_mem_op(ec, OP_EQ, old_ec, sizeof(extend_cell_t)); + /* Cleanup */ + mock_clean_saved_logs(); + mocked_node = NULL; + mocked_supports_ed25519_link_authentication = 0; + mocked_ed25519_id = NULL; + memset(fake_ed25519_id, 0x00, sizeof(ed25519_public_key_t)); + + /* Provide supports link auth and ed id */ + memset(ec->node_id, 0xAA, sizeof(ec->node_id)); + memcpy(old_ec, ec, sizeof(extend_cell_t)); + /* Set up the fake variables */ + mocked_supports_ed25519_link_authentication = 1; + memset(fake_ed25519_id, 0xEE, sizeof(ed25519_public_key_t)); + mocked_ed25519_id = fake_ed25519_id; + /* Do the test */ + tt_int_op(circuit_extend_add_ed25519_helper(ec), OP_EQ, 0); + /* The ed pubkey should still be zeroed */ + tt_mem_op(&ec->ed_pubkey, OP_EQ, &zero_ec->ed_pubkey, sizeof(ec->ed_pubkey)); + /* In fact, nothing should have changed */ + tt_mem_op(ec, OP_EQ, old_ec, sizeof(extend_cell_t)); + /* Cleanup */ + mock_clean_saved_logs(); + mocked_node = NULL; + mocked_supports_ed25519_link_authentication = 0; + mocked_ed25519_id = NULL; + memset(fake_ed25519_id, 0x00, sizeof(ed25519_public_key_t)); + + /* Provide node and ed id */ + memset(ec->node_id, 0xAA, sizeof(ec->node_id)); + memcpy(old_ec, ec, sizeof(extend_cell_t)); + /* Set up the fake variables */ + mocked_node = fake_node; + memset(fake_ed25519_id, 0xEE, sizeof(ed25519_public_key_t)); + mocked_ed25519_id = fake_ed25519_id; + /* Do the test */ + tt_int_op(circuit_extend_add_ed25519_helper(ec), OP_EQ, 0); + /* The ed pubkey should still be zeroed */ + tt_mem_op(&ec->ed_pubkey, OP_EQ, &zero_ec->ed_pubkey, sizeof(ec->ed_pubkey)); + /* In fact, nothing should have changed */ + tt_mem_op(ec, OP_EQ, old_ec, sizeof(extend_cell_t)); + /* Cleanup */ + mock_clean_saved_logs(); + mocked_node = NULL; + mocked_supports_ed25519_link_authentication = 0; + mocked_ed25519_id = NULL; + memset(fake_ed25519_id, 0x00, sizeof(ed25519_public_key_t)); + + /* Now do the real lookup */ + memset(ec->node_id, 0xAA, sizeof(ec->node_id)); + memcpy(old_ec, ec, sizeof(extend_cell_t)); + /* Set up the fake variables */ + mocked_node = fake_node; + mocked_supports_ed25519_link_authentication = 1; + memset(fake_ed25519_id, 0xEE, sizeof(ed25519_public_key_t)); + mocked_ed25519_id = fake_ed25519_id; + /* Do the test */ + tt_int_op(circuit_extend_add_ed25519_helper(ec), OP_EQ, 0); + /* The ed pubkey should match */ + tt_mem_op(&ec->ed_pubkey, OP_EQ, fake_ed25519_id, sizeof(ec->ed_pubkey)); + /* Nothing else should have changed */ + memcpy(&ec->ed_pubkey, &old_ec->ed_pubkey, sizeof(ec->ed_pubkey)); + tt_mem_op(ec, OP_EQ, old_ec, sizeof(extend_cell_t)); + /* Cleanup */ + mock_clean_saved_logs(); + mocked_node = NULL; + mocked_supports_ed25519_link_authentication = 0; + mocked_ed25519_id = NULL; + memset(fake_ed25519_id, 0x00, sizeof(ed25519_public_key_t)); + + /* Now do the real lookup, but with a zeroed ed id */ + memset(ec->node_id, 0xAA, sizeof(ec->node_id)); + memcpy(old_ec, ec, sizeof(extend_cell_t)); + /* Set up the fake variables */ + mocked_node = fake_node; + mocked_supports_ed25519_link_authentication = 1; + memset(fake_ed25519_id, 0x00, sizeof(ed25519_public_key_t)); + mocked_ed25519_id = fake_ed25519_id; + /* Do the test */ + tt_int_op(circuit_extend_add_ed25519_helper(ec), OP_EQ, 0); + /* The ed pubkey should match */ + tt_mem_op(&ec->ed_pubkey, OP_EQ, fake_ed25519_id, sizeof(ec->ed_pubkey)); + /* Nothing else should have changed */ + memcpy(&ec->ed_pubkey, &old_ec->ed_pubkey, sizeof(ec->ed_pubkey)); + tt_mem_op(ec, OP_EQ, old_ec, sizeof(extend_cell_t)); + /* Cleanup */ + mock_clean_saved_logs(); + mocked_node = NULL; + mocked_supports_ed25519_link_authentication = 0; + mocked_ed25519_id = NULL; + memset(fake_ed25519_id, 0x00, sizeof(ed25519_public_key_t)); + + done: + UNMOCK(node_get_by_id); + UNMOCK(node_supports_ed25519_link_authentication); + UNMOCK(node_get_ed25519_id); + + tor_end_capture_bugs_(); + teardown_capture_of_logs(); + + tor_free(ec); + tor_free(old_ec); + tor_free(zero_ec); + + tor_free(fake_ed25519_id); + tor_free(fake_node); +} + +static or_options_t *mocked_options = NULL; +static const or_options_t * +mock_get_options(void) +{ + return mocked_options; +} + +#define PUBLIC_IPV4 "1.2.3.4" +#define INTERNAL_IPV4 "0.0.0.1" + +#define VALID_PORT 0x1234 + +/* Test the different cases in circuit_extend_lspec_valid_helper(). */ +static void +test_circuit_extend_lspec_valid(void *arg) +{ + (void)arg; + extend_cell_t *ec = tor_malloc_zero(sizeof(extend_cell_t)); + channel_t *p_chan = tor_malloc_zero(sizeof(channel_t)); + or_circuit_t *or_circ = tor_malloc_zero(sizeof(or_circuit_t)); + circuit_t *circ = TO_CIRCUIT(or_circ); + + or_options_t *fake_options = options_new(); + MOCK(get_options, mock_get_options); + mocked_options = fake_options; + + setup_full_capture_of_logs(LOG_INFO); + + /* Extend cell must be non-NULL */ + tor_capture_bugs_(1); + tt_int_op(circuit_extend_lspec_valid_helper(NULL, circ), OP_EQ, -1); + tt_int_op(smartlist_len(tor_get_captured_bug_log_()), OP_EQ, 1); + tt_str_op(smartlist_get(tor_get_captured_bug_log_(), 0), OP_EQ, + "!(ASSERT_PREDICT_UNLIKELY_(!ec))"); + tor_end_capture_bugs_(); + mock_clean_saved_logs(); + + /* Circuit must be non-NULL */ + tor_capture_bugs_(1); + tt_int_op(circuit_extend_lspec_valid_helper(ec, NULL), OP_EQ, -1); + tt_int_op(smartlist_len(tor_get_captured_bug_log_()), OP_EQ, 1); + tt_str_op(smartlist_get(tor_get_captured_bug_log_(), 0), OP_EQ, + "!(ASSERT_PREDICT_UNLIKELY_(!circ))"); + tor_end_capture_bugs_(); + mock_clean_saved_logs(); + + /* Extend cell and circuit must be non-NULL */ + tor_capture_bugs_(1); + tt_int_op(circuit_extend_lspec_valid_helper(NULL, NULL), OP_EQ, -1); + /* Since we're using IF_BUG_ONCE(), we might not log any bugs */ + tt_int_op(smartlist_len(tor_get_captured_bug_log_()), OP_GE, 0); + tt_int_op(smartlist_len(tor_get_captured_bug_log_()), OP_LE, 2); + tor_end_capture_bugs_(); + mock_clean_saved_logs(); + + /* IPv4 addr or port are 0, these should fail */ + tt_int_op(circuit_extend_lspec_valid_helper(ec, circ), OP_EQ, -1); + expect_log_msg("Client asked me to extend to " + "zero destination port or addr.\n"); + mock_clean_saved_logs(); + + tor_addr_parse(&ec->orport_ipv4.addr, PUBLIC_IPV4); + tt_int_op(circuit_extend_lspec_valid_helper(ec, circ), OP_EQ, -1); + expect_log_msg("Client asked me to extend to " + "zero destination port or addr.\n"); + mock_clean_saved_logs(); + tor_addr_make_null(&ec->orport_ipv4.addr, AF_INET); + + ec->orport_ipv4.port = VALID_PORT; + tt_int_op(circuit_extend_lspec_valid_helper(ec, circ), OP_EQ, -1); + expect_log_msg("Client asked me to extend to " + "zero destination port or addr.\n"); + mock_clean_saved_logs(); + ec->orport_ipv4.port = 0; + + /* IPv4 addr is internal, and port is valid. + * Result depends on ExtendAllowPrivateAddresses. */ + tor_addr_parse(&ec->orport_ipv4.addr, INTERNAL_IPV4); + ec->orport_ipv4.port = VALID_PORT; + + fake_options->ExtendAllowPrivateAddresses = 0; + tt_int_op(circuit_extend_lspec_valid_helper(ec, circ), OP_EQ, -1); + expect_log_msg("Client asked me to extend to a private address.\n"); + mock_clean_saved_logs(); + fake_options->ExtendAllowPrivateAddresses = 0; + + /* If we pass the private address check, but don't have the right + * OR circuit magic number, we trigger another bug */ + fake_options->ExtendAllowPrivateAddresses = 1; + tor_capture_bugs_(1); + tt_int_op(circuit_extend_lspec_valid_helper(ec, circ), OP_EQ, -1); + tt_int_op(smartlist_len(tor_get_captured_bug_log_()), OP_EQ, 1); + tt_str_op(smartlist_get(tor_get_captured_bug_log_(), 0), OP_EQ, + "!(ASSERT_PREDICT_UNLIKELY_(circ->magic != 0x98ABC04Fu))"); + tor_end_capture_bugs_(); + mock_clean_saved_logs(); + fake_options->ExtendAllowPrivateAddresses = 0; + + /* Now set the right magic */ + or_circ->base_.magic = OR_CIRCUIT_MAGIC; + + /* If we pass the OR circuit magic check, but don't have p_chan, + * we trigger another bug */ + fake_options->ExtendAllowPrivateAddresses = 1; + tor_capture_bugs_(1); + tt_int_op(circuit_extend_lspec_valid_helper(ec, circ), OP_EQ, -1); + tt_int_op(smartlist_len(tor_get_captured_bug_log_()), OP_EQ, 1); + tt_str_op(smartlist_get(tor_get_captured_bug_log_(), 0), OP_EQ, + "!(ASSERT_PREDICT_UNLIKELY_(!p_chan))"); + tor_end_capture_bugs_(); + mock_clean_saved_logs(); + fake_options->ExtendAllowPrivateAddresses = 0; + + /* We can also pass the OR circuit magic check with a public address */ + tor_addr_parse(&ec->orport_ipv4.addr, PUBLIC_IPV4); + fake_options->ExtendAllowPrivateAddresses = 0; + tor_capture_bugs_(1); + tt_int_op(circuit_extend_lspec_valid_helper(ec, circ), OP_EQ, -1); + /* Since we're using IF_BUG_ONCE(), expect 0-1 bug logs */ + tt_int_op(smartlist_len(tor_get_captured_bug_log_()), OP_GE, 0); + tt_int_op(smartlist_len(tor_get_captured_bug_log_()), OP_LE, 1); + tor_end_capture_bugs_(); + mock_clean_saved_logs(); + fake_options->ExtendAllowPrivateAddresses = 0; + + tor_addr_make_null(&ec->orport_ipv4.addr, AF_INET); + ec->orport_ipv4.port = 0x0000; + + /* Now let's fake a p_chan and the addresses */ + tor_addr_parse(&ec->orport_ipv4.addr, PUBLIC_IPV4); + ec->orport_ipv4.port = VALID_PORT; + or_circ->p_chan = p_chan; + + /* This is a trivial failure: node_id and p_chan->identity_digest are both + * zeroed */ + tt_int_op(circuit_extend_lspec_valid_helper(ec, circ), OP_EQ, -1); + expect_log_msg("Client asked me to extend back to the previous hop.\n"); + mock_clean_saved_logs(); + + /* Let's check with non-zero identities as well */ + memset(ec->node_id, 0xAA, sizeof(ec->node_id)); + memset(p_chan->identity_digest, 0xAA, sizeof(p_chan->identity_digest)); + + tt_int_op(circuit_extend_lspec_valid_helper(ec, circ), OP_EQ, -1); + expect_log_msg("Client asked me to extend back to the previous hop.\n"); + mock_clean_saved_logs(); + + memset(ec->node_id, 0, sizeof(ec->node_id)); + memset(p_chan->identity_digest, 0, sizeof(p_chan->identity_digest)); + + /* Let's pass the node_id test */ + memset(ec->node_id, 0xAA, sizeof(ec->node_id)); + memset(p_chan->identity_digest, 0xBB, sizeof(p_chan->identity_digest)); + + /* ed_pubkey is zero, and that's allowed, so we should succeed */ + tt_int_op(circuit_extend_lspec_valid_helper(ec, circ), OP_EQ, 0); + mock_clean_saved_logs(); + + /* Fail on matching non-zero identities */ + memset(&ec->ed_pubkey, 0xEE, sizeof(ec->ed_pubkey)); + memset(&p_chan->ed25519_identity, 0xEE, sizeof(p_chan->ed25519_identity)); + + tt_int_op(circuit_extend_lspec_valid_helper(ec, circ), OP_EQ, -1); + expect_log_msg("Client asked me to extend back to the previous hop " + "(by Ed25519 ID).\n"); + mock_clean_saved_logs(); + + memset(&ec->ed_pubkey, 0, sizeof(ec->ed_pubkey)); + memset(&p_chan->ed25519_identity, 0, sizeof(p_chan->ed25519_identity)); + + /* Succeed on different, non-zero identities */ + memset(&ec->ed_pubkey, 0xDD, sizeof(ec->ed_pubkey)); + memset(&p_chan->ed25519_identity, 0xEE, sizeof(p_chan->ed25519_identity)); + + tt_int_op(circuit_extend_lspec_valid_helper(ec, circ), OP_EQ, 0); + mock_clean_saved_logs(); + + memset(&ec->ed_pubkey, 0, sizeof(ec->ed_pubkey)); + memset(&p_chan->ed25519_identity, 0, sizeof(p_chan->ed25519_identity)); + + /* Succeed if the client knows the identity, but we don't */ + memset(&ec->ed_pubkey, 0xDD, sizeof(ec->ed_pubkey)); + memset(&p_chan->ed25519_identity, 0x00, sizeof(p_chan->ed25519_identity)); + + tt_int_op(circuit_extend_lspec_valid_helper(ec, circ), OP_EQ, 0); + mock_clean_saved_logs(); + + memset(&ec->ed_pubkey, 0, sizeof(ec->ed_pubkey)); + memset(&p_chan->ed25519_identity, 0, sizeof(p_chan->ed25519_identity)); + + /* Succeed if we know the identity, but the client doesn't */ + memset(&ec->ed_pubkey, 0x00, sizeof(ec->ed_pubkey)); + memset(&p_chan->ed25519_identity, 0xEE, sizeof(p_chan->ed25519_identity)); + + tt_int_op(circuit_extend_lspec_valid_helper(ec, circ), OP_EQ, 0); + mock_clean_saved_logs(); + + memset(&ec->ed_pubkey, 0, sizeof(ec->ed_pubkey)); + memset(&p_chan->ed25519_identity, 0, sizeof(p_chan->ed25519_identity)); + + /* Cleanup the node ids */ + memset(ec->node_id, 0, sizeof(ec->node_id)); + memset(p_chan->identity_digest, 0, sizeof(p_chan->identity_digest)); + + /* Cleanup the p_chan and the addresses */ + tor_addr_make_null(&ec->orport_ipv4.addr, AF_UNSPEC); + ec->orport_ipv4.port = 0; + or_circ->p_chan = NULL; + + done: + tor_end_capture_bugs_(); + teardown_capture_of_logs(); + + UNMOCK(get_options); + or_options_free(fake_options); + mocked_options = NULL; + + tor_free(ec); + tor_free(or_circ); + tor_free(p_chan); +} + +static int mock_circuit_close_calls = 0; +static void +mock_circuit_mark_for_close_(circuit_t *circ, int reason, + int line, const char *cfile) +{ + (void)circ; + (void)reason; + (void)line; + (void)cfile; + mock_circuit_close_calls++; +} + +static int mock_channel_connect_calls = 0; +static channel_t *mock_channel_connect_nchan = NULL; +static channel_t * +mock_channel_connect_for_circuit(const tor_addr_t *addr, + uint16_t port, + const char *id_digest, + const struct ed25519_public_key_t *ed_id) +{ + (void)addr; + (void)port; + (void)id_digest; + (void)ed_id; + mock_channel_connect_calls++; + return mock_channel_connect_nchan; +} + +/* Test the different cases in circuit_open_connection_for_extend(). */ +static void +test_circuit_open_connection_for_extend(void *arg) +{ + (void)arg; + extend_cell_t *ec = tor_malloc_zero(sizeof(extend_cell_t)); + circuit_t *circ = tor_malloc_zero(sizeof(circuit_t)); + channel_t *fake_n_chan = tor_malloc_zero(sizeof(channel_t)); + + MOCK(circuit_mark_for_close_, mock_circuit_mark_for_close_); + mock_circuit_close_calls = 0; + MOCK(channel_connect_for_circuit, mock_channel_connect_for_circuit); + mock_channel_connect_calls = 0; + mock_channel_connect_nchan = NULL; + + setup_full_capture_of_logs(LOG_INFO); + + /* Circuit must be non-NULL */ + mock_circuit_close_calls = 0; + mock_channel_connect_calls = 0; + tor_capture_bugs_(1); + circuit_open_connection_for_extend(ec, NULL, 0); + /* We can't close a NULL circuit */ + tt_int_op(mock_circuit_close_calls, OP_EQ, 0); + tt_int_op(mock_channel_connect_calls, OP_EQ, 0); + tt_int_op(smartlist_len(tor_get_captured_bug_log_()), OP_EQ, 1); + tt_str_op(smartlist_get(tor_get_captured_bug_log_(), 0), OP_EQ, + "!(ASSERT_PREDICT_UNLIKELY_(!circ))"); + tor_end_capture_bugs_(); + mock_clean_saved_logs(); + + /* Extend cell must be non-NULL */ + mock_circuit_close_calls = 0; + mock_channel_connect_calls = 0; + tor_capture_bugs_(1); + circuit_open_connection_for_extend(NULL, circ, 0); + tt_int_op(mock_circuit_close_calls, OP_EQ, 1); + tt_int_op(mock_channel_connect_calls, OP_EQ, 0); + tt_int_op(smartlist_len(tor_get_captured_bug_log_()), OP_EQ, 1); + tt_str_op(smartlist_get(tor_get_captured_bug_log_(), 0), OP_EQ, + "!(ASSERT_PREDICT_UNLIKELY_(!ec))"); + tor_end_capture_bugs_(); + mock_clean_saved_logs(); + + /* Extend cell and circuit must be non-NULL */ + mock_circuit_close_calls = 0; + mock_channel_connect_calls = 0; + tor_capture_bugs_(1); + circuit_open_connection_for_extend(NULL, NULL, 0); + /* We can't close a NULL circuit */ + tt_int_op(mock_circuit_close_calls, OP_EQ, 0); + tt_int_op(mock_channel_connect_calls, OP_EQ, 0); + /* Since we're using IF_BUG_ONCE(), we might not log any bugs */ + tt_int_op(smartlist_len(tor_get_captured_bug_log_()), OP_GE, 0); + tt_int_op(smartlist_len(tor_get_captured_bug_log_()), OP_LE, 2); + tor_end_capture_bugs_(); + mock_clean_saved_logs(); + + /* Succeed, but don't try to open a connection */ + mock_circuit_close_calls = 0; + mock_channel_connect_calls = 0; + circuit_open_connection_for_extend(ec, circ, 0); + /* If we haven't closed the circuit, that's success */ + tt_int_op(mock_circuit_close_calls, OP_EQ, 0); + tt_int_op(mock_channel_connect_calls, OP_EQ, 0); + /* Check state */ + tt_ptr_op(circ->n_hop, OP_NE, NULL); + tt_ptr_op(circ->n_chan_create_cell, OP_NE, NULL); + tt_int_op(circ->state, OP_EQ, CIRCUIT_STATE_CHAN_WAIT); + /* Cleanup */ + mock_clean_saved_logs(); + tor_free(circ->n_hop); + tor_free(circ->n_chan_create_cell); + circ->state = 0; + + /* Try to open a connection, but fail with a NULL n_chan */ + mock_circuit_close_calls = 0; + mock_channel_connect_calls = 0; + circuit_open_connection_for_extend(ec, circ, 1); + /* Try to connect, but fail, and close the circuit */ + tt_int_op(mock_circuit_close_calls, OP_EQ, 1); + tt_int_op(mock_channel_connect_calls, OP_EQ, 1); + expect_log_msg("Launching n_chan failed. Closing circuit.\n"); + /* Check state */ + tt_ptr_op(circ->n_hop, OP_NE, NULL); + tt_ptr_op(circ->n_chan_create_cell, OP_NE, NULL); + tt_int_op(circ->state, OP_EQ, CIRCUIT_STATE_CHAN_WAIT); + /* Cleanup */ + mock_clean_saved_logs(); + tor_free(circ->n_hop); + tor_free(circ->n_chan_create_cell); + circ->state = 0; + + /* Try to open a connection, and succeed, because n_chan is not NULL */ + mock_channel_connect_nchan = fake_n_chan; + mock_circuit_close_calls = 0; + mock_channel_connect_calls = 0; + circuit_open_connection_for_extend(ec, circ, 1); + /* Try to connect, and succeed, leaving the circuit open */ + tt_int_op(mock_circuit_close_calls, OP_EQ, 0); + tt_int_op(mock_channel_connect_calls, OP_EQ, 1); + /* Check state */ + tt_ptr_op(circ->n_hop, OP_NE, NULL); + tt_ptr_op(circ->n_chan_create_cell, OP_NE, NULL); + tt_int_op(circ->state, OP_EQ, CIRCUIT_STATE_CHAN_WAIT); + /* Cleanup */ + mock_clean_saved_logs(); + tor_free(circ->n_hop); + tor_free(circ->n_chan_create_cell); + circ->state = 0; + mock_channel_connect_nchan = NULL; + + done: + tor_end_capture_bugs_(); + teardown_capture_of_logs(); + + UNMOCK(circuit_mark_for_close_); + mock_circuit_close_calls = 0; + UNMOCK(channel_connect_for_circuit); + mock_channel_connect_calls = 0; + + tor_free(ec); + tor_free(circ->n_hop); + tor_free(circ->n_chan_create_cell); + tor_free(circ); + tor_free(fake_n_chan); +} + +/* Guaranteed to be initialised to zero. */ +static extend_cell_t mock_extend_cell_parse_cell_out; +static int mock_extend_cell_parse_result = 0; +static int mock_extend_cell_parse_calls = 0; + +static int +mock_extend_cell_parse(extend_cell_t *cell_out, + const uint8_t command, + const uint8_t *payload_in, + size_t payload_len) +{ + (void)command; + (void)payload_in; + (void)payload_len; + + mock_extend_cell_parse_calls++; + memcpy(cell_out, &mock_extend_cell_parse_cell_out, + sizeof(extend_cell_t)); + return mock_extend_cell_parse_result; +} + +static int mock_channel_get_for_extend_calls = 0; +static int mock_channel_get_for_extend_launch_out = 0; +static channel_t *mock_channel_get_for_extend_nchan = NULL; +static channel_t * +mock_channel_get_for_extend(const char *rsa_id_digest, + const ed25519_public_key_t *ed_id, + const tor_addr_t *target_addr, + const char **msg_out, + int *launch_out) +{ + (void)rsa_id_digest; + (void)ed_id; + (void)target_addr; + + /* channel_get_for_extend() requires non-NULL arguments */ + tt_ptr_op(msg_out, OP_NE, NULL); + tt_ptr_op(launch_out, OP_NE, NULL); + + mock_channel_get_for_extend_calls++; + *msg_out = NULL; + *launch_out = mock_channel_get_for_extend_launch_out; + return mock_channel_get_for_extend_nchan; + + done: + return NULL; +} + +static const char * +mock_channel_get_canonical_remote_descr(channel_t *chan) +{ + (void)chan; + return "mock_channel_get_canonical_remote_descr()"; +} + +static int mock_circuit_deliver_create_cell_calls = 0; +static int mock_circuit_deliver_create_cell_result = 0; +static int +mock_circuit_deliver_create_cell(circuit_t *circ, + const struct create_cell_t *create_cell, + int relayed) +{ + (void)create_cell; + + /* circuit_deliver_create_cell() requires non-NULL arguments, + * but we only check circ and circ->n_chan here. */ + tt_ptr_op(circ, OP_NE, NULL); + tt_ptr_op(circ->n_chan, OP_NE, NULL); + + /* We should only ever get relayed cells from extends */ + tt_int_op(relayed, OP_EQ, 1); + + mock_circuit_deliver_create_cell_calls++; + return mock_circuit_deliver_create_cell_result; + + done: + return -1; +} + +/* Test the different cases in circuit_extend(). */ +static void +test_circuit_extend(void *arg) +{ + (void)arg; + cell_t *cell = tor_malloc_zero(sizeof(cell_t)); + channel_t *p_chan = tor_malloc_zero(sizeof(channel_t)); + or_circuit_t *or_circ = tor_malloc_zero(sizeof(or_circuit_t)); + circuit_t *circ = TO_CIRCUIT(or_circ); + channel_t *fake_n_chan = tor_malloc_zero(sizeof(channel_t)); + + server = 0; + MOCK(server_mode, mock_server_mode); + + /* Mock a debug function, but otherwise ignore it */ + MOCK(channel_get_canonical_remote_descr, + mock_channel_get_canonical_remote_descr); + + setup_full_capture_of_logs(LOG_INFO); + + /* Circuit must be non-NULL */ + tor_capture_bugs_(1); + tt_int_op(circuit_extend(cell, NULL), OP_EQ, -1); + tt_int_op(smartlist_len(tor_get_captured_bug_log_()), OP_EQ, 1); + tt_str_op(smartlist_get(tor_get_captured_bug_log_(), 0), OP_EQ, + "!(ASSERT_PREDICT_UNLIKELY_(!circ))"); + tor_end_capture_bugs_(); + mock_clean_saved_logs(); + + /* Cell must be non-NULL */ + tor_capture_bugs_(1); + tt_int_op(circuit_extend(NULL, circ), OP_EQ, -1); + tt_int_op(smartlist_len(tor_get_captured_bug_log_()), OP_EQ, 1); + tt_str_op(smartlist_get(tor_get_captured_bug_log_(), 0), OP_EQ, + "!(ASSERT_PREDICT_UNLIKELY_(!cell))"); + tor_end_capture_bugs_(); + mock_clean_saved_logs(); + + /* Extend cell and circuit must be non-NULL */ + tor_capture_bugs_(1); + tt_int_op(circuit_extend(NULL, NULL), OP_EQ, -1); + /* Since we're using IF_BUG_ONCE(), we might not log any bugs */ + tt_int_op(smartlist_len(tor_get_captured_bug_log_()), OP_GE, 0); + tt_int_op(smartlist_len(tor_get_captured_bug_log_()), OP_LE, 2); + tor_end_capture_bugs_(); + mock_clean_saved_logs(); + + /* Clients can't extend */ + server = 0; + tt_int_op(circuit_extend(cell, circ), OP_EQ, -1); + expect_log_msg("Got an extend cell, but running as a client. Closing.\n"); + mock_clean_saved_logs(); + + /* But servers can. Unpack the cell, but fail parsing. */ + server = 1; + tt_int_op(circuit_extend(cell, circ), OP_EQ, -1); + expect_log_msg("Can't parse extend cell. Closing circuit.\n"); + mock_clean_saved_logs(); + + /* Now mock parsing */ + MOCK(extend_cell_parse, mock_extend_cell_parse); + + /* And make parsing succeed, but fail on adding ed25519 */ + memset(&mock_extend_cell_parse_cell_out, 0, + sizeof(mock_extend_cell_parse_cell_out)); + mock_extend_cell_parse_result = 0; + mock_extend_cell_parse_calls = 0; + + tt_int_op(circuit_extend(cell, circ), OP_EQ, -1); + tt_int_op(mock_extend_cell_parse_calls, OP_EQ, 1); + expect_log_msg( + "Client asked me to extend without specifying an id_digest.\n"); + mock_clean_saved_logs(); + mock_extend_cell_parse_calls = 0; + + /* Now add a node_id. Fail the lspec check because IPv4 and port are zero. */ + memset(&mock_extend_cell_parse_cell_out.node_id, 0xAA, + sizeof(mock_extend_cell_parse_cell_out.node_id)); + + tt_int_op(circuit_extend(cell, circ), OP_EQ, -1); + tt_int_op(mock_extend_cell_parse_calls, OP_EQ, 1); + expect_log_msg("Client asked me to extend to " + "zero destination port or addr.\n"); + mock_clean_saved_logs(); + mock_extend_cell_parse_calls = 0; + + /* Now add a valid IPv4 and port. Fail the OR circuit magic check. */ + tor_addr_parse(&mock_extend_cell_parse_cell_out.orport_ipv4.addr, + PUBLIC_IPV4); + mock_extend_cell_parse_cell_out.orport_ipv4.port = VALID_PORT; + + tor_capture_bugs_(1); + tt_int_op(circuit_extend(cell, circ), OP_EQ, -1); + tt_int_op(mock_extend_cell_parse_calls, OP_EQ, 1); + tt_int_op(smartlist_len(tor_get_captured_bug_log_()), OP_EQ, 1); + tt_str_op(smartlist_get(tor_get_captured_bug_log_(), 0), OP_EQ, + "!(ASSERT_PREDICT_UNLIKELY_(circ->magic != 0x98ABC04Fu))"); + tor_end_capture_bugs_(); + mock_clean_saved_logs(); + mock_extend_cell_parse_calls = 0; + + /* Now add the right magic and a p_chan. */ + or_circ->base_.magic = OR_CIRCUIT_MAGIC; + or_circ->p_chan = p_chan; + + /* Mock channel_get_for_extend(), so it doesn't crash. */ + mock_channel_get_for_extend_calls = 0; + MOCK(channel_get_for_extend, mock_channel_get_for_extend); + + /* Test circuit not established, but don't launch another one */ + mock_channel_get_for_extend_launch_out = 0; + mock_channel_get_for_extend_nchan = NULL; + tt_int_op(circuit_extend(cell, circ), OP_EQ, 0); + tt_int_op(mock_extend_cell_parse_calls, OP_EQ, 1); + tt_int_op(mock_channel_get_for_extend_calls, OP_EQ, 1); + + /* cleanup */ + mock_clean_saved_logs(); + mock_extend_cell_parse_calls = 0; + mock_channel_get_for_extend_calls = 0; + /* circ and or_circ are the same object */ + tor_free(circ->n_hop); + tor_free(circ->n_chan_create_cell); + + /* Mock channel_connect_for_circuit(), so we don't crash */ + mock_channel_connect_calls = 0; + MOCK(channel_connect_for_circuit, mock_channel_connect_for_circuit); + + /* Test circuit not established, and successful launch of a channel */ + mock_channel_get_for_extend_launch_out = 1; + mock_channel_get_for_extend_nchan = NULL; + mock_channel_connect_nchan = fake_n_chan; + tt_int_op(circuit_extend(cell, circ), OP_EQ, 0); + tt_int_op(mock_extend_cell_parse_calls, OP_EQ, 1); + tt_int_op(mock_channel_get_for_extend_calls, OP_EQ, 1); + tt_int_op(mock_channel_connect_calls, OP_EQ, 1); + + /* cleanup */ + mock_clean_saved_logs(); + mock_extend_cell_parse_calls = 0; + mock_channel_get_for_extend_calls = 0; + mock_channel_connect_calls = 0; + /* circ and or_circ are the same object */ + tor_free(circ->n_hop); + tor_free(circ->n_chan_create_cell); + + /* Mock circuit_deliver_create_cell(), so it doesn't crash */ + mock_circuit_deliver_create_cell_calls = 0; + MOCK(circuit_deliver_create_cell, mock_circuit_deliver_create_cell); + + /* Test circuit established, re-using channel, successful delivery */ + mock_channel_get_for_extend_launch_out = 0; + mock_channel_get_for_extend_nchan = fake_n_chan; + mock_channel_connect_nchan = NULL; + mock_circuit_deliver_create_cell_result = 0; + tt_int_op(circuit_extend(cell, circ), OP_EQ, 0); + tt_int_op(mock_extend_cell_parse_calls, OP_EQ, 1); + tt_int_op(mock_channel_get_for_extend_calls, OP_EQ, 1); + tt_int_op(mock_channel_connect_calls, OP_EQ, 0); + tt_int_op(mock_circuit_deliver_create_cell_calls, OP_EQ, 1); + tt_ptr_op(circ->n_chan, OP_EQ, fake_n_chan); + + /* cleanup */ + circ->n_chan = NULL; + mock_clean_saved_logs(); + mock_extend_cell_parse_calls = 0; + mock_channel_get_for_extend_calls = 0; + mock_channel_connect_calls = 0; + mock_circuit_deliver_create_cell_calls = 0; + /* circ and or_circ are the same object */ + tor_free(circ->n_hop); + tor_free(circ->n_chan_create_cell); + + /* Test circuit established, re-using channel, failed delivery */ + mock_channel_get_for_extend_launch_out = 0; + mock_channel_get_for_extend_nchan = fake_n_chan; + mock_channel_connect_nchan = NULL; + mock_circuit_deliver_create_cell_result = -1; + tt_int_op(circuit_extend(cell, circ), OP_EQ, -1); + tt_int_op(mock_extend_cell_parse_calls, OP_EQ, 1); + tt_int_op(mock_channel_get_for_extend_calls, OP_EQ, 1); + tt_int_op(mock_channel_connect_calls, OP_EQ, 0); + tt_int_op(mock_circuit_deliver_create_cell_calls, OP_EQ, 1); + tt_ptr_op(circ->n_chan, OP_EQ, fake_n_chan); + + /* cleanup */ + circ->n_chan = NULL; + mock_clean_saved_logs(); + mock_extend_cell_parse_calls = 0; + mock_channel_get_for_extend_calls = 0; + mock_channel_connect_calls = 0; + mock_circuit_deliver_create_cell_calls = 0; + /* circ and or_circ are the same object */ + tor_free(circ->n_hop); + tor_free(circ->n_chan_create_cell); + + done: + tor_end_capture_bugs_(); + teardown_capture_of_logs(); + + UNMOCK(server_mode); + server = 0; + + UNMOCK(channel_get_canonical_remote_descr); + + UNMOCK(extend_cell_parse); + memset(&mock_extend_cell_parse_cell_out, 0, + sizeof(mock_extend_cell_parse_cell_out)); + mock_extend_cell_parse_result = 0; + mock_extend_cell_parse_calls = 0; + + UNMOCK(channel_get_for_extend); + mock_channel_get_for_extend_calls = 0; + mock_channel_get_for_extend_launch_out = 0; + mock_channel_get_for_extend_nchan = NULL; + + UNMOCK(channel_connect_for_circuit); + mock_channel_connect_calls = 0; + mock_channel_connect_nchan = NULL; + + UNMOCK(circuit_deliver_create_cell); + mock_circuit_deliver_create_cell_calls = 0; + mock_circuit_deliver_create_cell_result = 0; + + tor_free(cell); + /* circ and or_circ are the same object */ + tor_free(circ->n_hop); + tor_free(circ->n_chan_create_cell); + tor_free(or_circ); + tor_free(p_chan); + tor_free(fake_n_chan); +} + +/* Test the different cases in onionskin_answer(). */ +static void +test_onionskin_answer(void *arg) +{ + (void)arg; + created_cell_t *created_cell = tor_malloc_zero(sizeof(created_cell_t)); + or_circuit_t *or_circ = tor_malloc_zero(sizeof(or_circuit_t)); + char keys[CPATH_KEY_MATERIAL_LEN] = {0}; + uint8_t rend_circ_nonce[DIGEST_LEN] = {0}; + + setup_full_capture_of_logs(LOG_INFO); + + /* Circuit must be non-NULL */ + tor_capture_bugs_(1); + tt_int_op(onionskin_answer(NULL, created_cell, + keys, CPATH_KEY_MATERIAL_LEN, + rend_circ_nonce), OP_EQ, -1); + tt_int_op(smartlist_len(tor_get_captured_bug_log_()), OP_EQ, 1); + tt_str_op(smartlist_get(tor_get_captured_bug_log_(), 0), OP_EQ, + "!(ASSERT_PREDICT_UNLIKELY_(!circ))"); + tor_end_capture_bugs_(); + mock_clean_saved_logs(); + + /* Created cell must be non-NULL */ + tor_capture_bugs_(1); + tt_int_op(onionskin_answer(or_circ, NULL, + keys, CPATH_KEY_MATERIAL_LEN, + rend_circ_nonce), OP_EQ, -1); + tt_int_op(smartlist_len(tor_get_captured_bug_log_()), OP_EQ, 1); + tt_str_op(smartlist_get(tor_get_captured_bug_log_(), 0), OP_EQ, + "!(ASSERT_PREDICT_UNLIKELY_(!created_cell))"); + tor_end_capture_bugs_(); + mock_clean_saved_logs(); + + /* Keys must be non-NULL */ + tor_capture_bugs_(1); + tt_int_op(onionskin_answer(or_circ, created_cell, + NULL, CPATH_KEY_MATERIAL_LEN, + rend_circ_nonce), OP_EQ, -1); + tt_int_op(smartlist_len(tor_get_captured_bug_log_()), OP_EQ, 1); + tt_str_op(smartlist_get(tor_get_captured_bug_log_(), 0), OP_EQ, + "!(ASSERT_PREDICT_UNLIKELY_(!keys))"); + tor_end_capture_bugs_(); + mock_clean_saved_logs(); + + /* The rend circuit nonce must be non-NULL */ + tor_capture_bugs_(1); + tt_int_op(onionskin_answer(or_circ, created_cell, + keys, CPATH_KEY_MATERIAL_LEN, + NULL), OP_EQ, -1); + tt_int_op(smartlist_len(tor_get_captured_bug_log_()), OP_EQ, 1); + tt_str_op(smartlist_get(tor_get_captured_bug_log_(), 0), OP_EQ, + "!(ASSERT_PREDICT_UNLIKELY_(!rend_circ_nonce))"); + tor_end_capture_bugs_(); + mock_clean_saved_logs(); + + /* Also, the keys length must be CPATH_KEY_MATERIAL_LEN, but we can't catch + * asserts in unit tests. */ + + /* Fail when formatting the created cell */ + tt_int_op(onionskin_answer(or_circ, created_cell, + keys, CPATH_KEY_MATERIAL_LEN, + rend_circ_nonce), OP_EQ, -1); + expect_log_msg("couldn't format created cell (type=0, len=0).\n"); + mock_clean_saved_logs(); + + /* TODO: test the rest of onionskin_answer(), see #33860 */ + /* TODO: mock created_cell_format for the next test */ + + done: + tor_end_capture_bugs_(); + teardown_capture_of_logs(); + + tor_free(created_cell); + tor_free(or_circ); +} + +#define TEST(name, flags, setup, cleanup) \ + { #name, test_ ## name, flags, setup, cleanup } + +#define TEST_NEW_ROUTE_LEN(name, flags) \ + { #name, test_new_route_len_ ## name, flags, NULL, NULL } + +#define TEST_CIRCUIT(name, flags) \ + { #name, test_circuit_ ## name, flags, NULL, NULL } + struct testcase_t circuitbuild_tests[] = { - { "noexit", test_new_route_len_noexit, 0, NULL, NULL }, - { "safe_exit", test_new_route_len_safe_exit, 0, NULL, NULL }, - { "unsafe_exit", test_new_route_len_unsafe_exit, 0, NULL, NULL }, - { "unhandled_exit", test_new_route_len_unhandled_exit, 0, NULL, NULL }, - { "upgrade_from_guard_wait", test_upgrade_from_guard_wait, TT_FORK, - &helper_pubsub_setup, NULL }, + TEST_NEW_ROUTE_LEN(noexit, 0), + TEST_NEW_ROUTE_LEN(safe_exit, 0), + TEST_NEW_ROUTE_LEN(unsafe_exit, 0), + TEST_NEW_ROUTE_LEN(unhandled_exit, 0), + + TEST(upgrade_from_guard_wait, TT_FORK, &helper_pubsub_setup, NULL), + + TEST_CIRCUIT(extend_state_valid, TT_FORK), + TEST_CIRCUIT(extend_add_ed25519, TT_FORK), + TEST_CIRCUIT(extend_lspec_valid, TT_FORK), + TEST_CIRCUIT(open_connection_for_extend, TT_FORK), + TEST_CIRCUIT(extend, TT_FORK), + + TEST(onionskin_answer, TT_FORK, NULL, NULL), + END_OF_TESTCASES }; |