Skip to content

Commit c9b6c60

Browse files
authored
Validate hostnames in DNS responses and discard from malicious servers (#406)
To prevent possible users having XSS issues due to intentionally malformed DNS replies, validate hostnames returned in responses and return EBADRESP if they are not valid. It is not clear what legitimate issues this may cause at this point. Bug Reported By: philipp.jeitner@sit.fraunhofer.de Fix By: Brad House (@bradh352)
1 parent 44c009b commit c9b6c60

File tree

8 files changed

+78
-28
lines changed

8 files changed

+78
-28
lines changed

src/lib/ares__parse_into_addrinfo.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ int ares__parse_into_addrinfo2(const unsigned char *abuf,
7070

7171
/* Expand the name from the question, and skip past the question. */
7272
aptr = abuf + HFIXEDSZ;
73-
status = ares__expand_name_for_response(aptr, abuf, alen, question_hostname, &len);
73+
status = ares__expand_name_for_response(aptr, abuf, alen, question_hostname, &len, 0);
7474
if (status != ARES_SUCCESS)
7575
return status;
7676
if (aptr + len + QFIXEDSZ > abuf + alen)
@@ -86,7 +86,7 @@ int ares__parse_into_addrinfo2(const unsigned char *abuf,
8686
for (i = 0; i < (int)ancount; i++)
8787
{
8888
/* Decode the RR up to the data field. */
89-
status = ares__expand_name_for_response(aptr, abuf, alen, &rr_name, &len);
89+
status = ares__expand_name_for_response(aptr, abuf, alen, &rr_name, &len, 0);
9090
if (status != ARES_SUCCESS)
9191
{
9292
rr_name = NULL;
@@ -188,7 +188,7 @@ int ares__parse_into_addrinfo2(const unsigned char *abuf,
188188
{
189189
got_cname = 1;
190190
status = ares__expand_name_for_response(aptr, abuf, alen, &rr_data,
191-
&len);
191+
&len, 1);
192192
if (status != ARES_SUCCESS)
193193
{
194194
goto failed_stat;

src/lib/ares_expand_name.c

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
#define MAX_INDIRS 50
3131

3232
static int name_length(const unsigned char *encoded, const unsigned char *abuf,
33-
int alen);
33+
int alen, int is_hostname);
3434

3535
/* Reserved characters for names that need to be escaped */
3636
static int is_reservedch(int ch)
@@ -52,6 +52,31 @@ static int is_reservedch(int ch)
5252
return 0;
5353
}
5454

55+
static int ares__isprint(int ch)
56+
{
57+
if (ch >= 0x20 && ch <= 0x7E)
58+
return 1;
59+
return 0;
60+
}
61+
62+
/* Character set allowed by hostnames */
63+
static int is_hostnamech(int ch)
64+
{
65+
/* [A-Za-z0-9-.]
66+
* Don't use isalnum() as it is locale-specific
67+
*/
68+
if (ch >= 'A' && ch <= 'Z')
69+
return 1;
70+
if (ch >= 'a' && ch <= 'z')
71+
return 1;
72+
if (ch >= '0' && ch <= '9')
73+
return 1;
74+
if (ch == '-' || ch == '.')
75+
return 1;
76+
77+
return 0;
78+
}
79+
5580
/* Expand an RFC1035-encoded domain name given by encoded. The
5681
* containing message is given by abuf and alen. The result given by
5782
* *s, which is set to a NUL-terminated allocated buffer. *enclen is
@@ -74,10 +99,15 @@ static int is_reservedch(int ch)
7499
*
75100
* Since the expanded name uses '.' as a label separator, we use
76101
* backslashes to escape periods or backslashes in the expanded name.
102+
*
103+
* If the result is expected to be a hostname, then no escaped data is allowed
104+
* and will return error.
77105
*/
78106

79-
int ares_expand_name(const unsigned char *encoded, const unsigned char *abuf,
80-
int alen, char **s, long *enclen)
107+
int ares__expand_name_validated(const unsigned char *encoded,
108+
const unsigned char *abuf,
109+
int alen, char **s, long *enclen,
110+
int is_hostname)
81111
{
82112
int len, indir = 0;
83113
char *q;
@@ -87,7 +117,7 @@ int ares_expand_name(const unsigned char *encoded, const unsigned char *abuf,
87117
size_t uns;
88118
} nlen;
89119

90-
nlen.sig = name_length(encoded, abuf, alen);
120+
nlen.sig = name_length(encoded, abuf, alen, is_hostname);
91121
if (nlen.sig < 0)
92122
return ARES_EBADNAME;
93123

@@ -135,9 +165,8 @@ int ares_expand_name(const unsigned char *encoded, const unsigned char *abuf,
135165
{
136166
/* Output as \DDD for consistency with RFC1035 5.1, except
137167
* for the special case of a root name response */
138-
if (!isprint(*p) && !(name_len == 1 && *p == 0))
168+
if (!ares__isprint(*p) && !(name_len == 1 && *p == 0))
139169
{
140-
141170
*q++ = '\\';
142171
*q++ = '0' + *p / 100;
143172
*q++ = '0' + (*p % 100) / 10;
@@ -170,11 +199,18 @@ int ares_expand_name(const unsigned char *encoded, const unsigned char *abuf,
170199
return ARES_SUCCESS;
171200
}
172201

202+
203+
int ares_expand_name(const unsigned char *encoded, const unsigned char *abuf,
204+
int alen, char **s, long *enclen)
205+
{
206+
return ares__expand_name_validated(encoded, abuf, alen, s, enclen, 0);
207+
}
208+
173209
/* Return the length of the expansion of an encoded domain name, or
174210
* -1 if the encoding is invalid.
175211
*/
176212
static int name_length(const unsigned char *encoded, const unsigned char *abuf,
177-
int alen)
213+
int alen, int is_hostname)
178214
{
179215
int n = 0, offset, indir = 0, top;
180216

@@ -212,16 +248,22 @@ static int name_length(const unsigned char *encoded, const unsigned char *abuf,
212248

213249
while (offset--)
214250
{
215-
if (!isprint(*encoded) && !(name_len == 1 && *encoded == 0))
251+
if (!ares__isprint(*encoded) && !(name_len == 1 && *encoded == 0))
216252
{
253+
if (is_hostname)
254+
return -1;
217255
n += 4;
218256
}
219257
else if (is_reservedch(*encoded))
220258
{
259+
if (is_hostname)
260+
return -1;
221261
n += 2;
222262
}
223263
else
224264
{
265+
if (is_hostname && !is_hostnamech(*encoded))
266+
return -1;
225267
n += 1;
226268
}
227269
encoded++;
@@ -244,12 +286,14 @@ static int name_length(const unsigned char *encoded, const unsigned char *abuf,
244286
return (n) ? n - 1 : n;
245287
}
246288

247-
/* Like ares_expand_name but returns EBADRESP in case of invalid input. */
289+
/* Like ares_expand_name_validated but returns EBADRESP in case of invalid
290+
* input. */
248291
int ares__expand_name_for_response(const unsigned char *encoded,
249292
const unsigned char *abuf, int alen,
250-
char **s, long *enclen)
293+
char **s, long *enclen, int is_hostname)
251294
{
252-
int status = ares_expand_name(encoded, abuf, alen, s, enclen);
295+
int status = ares__expand_name_validated(encoded, abuf, alen, s, enclen,
296+
is_hostname);
253297
if (status == ARES_EBADNAME)
254298
status = ARES_EBADRESP;
255299
return status;

src/lib/ares_parse_ns_reply.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ int ares_parse_ns_reply( const unsigned char* abuf, int alen,
6262

6363
/* Expand the name from the question, and skip past the question. */
6464
aptr = abuf + HFIXEDSZ;
65-
status = ares__expand_name_for_response( aptr, abuf, alen, &hostname, &len);
65+
status = ares__expand_name_for_response( aptr, abuf, alen, &hostname, &len, 0);
6666
if ( status != ARES_SUCCESS )
6767
return status;
6868
if ( aptr + len + QFIXEDSZ > abuf + alen )
@@ -85,7 +85,7 @@ int ares_parse_ns_reply( const unsigned char* abuf, int alen,
8585
for ( i = 0; i < ( int ) ancount; i++ )
8686
{
8787
/* Decode the RR up to the data field. */
88-
status = ares__expand_name_for_response( aptr, abuf, alen, &rr_name, &len );
88+
status = ares__expand_name_for_response( aptr, abuf, alen, &rr_name, &len, 0);
8989
if ( status != ARES_SUCCESS )
9090
break;
9191
aptr += len;
@@ -110,7 +110,7 @@ int ares_parse_ns_reply( const unsigned char* abuf, int alen,
110110
{
111111
/* Decode the RR data and add it to the nameservers list */
112112
status = ares__expand_name_for_response( aptr, abuf, alen, &rr_data,
113-
&len);
113+
&len, 1);
114114
if ( status != ARES_SUCCESS )
115115
{
116116
ares_free(rr_name);

src/lib/ares_parse_ptr_reply.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ int ares_parse_ptr_reply(const unsigned char *abuf, int alen, const void *addr,
6363

6464
/* Expand the name from the question, and skip past the question. */
6565
aptr = abuf + HFIXEDSZ;
66-
status = ares__expand_name_for_response(aptr, abuf, alen, &ptrname, &len);
66+
status = ares__expand_name_for_response(aptr, abuf, alen, &ptrname, &len, 0);
6767
if (status != ARES_SUCCESS)
6868
return status;
6969
if (aptr + len + QFIXEDSZ > abuf + alen)
@@ -84,7 +84,7 @@ int ares_parse_ptr_reply(const unsigned char *abuf, int alen, const void *addr,
8484
for (i = 0; i < (int)ancount; i++)
8585
{
8686
/* Decode the RR up to the data field. */
87-
status = ares__expand_name_for_response(aptr, abuf, alen, &rr_name, &len);
87+
status = ares__expand_name_for_response(aptr, abuf, alen, &rr_name, &len, 0);
8888
if (status != ARES_SUCCESS)
8989
break;
9090
aptr += len;
@@ -110,7 +110,7 @@ int ares_parse_ptr_reply(const unsigned char *abuf, int alen, const void *addr,
110110
{
111111
/* Decode the RR data and set hostname to it. */
112112
status = ares__expand_name_for_response(aptr, abuf, alen, &rr_data,
113-
&len);
113+
&len, 1);
114114
if (status != ARES_SUCCESS)
115115
{
116116
ares_free(rr_name);
@@ -146,7 +146,7 @@ int ares_parse_ptr_reply(const unsigned char *abuf, int alen, const void *addr,
146146
{
147147
/* Decode the RR data and replace ptrname with it. */
148148
status = ares__expand_name_for_response(aptr, abuf, alen, &rr_data,
149-
&len);
149+
&len, 1);
150150
if (status != ARES_SUCCESS)
151151
{
152152
ares_free(rr_name);

src/lib/ares_parse_soa_reply.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ ares_parse_soa_reply(const unsigned char *abuf, int alen,
6060
aptr = abuf + HFIXEDSZ;
6161

6262
/* query name */
63-
status = ares__expand_name_for_response(aptr, abuf, alen, &qname, &len);
63+
status = ares__expand_name_for_response(aptr, abuf, alen, &qname, &len, 0);
6464
if (status != ARES_SUCCESS)
6565
goto failed_stat;
6666

@@ -83,7 +83,7 @@ ares_parse_soa_reply(const unsigned char *abuf, int alen,
8383
for (i = 0; i < ancount; i++)
8484
{
8585
rr_name = NULL;
86-
status = ares__expand_name_for_response (aptr, abuf, alen, &rr_name, &len);
86+
status = ares__expand_name_for_response (aptr, abuf, alen, &rr_name, &len, 0);
8787
if (status != ARES_SUCCESS)
8888
{
8989
ares_free(rr_name);
@@ -120,7 +120,7 @@ ares_parse_soa_reply(const unsigned char *abuf, int alen,
120120

121121
/* nsname */
122122
status = ares__expand_name_for_response(aptr, abuf, alen, &soa->nsname,
123-
&len);
123+
&len, 0);
124124
if (status != ARES_SUCCESS)
125125
{
126126
ares_free(rr_name);
@@ -130,7 +130,7 @@ ares_parse_soa_reply(const unsigned char *abuf, int alen,
130130

131131
/* hostmaster */
132132
status = ares__expand_name_for_response(aptr, abuf, alen,
133-
&soa->hostmaster, &len);
133+
&soa->hostmaster, &len, 0);
134134
if (status != ARES_SUCCESS)
135135
{
136136
ares_free(rr_name);

src/lib/ares_private.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,9 +356,13 @@ int ares__read_line(FILE *fp, char **buf, size_t *bufsize);
356356
void ares__free_query(struct query *query);
357357
unsigned short ares__generate_new_id(rc4_key* key);
358358
struct timeval ares__tvnow(void);
359+
int ares__expand_name_validated(const unsigned char *encoded,
360+
const unsigned char *abuf,
361+
int alen, char **s, long *enclen,
362+
int is_hostname);
359363
int ares__expand_name_for_response(const unsigned char *encoded,
360364
const unsigned char *abuf, int alen,
361-
char **s, long *enclen);
365+
char **s, long *enclen, int is_hostname);
362366
void ares__init_servers_state(ares_channel channel);
363367
void ares__destroy_servers_state(ares_channel channel);
364368
int ares__parse_qtype_reply(const unsigned char* abuf, int alen, int* qtype);

src/lib/ares_process.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -605,8 +605,7 @@ static void process_answer(ares_channel channel, unsigned char *abuf,
605605
packetsz = PACKETSZ;
606606
/* If we use EDNS and server answers with FORMERR without an OPT RR, the protocol
607607
* extension is not understood by the responder. We must retry the query
608-
* without EDNS enabled.
609-
*/
608+
* without EDNS enabled. */
610609
if (channel->flags & ARES_FLAG_EDNS)
611610
{
612611
packetsz = channel->ednspsz;

test/ares-test-parse.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ TEST_F(LibraryTest, ParseIndirectRootName) {
6060
ares_free_hostent(host);
6161
}
6262

63+
64+
#if 0 /* We are validating hostnames now, its not clear how this would ever be valid */
6365
TEST_F(LibraryTest, ParseEscapedName) {
6466
std::vector<byte> data = {
6567
0x12, 0x34, // qid
@@ -105,6 +107,7 @@ TEST_F(LibraryTest, ParseEscapedName) {
105107
EXPECT_EQ('c', hent.name_[6]);
106108
ares_free_hostent(host);
107109
}
110+
#endif
108111

109112
TEST_F(LibraryTest, ParsePartialCompressedName) {
110113
std::vector<byte> data = {

0 commit comments

Comments
 (0)