Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
CVE-2015-7547: getaddrinfo() stack-based buffer overflow (Bug 18665).
* A stack-based buffer overflow was found in libresolv when invoked from
  libnss_dns, allowing specially crafted DNS responses to seize control
  of execution flow in the DNS client.  The buffer overflow occurs in
  the functions send_dg (send datagram) and send_vc (send TCP) for the
  NSS module libnss_dns.so.2 when calling getaddrinfo with AF_UNSPEC
  family.  The use of AF_UNSPEC triggers the low-level resolver code to
  send out two parallel queries for A and AAAA.  A mismanagement of the
  buffers used for those queries could result in the response of a query
  writing beyond the alloca allocated buffer created by
  _nss_dns_gethostbyname4_r.  Buffer management is simplified to remove
  the overflow.  Thanks to the Google Security Team and Red Hat for
  reporting the security impact of this issue, and Robert Holiday of
  Ciena for reporting the related bug 18665. (CVE-2015-7547)

See also:
https://sourceware.org/ml/libc-alpha/2016-02/msg00416.html
https://sourceware.org/ml/libc-alpha/2016-02/msg00418.html
  • Loading branch information
Carlos O'Donell committed Feb 17, 2016
1 parent 2c8f75f commit e9db92d
Show file tree
Hide file tree
Showing 5 changed files with 339 additions and 66 deletions.
17 changes: 16 additions & 1 deletion ChangeLog
@@ -1,4 +1,19 @@
2016-02-14 Carlos O'Donelll <carlos@redhat.com>
2016-02-15 Carlos O'Donell <carlos@redhat.com>

[BZ #18665]
* resolv/nss_dns/dns-host.c (gaih_getanswer_slice): Always set
*herrno_p.
(gaih_getanswer): Document functional behviour. Return tryagain
if any result is tryagain.
* resolv/res_query.c (__libc_res_nsearch): Set buffer size to zero
when freed.
* resolv/res_send.c: Add copyright text.
(__libc_res_nsend): Document that MAXPACKET is expected.
(send_vc): Document. Remove buffer reuse.
(send_dg): Document. Remove buffer reuse. Set *thisanssizp to set the
size of the buffer. Add Dprint for truncated UDP buffer.

2016-02-14 Carlos O'Donell <carlos@redhat.com>

* manual/install.texi: Latest tested is GCC 5.3, texinfo 6.0, gawk
4.1.3, and sed 4.2.2. Remove po2test.sed comments.
Expand Down
14 changes: 14 additions & 0 deletions NEWS
Expand Up @@ -79,6 +79,20 @@ Security related changes:
depending on the length of the string passed as an argument to the
functions. Reported by Joseph Myers.

* A stack-based buffer overflow was found in libresolv when invoked from
libnss_dns, allowing specially crafted DNS responses to seize control
of execution flow in the DNS client. The buffer overflow occurs in
the functions send_dg (send datagram) and send_vc (send TCP) for the
NSS module libnss_dns.so.2 when calling getaddrinfo with AF_UNSPEC
family. The use of AF_UNSPEC triggers the low-level resolver code to
send out two parallel queries for A and AAAA. A mismanagement of the
buffers used for those queries could result in the response of a query
writing beyond the alloca allocated buffer created by
_nss_dns_gethostbyname4_r. Buffer management is simplified to remove
the overflow. Thanks to the Google Security Team and Red Hat for
reporting the security impact of this issue, and Robert Holiday of
Ciena for reporting the related bug 18665. (CVE-2015-7547)

* The following bugs are resolved with this release:

[The release manager will add the list generated by
Expand Down
111 changes: 109 additions & 2 deletions resolv/nss_dns/dns-host.c
Expand Up @@ -1031,7 +1031,10 @@ gaih_getanswer_slice (const querybuf *answer, int anslen, const char *qname,
int h_namelen = 0;

if (ancount == 0)
return NSS_STATUS_NOTFOUND;
{
*h_errnop = HOST_NOT_FOUND;
return NSS_STATUS_NOTFOUND;
}

while (ancount-- > 0 && cp < end_of_message && had_error == 0)
{
Expand Down Expand Up @@ -1208,7 +1211,14 @@ gaih_getanswer_slice (const querybuf *answer, int anslen, const char *qname,
/* Special case here: if the resolver sent a result but it only
contains a CNAME while we are looking for a T_A or T_AAAA record,
we fail with NOTFOUND instead of TRYAGAIN. */
return canon == NULL ? NSS_STATUS_TRYAGAIN : NSS_STATUS_NOTFOUND;
if (canon != NULL)
{
*h_errnop = HOST_NOT_FOUND;
return NSS_STATUS_NOTFOUND;
}

*h_errnop = NETDB_INTERNAL;
return NSS_STATUS_TRYAGAIN;
}


Expand All @@ -1222,11 +1232,101 @@ gaih_getanswer (const querybuf *answer1, int anslen1, const querybuf *answer2,

enum nss_status status = NSS_STATUS_NOTFOUND;

/* Combining the NSS status of two distinct queries requires some
compromise and attention to symmetry (A or AAAA queries can be
returned in any order). What follows is a breakdown of how this
code is expected to work and why. We discuss only SUCCESS,
TRYAGAIN, NOTFOUND and UNAVAIL, since they are the only returns
that apply (though RETURN and MERGE exist). We make a distinction
between TRYAGAIN (recoverable) and TRYAGAIN' (not-recoverable).
A recoverable TRYAGAIN is almost always due to buffer size issues
and returns ERANGE in errno and the caller is expected to retry
with a larger buffer.
Lastly, you may be tempted to make significant changes to the
conditions in this code to bring about symmetry between responses.
Please don't change anything without due consideration for
expected application behaviour. Some of the synthesized responses
aren't very well thought out and sometimes appear to imply that
IPv4 responses are always answer 1, and IPv6 responses are always
answer 2, but that's not true (see the implementation of send_dg
and send_vc to see response can arrive in any order, particularly
for UDP). However, we expect it holds roughly enough of the time
that this code works, but certainly needs to be fixed to make this
a more robust implementation.
----------------------------------------------
| Answer 1 Status / | Synthesized | Reason |
| Answer 2 Status | Status | |
|--------------------------------------------|
| SUCCESS/SUCCESS | SUCCESS | [1] |
| SUCCESS/TRYAGAIN | TRYAGAIN | [5] |
| SUCCESS/TRYAGAIN' | SUCCESS | [1] |
| SUCCESS/NOTFOUND | SUCCESS | [1] |
| SUCCESS/UNAVAIL | SUCCESS | [1] |
| TRYAGAIN/SUCCESS | TRYAGAIN | [2] |
| TRYAGAIN/TRYAGAIN | TRYAGAIN | [2] |
| TRYAGAIN/TRYAGAIN' | TRYAGAIN | [2] |
| TRYAGAIN/NOTFOUND | TRYAGAIN | [2] |
| TRYAGAIN/UNAVAIL | TRYAGAIN | [2] |
| TRYAGAIN'/SUCCESS | SUCCESS | [3] |
| TRYAGAIN'/TRYAGAIN | TRYAGAIN | [3] |
| TRYAGAIN'/TRYAGAIN' | TRYAGAIN' | [3] |
| TRYAGAIN'/NOTFOUND | TRYAGAIN' | [3] |
| TRYAGAIN'/UNAVAIL | UNAVAIL | [3] |
| NOTFOUND/SUCCESS | SUCCESS | [3] |
| NOTFOUND/TRYAGAIN | TRYAGAIN | [3] |
| NOTFOUND/TRYAGAIN' | TRYAGAIN' | [3] |
| NOTFOUND/NOTFOUND | NOTFOUND | [3] |
| NOTFOUND/UNAVAIL | UNAVAIL | [3] |
| UNAVAIL/SUCCESS | UNAVAIL | [4] |
| UNAVAIL/TRYAGAIN | UNAVAIL | [4] |
| UNAVAIL/TRYAGAIN' | UNAVAIL | [4] |
| UNAVAIL/NOTFOUND | UNAVAIL | [4] |
| UNAVAIL/UNAVAIL | UNAVAIL | [4] |
----------------------------------------------
[1] If the first response is a success we return success.
This ignores the state of the second answer and in fact
incorrectly sets errno and h_errno to that of the second
answer. However because the response is a success we ignore
*errnop and *h_errnop (though that means you touched errno on
success). We are being conservative here and returning the
likely IPv4 response in the first answer as a success.
[2] If the first response is a recoverable TRYAGAIN we return
that instead of looking at the second response. The
expectation here is that we have failed to get an IPv4 response
and should retry both queries.
[3] If the first response was not a SUCCESS and the second
response is not NOTFOUND (had a SUCCESS, need to TRYAGAIN,
or failed entirely e.g. TRYAGAIN' and UNAVAIL) then use the
result from the second response, otherwise the first responses
status is used. Again we have some odd side-effects when the
second response is NOTFOUND because we overwrite *errnop and
*h_errnop that means that a first answer of NOTFOUND might see
its *errnop and *h_errnop values altered. Whether it matters
in practice that a first response NOTFOUND has the wrong
*errnop and *h_errnop is undecided.
[4] If the first response is UNAVAIL we return that instead of
looking at the second response. The expectation here is that
it will have failed similarly e.g. configuration failure.
[5] Testing this code is complicated by the fact that truncated
second response buffers might be returned as SUCCESS if the
first answer is a SUCCESS. To fix this we add symmetry to
TRYAGAIN with the second response. If the second response
is a recoverable error we now return TRYAGIN even if the first
response was SUCCESS. */

if (anslen1 > 0)
status = gaih_getanswer_slice(answer1, anslen1, qname,
&pat, &buffer, &buflen,
errnop, h_errnop, ttlp,
&first);

if ((status == NSS_STATUS_SUCCESS || status == NSS_STATUS_NOTFOUND
|| (status == NSS_STATUS_TRYAGAIN
/* We want to look at the second answer in case of an
Expand All @@ -1242,8 +1342,15 @@ gaih_getanswer (const querybuf *answer1, int anslen1, const querybuf *answer2,
&pat, &buffer, &buflen,
errnop, h_errnop, ttlp,
&first);
/* Use the second response status in some cases. */
if (status != NSS_STATUS_SUCCESS && status2 != NSS_STATUS_NOTFOUND)
status = status2;
/* Do not return a truncated second response (unless it was
unavoidable e.g. unrecoverable TRYAGAIN). */
if (status == NSS_STATUS_SUCCESS
&& (status2 == NSS_STATUS_TRYAGAIN
&& *errnop == ERANGE && *h_errnop != NO_RECOVERY))
status = NSS_STATUS_TRYAGAIN;
}

return status;
Expand Down
3 changes: 3 additions & 0 deletions resolv/res_query.c
Expand Up @@ -396,6 +396,7 @@ __libc_res_nsearch(res_state statp,
{
free (*answerp2);
*answerp2 = NULL;
*nanswerp2 = 0;
*answerp2_malloced = 0;
}
}
Expand Down Expand Up @@ -447,6 +448,7 @@ __libc_res_nsearch(res_state statp,
{
free (*answerp2);
*answerp2 = NULL;
*nanswerp2 = 0;
*answerp2_malloced = 0;
}

Expand Down Expand Up @@ -521,6 +523,7 @@ __libc_res_nsearch(res_state statp,
{
free (*answerp2);
*answerp2 = NULL;
*nanswerp2 = 0;
*answerp2_malloced = 0;
}
if (saved_herrno != -1)
Expand Down

0 comments on commit e9db92d

Please sign in to comment.