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



commit e5969c363f08f825c8413a7828df9ddabe8e7997
Author: Patrick Griffis <pgriffis igalia com>
Date:   Thu Jan 24 14:47:35 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.

 gio/gresolver.c             |  52 +++++++++++--
 gio/tests/network-address.c | 179 ++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 221 insertions(+), 10 deletions(-)
---
diff --git a/gio/gresolver.c b/gio/gresolver.c
index 6a33634c5..03f72365d 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,16 @@ handle_ip_address (const char  *hostname,
       return TRUE;
     }
 
+  /* Always resolve localhost to a loopback address so it can be reliably considered secure */
+  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 +407,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 +546,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 4a2718e24..be7a0c265 100644
--- a/gio/tests/network-address.c
+++ b/gio/tests/network-address.c
@@ -414,13 +414,134 @@ 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 */
+  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, gpointer user_data)
 {
@@ -453,11 +574,14 @@ got_addr (GObject *source_object, GAsyncResult *result, gpointer user_data)
       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);
+        }
     }
 }
 
@@ -491,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)
 {
@@ -901,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]