]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Improve error handling in idn_ace_to_locale()
authorMichał Kępień <michal@isc.org>
Tue, 10 Jul 2018 12:34:35 +0000 (14:34 +0200)
committerMichał Kępień <michal@isc.org>
Tue, 10 Jul 2018 12:34:35 +0000 (14:34 +0200)
While idn2_to_unicode_8zlz() takes a 'flags' argument, it is ignored and
thus cannot be used to perform IDN checks on the output string.

The bug in libidn2 versions before 2.0.5 was not that a call to
idn2_to_unicode_8zlz() with certain flags set did not cause IDN checks
to be performed.  The bug was that idn2_to_unicode_8zlz() did not check
whether a conversion can be performed between UTF-8 and the current
locale's character encoding.  In other words, with libidn2 version
2.0.5+, if the current locale's character encoding is ASCII, then
idn2_to_unicode_8zlz() will fail when it is passed any Punycode string
which decodes to a non-ASCII string, even if it is a valid IDNA2008
name.

Rework idn_ace_to_locale() so that invalid IDNA2008 names are properly
and consistently detected for all libidn2 versions and locales.

Update the "idna" system test accordingly.  Add checks for processing a
server response containing Punycode which decodes to an invalid IDNA2008
name.  Fix invalid subtest description.

bin/dig/dighost.c
bin/tests/system/idna/tests.sh

index 30d523137520d9b8fed171b995864ee17cb1a095..b31900934b25673158bad9a78a833fb0c8e44219 100644 (file)
@@ -4282,16 +4282,61 @@ idn_locale_to_ace(const char *src, char *dst, size_t dstlen) {
  */
 static void
 idn_ace_to_locale(const char *src, char **dst) {
-       char *local_src;
+       char *local_src, *utf8_src;
        int res;
 
-       res = idn2_to_unicode_8zlz(src, &local_src,
-                                  IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL);
+       /*
+        * We need to:
+        *
+        *  1) check whether 'src' is a valid IDNA2008 name,
+        *  2) if it is, output it in the current locale's character encoding.
+        *
+        * Unlike idn2_to_ascii_*(), idn2_to_unicode_*() functions are unable
+        * to perform IDNA2008 validity checks.  Thus, we need to decode any
+        * Punycode in 'src', check if the resulting name is a valid IDNA2008
+        * name, and only once we ensure it is, output that name in the current
+        * locale's character encoding.
+        *
+        * We could just use idn2_to_unicode_8zlz() + idn2_to_ascii_lz(), but
+        * then we would not be able to universally tell invalid names and
+        * character encoding errors apart (if the current locale uses ASCII
+        * for character encoding, the former function would fail even for a
+        * valid IDNA2008 name, as long as it contained any non-ASCII
+        * character).  Thus, we need to take a longer route.
+        *
+        * First, convert 'src' to UTF-8, ignoring the current locale.
+        */
+       res = idn2_to_unicode_8z8z(src, &utf8_src, 0);
+       if (res != IDN2_OK) {
+               fatal("Bad ACE string '%s' (%s), use +noidnout",
+                     src, idn2_strerror(res));
+       }
+
+       /*
+        * Then, check whether decoded 'src' is a valid IDNA2008 name.
+        */
+       res = idn2_to_ascii_8z(utf8_src, NULL, IDN2_NONTRANSITIONAL);
        if (res != IDN2_OK) {
                fatal("'%s' is not a legal IDNA2008 name (%s), use +noidnout",
                      src, idn2_strerror(res));
        }
 
+       /*
+        * Finally, try converting the decoded 'src' into the current locale's
+        * character encoding.
+        */
+       res = idn2_to_unicode_8zlz(utf8_src, &local_src, 0);
+       if (res != IDN2_OK) {
+               fatal("Cannot represent '%s' in the current locale (%s), "
+                     "use +noidnout or a different locale",
+                     src, idn2_strerror(res));
+       }
+
+       /*
+        * Free the interim conversion result.
+        */
+       idn2_free(utf8_src);
+
        *dst = local_src;
 }
 #endif /* HAVE_LIBIDN2 */
index d7a968306a4b31542f57783206db6b7e81f6b364..644aa3eccb9249d8a6d120d17176058cb56cc23b 100644 (file)
@@ -136,46 +136,6 @@ idna_fail() {
     status=`expr $status + $ret`
 }
 
-# Check if current version of libidn2 is >= a given version
-#
-# This requires that:
-# a) "pkg-config" exists on the system
-# b) The libidn2 installed has an associated ".pc" file
-# c) The system sort command supports "-V"
-#
-# $1 - Minimum version required
-#
-# Returns:
-# 0 - Version check is OK, libidn2 at required version or greater.
-# 1 - Version check was made, but libidn2 not at required version.
-# 2 - Could not carry out version check
-
-libidn_version_check() {
-    ret=2
-    if [ -n "`command -v pkg-config`" ]; then
-        version=`pkg-config --modversion --silence-errors libidn2`
-        if [ -n "$version" ]; then
-            # Does the sort command have a "-V" flag on this system?
-            sort -V 2>&1 > /dev/null << .
-.
-            if [ $? -eq 0 ]; then
-                # Sort -V exists.  Sort the IDN version and the minimum version
-                # required.  If the IDN version is greater than or equal to that
-                # version, it will appear last in the list.
-                last_version=`printf "%s\n" $version $1 | sort -V | tail -1`
-                if [ "$version" = "$last_version" ]; then
-                    ret=0
-                else
-                    ret=1
-                fi
-            fi
-        fi
-    fi
-
-    return $ret
-}
-
-
 # Function to check that case is preserved for an all-ASCII label.
 #
 # Without IDNA support, case-preservation is the expected behavior.
@@ -310,16 +270,7 @@ idna_enabled_test() {
     text="Checking fake A-label"
     idna_fail "$text" ""                   "xn--ahahah"
     idna_test "$text" "+noidnin +noidnout" "xn--ahahah" "xn--ahahah."
-
-    # Owing to issues with libdns, the next test will fail for versions of
-    # libidn earlier than 2.0.5.  For this reason, get the version (if
-    # available) and compare with 2.0.5.
-    libidn_version_check 2.0.5
-    if [ $? -ne 0 ]; then
-        echo_i "Skipping fake A-label +noidnin +idnout test (libidn2 version issues)"
-    else
-        idna_test "$text" "+noidnin   +idnout" "xn--ahahah" "xn--ahahah."
-    fi
+    idna_fail "$text" "+noidnin   +idnout" "xn--ahahah"
     idna_fail "$text" "+idnin   +noidnout" "xn--ahahah"
     idna_fail "$text" "+idnin     +idnout" "xn--ahahah"
 
@@ -327,7 +278,7 @@ idna_enabled_test() {
     # BIND rejects such labels: with +idnin
 
     label="xn--xflod18hstflod18hstflod18hstflod18hstflod18hstflod18-1iejjjj"
-    text="Checking punycode label shorter than minimum valid length"
+    text="Checking punycode label longer than maximum valid length"
     idna_fail "$text" ""                   "$label"
     idna_fail "$text" "+noidnin +noidnout" "$label"
     idna_fail "$text" "+noidnin   +idnout" "$label"
@@ -337,10 +288,11 @@ idna_enabled_test() {
     
 
 
-    # Tests of a valid unicode string but an invalid U-label
+    # Tests of a valid unicode string but an invalid U-label (input)
     #
-    # Symbols are not valid IDNA2008 names.  Check whether dig rejects them to
-    # ensure no IDNA2003 fallbacks are in place.
+    # Symbols are not valid IDNA2008 names.  Check whether dig rejects them
+    # when they are supplied on the command line to ensure no IDNA2003
+    # fallbacks are in place.
     #
     # +noidnin: "dig" should send unicode octets to the server and display the
     #           returned qname in the same form.
@@ -348,12 +300,34 @@ idna_enabled_test() {
     #
     # The +[no]idnout options should not have any effect on the test.
 
-    text="Checking invalid U-label"
+    text="Checking invalid input U-label"
     idna_fail "$text" ""                   "√.com"
     idna_test "$text" "+noidnin +noidnout" "√.com" "\226\136\154.com."
     idna_test "$text" "+noidnin +idnout"   "√.com" "\226\136\154.com."
     idna_fail "$text" "+idnin   +noidnout" "√.com"
     idna_fail "$text" "+idnin   +idnout"   "√.com"
+
+    # Tests of a valid unicode string but an invalid U-label (output)
+    #
+    # Symbols are not valid IDNA2008 names.  Check whether dig rejects them
+    # when they are received in DNS responses to ensure no IDNA2003 fallbacks
+    # are in place.
+    #
+    # Note that an invalid U-label is accepted even when +idnin is in effect
+    # because "xn--19g" is valid Punycode.
+    #
+    # +noidnout: "dig" should send the ACE string to the server and display the
+    #            returned qname.
+    # +idnout:   "dig" should generate an error.
+    #
+    # The +[no]idnin options should not have any effect on the test.
+
+    text="Checking invalid output U-label"
+    idna_fail "$text" ""                   "xn--19g"
+    idna_test "$text" "+noidnin +noidnout" "xn--19g" "xn--19g."
+    idna_fail "$text" "+noidnin +idnout"   "xn--19g"
+    idna_test "$text" "+idnin   +noidnout" "xn--19g" "xn--19g."
+    idna_fail "$text" "+idnin   +idnout"   "xn--19g"
 }