localhost is either 127.0.0.1 or ::1, nothing else.
authorflorian <florian@openbsd.org>
Mon, 20 Nov 2023 12:15:16 +0000 (12:15 +0000)
committerflorian <florian@openbsd.org>
Mon, 20 Nov 2023 12:15:16 +0000 (12:15 +0000)
RFC 6761, 6.3 Domain Name Reservation Considerations for "localhost.":
   3.  Name resolution APIs and libraries SHOULD recognize localhost
       names as special and SHOULD always return the IP loopback address
       for address queries and negative responses for all other query
       types.  Name resolution APIs SHOULD NOT send queries for
       localhost names to their configured caching DNS server(s).

This makes sure that the getaddrinfo(3) and gethostbyname(3) family of
functions always return the loopback address and do not send queries
to name servers. This includes "localhost", "localhost." and
everything under ".localhost" and ".localhost.".

For example, a host underneath the .com.ar zone will per default have
a search list of "com.ar.". resolv.conf(5) has a default of "lookup
bind file". Both combined will result in lookups for "localhost" to
not return 127.0.0.1 because localhost.com.ar is registered in DNS.

It has been known for decades that this is a problem, especially for
localhost.

Problem recently spotted by gonzalo@ and debugged by sthen@

Testing sthen, gonzalo
Input & OK phessler, eric, millert
OK sthen, kn, deraadt

lib/libc/asr/asr_private.h
lib/libc/asr/asr_utils.c
lib/libc/asr/getaddrinfo_async.c
lib/libc/asr/gethostnamadr_async.c

index 64f044f..27f5421 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: asr_private.h,v 1.48 2022/11/17 17:39:41 florian Exp $        */
+/*     $OpenBSD: asr_private.h,v 1.49 2023/11/20 12:15:16 florian Exp $        */
 /*
  * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
  *
@@ -299,6 +299,7 @@ int _asr_sockaddr_from_str(struct sockaddr *, int, const char *);
 ssize_t _asr_dname_from_fqdn(const char *, char *, size_t);
 ssize_t _asr_addr_as_fqdn(const char *, int, char *, size_t);
 int hnok_lenient(const char *);
+int _asr_is_localhost(const char*);
 
 /* asr.c */
 void _asr_resolver_done(void *);
index 9ae9bfa..ab8603a 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: asr_utils.c,v 1.21 2023/03/15 22:12:00 millert Exp $  */
+/*     $OpenBSD: asr_utils.c,v 1.22 2023/11/20 12:15:16 florian Exp $  */
 /*
  * Copyright (c) 2009-2012     Eric Faurot     <eric@faurot.net>
  *
@@ -582,3 +582,35 @@ hnok_lenient(const char *dn)
        }
        return 1;
 }
+
+/* Check if the hostname is localhost or if it's in the localhost domain */
+int
+_asr_is_localhost(const char *hn)
+{
+       size_t   hnlen, localhostlen;
+
+       if (hn == NULL)
+               return 0;
+
+       if (strcasecmp(hn, "localhost") == 0 ||
+           strcasecmp(hn, "localhost.") == 0)
+               return 1;
+
+       hnlen = strlen(hn);
+       localhostlen = strlen(".localhost");
+
+       if (hnlen < localhostlen)
+               return 0;
+
+       if (strcasecmp(hn + hnlen - localhostlen, ".localhost") == 0)
+               return 1;
+
+       localhostlen++;
+       if (hnlen < localhostlen)
+               return 0;
+
+       if (strcasecmp(hn + hnlen - localhostlen, ".localhost.") == 0)
+               return 1;
+
+       return 0;
+}
index 5e0d186..d5a9561 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: getaddrinfo_async.c,v 1.59 2022/12/27 17:10:06 jmc Exp $      */
+/*     $OpenBSD: getaddrinfo_async.c,v 1.60 2023/11/20 12:15:16 florian Exp $  */
 /*
  * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
  *
@@ -115,7 +115,7 @@ getaddrinfo_async_run(struct asr_query *as, struct asr_result *ar)
        char             fqdn[MAXDNAME];
        const char      *str;
        struct addrinfo *ai;
-       int              i, family, r;
+       int              i, family, r, is_localhost;
        FILE            *f;
        union {
                struct sockaddr         sa;
@@ -228,8 +228,19 @@ getaddrinfo_async_run(struct asr_query *as, struct asr_result *ar)
 
                ar->ar_gai_errno = 0;
 
-               /* If hostname is NULL, use local address */
-               if (as->as.ai.hostname == NULL) {
+               is_localhost = _asr_is_localhost(as->as.ai.hostname);
+               /*
+                * If hostname is NULL, "localhost" or falls within the
+                * ".localhost." domain, use local address.
+                * RFC 6761, 6.3:
+                * 3. Name resolution APIs and libraries SHOULD recognize
+                * localhost names as special and SHOULD always return the IP
+                * loopback address for address queries and negative responses
+                * for all other query types.  Name resolution APIs SHOULD NOT
+                * send queries for localhost names to their configured caching
+                * DNS server(s).
+                */
+               if (as->as.ai.hostname == NULL || is_localhost) {
                        for (family = iter_family(as, 1);
                            family != -1;
                            family = iter_family(as, 0)) {
@@ -238,11 +249,12 @@ getaddrinfo_async_run(struct asr_query *as, struct asr_result *ar)
                                 * those, rather than parsing over and over.
                                 */
                                if (family == PF_INET)
-                                       str = (ai->ai_flags & AI_PASSIVE) ? \
-                                               "0.0.0.0" : "127.0.0.1";
+                                       str = (ai->ai_flags & AI_PASSIVE &&
+                                           !is_localhost) ? "0.0.0.0" :
+                                           "127.0.0.1";
                                else /* PF_INET6 */
-                                       str = (ai->ai_flags & AI_PASSIVE) ? \
-                                               "::" : "::1";
+                                       str = (ai->ai_flags & AI_PASSIVE &&
+                                           !is_localhost) ? "::" : "::1";
                                 /* This can't fail */
                                _asr_sockaddr_from_str(&sa.sa, family, str);
                                if ((r = addrinfo_add(as, &sa.sa, NULL))) {
index bdc00cb..ed998cf 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: gethostnamadr_async.c,v 1.47 2023/11/14 08:27:33 florian Exp $        */
+/*     $OpenBSD: gethostnamadr_async.c,v 1.48 2023/11/20 12:15:16 florian Exp $        */
 /*
  * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
  *
@@ -209,6 +209,37 @@ gethostnamadr_async_run(struct asr_query *as, struct asr_result *ar)
                                async_set_state(as, ASR_STATE_HALT);
                                break;
                        }
+
+                       /*
+                        * If hostname is "localhost" or falls within the
+                        * ".localhost." domain, use local address.
+                        * RFC 6761, 6.3:
+                        * 3. Name resolution APIs and libraries SHOULD
+                        * recognize localhost names as special and SHOULD
+                        * always return the IP loopback address for address
+                        * queries and negative responses for all other query
+                        * types.  Name resolution APIs SHOULD NOT send queries
+                        * for localhost names to their configured caching DNS
+                        * server(s).
+                        */
+
+                       if (_asr_is_localhost(as->as.hostnamadr.name)) {
+                               inet_pton(as->as.hostnamadr.family,
+                                   as->as.hostnamadr.family == AF_INET ?
+                                   "127.0.0.1" : "::1", addr);
+                               h = hostent_from_addr(as->as.hostnamadr.family,
+                                   as->as.hostnamadr.name, addr);
+                               if (h == NULL) {
+                                       ar->ar_errno = errno;
+                                       ar->ar_h_errno = NETDB_INTERNAL;
+                               }
+                               else {
+                                       ar->ar_hostent = &h->h;
+                                       ar->ar_h_errno = NETDB_SUCCESS;
+                               }
+                               async_set_state(as, ASR_STATE_HALT);
+                               break;
+                       }
                }
                async_set_state(as, ASR_STATE_NEXT_DB);
                break;