mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-11-10 03:16:17 +01:00
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
- 8da2a80724
- https://bugs.webkit.org/show_bug.cgi?id=171934
- https://tools.ietf.org/html/draft-west-let-localhost-be-localhost-06
This commit is contained in:
parent
01acb8907f
commit
ea99872e45
@ -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"
|
/* Note that this does not follow the "FALSE means @error is set"
|
||||||
* convention. The return value tells the caller whether it should
|
* convention. The return value tells the caller whether it should
|
||||||
* return @addrs and @error to the caller right away, or if 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.
|
* continue and trying to resolve the name as a hostname.
|
||||||
*/
|
*/
|
||||||
static gboolean
|
static gboolean
|
||||||
handle_ip_address (const char *hostname,
|
handle_ip_address_or_localhost (const char *hostname,
|
||||||
GList **addrs,
|
GList **addrs,
|
||||||
GError **error)
|
GResolverNameLookupFlags flags,
|
||||||
|
GError **error)
|
||||||
{
|
{
|
||||||
GInetAddress *addr;
|
GInetAddress *addr;
|
||||||
|
|
||||||
@ -355,6 +390,28 @@ handle_ip_address (const char *hostname,
|
|||||||
return TRUE;
|
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;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -374,7 +431,7 @@ lookup_by_name_real (GResolver *resolver,
|
|||||||
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
||||||
|
|
||||||
/* Check if @hostname is just an IP address */
|
/* 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;
|
return addrs;
|
||||||
|
|
||||||
if (g_hostname_is_non_ascii (hostname))
|
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));
|
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 */
|
/* 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;
|
GTask *task;
|
||||||
|
|
||||||
|
@ -112,6 +112,12 @@ do_lookup_by_name (GTask *task,
|
|||||||
else
|
else
|
||||||
g_task_return_pointer (task, g_list_copy_deep (self->ipv6_results, copy_object, NULL), NULL);
|
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
|
else
|
||||||
g_assert_not_reached ();
|
g_assert_not_reached ();
|
||||||
}
|
}
|
||||||
@ -130,6 +136,22 @@ lookup_by_name_with_flags_async (GResolver *resolver,
|
|||||||
g_object_unref (task);
|
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 *
|
static GList *
|
||||||
lookup_by_name_with_flags_finish (GResolver *resolver,
|
lookup_by_name_with_flags_finish (GResolver *resolver,
|
||||||
GAsyncResult *result,
|
GAsyncResult *result,
|
||||||
@ -160,6 +182,7 @@ mock_resolver_class_init (MockResolverClass *klass)
|
|||||||
GObjectClass *object_class = G_OBJECT_CLASS (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_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_with_flags_finish = lookup_by_name_with_flags_finish;
|
||||||
|
resolver_class->lookup_by_name = lookup_by_name;
|
||||||
object_class->finalize = mock_resolver_finalize;
|
object_class->finalize = mock_resolver_finalize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -418,6 +418,131 @@ test_loopback_sync (void)
|
|||||||
g_object_unref (addr);
|
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 {
|
typedef struct {
|
||||||
GList/*<owned GSocketAddress> */ *addrs; /* owned */
|
GList/*<owned GSocketAddress> */ *addrs; /* owned */
|
||||||
GMainLoop *loop; /* owned */
|
GMainLoop *loop; /* owned */
|
||||||
@ -536,6 +661,51 @@ test_loopback_async (void)
|
|||||||
g_object_unref (addr);
|
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
|
static void
|
||||||
test_to_string (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/basic", test_loopback_basic);
|
||||||
g_test_add_func ("/network-address/loopback/sync", test_loopback_sync);
|
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/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_func ("/network-address/to-string", test_to_string);
|
||||||
|
|
||||||
g_test_add ("/network-address/happy-eyeballs/basic", HappyEyeballsFixture, NULL,
|
g_test_add ("/network-address/happy-eyeballs/basic", HappyEyeballsFixture, NULL,
|
||||||
|
Loading…
Reference in New Issue
Block a user