Improve error handling in idn_ace_to_locale()

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.
This commit is contained in:
Michał Kępień 2018-07-10 14:34:35 +02:00
parent e5ef038134
commit b896fc4972
2 changed files with 77 additions and 58 deletions

View 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 */

View 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"
}