Created
February 23, 2016 13:03
-
-
Save tbell83/780040053cf79c2b48d8 to your computer and use it in GitHub Desktop.
CVE-2015-7547 patch for lucid eglibc_2.11.1-0ubuntu7.21
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Index: eglibc-2.11.1/resolv/nss_dns/dns-host.c | |
| =================================================================== | |
| --- eglibc-2.11.1.orig/resolv/nss_dns/dns-host.c 2016-02-22 19:37:03.041133126 +0100 | |
| +++ eglibc-2.11.1/resolv/nss_dns/dns-host.c 2016-02-22 19:44:47.501119581 +0100 | |
| @@ -1025,7 +1025,10 @@ | |
| 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) | |
| { | |
| @@ -1206,7 +1209,14 @@ | |
| /* 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; | |
| } | |
| @@ -1220,11 +1230,103 @@ | |
| 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); | |
| + | |
| + /* Use the second response status in some cases. */ | |
| + | |
| if ((status == NSS_STATUS_SUCCESS || status == NSS_STATUS_NOTFOUND | |
| || (status == NSS_STATUS_TRYAGAIN | |
| && (errno != ERANGE || *h_errnop != NO_RECOVERY))) | |
| @@ -1236,6 +1338,12 @@ | |
| &first); | |
| 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; | |
| Index: eglibc-2.11.1/resolv/res_query.c | |
| =================================================================== | |
| --- eglibc-2.11.1.orig/resolv/res_query.c 2016-02-22 19:45:09.601826296 +0100 | |
| +++ eglibc-2.11.1/resolv/res_query.c 2016-02-22 19:57:25.111127023 +0100 | |
| @@ -390,6 +390,7 @@ | |
| { | |
| free (*answerp2); | |
| *answerp2 = NULL; | |
| + *nanswerp2 = 0; | |
| } | |
| } | |
| @@ -429,6 +430,7 @@ | |
| { | |
| free (*answerp2); | |
| *answerp2 = NULL; | |
| + *nanswerp2 = 0; | |
| } | |
| /* | |
| @@ -500,6 +502,7 @@ | |
| { | |
| free (*answerp2); | |
| *answerp2 = NULL; | |
| + *nanswerp2 = 0; | |
| } | |
| if (saved_herrno != -1) | |
| RES_SET_H_ERRNO(statp, saved_herrno); | |
| Index: eglibc-2.11.1/resolv/res_send.c | |
| =================================================================== | |
| --- eglibc-2.11.1.orig/resolv/res_send.c 2016-02-22 19:57:36.431096292 +0100 | |
| +++ eglibc-2.11.1/resolv/res_send.c 2016-02-22 20:18:13.281115935 +0100 | |
| @@ -364,6 +364,8 @@ | |
| #ifdef USE_HOOKS | |
| if (__builtin_expect (statp->qhook || statp->rhook, 0)) { | |
| if (anssiz < MAXPACKET && ansp) { | |
| + /* Always allocate MAXPACKET, callers expect | |
| + this specific size. */ | |
| u_char *buf = malloc (MAXPACKET); | |
| if (buf == NULL) | |
| return (-1); | |
| @@ -661,11 +663,7 @@ | |
| { | |
| 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_in6 *nsap = EXT(statp).nsaddrs[ns]; | |
| int truncating, connreset, resplen, n; | |
| struct iovec iov[4]; | |
| @@ -741,6 +739,8 @@ | |
| * 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: | |
| @@ -777,33 +777,14 @@ | |
| 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. */ | |
| -#ifdef _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; | |
| @@ -811,10 +792,14 @@ | |
| anhp = (HEADER *) *thisansp; | |
| *thisresplenp = rlen; | |
| - if (rlen > *thisanssizp) { | |
| - /* Yes, we test ANSCP here. If we have two buffers | |
| - both will be allocatable. */ | |
| - if (__builtin_expect (anscp != NULL, 1)) { | |
| + /* 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; | |
| @@ -824,6 +809,9 @@ | |
| *thisanssizp = MAXPACKET; | |
| *thisansp = newp; | |
| 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, | |
| @@ -999,8 +987,6 @@ | |
| { | |
| 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; | |
| @@ -1032,6 +1018,8 @@ | |
| 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; | |
| @@ -1128,50 +1116,51 @@ | |
| 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. */ | |
| -#ifdef _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) | |
| + /* Is the size too small? */ | |
| && (ioctl (pfd[0].fd, FIONREAD, thisresplenp) < 0 | |
| || *thisanssizp < *thisresplenp)) { | |
| + /* 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; | |
| } | |
| } | |
| + /* 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); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment