summaryrefslogtreecommitdiff
path: root/inet
diff options
context:
space:
mode:
authorFlorian Weimer <fweimer@redhat.com>2018-05-23 15:26:19 +0200
committerFlorian Weimer <fweimer@redhat.com>2018-05-23 15:27:24 +0200
commit7f9f1ecb710eac4d65bb02785ddf288cac098323 (patch)
treeb93086996bfb5edf0221b895128ef5a6e709dead /inet
parent5f7b841d3aebdccc2baed27cb4b22ddb08cd7c0c (diff)
Switch IDNA implementation to libidn2 [BZ #19728] [BZ #19729] [BZ #22247]
This provides an implementation of the IDNA2008 standard and fixes CVE-2016-6261, CVE-2016-6263, CVE-2017-14062.
Diffstat (limited to 'inet')
-rw-r--r--inet/Makefile12
-rw-r--r--inet/Versions2
-rw-r--r--inet/getnameinfo.c56
-rw-r--r--inet/idna.c182
-rw-r--r--inet/idna_name_classify.c75
-rw-r--r--inet/net-internal.h27
-rw-r--r--inet/tst-idna_name_classify.c73
7 files changed, 390 insertions, 37 deletions
diff --git a/inet/Makefile b/inet/Makefile
index 5d02c37626..09f5ba78fc 100644
--- a/inet/Makefile
+++ b/inet/Makefile
@@ -45,7 +45,7 @@ routines := htonl htons \
in6_addr getnameinfo if_index ifaddrs inet6_option \
getipv4sourcefilter setipv4sourcefilter \
getsourcefilter setsourcefilter inet6_opt inet6_rth \
- inet6_scopeid_pton deadline
+ inet6_scopeid_pton deadline idna idna_name_classify
aux := check_pf check_native ifreq
@@ -59,12 +59,20 @@ tests := htontest test_ifindex tst-ntoa tst-ether_aton tst-network \
tests-static += tst-deadline
tests-internal += tst-deadline
+# tst-idna_name_classify must be linked statically because it tests
+# internal functionality.
+tests-static += tst-idna_name_classify
+tests-internal += tst-idna_name_classify
+
# tst-inet6_scopeid_pton also needs internal functions but does not
# need to be linked statically.
tests-internal += tst-inet6_scopeid_pton
include ../Rules
+LOCALES := en_US.UTF-8 en_US.ISO-8859-1
+include ../gen-locales.mk
+
ifeq ($(have-thread-library),yes)
CFLAGS-gethstbyad_r.c += -fexceptions
@@ -103,3 +111,5 @@ endif
ifeq ($(build-static-nss),yes)
CFLAGS += -DSTATIC_NSS
endif
+
+$(objpfx)tst-idna_name_classify.out: $(gen-locales)
diff --git a/inet/Versions b/inet/Versions
index 6f663f3648..9b3661e046 100644
--- a/inet/Versions
+++ b/inet/Versions
@@ -88,5 +88,7 @@ libc {
# Used from nscd.
__inet6_scopeid_pton;
+ __idna_to_dns_encoding;
+ __idna_from_dns_encoding;
}
}
diff --git a/inet/getnameinfo.c b/inet/getnameinfo.c
index a20d20b7cd..5d4978e383 100644
--- a/inet/getnameinfo.c
+++ b/inet/getnameinfo.c
@@ -71,10 +71,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <sys/utsname.h>
#include <libc-lock.h>
#include <scratch_buffer.h>
-
-#ifdef HAVE_LIBIDN
-# include <idna.h>
-#endif
+#include <net-internal.h>
#ifndef min
# define min(x,y) (((x) > (y)) ? (y) : (x))
@@ -82,6 +79,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
libc_freeres_ptr (static char *domain);
+/* Former NI_IDN_ALLOW_UNASSIGNED, NI_IDN_USE_STD3_ASCII_RULES flags,
+ now ignored. */
+#define DEPRECATED_NI_IDN 192
static char *
nrl_domainname (void)
@@ -285,41 +285,28 @@ gni_host_inet_name (struct scratch_buffer *tmpbuf,
/* Terminate the string after the prefix. */
*c = '\0';
-#ifdef HAVE_LIBIDN
/* If requested, convert from the IDN format. */
- if (flags & NI_IDN)
+ bool do_idn = flags & NI_IDN;
+ char *h_name;
+ if (do_idn)
{
- int idn_flags = 0;
- if (flags & NI_IDN_ALLOW_UNASSIGNED)
- idn_flags |= IDNA_ALLOW_UNASSIGNED;
- if (flags & NI_IDN_USE_STD3_ASCII_RULES)
- idn_flags |= IDNA_USE_STD3_ASCII_RULES;
-
- char *out;
- int rc = __idna_to_unicode_lzlz (h->h_name, &out,
- idn_flags);
- if (rc != IDNA_SUCCESS)
- {
- if (rc == IDNA_MALLOC_ERROR)
- return EAI_MEMORY;
- if (rc == IDNA_DLOPEN_ERROR)
- return EAI_SYSTEM;
- return EAI_IDN_ENCODE;
- }
-
- if (out != h->h_name)
- {
- h->h_name = strdupa (out);
- free (out);
- }
+ int rc = __idna_from_dns_encoding (h->h_name, &h_name);
+ if (rc == EAI_IDN_ENCODE)
+ /* Use the punycode name as a fallback. */
+ do_idn = false;
+ else if (rc != 0)
+ return rc;
}
-#endif
+ if (!do_idn)
+ h_name = h->h_name;
- size_t len = strlen (h->h_name) + 1;
+ size_t len = strlen (h_name) + 1;
if (len > hostlen)
return EAI_OVERFLOW;
+ memcpy (host, h_name, len);
- memcpy (host, h->h_name, len);
+ if (do_idn)
+ free (h_name);
return 0;
}
@@ -501,10 +488,7 @@ getnameinfo (const struct sockaddr *sa, socklen_t addrlen, char *host,
int flags)
{
if (flags & ~(NI_NUMERICHOST|NI_NUMERICSERV|NI_NOFQDN|NI_NAMEREQD|NI_DGRAM
-#ifdef HAVE_LIBIDN
- |NI_IDN|NI_IDN_ALLOW_UNASSIGNED|NI_IDN_USE_STD3_ASCII_RULES
-#endif
- ))
+ |NI_IDN|DEPRECATED_NI_IDN))
return EAI_BADFLAGS;
if (sa == NULL || addrlen < sizeof (sa_family_t))
diff --git a/inet/idna.c b/inet/idna.c
new file mode 100644
index 0000000000..c561bf2e9e
--- /dev/null
+++ b/inet/idna.c
@@ -0,0 +1,182 @@
+/* IDNA functions, forwarding to implementations in libidn2.
+ Copyright (C) 2018 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#include <allocate_once.h>
+#include <dlfcn.h>
+#include <inet/net-internal.h>
+#include <netdb.h>
+#include <stdbool.h>
+
+/* Use the soname and version to locate libidn2, to ensure a
+ compatible ABI. */
+#define LIBIDN2_SONAME "libidn2.so.0"
+#define LIBIDN2_VERSION "IDN2_0.0.0"
+
+/* Return codes from libidn2. */
+enum
+ {
+ IDN2_OK = 0,
+ IDN2_MALLOC = -100,
+ };
+
+/* Functions from libidn2. */
+struct functions
+{
+ void *handle;
+ int (*lookup_ul) (const char *src, char **result, int flags);
+ int (*to_unicode_lzlz) (const char *name, char **result, int flags);
+};
+
+static void *
+functions_allocate (void *closure)
+{
+ struct functions *result = malloc (sizeof (*result));
+ if (result == NULL)
+ return NULL;
+
+ void *handle = __libc_dlopen (LIBIDN2_SONAME);
+ if (handle == NULL)
+ /* Do not cache open failures. The library may appear
+ later. */
+ {
+ free (result);
+ return NULL;
+ }
+
+ void *ptr_lookup_ul
+ = __libc_dlvsym (handle, "idn2_lookup_ul", LIBIDN2_VERSION);
+ void *ptr_to_unicode_lzlz
+ = __libc_dlvsym (handle, "idn2_to_unicode_lzlz", LIBIDN2_VERSION);
+ if (ptr_lookup_ul == NULL || ptr_to_unicode_lzlz == NULL)
+ {
+ __libc_dlclose (handle);
+ free (result);
+ return NULL;
+ }
+
+ result->handle = handle;
+ result->lookup_ul = ptr_lookup_ul;
+ result->to_unicode_lzlz = ptr_to_unicode_lzlz;
+#ifdef PTR_MANGLE
+ PTR_MANGLE (result->lookup_ul);
+ PTR_MANGLE (result->to_unicode_lzlz);
+#endif
+
+ return result;
+}
+
+static void
+functions_deallocate (void *closure, void *ptr)
+{
+ struct functions *functions = ptr;
+ __libc_dlclose (functions->handle);
+ free (functions);
+}
+
+/* Ensure that *functions is initialized and return the value of the
+ pointer. If the library cannot be loaded, return NULL. */
+static inline struct functions *
+get_functions (void)
+{
+ static void *functions;
+ return allocate_once (&functions, functions_allocate, functions_deallocate,
+ NULL);
+}
+
+/* strdup with an EAI_* error code. */
+static int
+gai_strdup (const char *name, char **result)
+{
+ char *ptr = __strdup (name);
+ if (ptr == NULL)
+ return EAI_MEMORY;
+ *result = ptr;
+ return 0;
+}
+
+int
+__idna_to_dns_encoding (const char *name, char **result)
+{
+ switch (__idna_name_classify (name))
+ {
+ case idna_name_ascii:
+ /* Nothing to convert. */
+ return gai_strdup (name, result);
+ case idna_name_nonascii:
+ /* Encoding needed. Handled below. */
+ break;
+ case idna_name_nonascii_backslash:
+ case idna_name_encoding_error:
+ return EAI_IDN_ENCODE;
+ case idna_name_memory_error:
+ return EAI_MEMORY;
+ case idna_name_error:
+ return EAI_SYSTEM;
+ }
+
+ struct functions *functions = get_functions ();
+ if (functions == NULL)
+ /* We report this as an encoding error (assuming that libidn2 is
+ not installed), although the root cause may be a temporary
+ error condition due to resource shortage. */
+ return EAI_IDN_ENCODE;
+ char *ptr = NULL;
+ __typeof__ (functions->lookup_ul) fptr = functions->lookup_ul;
+#ifdef PTR_DEMANGLE
+ PTR_DEMANGLE (fptr);
+#endif
+ int ret = fptr (name, &ptr, 0);
+ if (ret == 0)
+ {
+ /* Assume that idn2_free is equivalent to free. */
+ *result = ptr;
+ return 0;
+ }
+ else if (ret == IDN2_MALLOC)
+ return EAI_MEMORY;
+ else
+ return EAI_IDN_ENCODE;
+}
+libc_hidden_def (__idna_to_dns_encoding)
+
+int
+__idna_from_dns_encoding (const char *name, char **result)
+{
+ struct functions *functions = get_functions ();
+ if (functions == NULL)
+ /* Simply use the encoded name, assuming that it is not punycode
+ (but even a punycode name would be syntactically valid). */
+ return gai_strdup (name, result);
+ char *ptr = NULL;
+ __typeof__ (functions->to_unicode_lzlz) fptr = functions->to_unicode_lzlz;
+#ifdef PTR_DEMANGLE
+ PTR_DEMANGLE (fptr);
+#endif
+ int ret = fptr (name, &ptr, 0);
+ if (ret == 0)
+ {
+ /* Assume that idn2_free is equivalent to free. */
+ *result = ptr;
+ return 0;
+ }
+ else if (ret == IDN2_MALLOC)
+ return EAI_MEMORY;
+ else
+ return EAI_IDN_ENCODE;
+}
+libc_hidden_def (__idna_from_dns_encoding)
diff --git a/inet/idna_name_classify.c b/inet/idna_name_classify.c
new file mode 100644
index 0000000000..3683e1133f
--- /dev/null
+++ b/inet/idna_name_classify.c
@@ -0,0 +1,75 @@
+/* Classify a domain name for IDNA purposes.
+ Copyright (C) 2018 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#include <errno.h>
+#include <inet/net-internal.h>
+#include <stdbool.h>
+#include <string.h>
+#include <wchar.h>
+
+enum idna_name_classification
+__idna_name_classify (const char *name)
+{
+ mbstate_t mbs;
+ memset (&mbs, 0, sizeof (mbs));
+ const char *p = name;
+ const char *end = p + strlen (p) + 1;
+ bool nonascii = false;
+ bool backslash = false;
+ while (true)
+ {
+ wchar_t wc;
+ size_t result = mbrtowc (&wc, p, end - p, &mbs);
+ if (result == 0)
+ /* NUL terminator was reached. */
+ break;
+ else if (result == (size_t) -2)
+ /* Incomplete trailing multi-byte character. This is an
+ encoding error becaue we received the full name. */
+ return idna_name_encoding_error;
+ else if (result == (size_t) -1)
+ {
+ /* Other error, including EILSEQ. */
+ if (errno == EILSEQ)
+ return idna_name_encoding_error;
+ else if (errno == ENOMEM)
+ return idna_name_memory_error;
+ else
+ return idna_name_error;
+ }
+ else
+ {
+ /* A wide character was decoded. */
+ p += result;
+ if (wc == L'\\')
+ backslash = true;
+ else if (wc > 127)
+ nonascii = true;
+ }
+ }
+
+ if (nonascii)
+ {
+ if (backslash)
+ return idna_name_nonascii_backslash;
+ else
+ return idna_name_nonascii;
+ }
+ else
+ return idna_name_ascii;
+}
diff --git a/inet/net-internal.h b/inet/net-internal.h
index 8a9505cf99..0ba6736aef 100644
--- a/inet/net-internal.h
+++ b/inet/net-internal.h
@@ -29,6 +29,33 @@ int __inet6_scopeid_pton (const struct in6_addr *address,
libc_hidden_proto (__inet6_scopeid_pton)
+/* IDNA conversion. These functions convert domain names between the
+ current multi-byte character set and the IDNA encoding. On
+ success, the result string is written to *RESULT (which the caller
+ has to free), and zero is returned. On error, an EAI_* error code
+ is returned (see <netdb.h>), and *RESULT is not changed. */
+int __idna_to_dns_encoding (const char *name, char **result);
+libc_hidden_proto (__idna_to_dns_encoding)
+int __idna_from_dns_encoding (const char *name, char **result);
+libc_hidden_proto (__idna_from_dns_encoding)
+
+
+/* Return value of __idna_name_classify below. */
+enum idna_name_classification
+{
+ idna_name_ascii, /* No non-ASCII characters. */
+ idna_name_nonascii, /* Non-ASCII characters, no backslash. */
+ idna_name_nonascii_backslash, /* Non-ASCII characters with backslash. */
+ idna_name_encoding_error, /* Decoding error. */
+ idna_name_memory_error, /* Memory allocation failure. */
+ idna_name_error, /* Other error during decoding. Check errno. */
+};
+
+/* Check the specified name for non-ASCII characters and backslashes
+ or encoding errors. */
+enum idna_name_classification __idna_name_classify (const char *name)
+ attribute_hidden;
+
/* Deadline handling for enforcing timeouts.
Code should call __deadline_current_time to obtain the current time
diff --git a/inet/tst-idna_name_classify.c b/inet/tst-idna_name_classify.c
new file mode 100644
index 0000000000..c4a2c91329
--- /dev/null
+++ b/inet/tst-idna_name_classify.c
@@ -0,0 +1,73 @@
+/* Test IDNA name classification.
+ Copyright (C) 2018 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#include <inet/net-internal.h>
+#include <locale.h>
+#include <stdio.h>
+#include <support/check.h>
+
+static void
+locale_insensitive_tests (void)
+{
+ TEST_COMPARE (__idna_name_classify (""), idna_name_ascii);
+ TEST_COMPARE (__idna_name_classify ("abc"), idna_name_ascii);
+ TEST_COMPARE (__idna_name_classify (".."), idna_name_ascii);
+ TEST_COMPARE (__idna_name_classify ("\001abc\177"), idna_name_ascii);
+ TEST_COMPARE (__idna_name_classify ("\\065bc"), idna_name_ascii);
+}
+
+static int
+do_test (void)
+{
+ puts ("info: C locale tests");
+ locale_insensitive_tests ();
+ TEST_COMPARE (__idna_name_classify ("abc\200def"),
+ idna_name_encoding_error);
+ TEST_COMPARE (__idna_name_classify ("abc\200\\def"),
+ idna_name_encoding_error);
+ TEST_COMPARE (__idna_name_classify ("abc\377def"),
+ idna_name_encoding_error);
+
+ puts ("info: en_US.ISO-8859-1 locale tests");
+ if (setlocale (LC_CTYPE, "en_US.ISO-8859-1") == 0)
+ FAIL_EXIT1 ("setlocale for en_US.ISO-8859-1: %m\n");
+ locale_insensitive_tests ();
+ TEST_COMPARE (__idna_name_classify ("abc\200def"), idna_name_nonascii);
+ TEST_COMPARE (__idna_name_classify ("abc\377def"), idna_name_nonascii);
+ TEST_COMPARE (__idna_name_classify ("abc\\\200def"),
+ idna_name_nonascii_backslash);
+ TEST_COMPARE (__idna_name_classify ("abc\200\\def"),
+ idna_name_nonascii_backslash);
+
+ puts ("info: en_US.UTF-8 locale tests");
+ if (setlocale (LC_CTYPE, "en_US.UTF-8") == 0)
+ FAIL_EXIT1 ("setlocale for en_US.UTF-8: %m\n");
+ locale_insensitive_tests ();
+ TEST_COMPARE (__idna_name_classify ("abc\xc3\x9f""def"), idna_name_nonascii);
+ TEST_COMPARE (__idna_name_classify ("abc\\\xc3\x9f""def"),
+ idna_name_nonascii_backslash);
+ TEST_COMPARE (__idna_name_classify ("abc\xc3\x9f\\def"),
+ idna_name_nonascii_backslash);
+ TEST_COMPARE (__idna_name_classify ("abc\200def"), idna_name_encoding_error);
+ TEST_COMPARE (__idna_name_classify ("abc\xc3""def"), idna_name_encoding_error);
+ TEST_COMPARE (__idna_name_classify ("abc\xc3"), idna_name_encoding_error);
+
+ return 0;
+}
+
+#include <support/test-driver.c>