From 01acb8907f24fe9311d1adb1c9dc380113a296ae Mon Sep 17 00:00:00 2001 From: Patrick Griffis Date: Tue, 5 Feb 2019 08:50:09 -0500 Subject: [PATCH 1/2] tests: Don't block mainloop for delays in gnetworkaddress tests --- gio/tests/network-address.c | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/gio/tests/network-address.c b/gio/tests/network-address.c index bda76050a..7883a8884 100644 --- a/gio/tests/network-address.c +++ b/gio/tests/network-address.c @@ -421,10 +421,22 @@ test_loopback_sync (void) typedef struct { GList/* */ *addrs; /* owned */ GMainLoop *loop; /* owned */ + GSocketAddressEnumerator *enumerator; /* unowned */ guint delay_ms; gint expected_error_code; } AsyncData; +static void got_addr (GObject *source_object, GAsyncResult *result, gpointer user_data); + +static int +on_delayed_get_addr (gpointer user_data) +{ + AsyncData *data = user_data; + g_socket_address_enumerator_next_async (data->enumerator, NULL, + got_addr, user_data); + return G_SOURCE_REMOVE; +} + static void got_addr (GObject *source_object, GAsyncResult *result, @@ -459,11 +471,14 @@ got_addr (GObject *source_object, g_assert (G_IS_INET_SOCKET_ADDRESS (a)); data->addrs = g_list_prepend (data->addrs, a); - if (data->delay_ms) - g_usleep (data->delay_ms * 1000); - - g_socket_address_enumerator_next_async (enumerator, NULL, - got_addr, user_data); + if (!data->delay_ms) + g_socket_address_enumerator_next_async (enumerator, NULL, + got_addr, user_data); + else + { + data->enumerator = enumerator; + g_timeout_add (data->delay_ms, on_delayed_get_addr, data); + } } } From ea99872e45f45083d20b7ed993ed218c323f0a1b Mon Sep 17 00:00:00 2001 From: Patrick Griffis Date: Tue, 5 Feb 2019 08:53:44 -0500 Subject: [PATCH 2/2] Always resolve localhost to loopback address This always resolves "localhost" to a loopback address which has security benefits such as preventing a malicious dns server redirecting local connections and allows software to assume it is a secure hostname. This is being adopted by web browsers: - https://w3c.github.io/webappsec-secure-contexts/ - https://groups.google.com/a/chromium.org/forum/#!msg/blink-dev/RC9dSw-O3fE/E3_0XaT0BAAJ - https://chromium.googlesource.com/chromium/src.git/+/8da2a80724a9b896890602ff77ef2216cb951399 - https://bugs.webkit.org/show_bug.cgi?id=171934 - https://tools.ietf.org/html/draft-west-let-localhost-be-localhost-06 --- gio/gresolver.c | 67 ++++++++++++-- gio/tests/mock-resolver.c | 23 +++++ gio/tests/network-address.c | 172 ++++++++++++++++++++++++++++++++++++ 3 files changed, 257 insertions(+), 5 deletions(-) diff --git a/gio/gresolver.c b/gio/gresolver.c index 732d21709..651e7d0ef 100644 --- a/gio/gresolver.c +++ b/gio/gresolver.c @@ -296,15 +296,50 @@ remove_duplicates (GList *addrs) } } +static gboolean +hostname_is_localhost (const char *hostname) +{ + size_t len = strlen (hostname); + const char *p; + + /* Match "localhost", "localhost.", "*.localhost" and "*.localhost." */ + if (len < strlen ("localhost")) + return FALSE; + + if (hostname[len - 1] == '.') + len--; + + /* Scan backwards in @hostname to find the right-most dot (excluding the final dot, if it exists, as it was chopped off above). + * We can’t use strrchr() because because we need to operate with string lengths. + * End with @p pointing to the character after the right-most dot. */ + p = hostname + len - 1; + while (p >= hostname) + { + if (*p == '.') + { + p++; + break; + } + else if (p == hostname) + break; + p--; + } + + len -= p - hostname; + + return g_ascii_strncasecmp (p, "localhost", MAX (len, strlen ("localhost"))) == 0; +} + /* Note that this does not follow the "FALSE means @error is set" * convention. The return value tells the caller whether it should * return @addrs and @error to the caller right away, or if it should * continue and trying to resolve the name as a hostname. */ static gboolean -handle_ip_address (const char *hostname, - GList **addrs, - GError **error) +handle_ip_address_or_localhost (const char *hostname, + GList **addrs, + GResolverNameLookupFlags flags, + GError **error) { GInetAddress *addr; @@ -355,6 +390,28 @@ handle_ip_address (const char *hostname, return TRUE; } + /* Always resolve localhost to a loopback address so it can be reliably considered secure. + This behavior is being adopted by browsers: + - https://w3c.github.io/webappsec-secure-contexts/ + - https://groups.google.com/a/chromium.org/forum/#!msg/blink-dev/RC9dSw-O3fE/E3_0XaT0BAAJ + - https://chromium.googlesource.com/chromium/src.git/+/8da2a80724a9b896890602ff77ef2216cb951399 + - https://bugs.webkit.org/show_bug.cgi?id=171934 + - https://tools.ietf.org/html/draft-west-let-localhost-be-localhost-06 + */ + if (hostname_is_localhost (hostname)) + { + if (flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV6_ONLY) + *addrs = g_list_append (*addrs, g_inet_address_new_loopback (G_SOCKET_FAMILY_IPV6)); + if (flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY) + *addrs = g_list_append (*addrs, g_inet_address_new_loopback (G_SOCKET_FAMILY_IPV4)); + if (*addrs == NULL) + { + *addrs = g_list_append (*addrs, g_inet_address_new_loopback (G_SOCKET_FAMILY_IPV6)); + *addrs = g_list_append (*addrs, g_inet_address_new_loopback (G_SOCKET_FAMILY_IPV4)); + } + return TRUE; + } + return FALSE; } @@ -374,7 +431,7 @@ lookup_by_name_real (GResolver *resolver, g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* Check if @hostname is just an IP address */ - if (handle_ip_address (hostname, &addrs, error)) + if (handle_ip_address_or_localhost (hostname, &addrs, flags, error)) return addrs; if (g_hostname_is_non_ascii (hostname)) @@ -513,7 +570,7 @@ lookup_by_name_async_real (GResolver *resolver, g_return_if_fail (!(flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY && flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV6_ONLY)); /* Check if @hostname is just an IP address */ - if (handle_ip_address (hostname, &addrs, &error)) + if (handle_ip_address_or_localhost (hostname, &addrs, flags, &error)) { GTask *task; diff --git a/gio/tests/mock-resolver.c b/gio/tests/mock-resolver.c index 271aa2cb1..3a7532134 100644 --- a/gio/tests/mock-resolver.c +++ b/gio/tests/mock-resolver.c @@ -112,6 +112,12 @@ do_lookup_by_name (GTask *task, else g_task_return_pointer (task, g_list_copy_deep (self->ipv6_results, copy_object, NULL), NULL); } + else if (flags == G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT) + { + /* This is only the minimal implementation needed for some tests */ + g_assert (self->ipv4_error == NULL && self->ipv6_error == NULL && self->ipv6_results == NULL); + g_task_return_pointer (task, g_list_copy_deep (self->ipv4_results, copy_object, NULL), NULL); + } else g_assert_not_reached (); } @@ -130,6 +136,22 @@ lookup_by_name_with_flags_async (GResolver *resolver, g_object_unref (task); } +static GList * +lookup_by_name (GResolver *resolver, + const gchar *hostname, + GCancellable *cancellable, + GError **error) +{ + GList *result = NULL; + GTask *task = g_task_new (resolver, cancellable, NULL, NULL); + g_task_set_task_data (task, GUINT_TO_POINTER (G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT), NULL); + g_task_run_in_thread_sync (task, do_lookup_by_name); + result = g_task_propagate_pointer (task, error); + g_object_unref (task); + return result; +} + + static GList * lookup_by_name_with_flags_finish (GResolver *resolver, GAsyncResult *result, @@ -160,6 +182,7 @@ mock_resolver_class_init (MockResolverClass *klass) GObjectClass *object_class = G_OBJECT_CLASS (klass); resolver_class->lookup_by_name_with_flags_async = lookup_by_name_with_flags_async; resolver_class->lookup_by_name_with_flags_finish = lookup_by_name_with_flags_finish; + resolver_class->lookup_by_name = lookup_by_name; object_class->finalize = mock_resolver_finalize; } diff --git a/gio/tests/network-address.c b/gio/tests/network-address.c index 7883a8884..f06cd755b 100644 --- a/gio/tests/network-address.c +++ b/gio/tests/network-address.c @@ -418,6 +418,131 @@ test_loopback_sync (void) g_object_unref (addr); } +static void +test_localhost_sync (void) +{ + GSocketConnectable *addr; /* owned */ + GSocketAddressEnumerator *enumerator; /* owned */ + GSocketAddress *a; /* owned */ + GError *error = NULL; + GResolver *original_resolver; /* owned */ + MockResolver *mock_resolver; /* owned */ + GList *ipv4_results = NULL; /* owned */ + + /* This test ensures that variations of the "localhost" hostname always resolve to a loopback address */ + + /* Set up a DNS resolver that returns nonsense for "localhost" */ + original_resolver = g_resolver_get_default (); + mock_resolver = mock_resolver_new (); + g_resolver_set_default (G_RESOLVER (mock_resolver)); + ipv4_results = g_list_append (ipv4_results, g_inet_address_new_from_string ("123.123.123.123")); + mock_resolver_set_ipv4_results (mock_resolver, ipv4_results); + + addr = g_network_address_new ("localhost.", 616); + enumerator = g_socket_connectable_enumerate (addr); + + /* IPv6 address. */ + a = g_socket_address_enumerator_next (enumerator, NULL, &error); + g_assert_no_error (error); + assert_socket_address_matches (a, "::1", 616); + g_object_unref (a); + + /* IPv4 address. */ + a = g_socket_address_enumerator_next (enumerator, NULL, &error); + g_assert_no_error (error); + assert_socket_address_matches (a, "127.0.0.1", 616); + g_object_unref (a); + + /* End of results. */ + g_assert_null (g_socket_address_enumerator_next (enumerator, NULL, &error)); + g_assert_no_error (error); + g_object_unref (enumerator); + g_object_unref (addr); + + addr = g_network_address_new (".localhost", 616); + enumerator = g_socket_connectable_enumerate (addr); + + /* IPv6 address. */ + a = g_socket_address_enumerator_next (enumerator, NULL, &error); + g_assert_no_error (error); + assert_socket_address_matches (a, "::1", 616); + g_object_unref (a); + + /* IPv4 address. */ + a = g_socket_address_enumerator_next (enumerator, NULL, &error); + g_assert_no_error (error); + assert_socket_address_matches (a, "127.0.0.1", 616); + g_object_unref (a); + + /* End of results. */ + g_assert_null (g_socket_address_enumerator_next (enumerator, NULL, &error)); + g_assert_no_error (error); + g_object_unref (enumerator); + g_object_unref (addr); + + addr = g_network_address_new ("foo.localhost", 616); + enumerator = g_socket_connectable_enumerate (addr); + + /* IPv6 address. */ + a = g_socket_address_enumerator_next (enumerator, NULL, &error); + g_assert_no_error (error); + assert_socket_address_matches (a, "::1", 616); + g_object_unref (a); + + /* IPv4 address. */ + a = g_socket_address_enumerator_next (enumerator, NULL, &error); + g_assert_no_error (error); + assert_socket_address_matches (a, "127.0.0.1", 616); + g_object_unref (a); + + /* End of results. */ + g_assert_null (g_socket_address_enumerator_next (enumerator, NULL, &error)); + g_assert_no_error (error); + g_object_unref (enumerator); + g_object_unref (addr); + + addr = g_network_address_new (".localhost.", 616); + enumerator = g_socket_connectable_enumerate (addr); + + /* IPv6 address. */ + a = g_socket_address_enumerator_next (enumerator, NULL, &error); + g_assert_no_error (error); + assert_socket_address_matches (a, "::1", 616); + g_object_unref (a); + + /* IPv4 address. */ + a = g_socket_address_enumerator_next (enumerator, NULL, &error); + g_assert_no_error (error); + assert_socket_address_matches (a, "127.0.0.1", 616); + g_object_unref (a); + + /* End of results. */ + g_assert_null (g_socket_address_enumerator_next (enumerator, NULL, &error)); + g_assert_no_error (error); + g_object_unref (enumerator); + g_object_unref (addr); + + addr = g_network_address_new ("invalid", 616); + enumerator = g_socket_connectable_enumerate (addr); + + /* IPv4 address. */ + a = g_socket_address_enumerator_next (enumerator, NULL, &error); + g_assert_no_error (error); + assert_socket_address_matches (a, "123.123.123.123", 616); + g_object_unref (a); + + /* End of results. */ + g_assert_null (g_socket_address_enumerator_next (enumerator, NULL, &error)); + g_assert_no_error (error); + g_object_unref (enumerator); + g_object_unref (addr); + + g_resolver_set_default (original_resolver); + g_list_free_full (ipv4_results, (GDestroyNotify) g_object_unref); + g_object_unref (original_resolver); + g_object_unref (mock_resolver); +} + typedef struct { GList/* */ *addrs; /* owned */ GMainLoop *loop; /* owned */ @@ -536,6 +661,51 @@ test_loopback_async (void) g_object_unref (addr); } +static void +test_localhost_async (void) +{ + GSocketConnectable *addr; /* owned */ + GSocketAddressEnumerator *enumerator; /* owned */ + AsyncData data = { 0, }; + GResolver *original_resolver; /* owned */ + MockResolver *mock_resolver; /* owned */ + GList *ipv4_results = NULL; /* owned */ + + /* This test ensures that variations of the "localhost" hostname always resolve to a loopback address */ + + /* Set up a DNS resolver that returns nonsense for "localhost" */ + original_resolver = g_resolver_get_default (); + mock_resolver = mock_resolver_new (); + g_resolver_set_default (G_RESOLVER (mock_resolver)); + ipv4_results = g_list_append (ipv4_results, g_inet_address_new_from_string ("123.123.123.123")); + mock_resolver_set_ipv4_results (mock_resolver, ipv4_results); + + addr = g_network_address_new ("localhost", 610); + enumerator = g_socket_connectable_enumerate (addr); + + /* Get all the addresses. */ + data.addrs = NULL; + data.delay_ms = 1; + data.loop = g_main_loop_new (NULL, FALSE); + + g_socket_address_enumerator_next_async (enumerator, NULL, got_addr, &data); + g_main_loop_run (data.loop); + + /* Check results. */ + g_assert_cmpuint (g_list_length (data.addrs), ==, 2); + assert_socket_address_matches (data.addrs->data, "::1", 610); + assert_socket_address_matches (data.addrs->next->data, "127.0.0.1", 610); + + g_resolver_set_default (original_resolver); + g_list_free_full (data.addrs, (GDestroyNotify) g_object_unref); + g_list_free_full (ipv4_results, (GDestroyNotify) g_object_unref); + g_object_unref (original_resolver); + g_object_unref (mock_resolver); + g_object_unref (enumerator); + g_object_unref (addr); + g_main_loop_unref (data.loop); +} + static void test_to_string (void) { @@ -1054,6 +1224,8 @@ main (int argc, char *argv[]) g_test_add_func ("/network-address/loopback/basic", test_loopback_basic); g_test_add_func ("/network-address/loopback/sync", test_loopback_sync); g_test_add_func ("/network-address/loopback/async", test_loopback_async); + g_test_add_func ("/network-address/localhost/async", test_localhost_async); + g_test_add_func ("/network-address/localhost/sync", test_localhost_sync); g_test_add_func ("/network-address/to-string", test_to_string); g_test_add ("/network-address/happy-eyeballs/basic", HappyEyeballsFixture, NULL,