Message ID | 2a52948e0655cd2c5a81ea654259b6303aefe3c0.1660123636.git.fweimer@redhat.com |
---|---|
State | New |
Headers | show |
Series | nss_dns: Fix handling of non-host CNAMEs (bug 12154) | expand |
On 2022-08-10 05:31, Florian Weimer via Libc-alpha wrote: > Introduce struct alloc_buffer to this function, and use it and > struct ns_rr_cursor in gaih_getanswer_slice. Adjust gaih_getanswer > and gaih_getanswer_noaaaa accordingly. > --- > resolv/nss_dns/dns-host.c | 441 ++++++++++++++------------------------ > 1 file changed, 161 insertions(+), 280 deletions(-) > > diff --git a/resolv/nss_dns/dns-host.c b/resolv/nss_dns/dns-host.c > index 809a269a7c..5166e5d254 100644 > --- a/resolv/nss_dns/dns-host.c > +++ b/resolv/nss_dns/dns-host.c > @@ -100,13 +100,6 @@ > #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 > @@ -125,18 +118,18 @@ static enum nss_status getanswer_ptr (unsigned char *packet, size_t packetlen, > 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); > > @@ -408,17 +401,13 @@ _nss_dns_gethostbyname4_r (const char *name, struct gaih_addrtuple **pat, > 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; > @@ -427,22 +416,21 @@ _nss_dns_gethostbyname4_r (const char *name, struct gaih_addrtuple **pat, > 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) > { > @@ -473,12 +461,20 @@ _nss_dns_gethostbyname4_r (const char *name, struct gaih_addrtuple **pat, > __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; > @@ -892,259 +888,153 @@ getanswer_ptr (unsigned char *packet, size_t packetlen, > 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); Could you please add a comment on why a barrier is needed here? > + asm ("":::"memory"); > + 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 > @@ -1236,36 +1126,32 @@ gaih_getanswer (const querybuf *answer1, int anslen1, const querybuf *answer2, > 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. */ NSS_STATUS_TRYAGAIN? It does the right thing in the caller eventually, but this doesn't seem semantically correct. > + return NSS_STATUS_SUCCESS; > + } > + > + 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); > + if (alloc_buffer_has_failed (abuf)) > + /* Let the caller implement the buffer resizing protocol. */ > + return NSS_STATUS_SUCCESS; Same, wouldn't NSS_STATUS_TRYAGAIN be more correct here? > /* 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; > @@ -1273,18 +1159,13 @@ gaih_getanswer (const querybuf *answer1, int anslen1, const querybuf *answer2, > > /* 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; > }
* Siddhesh Poyarekar: > On 2022-08-10 05:31, Florian Weimer via Libc-alpha wrote: > > Could you please add a comment on why a barrier is needed here? > >> + asm ("":::"memory"); It's a leftover from debugging. I'll drop it in a repost. >> @@ -1236,36 +1126,32 @@ gaih_getanswer (const querybuf *answer1, int anslen1, const querybuf *answer2, >> 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. */ > > NSS_STATUS_TRYAGAIN? It does the right thing in the caller > eventually, but this doesn't seem semantically correct. I'm going to add more comments and switch this to NSS_STATUS_TRYAGAIN. For one of the other functions, internal NSS_STATUS_SUCCESS is the only case in which a resize is attempted. See the code around line 267. >> + return NSS_STATUS_SUCCESS; >> + } >> + >> + 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); >> + if (alloc_buffer_has_failed (abuf)) >> + /* Let the caller implement the buffer resizing protocol. */ >> + return NSS_STATUS_SUCCESS; > > Same, wouldn't NSS_STATUS_TRYAGAIN be more correct here? I think we can remove this early return because the caller ignores the status anyway for the buffer-exhausted case, so we do not need to special-case it here. I'll repost this with the barrier removed and more comments added. Thanks, Florian
diff --git a/resolv/nss_dns/dns-host.c b/resolv/nss_dns/dns-host.c index 809a269a7c..5166e5d254 100644 --- a/resolv/nss_dns/dns-host.c +++ b/resolv/nss_dns/dns-host.c @@ -100,13 +100,6 @@ #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 @@ -125,18 +118,18 @@ static enum nss_status getanswer_ptr (unsigned char *packet, size_t packetlen, 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); @@ -408,17 +401,13 @@ _nss_dns_gethostbyname4_r (const char *name, struct gaih_addrtuple **pat, 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; @@ -427,22 +416,21 @@ _nss_dns_gethostbyname4_r (const char *name, struct gaih_addrtuple **pat, 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) { @@ -473,12 +461,20 @@ _nss_dns_gethostbyname4_r (const char *name, struct gaih_addrtuple **pat, __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; @@ -892,259 +888,153 @@ getanswer_ptr (unsigned char *packet, size_t packetlen, 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); + asm ("":::"memory"); + 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 @@ -1236,36 +1126,32 @@ gaih_getanswer (const querybuf *answer1, int anslen1, const querybuf *answer2, 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. */ + return NSS_STATUS_SUCCESS; + } + + 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); + if (alloc_buffer_has_failed (abuf)) + /* Let the caller implement the buffer resizing protocol. */ + return 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; @@ -1273,18 +1159,13 @@ gaih_getanswer (const querybuf *answer1, int anslen1, const querybuf *answer2, /* 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; }