From 861337fd6d286329537e07f667602992b82e921f Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 20 Dec 2019 14:30:51 +1000 Subject: [PATCH 1/6] router: Stop advertising incorrect auto IPv6 ORPorts When IPv6 ORPorts are set to "auto", tor relays and bridges would advertise an incorrect port in their descriptor. This may be a low-severity memory safety issue, because the published port number may be derived from uninitialised or out-of-bounds memory reads. Fixes bug 32588; bugfix on 0.2.3.9-alpha. --- changes/bug32588 | 4 ++ src/feature/relay/router.c | 76 ++++++++++++++++++++++++-------------- src/feature/relay/router.h | 2 + 3 files changed, 54 insertions(+), 28 deletions(-) create mode 100644 changes/bug32588 diff --git a/changes/bug32588 b/changes/bug32588 new file mode 100644 index 0000000000..f31f2ce1ad --- /dev/null +++ b/changes/bug32588 @@ -0,0 +1,4 @@ + o Minor bugfixes (relays): + - Stop advertising incorrect IPv6 ORPorts in relay and bridge descriptors, + when the IPv6 port was configured as "auto". + Fixes bug 32588; bugfix on 0.2.3.9-alpha diff --git a/src/feature/relay/router.c b/src/feature/relay/router.c index e91550a78c..760daa249e 100644 --- a/src/feature/relay/router.c +++ b/src/feature/relay/router.c @@ -1433,6 +1433,49 @@ router_get_advertised_or_port_by_af(const or_options_t *options, return port; } +/** As router_get_advertised_or_port(), but returns the IPv6 address and + * port in ipv6_ap_out, which must not be NULL. Returns a null address and + * zero port, if no ORPort is found. */ +void +router_get_advertised_ipv6_or_ap(const or_options_t *options, + tor_addr_port_t *ipv6_ap_out) +{ + /* Bug in calling function, we can't return a sensible result, and it + * shouldn't use the NULL pointer once we return. */ + tor_assert(ipv6_ap_out); + + /* If there is no valid IPv6 ORPort, return a null address and port. */ + tor_addr_make_null(&ipv6_ap_out->addr, AF_INET6); + ipv6_ap_out->port = 0; + + const tor_addr_t *addr = get_first_advertised_addr_by_type_af( + CONN_TYPE_OR_LISTENER, + AF_INET6); + const uint16_t port = router_get_advertised_or_port_by_af( + options, + AF_INET6); + + if (!addr || port == 0) { + log_info(LD_CONFIG, "There is no advertised IPv6 ORPort."); + return; + } + + /* Like IPv4, if the relay is configured using the default + * authorities, disallow internal IPs. Otherwise, allow them. */ + const int default_auth = using_default_dir_authorities(options); + if (! tor_addr_is_internal(addr, 0) || ! default_auth) { + tor_addr_copy(&ipv6_ap_out->addr, addr); + ipv6_ap_out->port = port; + } else { + char addrbuf[TOR_ADDR_BUF_LEN]; + log_warn(LD_CONFIG, + "Unable to use configured IPv6 address \"%s\" in a " + "descriptor. Skipping it. " + "Try specifying a globally reachable address explicitly.", + tor_addr_to_str(addrbuf, addr, sizeof(addrbuf), 1)); + } +} + /** Return the port that we should advertise as our DirPort; * this is one of three possibilities: * The one that is passed as dirport if the DirPort option is 0, or @@ -1848,34 +1891,11 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e) sizeof(curve25519_public_key_t)); /* For now, at most one IPv6 or-address is being advertised. */ - { - const port_cfg_t *ipv6_orport = NULL; - SMARTLIST_FOREACH_BEGIN(get_configured_ports(), const port_cfg_t *, p) { - if (p->type == CONN_TYPE_OR_LISTENER && - ! p->server_cfg.no_advertise && - ! p->server_cfg.bind_ipv4_only && - tor_addr_family(&p->addr) == AF_INET6) { - /* Like IPv4, if the relay is configured using the default - * authorities, disallow internal IPs. Otherwise, allow them. */ - const int default_auth = using_default_dir_authorities(options); - if (! tor_addr_is_internal(&p->addr, 0) || ! default_auth) { - ipv6_orport = p; - break; - } else { - char addrbuf[TOR_ADDR_BUF_LEN]; - log_warn(LD_CONFIG, - "Unable to use configured IPv6 address \"%s\" in a " - "descriptor. Skipping it. " - "Try specifying a globally reachable address explicitly.", - tor_addr_to_str(addrbuf, &p->addr, sizeof(addrbuf), 1)); - } - } - } SMARTLIST_FOREACH_END(p); - if (ipv6_orport) { - tor_addr_copy(&ri->ipv6_addr, &ipv6_orport->addr); - ri->ipv6_orport = ipv6_orport->port; - } - } + tor_addr_port_t ipv6_orport; + router_get_advertised_ipv6_or_ap(options, &ipv6_orport); + /* If there is no valud IPv6 ORPort, the address and port are null. */ + tor_addr_copy(&ri->ipv6_addr, &ipv6_orport.addr); + ri->ipv6_orport = ipv6_orport.port; ri->identity_pkey = crypto_pk_dup_key(get_server_identity_key()); if (BUG(crypto_pk_get_digest(ri->identity_pkey, diff --git a/src/feature/relay/router.h b/src/feature/relay/router.h index bd6a8a012e..ab1f771017 100644 --- a/src/feature/relay/router.h +++ b/src/feature/relay/router.h @@ -59,6 +59,8 @@ int init_keys_client(void); uint16_t router_get_active_listener_port_by_type_af(int listener_type, sa_family_t family); uint16_t router_get_advertised_or_port(const or_options_t *options); +void router_get_advertised_ipv6_or_ap(const or_options_t *options, + tor_addr_port_t *ipv6_ap_out); uint16_t router_get_advertised_or_port_by_af(const or_options_t *options, sa_family_t family); uint16_t router_get_advertised_dir_port(const or_options_t *options, From bac8bc0ff11d5b4502f2e71138d23f71fc25e72a Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 8 Jan 2020 17:34:37 +1000 Subject: [PATCH 2/6] router: Refactor IPv6 ORPort function logic Return early when there is no suitable IPv6 ORPort. Show the address and port on error, using a convenience function. Code simplification and refactoring. Cleanup after 32588. --- src/feature/relay/router.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/feature/relay/router.c b/src/feature/relay/router.c index 760daa249e..283f7c326b 100644 --- a/src/feature/relay/router.c +++ b/src/feature/relay/router.c @@ -1460,20 +1460,21 @@ router_get_advertised_ipv6_or_ap(const or_options_t *options, return; } - /* Like IPv4, if the relay is configured using the default - * authorities, disallow internal IPs. Otherwise, allow them. */ + /* If the relay is configured using the default authorities, disallow + * internal IPs. Otherwise, allow them. For IPv4 ORPorts and DirPorts, + * this check is done in resolve_my_address(). See #33681. */ const int default_auth = using_default_dir_authorities(options); - if (! tor_addr_is_internal(addr, 0) || ! default_auth) { - tor_addr_copy(&ipv6_ap_out->addr, addr); - ipv6_ap_out->port = port; - } else { - char addrbuf[TOR_ADDR_BUF_LEN]; + if (tor_addr_is_internal(addr, 0) && default_auth) { log_warn(LD_CONFIG, - "Unable to use configured IPv6 address \"%s\" in a " + "Unable to use configured IPv6 ORPort \"%s\" in a " "descriptor. Skipping it. " "Try specifying a globally reachable address explicitly.", - tor_addr_to_str(addrbuf, addr, sizeof(addrbuf), 1)); + fmt_addrport(addr, port)); + return; } + + tor_addr_copy(&ipv6_ap_out->addr, addr); + ipv6_ap_out->port = port; } /** Return the port that we should advertise as our DirPort; From 6ffe073db741afcee90d078a30c680b87b7ad327 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Thu, 19 Mar 2020 09:17:09 -0400 Subject: [PATCH 3/6] Add tests for get_first_advertised_{addr,port}_by_type_af() --- src/app/config/config.c | 5 +--- src/app/config/config.h | 4 +++ src/test/test_config.c | 66 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 4 deletions(-) diff --git a/src/app/config/config.c b/src/app/config/config.c index cbca7d3899..aa5c92e4e1 100644 --- a/src/app/config/config.c +++ b/src/app/config/config.c @@ -809,9 +809,6 @@ static int normalize_nickname_list(config_line_t **normalized_out, char **msg); static char *get_bindaddr_from_transport_listen_line(const char *line, const char *transport); -static int parse_ports(or_options_t *options, int validate_only, - char **msg_out, int *n_ports_out, - int *world_writable_control_socket); static int check_server_ports(const smartlist_t *ports, const or_options_t *options, int *num_low_ports_out); @@ -7370,7 +7367,7 @@ count_real_listeners(const smartlist_t *ports, int listenertype, * If validate_only is false, set configured_client_ports to the * new list of ports parsed from options. **/ -static int +STATIC int parse_ports(or_options_t *options, int validate_only, char **msg, int *n_ports_out, int *world_writable_control_socket) diff --git a/src/app/config/config.h b/src/app/config/config.h index 301faf7067..6852d352dc 100644 --- a/src/app/config/config.h +++ b/src/app/config/config.h @@ -295,6 +295,10 @@ STATIC int open_and_add_file_log(const log_severity_list_t *severity, const char *fname, int truncate_log); +STATIC int parse_ports(or_options_t *options, int validate_only, + char **msg, int *n_ports_out, + int *world_writable_control_socket); + #endif /* defined(CONFIG_PRIVATE) */ #endif /* !defined(TOR_CONFIG_H) */ diff --git a/src/test/test_config.c b/src/test/test_config.c index 855725411a..d648666f6e 100644 --- a/src/test/test_config.c +++ b/src/test/test_config.c @@ -4860,6 +4860,71 @@ test_config_parse_port_config__ports__server_options(void *data) config_free_lines(config_port_valid); config_port_valid = NULL; } +static void +test_config_get_first_advertised(void *data) +{ + (void)data; + int r, w=0, n=0; + char *msg=NULL; + or_options_t *opts = options_new(); + int port; + const tor_addr_t *addr; + + // no ports are configured? We get NULL. + port = get_first_advertised_port_by_type_af(CONN_TYPE_OR_LISTENER, + AF_INET); + tt_int_op(port, OP_EQ, 0); + addr = get_first_advertised_addr_by_type_af(CONN_TYPE_OR_LISTENER, + AF_INET); + tt_ptr_op(addr, OP_EQ, NULL); + + port = get_first_advertised_port_by_type_af(CONN_TYPE_OR_LISTENER, + AF_INET6); + tt_int_op(port, OP_EQ, 0); + addr = get_first_advertised_addr_by_type_af(CONN_TYPE_OR_LISTENER, + AF_INET6); + tt_ptr_op(addr, OP_EQ, NULL); + + config_line_append(&opts->ORPort_lines, "ORPort", "[1234::5678]:8080"); + config_line_append(&opts->ORPort_lines, "ORPort", + "1.2.3.4:9999 noadvertise"); + config_line_append(&opts->ORPort_lines, "ORPort", + "5.6.7.8:9911 nolisten"); + + r = parse_ports(opts, 0, &msg, &n, &w); + tt_assert(r == 0); + + // UNSPEC gets us nothing. + port = get_first_advertised_port_by_type_af(CONN_TYPE_OR_LISTENER, + AF_UNSPEC); + tt_int_op(port, OP_EQ, 0); + addr = get_first_advertised_addr_by_type_af(CONN_TYPE_OR_LISTENER, + AF_UNSPEC); + tt_ptr_op(addr, OP_EQ, NULL); + + // Try AF_INET. + port = get_first_advertised_port_by_type_af(CONN_TYPE_OR_LISTENER, + AF_INET); + tt_int_op(port, OP_EQ, 9911); + addr = get_first_advertised_addr_by_type_af(CONN_TYPE_OR_LISTENER, + AF_INET); + tt_ptr_op(addr, OP_NE, NULL); + tt_str_op(fmt_addrport(addr,port), OP_EQ, "5.6.7.8:9911"); + + // Try AF_INET6 + port = get_first_advertised_port_by_type_af(CONN_TYPE_OR_LISTENER, + AF_INET6); + tt_int_op(port, OP_EQ, 8080); + addr = get_first_advertised_addr_by_type_af(CONN_TYPE_OR_LISTENER, + AF_INET6); + tt_ptr_op(addr, OP_NE, NULL); + tt_str_op(fmt_addrport(addr,port), OP_EQ, "[1234::5678]:8080"); + + done: + or_options_free(opts); + config_free_all(); +} + static void test_config_parse_log_severity(void *data) { @@ -5920,6 +5985,7 @@ struct testcase_t config_tests[] = { CONFIG_TEST(parse_port_config__ports__no_ports_given, 0), CONFIG_TEST(parse_port_config__ports__server_options, 0), CONFIG_TEST(parse_port_config__ports__ports_given, 0), + CONFIG_TEST(get_first_advertised, TT_FORK), CONFIG_TEST(parse_log_severity, 0), CONFIG_TEST(include_limit, 0), CONFIG_TEST(include_does_not_exist, 0), From 1ba79d4567c95f75daf171878faaa07b78d89130 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Thu, 19 Mar 2020 09:36:47 -0400 Subject: [PATCH 4/6] Add a test for router_get_advertised_or_port_by_af(). --- src/test/test_router.c | 54 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/test/test_router.c b/src/test/test_router.c index 601881a124..5699d5472b 100644 --- a/src/test/test_router.c +++ b/src/test/test_router.c @@ -7,9 +7,12 @@ * \brief Unittests for code in router.c **/ +#define CONFIG_PRIVATE +#define CONNECTION_PRIVATE #include "core/or/or.h" #include "app/config/config.h" #include "core/mainloop/mainloop.h" +#include "core/mainloop/connection.h" #include "feature/hibernate/hibernate.h" #include "feature/nodelist/routerinfo_st.h" #include "feature/nodelist/routerlist.h" @@ -17,6 +20,9 @@ #include "feature/stats/rephist.h" #include "lib/crypt_ops/crypto_curve25519.h" #include "lib/crypt_ops/crypto_ed25519.h" +#include "lib/encoding/confline.h" + +#include "core/or/listener_connection_st.h" /* Test suite stuff */ #include "test/test.h" @@ -231,11 +237,59 @@ test_router_check_descriptor_bandwidth_changed(void *arg) UNMOCK(we_are_hibernating); } +static smartlist_t *fake_connection_array = NULL; +static smartlist_t * +mock_get_connection_array(void) +{ + return fake_connection_array; +} + +static void +test_router_get_advertised_or_port(void *arg) +{ + (void)arg; + // uint16_t port; + int r, w=0, n=0; + char *msg=NULL; + or_options_t *opts = options_new(); + listener_connection_t *listener = NULL; + + // Set up a couple of configured ports. + config_line_append(&opts->ORPort_lines, "ORPort", "[1234::5678]:9999"); + config_line_append(&opts->ORPort_lines, "ORPort", "5.6.7.8:auto"); + r = parse_ports(opts, 0, &msg, &n, &w); + tt_assert(r == 0); + + // There are no listeners, so the "auto" case will turn up no results. + tt_int_op(0, OP_EQ, router_get_advertised_or_port_by_af(opts, AF_INET)); + + // This will return the matching value from the configured port. + tt_int_op(9999, OP_EQ, router_get_advertised_or_port_by_af(opts, AF_INET6)); + + // Now set up a dummy listener. + MOCK(get_connection_array, mock_get_connection_array); + fake_connection_array = smartlist_new(); + listener = listener_connection_new(CONN_TYPE_OR_LISTENER, AF_INET); + TO_CONN(listener)->port = 54321; + smartlist_add(fake_connection_array, TO_CONN(listener)); + + // We should get a port this time. + tt_int_op(54321, OP_EQ, router_get_advertised_or_port_by_af(opts, AF_INET)); + + done: + or_options_free(opts); + config_free_all(); + smartlist_free(fake_connection_array); + connection_free_minimal(TO_CONN(listener)); + UNMOCK(get_connection_array); +} + #define ROUTER_TEST(name, flags) \ { #name, test_router_ ## name, flags, NULL, NULL } struct testcase_t router_tests[] = { ROUTER_TEST(check_descriptor_bandwidth_changed, TT_FORK), ROUTER_TEST(dump_router_to_string_no_bridge_distribution_method, TT_FORK), + ROUTER_TEST(get_advertised_or_port, TT_FORK), END_OF_TESTCASES }; From 1251265a0f49c7446f3e00854eda8091e84d9e96 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Thu, 19 Mar 2020 09:44:27 -0400 Subject: [PATCH 5/6] Extend test to handle router_get_advertised_ipv6_or_ap --- src/test/test_router.c | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/test/test_router.c b/src/test/test_router.c index 5699d5472b..8c772c6817 100644 --- a/src/test/test_router.c +++ b/src/test/test_router.c @@ -253,28 +253,49 @@ test_router_get_advertised_or_port(void *arg) char *msg=NULL; or_options_t *opts = options_new(); listener_connection_t *listener = NULL; + tor_addr_port_t ipv6; + + // Test one failing case of router_get_advertised_ipv6_or_ap(). + router_get_advertised_ipv6_or_ap(opts, &ipv6); + tt_str_op(fmt_addrport(&ipv6.addr, ipv6.port), OP_EQ, "[::]:0"); + + // And one failing case of router_get_advertised_or_port(). + tt_int_op(0, OP_EQ, router_get_advertised_or_port_by_af(opts, AF_INET)); + tt_int_op(0, OP_EQ, router_get_advertised_or_port(opts)); // Set up a couple of configured ports. - config_line_append(&opts->ORPort_lines, "ORPort", "[1234::5678]:9999"); - config_line_append(&opts->ORPort_lines, "ORPort", "5.6.7.8:auto"); + config_line_append(&opts->ORPort_lines, "ORPort", "[1234::5678]:auto"); + config_line_append(&opts->ORPort_lines, "ORPort", "5.6.7.8:9999"); r = parse_ports(opts, 0, &msg, &n, &w); tt_assert(r == 0); // There are no listeners, so the "auto" case will turn up no results. - tt_int_op(0, OP_EQ, router_get_advertised_or_port_by_af(opts, AF_INET)); + tt_int_op(0, OP_EQ, router_get_advertised_or_port_by_af(opts, AF_INET6)); + router_get_advertised_ipv6_or_ap(opts, &ipv6); + tt_str_op(fmt_addrport(&ipv6.addr, ipv6.port), OP_EQ, "[::]:0"); // This will return the matching value from the configured port. - tt_int_op(9999, OP_EQ, router_get_advertised_or_port_by_af(opts, AF_INET6)); + tt_int_op(9999, OP_EQ, router_get_advertised_or_port_by_af(opts, AF_INET)); + tt_int_op(9999, OP_EQ, router_get_advertised_or_port(opts)); // Now set up a dummy listener. MOCK(get_connection_array, mock_get_connection_array); fake_connection_array = smartlist_new(); - listener = listener_connection_new(CONN_TYPE_OR_LISTENER, AF_INET); + listener = listener_connection_new(CONN_TYPE_OR_LISTENER, AF_INET6); TO_CONN(listener)->port = 54321; smartlist_add(fake_connection_array, TO_CONN(listener)); // We should get a port this time. - tt_int_op(54321, OP_EQ, router_get_advertised_or_port_by_af(opts, AF_INET)); + tt_int_op(54321, OP_EQ, router_get_advertised_or_port_by_af(opts, AF_INET6)); + + // Test one succeeding case of router_get_advertised_ipv6_or_ap(). + router_get_advertised_ipv6_or_ap(opts, &ipv6); + tt_str_op(fmt_addrport(&ipv6.addr, ipv6.port), OP_EQ, + "[1234::5678]:54321"); + + // This will return the matching value from the configured port. + tt_int_op(9999, OP_EQ, router_get_advertised_or_port_by_af(opts, AF_INET)); + tt_int_op(9999, OP_EQ, router_get_advertised_or_port(opts)); done: or_options_free(opts); From 96ca14d98924fec8a18a592c491900a9142f6e71 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Thu, 19 Mar 2020 09:50:36 -0400 Subject: [PATCH 6/6] Add a test for the localhost case. --- src/test/test_router.c | 46 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/test/test_router.c b/src/test/test_router.c index 8c772c6817..e0d3adfdbd 100644 --- a/src/test/test_router.c +++ b/src/test/test_router.c @@ -248,7 +248,6 @@ static void test_router_get_advertised_or_port(void *arg) { (void)arg; - // uint16_t port; int r, w=0, n=0; char *msg=NULL; or_options_t *opts = options_new(); @@ -305,6 +304,50 @@ test_router_get_advertised_or_port(void *arg) UNMOCK(get_connection_array); } +static void +test_router_get_advertised_or_port_localhost(void *arg) +{ + (void)arg; + int r, w=0, n=0; + char *msg=NULL; + or_options_t *opts = options_new(); + tor_addr_port_t ipv6; + + // Set up a couple of configured ports on localhost. + config_line_append(&opts->ORPort_lines, "ORPort", "[::1]:9999"); + config_line_append(&opts->ORPort_lines, "ORPort", "127.0.0.1:8888"); + r = parse_ports(opts, 0, &msg, &n, &w); + tt_assert(r == 0); + + // We should refuse to advertise them, since we have default dirauths. + router_get_advertised_ipv6_or_ap(opts, &ipv6); + tt_str_op(fmt_addrport(&ipv6.addr, ipv6.port), OP_EQ, "[::]:0"); + // But the lower-level function should still report the correct value + tt_int_op(9999, OP_EQ, router_get_advertised_or_port_by_af(opts, AF_INET6)); + + // The IPv4 checks are done in resolve_my_address(), which doesn't use + // ORPorts so we can't test them here. (See #33681.) Both these lower-level + // functions should still report the correct value. + tt_int_op(8888, OP_EQ, router_get_advertised_or_port_by_af(opts, AF_INET)); + tt_int_op(8888, OP_EQ, router_get_advertised_or_port(opts)); + + // Now try with a fake authority set up. + config_line_append(&opts->DirAuthorities, "DirAuthority", + "127.0.0.1:1066 " + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); + + tt_int_op(9999, OP_EQ, router_get_advertised_or_port_by_af(opts, AF_INET6)); + router_get_advertised_ipv6_or_ap(opts, &ipv6); + tt_str_op(fmt_addrport(&ipv6.addr, ipv6.port), OP_EQ, "[::1]:9999"); + + tt_int_op(8888, OP_EQ, router_get_advertised_or_port_by_af(opts, AF_INET)); + tt_int_op(8888, OP_EQ, router_get_advertised_or_port(opts)); + + done: + or_options_free(opts); + config_free_all(); +} + #define ROUTER_TEST(name, flags) \ { #name, test_router_ ## name, flags, NULL, NULL } @@ -312,5 +355,6 @@ struct testcase_t router_tests[] = { ROUTER_TEST(check_descriptor_bandwidth_changed, TT_FORK), ROUTER_TEST(dump_router_to_string_no_bridge_distribution_method, TT_FORK), ROUTER_TEST(get_advertised_or_port, TT_FORK), + ROUTER_TEST(get_advertised_or_port_localhost, TT_FORK), END_OF_TESTCASES };