[glib/wip/tingping/localhost-is-local: 2/2] Always resolve localhost to loopback address
- From: Patrick Griffis <pgriffis src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glib/wip/tingping/localhost-is-local: 2/2] Always resolve localhost to loopback address
- Date: Tue, 5 Feb 2019 17:24:00 +0000 (UTC)
commit 28714e258224bab58301b83a243655c23c1accb3
Author: Patrick Griffis <pgriffis igalia com>
Date: Tue Feb 5 08:53:44 2019 -0500
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 | 59 +++++++++++++++--
gio/tests/network-address.c | 154 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 208 insertions(+), 5 deletions(-)
---
diff --git a/gio/gresolver.c b/gio/gresolver.c
index 6a33634c5..5d18d0b77 100644
--- a/gio/gresolver.c
+++ b/gio/gresolver.c
@@ -296,15 +296,47 @@ remove_duplicates (GList *addrs)
}
}
+static gboolean
+hostname_is_localhost (const char *hostname)
+{
+ size_t len = strlen (hostname);
+ const char *p;
+
+ /* Match "localhost", "localhost.", and "*.localhost" */
+ if (len < strlen ("localhost"))
+ return FALSE;
+
+ if (hostname[len - 1] == '.')
+ len--;
+
+ 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;
@@ -346,6 +378,23 @@ 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_IPV4_ONLY))
+ *addrs = g_list_append (*addrs, g_inet_address_new_loopback (G_SOCKET_FAMILY_IPV6));
+ if (!(flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV6_ONLY))
+ *addrs = g_list_append (*addrs, g_inet_address_new_loopback (G_SOCKET_FAMILY_IPV4));
+ return TRUE;
+ }
+
return FALSE;
}
@@ -365,7 +414,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))
@@ -504,7 +553,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/network-address.c b/gio/tests/network-address.c
index f1063789f..be7a0c265 100644
--- a/gio/tests/network-address.c
+++ b/gio/tests/network-address.c
@@ -414,6 +414,115 @@ 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 */
+
+ /* 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);
+
+
+ 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/*<owned GSocketAddress> */ *addrs; /* owned */
GMainLoop *loop; /* owned */
@@ -506,6 +615,49 @@ 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 */
+
+ /* 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)
{
@@ -916,6 +1068,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,
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]