[glib/wip/tingping/localhost-is-local: 77/77] Always resolve localhost to loopback address



commit f6ba297dc5efa3275b87a011cdc9ae4818f98467
Author: Patrick Griffis <tingping tingping se>
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]