reverted: --- b/resolv/nss_dns/dns-host.c +++ a/resolv/nss_dns/dns-host.c @@ -100,6 +100,13 @@ #endif #define MAXHOSTNAMELEN 256 +/* We need this time later. */ +typedef union querybuf +{ + HEADER hdr; + u_char buf[MAXPACKET]; +} querybuf; + /* For historic reasons, pointers to IP addresses are char *, so use a single list type for addresses and host names. */ #define DYNARRAY_STRUCT ptrlist @@ -118,18 +125,18 @@ char **hnamep, int *errnop, int *h_errnop, int32_t *ttlp); +static enum nss_status gaih_getanswer (const querybuf *answer1, int anslen1, + const querybuf *answer2, int anslen2, + const char *qname, -static enum nss_status gaih_getanswer (unsigned char *packet1, - size_t packet1len, - unsigned char *packet2, - size_t packet2len, - struct alloc_buffer *abuf, struct gaih_addrtuple **pat, + char *buffer, size_t buflen, int *errnop, int *h_errnop, int32_t *ttlp); +static enum nss_status gaih_getanswer_noaaaa (const querybuf *answer1, + int anslen1, + const char *qname, -static enum nss_status gaih_getanswer_noaaaa (unsigned char *packet, - size_t packetlen, - struct alloc_buffer *abuf, struct gaih_addrtuple **pat, + char *buffer, size_t buflen, int *errnop, int *h_errnop, int32_t *ttlp); @@ -401,13 +408,17 @@ name = cp; } + union + { + querybuf *buf; + u_char *ptr; + } host_buffer; + querybuf *orig_host_buffer; + host_buffer.buf = orig_host_buffer = (querybuf *) alloca (2048); - unsigned char dns_packet_buffer[2048]; - unsigned char *alt_dns_packet_buffer = dns_packet_buffer; u_char *ans2p = NULL; int nans2p = 0; int resplen2 = 0; int ans2p_malloced = 0; - struct alloc_buffer abuf = alloc_buffer_create (buffer, buflen); int olderr = errno; @@ -416,21 +427,22 @@ if ((ctx->resp->options & RES_NOAAAA) == 0) { n = __res_context_search (ctx, name, C_IN, T_QUERY_A_AND_AAAA, + host_buffer.buf->buf, 2048, &host_buffer.ptr, + &ans2p, &nans2p, &resplen2, &ans2p_malloced); - dns_packet_buffer, sizeof (dns_packet_buffer), - &alt_dns_packet_buffer, &ans2p, &nans2p, - &resplen2, &ans2p_malloced); if (n >= 0) + status = gaih_getanswer (host_buffer.buf, n, (const querybuf *) ans2p, + resplen2, name, pat, buffer, buflen, + errnop, herrnop, ttlp); - status = gaih_getanswer (alt_dns_packet_buffer, n, ans2p, resplen2, - &abuf, pat, errnop, herrnop, ttlp); } else { n = __res_context_search (ctx, name, C_IN, T_A, + host_buffer.buf->buf, 2048, NULL, + NULL, NULL, NULL, NULL); - dns_packet_buffer, sizeof (dns_packet_buffer), - NULL, NULL, NULL, NULL, NULL); if (n >= 0) + status = gaih_getanswer_noaaaa (host_buffer.buf, n, + name, pat, buffer, buflen, + errnop, herrnop, ttlp); - status = gaih_getanswer_noaaaa (alt_dns_packet_buffer, n, - &abuf, pat, errnop, herrnop, ttlp); } if (n < 0) { @@ -461,20 +473,12 @@ __set_errno (olderr); } - /* Implement the buffer resizing protocol. */ - if (alloc_buffer_has_failed (&abuf)) - { - *errnop = ERANGE; - *herrnop = NETDB_INTERNAL; - status = NSS_STATUS_TRYAGAIN; - } - /* Check whether ans2p was separately allocated. */ if (ans2p_malloced) free (ans2p); + if (host_buffer.buf != orig_host_buffer) + free (host_buffer.buf); - if (alt_dns_packet_buffer != dns_packet_buffer) - free (alt_dns_packet_buffer); __resolv_context_put (ctx); return status; @@ -888,152 +892,259 @@ return NSS_STATUS_TRYAGAIN; } -/* Parses DNS data found in PACKETLEN bytes at PACKET in struct - gaih_addrtuple address tuples. The new address tuples are linked - from **TAILP, with backing store allocated from ABUF, and *TAILP is - updated to point where the next tuple pointer should be stored. If - TTLP is not null, *TTLP is updated to reflect the minimum TTL. If - STORE_CANON is true, the canonical name is stored as part of the - first address tuple being written. */ static enum nss_status +gaih_getanswer_slice (const querybuf *answer, int anslen, const char *qname, + struct gaih_addrtuple ***patp, + char **bufferp, size_t *buflenp, + int *errnop, int *h_errnop, int32_t *ttlp, int *firstp) -gaih_getanswer_slice (unsigned char *packet, size_t packetlen, - struct alloc_buffer *abuf, - struct gaih_addrtuple ***tailp, - int *errnop, int *h_errnop, int32_t *ttlp, - bool store_canon) { + char *buffer = *bufferp; + size_t buflen = *buflenp; + + struct gaih_addrtuple **pat = *patp; + const HEADER *hp = &answer->hdr; + int ancount = ntohs (hp->ancount); + int qdcount = ntohs (hp->qdcount); + const u_char *cp = answer->buf + HFIXEDSZ; + const u_char *end_of_message = answer->buf + anslen; + if (__glibc_unlikely (qdcount != 1)) + { + *h_errnop = NO_RECOVERY; + return NSS_STATUS_UNAVAIL; + } + + u_char packtmp[NS_MAXCDNAME]; + int n = __ns_name_unpack (answer->buf, end_of_message, cp, + packtmp, sizeof packtmp); + /* We unpack the name to check it for validity. But we do not need + it later. */ + if (n != -1 && __ns_name_ntop (packtmp, buffer, buflen) == -1) + { + if (__glibc_unlikely (errno == EMSGSIZE)) + { + too_small: + *errnop = ERANGE; + *h_errnop = NETDB_INTERNAL; + return NSS_STATUS_TRYAGAIN; + } + + n = -1; + } + + if (__glibc_unlikely (n < 0)) + { + *errnop = errno; + *h_errnop = NO_RECOVERY; + return NSS_STATUS_UNAVAIL; + } + if (__glibc_unlikely (__libc_res_hnok (buffer) == 0)) - struct ns_rr_cursor c; - if (!__ns_rr_cursor_init (&c, packet, packetlen)) { + errno = EBADMSG; + *errnop = EBADMSG; - /* This should not happen because __res_context_query already - perfroms response validation. */ *h_errnop = NO_RECOVERY; return NSS_STATUS_UNAVAIL; } + cp += n + QFIXEDSZ; - bool haveanswer = false; /* Set to true if at least one address. */ - uint16_t qtype = ns_rr_cursor_qtype (&c); - int ancount = ns_rr_cursor_ancount (&c); - const unsigned char *expected_name = ns_rr_cursor_qname (&c); - /* expected_name may be updated to point into this buffer. */ - unsigned char name_buffer[NS_MAXCDNAME]; + int haveanswer = 0; + int had_error = 0; + char *canon = NULL; + char *h_name = NULL; + int h_namelen = 0; - /* This is a pointer to a possibly-compressed name in the packet. - Eventually it is equivalent to the canonical name. If needed, it - is uncompressed and translated to text form when the first - address tuple is encountered. */ - const unsigned char *compressed_alias_name = expected_name; + if (ancount == 0) - if (ancount == 0 || !__res_binary_hnok (compressed_alias_name)) { *h_errnop = HOST_NOT_FOUND; return NSS_STATUS_NOTFOUND; } + while (ancount-- > 0 && cp < end_of_message && had_error == 0) - for (; ancount > -0; --ancount) { + n = __ns_name_unpack (answer->buf, end_of_message, cp, + packtmp, sizeof packtmp); + if (n != -1 && + (h_namelen = __ns_name_ntop (packtmp, buffer, buflen)) == -1) + { + if (__glibc_unlikely (errno == EMSGSIZE)) + goto too_small; + + n = -1; + } + if (__glibc_unlikely (n < 0)) + { + ++had_error; + continue; + } + if (*firstp && canon == NULL && __libc_res_hnok (buffer)) + { + h_name = buffer; + buffer += h_namelen; + buflen -= h_namelen; + } + + cp += n; /* name */ + + if (__glibc_unlikely (cp + 10 > end_of_message)) + { + ++had_error; + continue; + } + + uint16_t type; + NS_GET16 (type, cp); + uint16_t class; + NS_GET16 (class, cp); + int32_t ttl; + NS_GET32 (ttl, cp); + NS_GET16 (n, cp); /* RDATA length. */ + + if (end_of_message - cp < n) - struct ns_rr_wire rr; - if (!__ns_rr_cursor_next (&c, &rr)) { + /* RDATA extends beyond the end of the packet. */ + ++had_error; + continue; - *h_errnop = NO_RECOVERY; - return NSS_STATUS_UNAVAIL; } + if (class != C_IN) + { + cp += n; + continue; + } - /* Update TTL for known record types. */ - if ((rr.rtype == T_CNAME || rr.rtype == qtype) - && ttlp != NULL && *ttlp > rr.ttl) - *ttlp = rr.ttl; + if (type == T_CNAME) - if (rr.rtype == T_CNAME) { + char tbuf[MAXDNAME]; + + /* A CNAME could also have a TTL entry. */ + if (ttlp != NULL && ttl < *ttlp) + *ttlp = ttl; + + n = __libc_dn_expand (answer->buf, end_of_message, cp, + tbuf, sizeof tbuf); + if (__glibc_unlikely (n < 0)) + { + ++had_error; + continue; + } + cp += n; + + if (*firstp && __libc_res_hnok (tbuf)) - /* NB: No check for owner name match, based on historic - precedent. Record the CNAME target as the new expected - name. */ - int n = __ns_name_unpack (c.begin, c.end, rr.rdata, - name_buffer, sizeof (name_buffer)); - if (n < 0) { + /* Reclaim buffer space. */ + if (h_name + h_namelen == buffer) + { + buffer = h_name; + buflen += h_namelen; + } + + n = strlen (tbuf) + 1; + if (__glibc_unlikely (n > buflen)) + goto too_small; + if (__glibc_unlikely (n >= MAXHOSTNAMELEN)) + { + ++had_error; + continue; + } + + canon = buffer; + buffer = __mempcpy (buffer, tbuf, n); + buflen -= n; + h_namelen = 0; - *h_errnop = NO_RECOVERY; - return NSS_STATUS_UNAVAIL; } + continue; - expected_name = name_buffer; - if (store_canon && __res_binary_hnok (name_buffer)) - /* This name can be used as a canonical name. Do not - translate to text form here to conserve buffer space. - Point to the compressed name because name_buffer can be - overwritten with an unusable name later. */ - compressed_alias_name = rr.rdata; } + + /* Stop parsing if we encounter a record with incorrect RDATA + length. */ + if (type == T_A || type == T_AAAA) - else if (rr.rtype == qtype - && __ns_samebinaryname (rr.rname, expected_name) - && rr.rdlength == rrtype_to_rdata_length (qtype)) { + if (n != rrtype_to_rdata_length (type)) - struct gaih_addrtuple *ntup - = alloc_buffer_alloc (abuf, struct gaih_addrtuple); - /* Delay error reporting to the callers (they implement the - ERANGE buffer resizing handshake). */ - if (ntup != NULL) { + ++had_error; + continue; - ntup->next = NULL; - if (store_canon && compressed_alias_name != NULL) - { - /* This assumes that all the CNAME records come - first. Use MAXHOSTNAMELEN instead of - NS_MAXCDNAME for additional length checking. - However, these checks are not expected to fail - because all size NS_MAXCDNAME names should into - the hname buffer because no escaping is - needed. */ - char unsigned nbuf[NS_MAXCDNAME]; - char hname[MAXHOSTNAMELEN + 1]; - if (__ns_name_unpack (c.begin, c.end, - compressed_alias_name, - nbuf, sizeof (nbuf)) >= 0 - && __ns_name_ntop (nbuf, hname, sizeof (hname)) >= 0) - /* Space checking is performed by the callers. */ - ntup->name = alloc_buffer_copy_string (abuf, hname); - store_canon = false; - } - else - ntup->name = NULL; - if (rr.rdlength == 4) - ntup->family = AF_INET; - else - ntup->family = AF_INET6; - memcpy (ntup->addr, rr.rdata, rr.rdlength); - ntup->scopeid = 0; - - /* Link in the new tuple, and update the tail pointer to - point to its next field. */ - **tailp = ntup; - *tailp = &ntup->next; - - haveanswer = true; } } + else + { + /* Skip unknown records. */ + cp += n; + continue; + } + + assert (type == T_A || type == T_AAAA); + if (*pat == NULL) + { + uintptr_t pad = (-(uintptr_t) buffer + % __alignof__ (struct gaih_addrtuple)); + buffer += pad; + buflen = buflen > pad ? buflen - pad : 0; + + if (__glibc_unlikely (buflen < sizeof (struct gaih_addrtuple))) + goto too_small; + + *pat = (struct gaih_addrtuple *) buffer; + buffer += sizeof (struct gaih_addrtuple); + buflen -= sizeof (struct gaih_addrtuple); + } + + (*pat)->name = NULL; + (*pat)->next = NULL; + + if (*firstp) + { + /* We compose a single hostent out of the entire chain of + entries, so the TTL of the hostent is essentially the lowest + TTL in the chain. */ + if (ttlp != NULL && ttl < *ttlp) + *ttlp = ttl; + + (*pat)->name = canon ?: h_name; + + *firstp = 0; + } + + (*pat)->family = type == T_A ? AF_INET : AF_INET6; + memcpy ((*pat)->addr, cp, n); + cp += n; + (*pat)->scopeid = 0; + + pat = &((*pat)->next); + + haveanswer = 1; } if (haveanswer) { + *patp = pat; + *bufferp = buffer; + *buflenp = buflen; + *h_errnop = NETDB_SUCCESS; return NSS_STATUS_SUCCESS; } + + /* 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. */ + if (canon != NULL) - else { - /* 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. */ *h_errnop = HOST_NOT_FOUND; return NSS_STATUS_NOTFOUND; } + + *h_errnop = NETDB_INTERNAL; + return NSS_STATUS_TRYAGAIN; } static enum nss_status +gaih_getanswer (const querybuf *answer1, int anslen1, const querybuf *answer2, + int anslen2, const char *qname, + struct gaih_addrtuple **pat, char *buffer, size_t buflen, -gaih_getanswer (unsigned char *packet1, size_t packet1len, - unsigned char *packet2, size_t packet2len, - struct alloc_buffer *abuf, struct gaih_addrtuple **pat, int *errnop, int *h_errnop, int32_t *ttlp) { + int first = 1; + enum nss_status status = NSS_STATUS_NOTFOUND; /* Combining the NSS status of two distinct queries requires some @@ -1045,10 +1156,7 @@ 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. - with a larger buffer. (The caller, _nss_dns_gethostbyname4_r, - ignores the return status if it detects that the result buffer - has been exhausted and generates a TRYAGAIN failure with an - ERANGE code.) Lastly, you may be tempted to make significant changes to the conditions in this code to bring about symmetry between responses. @@ -1128,30 +1236,36 @@ 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 + NSS_STATUS_TRYAGAIN only if the error is non-recoverable, i.e. + *h_errnop is NO_RECOVERY. If not, and if the failure was due to + an insufficient buffer (ERANGE), then we need to drop the results + and pass on the NSS_STATUS_TRYAGAIN to the caller so that it can + repeat the query with a larger buffer. */ + && (*errnop != ERANGE || *h_errnop == NO_RECOVERY))) + && answer2 != NULL && anslen2 > 0) - if (packet1len > 0) { + enum nss_status status2 = gaih_getanswer_slice(answer2, anslen2, qname, + &pat, &buffer, &buflen, + errnop, h_errnop, ttlp, + &first); - status = gaih_getanswer_slice (packet1, packet1len, - abuf, &pat, errnop, h_errnop, ttlp, true); - if (alloc_buffer_has_failed (abuf)) - /* Do not try parsing the second packet if a larger result - buffer is needed. The caller implements the resizing - protocol because *abuf has been exhausted. */ - return NSS_STATUS_TRYAGAIN; /* Ignored by the caller. */ - } - - if ((status == NSS_STATUS_SUCCESS || status == NSS_STATUS_NOTFOUND) - && packet2 != NULL && packet2len > 0) - { - enum nss_status status2 - = gaih_getanswer_slice (packet2, packet2len, - abuf, &pat, errnop, h_errnop, ttlp, - /* Success means that data with a - canonical name has already been - stored. Do not store the name again. */ - status != NSS_STATUS_SUCCESS); /* 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; @@ -1159,13 +1273,18 @@ /* Variant of gaih_getanswer without a second (AAAA) response. */ static enum nss_status +gaih_getanswer_noaaaa (const querybuf *answer1, int anslen1, const char *qname, + struct gaih_addrtuple **pat, + char *buffer, size_t buflen, -gaih_getanswer_noaaaa (unsigned char *packet, size_t packetlen, - struct alloc_buffer *abuf, struct gaih_addrtuple **pat, int *errnop, int *h_errnop, int32_t *ttlp) { + int first = 1; + enum nss_status status = NSS_STATUS_NOTFOUND; + if (anslen1 > 0) + status = gaih_getanswer_slice (answer1, anslen1, qname, + &pat, &buffer, &buflen, + errnop, h_errnop, ttlp, + &first); - if (packetlen > 0) - status = gaih_getanswer_slice (packet, packetlen, - abuf, &pat, errnop, h_errnop, ttlp, true); return status; }