Message ID | 56C32DB0.7090409@redhat.com |
---|---|
State | New |
Headers | show |
On 16 Feb 2016 09:09, Carlos O'Donell wrote: many of us have seen this already, so were you going to wait for much public review before pushing it ? assuming you'll be applying it to the older branches too. just nits below as i'm not familiar with resolver internals. > --- a/resolv/nss_dns/dns-host.c > +++ b/resolv/nss_dns/dns-host.c > > + answer 2, but that's not true (see the implemetnation of send_dg "implementation" > + and send_vc to see response can arrive in any order, particlarly "particularly" > + [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. inconsistency with indentation > + /* Do not return a truncated second response (unless it was > + unavoidable e.g. unrecoverable TRYAGAIN). */ 2nd line should use a tab > --- a/resolv/res_send.c > +++ b/resolv/res_send.c > > + Please also note that for TCP we send both queries over the same > + socket one after another. This technically violates best practice > + since the server is allowed to read the first query, respond, and > + then close the socket (to service another client). If the server > + does this, then the remaining second query in the socket data buffer > + will cause the server to send the client an RST which will arrive > + asynchronously and the client's OS will likely tear down the socket > + receive buffer resulting in a potentially short read and lost > + response data. This will force the client to retry the query again, > + and this process may repeat until all servers and connection resets > + are exhausted and then the query will fail. It's not known if this > + happens with any frequency in real DNS server implementations. This > + implementation should be corrected to use two sockets by default for > + parallel queries. should we open a bug now for this ? > + packets header feild TC will bet set to 1, indicating a truncated "field" > + /* Skip the second response if there is no second query. > + To do that we mark the second response as received. */ 2nd line should use tabs. this comes up twice in the file. -mike
On 02/16/2016 01:13 PM, Mike Frysinger wrote: > On 16 Feb 2016 09:09, Carlos O'Donell wrote: > > many of us have seen this already, so were you going to wait for much > public review before pushing it ? assuming you'll be applying it to > the older branches too. Thanks for the review. Committing the patch to master is not time sensitive except for the upcoming release. I will coordinate with Adhemerval so the branch is not cut until we commit this fix. I will be committing shortly. I will also be following up quickly if anyone finds any other problems (which I hope don't exist). I'll be looking at backports based on Fedora's requirements and those requirements from others. Any help from Debian, Ubuntu, or Gentoo to prepare and test those backports is much appreciated. Basically you want all of the tests in the regression test framework to pass cleanly both with and without valgrind. Obviously committing to master is a pre-requisite to backporting. > just nits below as i'm not familiar with resolver internals. > >> --- a/resolv/nss_dns/dns-host.c >> +++ b/ >> resolv/nss_dns/dns-host.c >> + answer 2, but that's not true (see the implemetnation of send_dg > > "implementation" Fixed. > >> + and send_vc to see response can arrive in any order, particlarly > > "particularly" Fixed. > >> + [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. > > inconsistency with indentation Fixed. >> + /* Do not return a truncated second response (unless it was >> + unavoidable e.g. unrecoverable TRYAGAIN). */ > > 2nd line should use a tab Fixed. >> --- a/resolv/res_send.c >> +++ b/resolv/res_send.c >> >> + Please also note that for TCP we send both queries over the same >> + socket one after another. This technically violates best practice >> + since the server is allowed to read the first query, respond, and >> + then close the socket (to service another client). If the server >> + does this, then the remaining second query in the socket data buffer >> + will cause the server to send the client an RST which will arrive >> + asynchronously and the client's OS will likely tear down the socket >> + receive buffer resulting in a potentially short read and lost >> + response data. This will force the client to retry the query again, >> + and this process may repeat until all servers and connection resets >> + are exhausted and then the query will fail. It's not known if this >> + happens with any frequency in real DNS server implementations. This >> + implementation should be corrected to use two sockets by default for >> + parallel queries. > > should we open a bug now for this ? Yes. Would you mind helping with that? >> + packets header feild TC will bet set to 1, indicating a truncated > > "field" Fixed (twice). >> + /* Skip the second response if there is no second query. >> + To do that we mark the second response as received. */ > > 2nd line should use tabs. this comes up twice in the file. Fixed (twice). Cheers, Carlos.
On 16-02-2016 16:24, Carlos O'Donell wrote: > On 02/16/2016 01:13 PM, Mike Frysinger wrote: >> On 16 Feb 2016 09:09, Carlos O'Donell wrote: >> >> many of us have seen this already, so were you going to wait for much >> public review before pushing it ? assuming you'll be applying it to >> the older branches too. > > Thanks for the review. > > Committing the patch to master is not time sensitive except for the > upcoming release. I will coordinate with Adhemerval so the branch is > not cut until we commit this fix. Although the date is set for next 18th, I will wait until you give me an ok. > > I will be committing shortly. I will also be following up quickly if > anyone finds any other problems (which I hope don't exist). > > I'll be looking at backports based on Fedora's requirements and those > requirements from others. Any help from Debian, Ubuntu, or Gentoo to > prepare and test those backports is much appreciated. Basically you > want all of the tests in the regression test framework to pass cleanly > both with and without valgrind. Obviously committing to master is a > pre-requisite to backporting. > >> just nits below as i'm not familiar with resolver internals. >> >>> --- a/resolv/nss_dns/dns-host.c >>> +++ b/ >>> resolv/nss_dns/dns-host.c >>> + answer 2, but that's not true (see the implemetnation of send_dg >> >> "implementation" > > Fixed. > >> >>> + and send_vc to see response can arrive in any order, particlarly >> >> "particularly" > > Fixed. > >> >>> + [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. >> >> inconsistency with indentation > > Fixed. > >>> + /* Do not return a truncated second response (unless it was >>> + unavoidable e.g. unrecoverable TRYAGAIN). */ >> >> 2nd line should use a tab > > Fixed. > >>> --- a/resolv/res_send.c >>> +++ b/resolv/res_send.c >>> >>> + Please also note that for TCP we send both queries over the same >>> + socket one after another. This technically violates best practice >>> + since the server is allowed to read the first query, respond, and >>> + then close the socket (to service another client). If the server >>> + does this, then the remaining second query in the socket data buffer >>> + will cause the server to send the client an RST which will arrive >>> + asynchronously and the client's OS will likely tear down the socket >>> + receive buffer resulting in a potentially short read and lost >>> + response data. This will force the client to retry the query again, >>> + and this process may repeat until all servers and connection resets >>> + are exhausted and then the query will fail. It's not known if this >>> + happens with any frequency in real DNS server implementations. This >>> + implementation should be corrected to use two sockets by default for >>> + parallel queries. >> >> should we open a bug now for this ? > > Yes. Would you mind helping with that? > >>> + packets header feild TC will bet set to 1, indicating a truncated >> >> "field" > > Fixed (twice). > >>> + /* Skip the second response if there is no second query. >>> + To do that we mark the second response as received. */ >> >> 2nd line should use tabs. this comes up twice in the file. > > Fixed (twice). > > Cheers, > Carlos. >
On 16 Feb 2016 17:31, Adhemerval Zanella wrote: > On 16-02-2016 16:24, Carlos O'Donell wrote: > > On 02/16/2016 01:13 PM, Mike Frysinger wrote: > >> On 16 Feb 2016 09:09, Carlos O'Donell wrote: > >> > >> many of us have seen this already, so were you going to wait for much > >> public review before pushing it ? assuming you'll be applying it to > >> the older branches too. > > > > Thanks for the review. > > > > Committing the patch to master is not time sensitive except for the > > upcoming release. I will coordinate with Adhemerval so the branch is > > not cut until we commit this fix. > > Although the date is set for next 18th, I will wait until you give me > an ok. you mean Feb 18th right ? -mike
On 16 Feb 2016 13:24, Carlos O'Donell wrote: > >> --- a/resolv/res_send.c > >> +++ b/resolv/res_send.c > >> > >> + Please also note that for TCP we send both queries over the same > >> + socket one after another. This technically violates best practice > >> + since the server is allowed to read the first query, respond, and > >> + then close the socket (to service another client). If the server > >> + does this, then the remaining second query in the socket data buffer > >> + will cause the server to send the client an RST which will arrive > >> + asynchronously and the client's OS will likely tear down the socket > >> + receive buffer resulting in a potentially short read and lost > >> + response data. This will force the client to retry the query again, > >> + and this process may repeat until all servers and connection resets > >> + are exhausted and then the query will fail. It's not known if this > >> + happens with any frequency in real DNS server implementations. This > >> + implementation should be corrected to use two sockets by default for > >> + parallel queries. > > > > should we open a bug now for this ? > > Yes. Would you mind helping with that? https://sourceware.org/bugzilla/show_bug.cgi?id=19646 -mike
On 02/16/2016 04:00 PM, Mike Frysinger wrote: > On 16 Feb 2016 13:24, Carlos O'Donell wrote: >>>> --- a/resolv/res_send.c >>>> +++ b/resolv/res_send.c >>>> >>>> + Please also note that for TCP we send both queries over the same >>>> + socket one after another. This technically violates best practice >>>> + since the server is allowed to read the first query, respond, and >>>> + then close the socket (to service another client). If the server >>>> + does this, then the remaining second query in the socket data buffer >>>> + will cause the server to send the client an RST which will arrive >>>> + asynchronously and the client's OS will likely tear down the socket >>>> + receive buffer resulting in a potentially short read and lost >>>> + response data. This will force the client to retry the query again, >>>> + and this process may repeat until all servers and connection resets >>>> + are exhausted and then the query will fail. It's not known if this >>>> + happens with any frequency in real DNS server implementations. This >>>> + implementation should be corrected to use two sockets by default for >>>> + parallel queries. >>> >>> should we open a bug now for this ? >> >> Yes. Would you mind helping with that? > > https://sourceware.org/bugzilla/show_bug.cgi?id=19646 Awesome. Thanks. Cheers, Carlos.
On 02/16/2016 02:31 PM, Adhemerval Zanella wrote: > > > On 16-02-2016 16:24, Carlos O'Donell wrote: >> On 02/16/2016 01:13 PM, Mike Frysinger wrote: >>> On 16 Feb 2016 09:09, Carlos O'Donell wrote: >>> >>> many of us have seen this already, so were you going to wait for much >>> public review before pushing it ? assuming you'll be applying it to >>> the older branches too. >> >> Thanks for the review. >> >> Committing the patch to master is not time sensitive except for the >> upcoming release. I will coordinate with Adhemerval so the branch is >> not cut until we commit this fix. > > Although the date is set for next 18th, I will wait until you give me > an ok. Pushed. NEWS updated. commit e9db92d3acfe1822d56d11abcea5bfc4c41cf6ca Author: Carlos O'Donell <carlos@systemhalted.org> Date: Tue Feb 16 21:26:37 2016 -0500 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 Cheers, Carlos.
On 16 Feb 2016, Carlos O'Donell told this: > - Via getaddrinfo with family AF_UNSPEC or AF_INET6 the overflowed > buffer is located on the stack via alloca (a 2048 byte fixed size > buffer for DNS responses). > > - At most 65535 bytes (MAX_PACKET) may be written to the alloca buffer > of 2048 bytes. Overflowing bytes are entirely under the control of the > attacker and are the result of a crafted DNS response. It seems plausible to me that this could have been ameliorated somewhat by -fstack-protecting glibc. I think I might revive my stack-protection patch in the ancient bug 7065, split it up properly and describe the pieces clearly, and ask for help with the one remaining bug in it (a crash in PI mutex unlocking which I have no understanding of whatsoever, but which is clearly caused or provoked by the patch). I just forward- ported it to 2.22 and it was more or less effortless, with its single known bug unchanged: if I were to forward-port it to the trunk, would people be amenable to suggesting how I could fix it so it isn't buggy and is perhaps even maintainable, or is the very idea of stack-protecting glibc still anathema? The patch does not -fstack-protect the dynamic loader, because that's crazy as a first try, though it does modify glibc startup so that statically linked programs are protected immediately, because it has to. But it does -fstack-protector{-all,-strong} everything else, including libc and the NSS libraries. It's been running on my x86-32 firewall for about eight years now without incident: I originally wrote it against 2.13! It seems like it's time to stop sitting on it out of fear of libc-alpha and actually *post* it for everyone to laugh at and hopefully try to improve...
On 02/17/2016 04:25 PM, Nix wrote: > On 16 Feb 2016, Carlos O'Donell told this: > >> - Via getaddrinfo with family AF_UNSPEC or AF_INET6 the overflowed >> buffer is located on the stack via alloca (a 2048 byte fixed size >> buffer for DNS responses). >> >> - At most 65535 bytes (MAX_PACKET) may be written to the alloca buffer >> of 2048 bytes. Overflowing bytes are entirely under the control of the >> attacker and are the result of a crafted DNS response. > > It seems plausible to me that this could have been ameliorated somewhat > by -fstack-protecting glibc. I think I might revive my stack-protection > patch in the ancient bug 7065, split it up properly and describe the > pieces clearly, and ask for help with the one remaining bug in it (a > crash in PI mutex unlocking which I have no understanding of whatsoever, > but which is clearly caused or provoked by the patch). I just forward- > ported it to 2.22 and it was more or less effortless, with its single > known bug unchanged: if I were to forward-port it to the trunk, would > people be amenable to suggesting how I could fix it so it isn't buggy > and is perhaps even maintainable, or is the very idea of > stack-protecting glibc still anathema? > > The patch does not -fstack-protect the dynamic loader, because that's > crazy as a first try, though it does modify glibc startup so that > statically linked programs are protected immediately, because it has to. > But it does -fstack-protector{-all,-strong} everything else, including > libc and the NSS libraries. > > It's been running on my x86-32 firewall for about eight years now > without incident: I originally wrote it against 2.13! It seems like it's > time to stop sitting on it out of fear of libc-alpha and actually *post* > it for everyone to laugh at and hopefully try to improve... It's a very good idea. I think we should stack protect libresolv, libdl, nscd, etc, and we do already. Extending that is only going to be a good thing. Please feel free to post again for review. Cheers, Carlos.
On 17 Feb 2016 16:43, Carlos O'Donell wrote: > It's a very good idea. I think we should stack protect libresolv, libdl, > nscd, etc, and we do already. Extending that is only going to be a good > thing. on a related note, seems like nscd should take advantage of seccomp & namespaces when available. that would also significantly mitigate on systems. any reason to not ? -mike
On 02/17/2016 05:20 PM, Mike Frysinger wrote: > On 17 Feb 2016 16:43, Carlos O'Donell wrote: >> It's a very good idea. I think we should stack protect libresolv, libdl, >> nscd, etc, and we do already. Extending that is only going to be a good >> thing. > > on a related note, seems like nscd should take advantage of seccomp & > namespaces when available. that would also significantly mitigate on > systems. any reason to not ? I see no reason why not. We would have to test for the availability of that functionality in as old a kernel as we support running on, but as newer kernels are booted the features should just turn on automatically. For now we've just been using SELinux in nscd to restrict the damage the daemon could do, but it could potentially be restricted even further. Cheers, Carlos.
On 17 Feb 2016 19:44, Carlos O'Donell wrote: > On 02/17/2016 05:20 PM, Mike Frysinger wrote: > > On 17 Feb 2016 16:43, Carlos O'Donell wrote: > >> It's a very good idea. I think we should stack protect libresolv, libdl, > >> nscd, etc, and we do already. Extending that is only going to be a good > >> thing. > > > > on a related note, seems like nscd should take advantage of seccomp & > > namespaces when available. that would also significantly mitigate on > > systems. any reason to not ? > > I see no reason why not. We would have to test for the availability of > that functionality in as old a kernel as we support running on, but > as newer kernels are booted the features should just turn on automatically. we'd always need to do runtime testing for features since people can disable both in their configs. doing the actual testing is pretty easy as they will return an error (EINVAL) if it's old/disabled. i've created some bugs and linked to them in the wiki's TODO at least. > For now we've just been using SELinux in nscd to restrict the damage the > daemon could do, but it could potentially be restricted even further. unfortunately SELinux is not as wide spread/adopted as one might hope. -mike
On 17 Feb 2016, Carlos O'Donell stated: >> pieces clearly, and ask for help with the one remaining bug in it (a >> crash in PI mutex unlocking which I have no understanding of whatsoever, >> but which is clearly caused or provoked by the patch). Crash identified and fixed. The advantage of coming back to something with a clear head after years. :) I'll post the patch series tomorrow, assuming it survives its massive test matrix overnight.
CVE-2015-7547 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. diff --git a/resolv/nss_dns/dns-host.c b/resolv/nss_dns/dns-host.c index a255d5e..47cfe27 100644 --- a/resolv/nss_dns/dns-host.c +++ b/resolv/nss_dns/dns-host.c @@ -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) { @@ -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; } @@ -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 implemetnation of send_dg + and send_vc to see response can arrive in any order, particlarly + 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 @@ -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; diff --git a/resolv/res_query.c b/resolv/res_query.c index 4a9b3b3..95470a9 100644 --- a/resolv/res_query.c +++ b/resolv/res_query.c @@ -396,6 +396,7 @@ __libc_res_nsearch(res_state statp, { free (*answerp2); *answerp2 = NULL; + *nanswerp2 = 0; *answerp2_malloced = 0; } } @@ -447,6 +448,7 @@ __libc_res_nsearch(res_state statp, { free (*answerp2); *answerp2 = NULL; + *nanswerp2 = 0; *answerp2_malloced = 0; } @@ -521,6 +523,7 @@ __libc_res_nsearch(res_state statp, { free (*answerp2); *answerp2 = NULL; + *nanswerp2 = 0; *answerp2_malloced = 0; } if (saved_herrno != -1) diff --git a/resolv/res_send.c b/resolv/res_send.c index a968b95..21843f1 100644 --- a/resolv/res_send.c +++ b/resolv/res_send.c @@ -1,3 +1,20 @@ +/* Copyright (C) 2016 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, 1989, 1993 * The Regents of the University of California. All rights reserved. @@ -355,6 +372,8 @@ __libc_res_nsend(res_state statp, const u_char *buf, int buflen, #ifdef USE_HOOKS if (__glibc_unlikely (statp->qhook || statp->rhook)) { if (anssiz < MAXPACKET && ansp) { + /* Always allocate MAXPACKET, callers expect + this specific size. */ u_char *buf = malloc (MAXPACKET); if (buf == NULL) return (-1); @@ -630,6 +649,77 @@ get_nsaddr (res_state statp, int n) return (struct sockaddr *) (void *) &statp->nsaddr_list[n]; } +/* The send_vc function is responsible for sending a DNS query over TCP + to the nameserver numbered NS from the res_state STATP i.e. + EXT(statp).nssocks[ns]. The function supports sending both IPv4 and + IPv6 queries at the same serially on the same socket. + + Please note that for TCP there is no way to disable sending both + queries, unlike UDP, which honours RES_SNGLKUP and RES_SNGLKUPREOP + and sends the queries serially and waits for the result after each + sent query. This implemetnation should be corrected to honour these + options. + + Please also note that for TCP we send both queries over the same + socket one after another. This technically violates best practice + since the server is allowed to read the first query, respond, and + then close the socket (to service another client). If the server + does this, then the remaining second query in the socket data buffer + will cause the server to send the client an RST which will arrive + asynchronously and the client's OS will likely tear down the socket + receive buffer resulting in a potentially short read and lost + response data. This will force the client to retry the query again, + and this process may repeat until all servers and connection resets + are exhausted and then the query will fail. It's not known if this + happens with any frequency in real DNS server implementations. This + implementation should be corrected to use two sockets by default for + parallel queries. + + The query stored in BUF of BUFLEN length is sent first followed by + the query stored in BUF2 of BUFLEN2 length. Queries are sent + serially on the same socket. + + Answers to the query are stored firstly in *ANSP up to a max of + *ANSSIZP bytes. If more than *ANSSIZP bytes are needed and ANSCP + is non-NULL (to indicate that modifying the answer buffer is allowed) + then malloc is used to allocate a new response buffer and ANSCP and + ANSP will both point to the new buffer. If more than *ANSSIZP bytes + are needed but ANSCP is NULL, then as much of the response as + possible is read into the buffer, but the results will be truncated. + When truncation happens because of a small answer buffer the DNS + packets header feild TC will bet set to 1, indicating a truncated + message and the rest of the socket data will be read and discarded. + + Answers to the query are stored secondly in *ANSP2 up to a max of + *ANSSIZP2 bytes, with the actual response length stored in + *RESPLEN2. If more than *ANSSIZP bytes are needed and ANSP2 + is non-NULL (required for a second query) then malloc is used to + allocate a new response buffer, *ANSSIZP2 is set to the new buffer + size and *ANSP2_MALLOCED is set to 1. + + The ANSP2_MALLOCED argument will eventually be removed as the + change in buffer pointer can be used to detect the buffer has + changed and that the caller should use free on the new buffer. + + Note that the answers may arrive in any order from the server and + therefore the first and second answer buffers may not correspond to + the first and second queries. + + It is not supported to call this function with a non-NULL ANSP2 + but a NULL ANSCP. Put another way, you can call send_vc with a + single unmodifiable buffer or two modifiable buffers, but no other + combination is supported. + + It is the caller's responsibility to free the malloc allocated + buffers by detecting that the pointers have changed from their + original values i.e. *ANSCP or *ANSP2 has changed. + + If errors are encountered then *TERRNO is set to an appropriate + errno value and a zero result is returned for a recoverable error, + and a less-than zero result is returned for a non-recoverable error. + + If no errors are encountered then *TERRNO is left unmodified and + a the length of the first response in bytes is returned. */ static int send_vc(res_state statp, const u_char *buf, int buflen, const u_char *buf2, int buflen2, @@ -639,11 +729,7 @@ send_vc(res_state statp, { const HEADER *hp = (HEADER *) buf; const HEADER *hp2 = (HEADER *) buf2; - u_char *ans = *ansp; - int orig_anssizp = *anssizp; - // XXX REMOVE - // int anssiz = *anssizp; - HEADER *anhp = (HEADER *) ans; + HEADER *anhp = (HEADER *) *ansp; struct sockaddr *nsap = get_nsaddr (statp, ns); int truncating, connreset, n; /* On some architectures compiler might emit a warning indicating @@ -731,6 +817,8 @@ send_vc(res_state statp, * Receive length & response */ int recvresp1 = 0; + /* Skip the second response if there is no second query. + To do that we mark the second response as received. */ int recvresp2 = buf2 == NULL; uint16_t rlen16; read_len: @@ -767,36 +855,14 @@ send_vc(res_state statp, u_char **thisansp; int *thisresplenp; if ((recvresp1 | recvresp2) == 0 || buf2 == NULL) { + /* We have not received any responses + yet or we only have one response to + receive. */ thisanssizp = anssizp; thisansp = anscp ?: ansp; assert (anscp != NULL || ansp2 == NULL); thisresplenp = &resplen; } else { - if (*anssizp != MAXPACKET) { - /* No buffer allocated for the first - reply. We can try to use the rest - of the user-provided buffer. */ - DIAG_PUSH_NEEDS_COMMENT; - DIAG_IGNORE_NEEDS_COMMENT (5, "-Wmaybe-uninitialized"); -#if _STRING_ARCH_unaligned - *anssizp2 = orig_anssizp - resplen; - *ansp2 = *ansp + resplen; -#else - int aligned_resplen - = ((resplen + __alignof__ (HEADER) - 1) - & ~(__alignof__ (HEADER) - 1)); - *anssizp2 = orig_anssizp - aligned_resplen; - *ansp2 = *ansp + aligned_resplen; -#endif - DIAG_POP_NEEDS_COMMENT; - } else { - /* The first reply did not fit into the - user-provided buffer. Maybe the second - answer will. */ - *anssizp2 = orig_anssizp; - *ansp2 = *ansp; - } - thisanssizp = anssizp2; thisansp = ansp2; thisresplenp = resplen2; @@ -804,10 +870,14 @@ send_vc(res_state statp, anhp = (HEADER *) *thisansp; *thisresplenp = rlen; - if (rlen > *thisanssizp) { - /* Yes, we test ANSCP here. If we have two buffers - both will be allocatable. */ - if (__glibc_likely (anscp != NULL)) { + /* Is the answer buffer too small? */ + if (*thisanssizp < rlen) { + /* If the current buffer is not the the static + user-supplied buffer then we can reallocate + it. */ + if (thisansp != NULL && thisansp != ansp) { + /* Always allocate MAXPACKET, callers expect + this specific size. */ u_char *newp = malloc (MAXPACKET); if (newp == NULL) { *terrno = ENOMEM; @@ -819,6 +889,9 @@ send_vc(res_state statp, if (thisansp == ansp2) *ansp2_malloced = 1; anhp = (HEADER *) newp; + /* A uint16_t can't be larger than MAXPACKET + thus it's safe to allocate MAXPACKET but + read RLEN bytes instead. */ len = rlen; } else { Dprint(statp->options & RES_DEBUG, @@ -948,6 +1021,66 @@ reopen (res_state statp, int *terrno, int ns) return 1; } +/* The send_dg function is responsible for sending a DNS query over UDP + to the nameserver numbered NS from the res_state STATP i.e. + EXT(statp).nssocks[ns]. The function supports IPv4 and IPv6 queries + along with the ability to send the query in parallel for both stacks + (default) or serially (RES_SINGLKUP). It also supports serial lookup + with a close and reopen of the socket used to talk to the server + (RES_SNGLKUPREOP) to work around broken name servers. + + The query stored in BUF of BUFLEN length is sent first followed by + the query stored in BUF2 of BUFLEN2 length. Queries are sent + in parallel (default) or serially (RES_SINGLKUP or RES_SNGLKUPREOP). + + Answers to the query are stored firstly in *ANSP up to a max of + *ANSSIZP bytes. If more than *ANSSIZP bytes are needed and ANSCP + is non-NULL (to indicate that modifying the answer buffer is allowed) + then malloc is used to allocate a new response buffer and ANSCP and + ANSP will both point to the new buffer. If more than *ANSSIZP bytes + are needed but ANSCP is NULL, then as much of the response as + possible is read into the buffer, but the results will be truncated. + When truncation happens because of a small answer buffer the DNS + packets header feild TC will bet set to 1, indicating a truncated + message, while the rest of the UDP packet is discarded. + + Answers to the query are stored secondly in *ANSP2 up to a max of + *ANSSIZP2 bytes, with the actual response length stored in + *RESPLEN2. If more than *ANSSIZP bytes are needed and ANSP2 + is non-NULL (required for a second query) then malloc is used to + allocate a new response buffer, *ANSSIZP2 is set to the new buffer + size and *ANSP2_MALLOCED is set to 1. + + The ANSP2_MALLOCED argument will eventually be removed as the + change in buffer pointer can be used to detect the buffer has + changed and that the caller should use free on the new buffer. + + Note that the answers may arrive in any order from the server and + therefore the first and second answer buffers may not correspond to + the first and second queries. + + It is not supported to call this function with a non-NULL ANSP2 + but a NULL ANSCP. Put another way, you can call send_vc with a + single unmodifiable buffer or two modifiable buffers, but no other + combination is supported. + + It is the caller's responsibility to free the malloc allocated + buffers by detecting that the pointers have changed from their + original values i.e. *ANSCP or *ANSP2 has changed. + + If an answer is truncated because of UDP datagram DNS limits then + *V_CIRCUIT is set to 1 and the return value non-zero to indicate to + the caller to retry with TCP. The value *GOTSOMEWHERE is set to 1 + if any progress was made reading a response from the nameserver and + is used by the caller to distinguish between ECONNREFUSED and + ETIMEDOUT (the latter if *GOTSOMEWHERE is 1). + + If errors are encountered then *TERRNO is set to an appropriate + errno value and a zero result is returned for a recoverable error, + and a less-than zero result is returned for a non-recoverable error. + + If no errors are encountered then *TERRNO is left unmodified and + a the length of the first response in bytes is returned. */ static int send_dg(res_state statp, const u_char *buf, int buflen, const u_char *buf2, int buflen2, @@ -957,8 +1090,6 @@ send_dg(res_state statp, { const HEADER *hp = (HEADER *) buf; const HEADER *hp2 = (HEADER *) buf2; - u_char *ans = *ansp; - int orig_anssizp = *anssizp; struct timespec now, timeout, finish; struct pollfd pfd[1]; int ptimeout; @@ -991,6 +1122,8 @@ send_dg(res_state statp, int need_recompute = 0; int nwritten = 0; int recvresp1 = 0; + /* Skip the second response if there is no second query. + To do that we mark the second response as received. */ int recvresp2 = buf2 == NULL; pfd[0].fd = EXT(statp).nssocks[ns]; pfd[0].events = POLLOUT; @@ -1154,55 +1287,56 @@ send_dg(res_state statp, int *thisresplenp; if ((recvresp1 | recvresp2) == 0 || buf2 == NULL) { + /* We have not received any responses + yet or we only have one response to + receive. */ thisanssizp = anssizp; thisansp = anscp ?: ansp; assert (anscp != NULL || ansp2 == NULL); thisresplenp = &resplen; } else { - if (*anssizp != MAXPACKET) { - /* No buffer allocated for the first - reply. We can try to use the rest - of the user-provided buffer. */ -#if _STRING_ARCH_unaligned - *anssizp2 = orig_anssizp - resplen; - *ansp2 = *ansp + resplen; -#else - int aligned_resplen - = ((resplen + __alignof__ (HEADER) - 1) - & ~(__alignof__ (HEADER) - 1)); - *anssizp2 = orig_anssizp - aligned_resplen; - *ansp2 = *ansp + aligned_resplen; -#endif - } else { - /* The first reply did not fit into the - user-provided buffer. Maybe the second - answer will. */ - *anssizp2 = orig_anssizp; - *ansp2 = *ansp; - } - thisanssizp = anssizp2; thisansp = ansp2; thisresplenp = resplen2; } if (*thisanssizp < MAXPACKET - /* Yes, we test ANSCP here. If we have two buffers - both will be allocatable. */ - && anscp + /* If the current buffer is not the the static + user-supplied buffer then we can reallocate + it. */ + && (thisansp != NULL && thisansp != ansp) #ifdef FIONREAD + /* Is the size too small? */ && (ioctl (pfd[0].fd, FIONREAD, thisresplenp) < 0 || *thisanssizp < *thisresplenp) #endif ) { + /* Always allocate MAXPACKET, callers expect + this specific size. */ u_char *newp = malloc (MAXPACKET); if (newp != NULL) { - *anssizp = MAXPACKET; - *thisansp = ans = newp; + *thisanssizp = MAXPACKET; + *thisansp = newp; if (thisansp == ansp2) *ansp2_malloced = 1; } } + /* We could end up with truncation if anscp was NULL + (not allowed to change caller's buffer) and the + response buffer size is too small. This isn't a + reliable way to detect truncation because the ioctl + may be an inaccurate report of the UDP message size. + Therefore we use this only to issue debug output. + To do truncation accurately with UDP we need + MSG_TRUNC which is only available on Linux. We + can abstract out the Linux-specific feature in the + future to detect truncation. */ + if (__glibc_unlikely (*thisanssizp < *thisresplenp)) { + Dprint(statp->options & RES_DEBUG, + (stdout, ";; response may be truncated (UDP)\n") + ); + } + HEADER *anhp = (HEADER *) *thisansp; socklen_t fromlen = sizeof(struct sockaddr_in6); assert (sizeof(from) <= fromlen);