1
0
mirror of https://sourceware.org/git/glibc.git synced 2025-08-07 06:43:00 +03:00

resolv: More precise checks in res_hnok, res_dnok [BZ #22409] [BZ #22412]

res_hnok rejected some host names used on the Internet, such as
www-.example.com.  res_hnok and res_dnok failed to perform basic syntax
checking on DNS domain names.

Also fix res_mailok, res_ownok.
This commit is contained in:
Florian Weimer
2017-11-11 11:51:08 +01:00
parent 9e0ad3049d
commit c0a25aa92b
4 changed files with 158 additions and 98 deletions

View File

@@ -1,3 +1,18 @@
2017-11-11 Florian Weimer <fweimer@redhat.com>
[BZ #22409]
[BZ #22412]
* resolv/res_comp.c (printable_string, binary_hnok)
(binary_leading_dash): New functions.
(res_hnok): Reimplement using these functions and ns_name_pton.
(res_ownok): Likewise.
(res_mailok): Reimplement using printable_string, ns_name_pton and
binary_hnok.
(res_dnok): Reimplement using printable_string and ns_name_pton.
* resolv/tst-res_hnok.c (tests): Add additional tests.
(LETTERDIGITS, PRINTABLE): Define.
(do_test): Adjust one_char results.
2017-11-11 Florian Weimer <fweimer@redhat.com> 2017-11-11 Florian Weimer <fweimer@redhat.com>
[BZ #22413] [BZ #22413]

3
NEWS
View File

@@ -60,6 +60,9 @@ Deprecated and removed features, and other changes affecting compatibility:
glibc has been removed. The --enable-add-ons configure option is now glibc has been removed. The --enable-add-ons configure option is now
ignored. ignored.
* The res_hnok, res_dnok, res_mailok and res_ownok functions now check that
the specified string can be parsed as a domain name.
Changes to build and runtime requirements: Changes to build and runtime requirements:
[Add changes to build and runtime requirements here] [Add changes to build and runtime requirements here]

View File

@@ -1,3 +1,21 @@
/* Domain name processing functions.
Copyright (C) 1995-2017 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/>. */
/* /*
* Copyright (c) 1985, 1993 * Copyright (c) 1985, 1993
* The Regents of the University of California. All rights reserved. * The Regents of the University of California. All rights reserved.
@@ -121,110 +139,118 @@ dn_skipname(const u_char *ptr, const u_char *eom) {
} }
libresolv_hidden_def (dn_skipname) libresolv_hidden_def (dn_skipname)
/* /* Return true if the string consists of printable ASCII characters
* Verify that a domain name uses an acceptable character set. only. */
*/ static bool
printable_string (const char *dn)
{
while (true)
{
char ch = *dn;
if (ch == '\0')
return true;
if (ch <= ' ' || ch > '~')
return false;
++dn;
}
}
/* /* Return true if DN points to a name consisting only of [0-9a-zA-Z_-]
* Note the conspicuous absence of ctype macros in these definitions. On characters. DN must be in DNS wire format, without
* non-ASCII hosts, we can't depend on string literals or ctype macros to compression. */
* tell us anything about network-format data. The rest of the BIND system static bool
* is not careful about this, but for some reason, we're doing it right here. binary_hnok (const unsigned char *dn)
*/ {
#define PERIOD 0x2e while (true)
#define hyphenchar(c) ((c) == 0x2d) {
#define underscorechar(c) ((c) == 0x5f) size_t label_length = *dn;
#define bslashchar(c) ((c) == 0x5c) if (label_length == 0)
#define periodchar(c) ((c) == PERIOD) break;
#define asterchar(c) ((c) == 0x2a) ++dn;
#define alphachar(c) (((c) >= 0x41 && (c) <= 0x5a) \ const unsigned char *label_end = dn + label_length;
|| ((c) >= 0x61 && (c) <= 0x7a)) do
#define digitchar(c) ((c) >= 0x30 && (c) <= 0x39) {
unsigned char ch = *dn;
if (!(('0' <= ch && ch <= '9')
|| ('A' <= ch && ch <= 'Z')
|| ('a' <= ch && ch <= 'z')
|| ch == '-' || ch == '_'))
return false;
++dn;
}
while (dn < label_end);
}
return true;
}
#define borderchar(c) (alphachar(c) || digitchar(c)) /* Return true if the binary domain name has a first labels which
#define middlechar(c) (borderchar(c) || hyphenchar(c) || underscorechar(c)) starts with '-'. */
#define domainchar(c) ((c) > 0x20 && (c) < 0x7f) static inline bool
binary_leading_dash (const unsigned char *dn)
{
return dn[0] > 0 && dn[1] == '-';
}
/* Return 1 if res_hnok is a valid host name. Labels must only
contain [0-9a-zA-Z_-] characters, and the name must not start with
a '-'. The latter is to avoid confusion with program options. */
int int
res_hnok(const char *dn) { res_hnok (const char *dn)
int pch = PERIOD, ch = *dn++; {
unsigned char buf[NS_MAXCDNAME];
while (ch != '\0') { if (!printable_string (dn)
int nch = *dn++; || ns_name_pton (dn, buf, sizeof (buf)) < 0
|| binary_leading_dash (buf))
if (periodchar(ch)) { return 0;
(void)NULL; return binary_hnok (buf);
} else if (periodchar(pch)) {
if (!borderchar(ch))
return (0);
} else if (periodchar(nch) || nch == '\0') {
if (!borderchar(ch))
return (0);
} else {
if (!middlechar(ch))
return (0);
}
pch = ch, ch = nch;
}
return (1);
} }
libresolv_hidden_def (res_hnok) libresolv_hidden_def (res_hnok)
/* /* Hostname-like (A, MX, WKS) owners can have "*" as their first label
* hostname-like (A, MX, WKS) owners can have "*" as their first label but must otherwise be as a host name. */
* but must otherwise be as a host name.
*/
int int
res_ownok(const char *dn) { res_ownok (const char *dn)
if (asterchar(dn[0])) { {
if (periodchar(dn[1])) unsigned char buf[NS_MAXCDNAME];
return (res_hnok(dn+2)); if (!printable_string (dn)
if (dn[1] == '\0') || ns_name_pton (dn, buf, sizeof (buf)) < 0
return (1); || binary_leading_dash (buf))
} return 0;
return (res_hnok(dn)); if (buf[0] == 1 && buf [1] == '*')
/* Skip over the leading "*." part. */
return binary_hnok (buf + 2);
else
return binary_hnok (buf);
} }
/* /* SOA RNAMEs and RP RNAMEs can have any byte in their first label,
* SOA RNAMEs and RP RNAMEs can have any printable character in their first but the rest of the name has to look like a host name. */
* label, but the rest of the name has to look like a host name.
*/
int int
res_mailok(const char *dn) { res_mailok (const char *dn)
int ch, escaped = 0; {
unsigned char buf[NS_MAXCDNAME];
if (!printable_string (dn)
|| ns_name_pton (dn, buf, sizeof (buf)) < 0)
return 0;
unsigned char label_length = buf[0];
/* "." is a valid missing representation */ /* "." is a valid missing representation */
if (*dn == '\0') if (label_length == 0)
return (1); return 1;
/* Skip over the first label. */
/* otherwise <label>.<hostname> */ unsigned char *tail = buf + 1 + label_length;
while ((ch = *dn++) != '\0') { if (*tail == 0)
if (!domainchar(ch)) /* More than one label is required (except for "."). */
return (0); return 0;
if (!escaped && periodchar(ch)) return binary_hnok (tail);
break;
if (escaped)
escaped = 0;
else if (bslashchar(ch))
escaped = 1;
}
if (periodchar(ch))
return (res_hnok(dn));
return (0);
} }
/* /* Return 1 if DN is a syntactically valid domain name. Empty names
* This function is quite liberal, since RFC 1034's character sets are only are accepted. */
* recommendations.
*/
int int
res_dnok(const char *dn) { res_dnok (const char *dn)
int ch; {
unsigned char buf[NS_MAXCDNAME];
while ((ch = *dn++) != '\0') return printable_string (dn) && ns_name_pton (dn, buf, sizeof (buf)) >= 0;
if (!domainchar(ch))
return (0);
return (1);
} }
libresolv_hidden_def (res_dnok) libresolv_hidden_def (res_dnok)

View File

@@ -51,19 +51,31 @@ static const struct test_case tests[] =
{ {
{ "", allok }, { "", allok },
{ ".", allok }, { ".", allok },
{ "..", 0 },
{ "www", allnomailok }, { "www", allnomailok },
{ "www.", allnomailok },
{ "example", allnomailok }, { "example", allnomailok },
{ "example.com", allok }, { "example.com", allok },
{ "www.example.com", allok }, { "www.example.com", allok },
{ "www.example.com.", allok }, { "www.example.com.", allok },
{ "www-.example.com.", allok },
{ "www.-example.com.", allok },
{ "*.example.com", dnok | mailok | ownok }, { "*.example.com", dnok | mailok | ownok },
{ "-v", dnok }, { "-v", dnok },
{ "-v.example.com", mailok | dnok }, { "-v.example.com", mailok | dnok },
{ "**.example.com", dnok | mailok }, { "**.example.com", dnok | mailok },
{ "www.example.com\\", 0 },
{ STRING63, allnomailok }, { STRING63, allnomailok },
{ STRING63 ".", allnomailok },
{ STRING63 "\\.", 0 },
{ STRING63 "z", 0 },
{ STRING63 ".example.com", allok }, { STRING63 ".example.com", allok },
{ STRING63 "." STRING63 "." STRING63 "." STRING60 "z", allok }, { STRING63 "." STRING63 "." STRING63 "." STRING60 "z", allok },
{ STRING63 "." STRING63 "." STRING63 "." STRING60 "z.", allok },
{ STRING63 "." STRING63 "." STRING63 "." STRING60 "zz", 0 },
{ STRING63 "." STRING63 "." STRING63 "." STRING60 "zzz", 0 },
{ "hostmaster@mail.example.com", dnok | mailok }, { "hostmaster@mail.example.com", dnok | mailok },
{ "hostmaster\\@mail.example.com", dnok | mailok },
{ "with whitespace", 0 }, { "with whitespace", 0 },
{ "with\twhitespace", 0 }, { "with\twhitespace", 0 },
{ "with\nwhitespace", 0 }, { "with\nwhitespace", 0 },
@@ -116,6 +128,12 @@ one_char (const char *prefix, const char *accepted, const char *suffix,
} }
} }
#define LETTERSDIGITS \
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
#define PRINTABLE \
"!\"#$%&'()*+,/:;<=>?@[\\]^`{|}~"
static int static int
do_test (void) do_test (void)
{ {
@@ -131,20 +149,18 @@ do_test (void)
} }
one_char one_char
("", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.", ("", LETTERSDIGITS "._", "", "res_hnok", res_hnok);
"", "res_hnok", res_hnok);
one_char one_char
("middle", ("middle",
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.-_", LETTERSDIGITS ".-_\\", /* "middle\\suffix" == "middlesuffix", so good. */
"suffix", "res_hnok", res_hnok); "suffix", "res_hnok", res_hnok);
one_char one_char
("middle", ("middle",
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.-_" LETTERSDIGITS ".-_" PRINTABLE,
"!\"#$%&'()*+,/:;<=>?@[\\]^`{|}~",
"suffix.example", "res_mailok", res_mailok); "suffix.example", "res_mailok", res_mailok);
one_char one_char
("mailbox.middle", ("mailbox.middle",
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.-_", LETTERSDIGITS ".-_\\",
"suffix.example", "res_mailok", res_mailok); "suffix.example", "res_mailok", res_mailok);
return 0; return 0;