From 31eb486c4624d1437d982ffdfc1f9d7d83c5ffd6 Mon Sep 17 00:00:00 2001 From: "teor (Tim Wilson-Brown)" Date: Tue, 15 Sep 2015 17:04:18 +1000 Subject: [PATCH] Add get_interface_address[6]_list for a list of interface IP addresses Add get_interface_address[6]_list by refactoring get_interface_address6. Add unit tests for new and existing functions. Preparation for ticket 17027. Patch by "teor". Patch on 42b8fb5a1523 (11 Nov 2007), released in 0.2.0.11-alpha. --- .../bug17027-reject-private-all-interfaces | 5 + src/common/address.c | 107 ++++++-- src/common/address.h | 28 +- src/test/test_address.c | 243 ++++++++++++++++-- 4 files changed, 341 insertions(+), 42 deletions(-) create mode 100644 changes/bug17027-reject-private-all-interfaces diff --git a/changes/bug17027-reject-private-all-interfaces b/changes/bug17027-reject-private-all-interfaces new file mode 100644 index 0000000000..2801642f8a --- /dev/null +++ b/changes/bug17027-reject-private-all-interfaces @@ -0,0 +1,5 @@ + o Minor bug fixes (security, exit policies): + - Add get_interface_address[6]_list by refactoring + get_interface_address6. Add unit tests for new and existing functions. + Preparation for ticket 17027. Patch by "teor". + Patch on 42b8fb5a1523 (11 Nov 2007), released in 0.2.0.11-alpha. diff --git a/src/common/address.c b/src/common/address.c index dd336257ef..0614256521 100644 --- a/src/common/address.c +++ b/src/common/address.c @@ -1503,7 +1503,7 @@ get_interface_addresses_raw(int severity) } /** Return true iff a is a multicast address. */ -static int +STATIC int tor_addr_is_multicast(const tor_addr_t *a) { sa_family_t family = tor_addr_family(a); @@ -1593,27 +1593,26 @@ get_interface_address6_via_udp_socket_hack(int severity, return r; } -/** Set *addr to the IP address (if any) of whatever interface - * connects to the Internet. This address should only be used in checking - * whether our address has changed. Return 0 on success, -1 on failure. +/** Set *addr to an arbitrary IP address (if any) of an interface that + * connects to the Internet. Prefer public IP addresses to internal IP + * addresses. This address should only be used in checking whether our + * address has changed, as it may be an internal IP address. Return 0 on + * success, -1 on failure. + * Prefer get_interface_address6_list for a list of all addresses on all + * interfaces which connect to the Internet. */ MOCK_IMPL(int, get_interface_address6,(int severity, sa_family_t family, tor_addr_t *addr)) { - /* XXX really, this function should yield a smartlist of addresses. */ smartlist_t *addrs; tor_assert(addr); - /* Try to do this the smart way if possible. */ - if ((addrs = get_interface_addresses_raw(severity))) { + /* Get a list of public or internal IPs in arbitrary order */ + if ((addrs = get_interface_address6_list(severity, family, 1))) { int rv = -1; + /* Find the first non-internal address, or the last internal address + * Ideally, we want the default route, see #12377 for details */ SMARTLIST_FOREACH_BEGIN(addrs, tor_addr_t *, a) { - if (family != AF_UNSPEC && family != tor_addr_family(a)) - continue; - if (tor_addr_is_loopback(a) || - tor_addr_is_multicast(a)) - continue; - tor_addr_copy(addr, a); rv = 0; @@ -1623,13 +1622,78 @@ get_interface_address6,(int severity, sa_family_t family, tor_addr_t *addr)) break; } SMARTLIST_FOREACH_END(a); - SMARTLIST_FOREACH(addrs, tor_addr_t *, a, tor_free(a)); - smartlist_free(addrs); + free_interface_address6_list(addrs); return rv; } + return -1; +} + +/** Free a smartlist of IP addresses returned by get_interface_address6_list. + */ +void free_interface_address6_list(smartlist_t *addrs) { + SMARTLIST_FOREACH(addrs, tor_addr_t *, a, tor_free(a)); + smartlist_free(addrs); +} + +/** Return a smartlist of the IP addresses of type family from all interfaces + * on the server. Excludes loopback and multicast addresses. Only includes + * internal addresses if include_internal is true. (Note that a relay behind + * NAT may use an internal address to connect to the Internet.) + * An empty smartlist means that there are no addresses of the selected type + * matching these criteria. + * Returns NULL on failure. + * Use free_interface_address6_list to free the returned list. + */ +MOCK_IMPL(smartlist_t *,get_interface_address6_list,(int severity, + sa_family_t family, + int include_internal)) +{ + smartlist_t *addrs; + tor_addr_t addr; + + /* Try to do this the smart way if possible. */ + if ((addrs = get_interface_addresses_raw(severity))) { + SMARTLIST_FOREACH_BEGIN(addrs, tor_addr_t *, a) { + if (family != AF_UNSPEC && family != tor_addr_family(a)){ + SMARTLIST_DEL_CURRENT(addrs, a); + tor_free(a); + continue; + } + + if (tor_addr_is_loopback(a) || + tor_addr_is_multicast(a)) { + SMARTLIST_DEL_CURRENT(addrs, a); + tor_free(a); + continue; + } + + if (!include_internal && tor_addr_is_internal(a, 0)){ + SMARTLIST_DEL_CURRENT(addrs, a); + tor_free(a); + continue; + } + } SMARTLIST_FOREACH_END(a); + } + + if (addrs && smartlist_len(addrs) > 0) { + return addrs; + } + + /* if we removed all entries as unsuitable */ + if (addrs) { + smartlist_free(addrs); + } + /* Okay, the smart way is out. */ - return get_interface_address6_via_udp_socket_hack(severity,family,addr); + get_interface_address6_via_udp_socket_hack(severity,family,&addr); + if (!include_internal && tor_addr_is_internal(&addr, 0)) { + return smartlist_new(); + } else { + addrs = smartlist_new(); + smartlist_add(addrs, tor_dup_addr(&addr)); + return addrs; + } } /* ====== @@ -1871,10 +1935,13 @@ tor_dup_ip(uint32_t addr) } /** - * Set *addr to the host-order IPv4 address (if any) of whatever - * interface connects to the Internet. This address should only be used in - * checking whether our address has changed. Return 0 on success, -1 on - * failure. + * Set *addr to a host-order IPv4 address (if any) of an + * interface that connects to the Internet. Prefer public IP addresses to + * internal IP addresses. This address should only be used in checking + * whether our address has changed, as it may be an internal IPv4 address. + * Return 0 on success, -1 on failure. + * Prefer get_interface_address_list6 for a list of all IPv4 and IPv6 + * addresses on all interfaces which connect to the Internet. */ MOCK_IMPL(int, get_interface_address,(int severity, uint32_t *addr)) diff --git a/src/common/address.h b/src/common/address.h index cd80615f93..c15dea8807 100644 --- a/src/common/address.h +++ b/src/common/address.h @@ -15,6 +15,7 @@ #include "orconfig.h" #include "torint.h" #include "compat.h" +#include "container.h" #ifdef ADDRESS_PRIVATE @@ -43,7 +44,6 @@ #endif // TODO win32 specific includes -#include "container.h" #endif // ADDRESS_PRIVATE /** The number of bits from an address to consider while doing a masked @@ -190,8 +190,13 @@ char *tor_dup_addr(const tor_addr_t *addr) ATTR_MALLOC; const char *fmt_addr_impl(const tor_addr_t *addr, int decorate); const char *fmt_addrport(const tor_addr_t *addr, uint16_t port); const char * fmt_addr32(uint32_t addr); + MOCK_DECL(int,get_interface_address6,(int severity, sa_family_t family, tor_addr_t *addr)); +void free_interface_address6_list(smartlist_t * addrs); +MOCK_DECL(smartlist_t *,get_interface_address6_list,(int severity, + sa_family_t family, + int include_internal)); /** Flag to specify how to do a comparison between addresses. In an "exact" * comparison, addresses are equivalent only if they are in the same family @@ -269,11 +274,32 @@ int addr_mask_get_bits(uint32_t mask); int tor_inet_ntoa(const struct in_addr *in, char *buf, size_t buf_len); char *tor_dup_ip(uint32_t addr) ATTR_MALLOC; MOCK_DECL(int,get_interface_address,(int severity, uint32_t *addr)); +/** Free a smartlist of IP addresses returned by get_interface_address_list. + */ +static INLINE void +free_interface_address_list(smartlist_t *addrs) +{ + free_interface_address6_list(addrs); +} +/** Return a smartlist of the IPv4 addresses of all interfaces on the server. + * Excludes loopback and multicast addresses. Only includes internal addresses + * if include_internal is true. (Note that a relay behind NAT may use an + * internal address to connect to the Internet.) + * An empty smartlist means that there are no IPv4 addresses. + * Returns NULL on failure. + * Use free_interface_address_list to free the returned list. + */ +static INLINE smartlist_t * +get_interface_address_list(int severity, int include_internal) +{ + return get_interface_address6_list(severity, AF_INET, include_internal); +} tor_addr_port_t *tor_addr_port_new(const tor_addr_t *addr, uint16_t port); #ifdef ADDRESS_PRIVATE STATIC smartlist_t *get_interface_addresses_raw(int severity); +STATIC int tor_addr_is_multicast(const tor_addr_t *a); STATIC int get_interface_address6_via_udp_socket_hack(int severity, sa_family_t family, tor_addr_t *addr); diff --git a/src/test/test_address.c b/src/test/test_address.c index 9d6456315c..5a4126799e 100644 --- a/src/test/test_address.c +++ b/src/test/test_address.c @@ -74,37 +74,82 @@ sockaddr_in_from_string(const char *ip_str, struct sockaddr_in *out) } /** Return 1 iff smartlist contains a tor_addr_t structure - * that points to 127.0.0.1. Otherwise, return 0. + * that is an IPv4 or IPv6 localhost address. Otherwise, return 0. */ static int smartlist_contains_localhost_tor_addr(smartlist_t *smartlist) { - int found_localhost = 0; - - struct sockaddr_in *sockaddr_localhost; - struct sockaddr_storage *sockaddr_to_check; - - sockaddr_localhost = sockaddr_in_from_string("127.0.0.1",NULL); - - sockaddr_to_check = tor_malloc(sizeof(struct sockaddr_in)); - SMARTLIST_FOREACH_BEGIN(smartlist, tor_addr_t *, tor_addr) { - tor_addr_to_sockaddr(tor_addr,0,(struct sockaddr *)sockaddr_to_check, - sizeof(struct sockaddr_in)); - - if (sockaddr_in_are_equal((struct sockaddr_in *)sockaddr_to_check, - sockaddr_localhost)) { - found_localhost = 1; - break; + if (tor_addr_is_loopback(tor_addr)) { + return 1; } } SMARTLIST_FOREACH_END(tor_addr); - tor_free(sockaddr_localhost); - tor_free(sockaddr_to_check); - - return found_localhost; + return 0; } +/** Return 1 iff smartlist contains a tor_addr_t structure + * that is an IPv4 or IPv6 multicast address. Otherwise, return 0. + */ +static int +smartlist_contains_multicast_tor_addr(smartlist_t *smartlist) +{ + SMARTLIST_FOREACH_BEGIN(smartlist, tor_addr_t *, tor_addr) { + if (tor_addr_is_multicast(tor_addr)) { + return 1; + } + } SMARTLIST_FOREACH_END(tor_addr); + + return 0; +} + +/** Return 1 iff smartlist contains a tor_addr_t structure + * that is an IPv4 or IPv6 internal address. Otherwise, return 0. + */ +static int +smartlist_contains_internal_tor_addr(smartlist_t *smartlist) +{ + SMARTLIST_FOREACH_BEGIN(smartlist, tor_addr_t *, tor_addr) { + if (tor_addr_is_internal(tor_addr, 0)) { + return 1; + } + } SMARTLIST_FOREACH_END(tor_addr); + + return 0; +} + +/** Return 1 iff smartlist contains a tor_addr_t structure + * that is an IPv4 address. Otherwise, return 0. + */ +static int +smartlist_contains_ipv4_tor_addr(smartlist_t *smartlist) +{ + SMARTLIST_FOREACH_BEGIN(smartlist, tor_addr_t *, tor_addr) { + if (tor_addr_is_v4(tor_addr)) { + return 1; + } + } SMARTLIST_FOREACH_END(tor_addr); + + return 0; +} + +/** Return 1 iff smartlist contains a tor_addr_t structure + * that is an IPv6 address. Otherwise, return 0. + */ +static int +smartlist_contains_ipv6_tor_addr(smartlist_t *smartlist) +{ + SMARTLIST_FOREACH_BEGIN(smartlist, tor_addr_t *, tor_addr) { + /* Since there's no tor_addr_is_v6, assume all non-v4s are v6 */ + if (!tor_addr_is_v4(tor_addr)) { + return 1; + } + } SMARTLIST_FOREACH_END(tor_addr); + + return 0; +} + + #ifdef HAVE_IFADDRS_TO_SMARTLIST static void test_address_ifaddrs_to_smartlist(void *arg) @@ -634,12 +679,168 @@ test_address_udp_socket_trick_blackbox(void *arg) return; } +static void +test_address_get_if_addrs_list_internal(void *arg) +{ + smartlist_t *results = NULL; + + (void)arg; + + results = get_interface_address_list(LOG_ERR, 1); + + tt_assert(results != NULL); + /* Assume every system has at least 1 non-local non-multicast IPv4 + * interface, even if it is an internal one */ + tt_int_op(smartlist_len(results),>=,1); + + tt_assert(!smartlist_contains_localhost_tor_addr(results)); + tt_assert(!smartlist_contains_multicast_tor_addr(results)); + /* The list may or may not contain internal addresses */ + + tt_assert(smartlist_contains_ipv4_tor_addr(results)); + tt_assert(!smartlist_contains_ipv6_tor_addr(results)); + +done: + free_interface_address_list(results); + return; +} + +static void +test_address_get_if_addrs_list_no_internal(void *arg) +{ + smartlist_t *results = NULL; + + (void)arg; + + results = get_interface_address_list(LOG_ERR, 0); + + tt_assert(results != NULL); + /* Work even on systems with only internal IPv4 addresses */ + tt_int_op(smartlist_len(results),>=,0); + + tt_assert(!smartlist_contains_localhost_tor_addr(results)); + tt_assert(!smartlist_contains_multicast_tor_addr(results)); + tt_assert(!smartlist_contains_internal_tor_addr(results)); + + /* The list may or may not contain IPv4 addresses */ + tt_assert(!smartlist_contains_ipv6_tor_addr(results)); + +done: + free_interface_address_list(results); + return; +} + +static void +test_address_get_if_addrs6_list_internal(void *arg) +{ + smartlist_t *results = NULL; + + (void)arg; + + results = get_interface_address6_list(LOG_ERR, AF_INET6, 1); + + tt_assert(results != NULL); + /* Work even on systems without IPv6 interfaces */ + tt_int_op(smartlist_len(results),>=,0); + + tt_assert(!smartlist_contains_localhost_tor_addr(results)); + tt_assert(!smartlist_contains_multicast_tor_addr(results)); + /* The list may or may not contain internal addresses */ + + tt_assert(!smartlist_contains_ipv4_tor_addr(results)); + /* The list may or may not contain IPv6 addresses */ + +done: + free_interface_address6_list(results); + return; +} + +static void +test_address_get_if_addrs6_list_no_internal(void *arg) +{ + smartlist_t *results = NULL; + + (void)arg; + + results = get_interface_address6_list(LOG_ERR, AF_INET6, 0); + + tt_assert(results != NULL); + /* Work even on systems without IPv6 interfaces */ + tt_int_op(smartlist_len(results),>=,0); + + tt_assert(!smartlist_contains_localhost_tor_addr(results)); + tt_assert(!smartlist_contains_multicast_tor_addr(results)); + tt_assert(!smartlist_contains_internal_tor_addr(results)); + + tt_assert(!smartlist_contains_ipv4_tor_addr(results)); + /* The list may or may not contain IPv6 addresses */ + +done: + free_interface_address6_list(results); + return; +} + +static void +test_address_get_if_addrs(void *arg) +{ + int rv; + uint32_t addr_h = 0; + tor_addr_t tor_addr; + + (void)arg; + + rv = get_interface_address(LOG_ERR, &addr_h); + + /* Assume every system has at least 1 non-local non-multicast IPv4 + * interface, even if it is an internal one */ + tt_assert(rv == 0); + tor_addr_from_ipv4h(&tor_addr, addr_h); + + tt_assert(!tor_addr_is_loopback(&tor_addr)); + tt_assert(!tor_addr_is_multicast(&tor_addr)); + /* The address may or may not be an internal address */ + + tt_assert(tor_addr_is_v4(&tor_addr)); + +done: + return; +} + +static void +test_address_get_if_addrs6(void *arg) +{ + int rv; + tor_addr_t tor_addr; + + (void)arg; + + rv = get_interface_address6(LOG_ERR, AF_INET6, &tor_addr); + + /* Work even on systems without IPv6 interfaces */ + if (rv == 0) { + tt_assert(!tor_addr_is_loopback(&tor_addr)); + tt_assert(!tor_addr_is_multicast(&tor_addr)); + /* The address may or may not be an internal address */ + + tt_assert(!tor_addr_is_v4(&tor_addr)); + } + +done: + return; +} + #define ADDRESS_TEST(name, flags) \ { #name, test_address_ ## name, flags, NULL, NULL } struct testcase_t address_tests[] = { ADDRESS_TEST(udp_socket_trick_whitebox, TT_FORK), ADDRESS_TEST(udp_socket_trick_blackbox, TT_FORK), + ADDRESS_TEST(get_if_addrs_list_internal, 0), + ADDRESS_TEST(get_if_addrs_list_no_internal, 0), + ADDRESS_TEST(get_if_addrs6_list_internal, 0), + ADDRESS_TEST(get_if_addrs6_list_no_internal, 0), + ADDRESS_TEST(get_if_addrs, 0), + ADDRESS_TEST(get_if_addrs6, 0), #ifdef HAVE_IFADDRS_TO_SMARTLIST ADDRESS_TEST(get_if_addrs_ifaddrs, TT_FORK), ADDRESS_TEST(ifaddrs_to_smartlist, 0),