You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
507 lines
15 KiB
507 lines
15 KiB
commit 32e5db37684ffcbc6ae34fcc6cdcf28670506baa |
|
Author: Florian Weimer <fweimer@redhat.com> |
|
Date: Tue Aug 30 10:02:49 2022 +0200 |
|
|
|
nss_dns: Rewrite _nss_dns_gethostbyaddr2_r and getanswer_ptr |
|
|
|
The simplification takes advantage of the split from getanswer_r. |
|
It fixes various aliases issues, and optimizes NSS buffer usage. |
|
The new DNS packet parsing helpers are used, too. |
|
|
|
Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org> |
|
(cherry picked from commit e32547d661a43da63368e488b6cfa9c53b4dcf92) |
|
|
|
diff --git a/resolv/nss_dns/dns-host.c b/resolv/nss_dns/dns-host.c |
|
index a6bf73a091968358..2cd7170f20b60588 100644 |
|
--- a/resolv/nss_dns/dns-host.c |
|
+++ b/resolv/nss_dns/dns-host.c |
|
@@ -70,6 +70,7 @@ |
|
* --Copyright-- |
|
*/ |
|
|
|
+#include <alloc_buffer.h> |
|
#include <assert.h> |
|
#include <ctype.h> |
|
#include <errno.h> |
|
@@ -117,10 +118,9 @@ static enum nss_status getanswer_r (struct resolv_context *ctx, |
|
struct hostent *result, char *buffer, |
|
size_t buflen, int *errnop, int *h_errnop, |
|
int map, int32_t *ttlp, char **canonp); |
|
-static enum nss_status getanswer_ptr (const querybuf *answer, int anslen, |
|
- const char *qname, |
|
- struct hostent *result, char *buffer, |
|
- size_t buflen, int *errnop, |
|
+static enum nss_status getanswer_ptr (unsigned char *packet, size_t packetlen, |
|
+ struct alloc_buffer *abuf, |
|
+ char **hnamep, int *errnop, |
|
int *h_errnop, int32_t *ttlp); |
|
|
|
static enum nss_status gaih_getanswer (const querybuf *answer1, int anslen1, |
|
@@ -457,36 +457,21 @@ _nss_dns_gethostbyaddr2_r (const void *addr, socklen_t len, int af, |
|
static const u_char tunnelled[] = { 0,0, 0,0, 0,0, 0,0, 0,0, 0,0 }; |
|
static const u_char v6local[] = { 0,0, 0,1 }; |
|
const u_char *uaddr = (const u_char *)addr; |
|
- struct host_data |
|
- { |
|
- char *aliases[MAX_NR_ALIASES]; |
|
- unsigned char host_addr[16]; /* IPv4 or IPv6 */ |
|
- char *h_addr_ptrs[MAX_NR_ADDRS + 1]; |
|
- char linebuffer[0]; |
|
- } *host_data = (struct host_data *) buffer; |
|
- union |
|
- { |
|
- querybuf *buf; |
|
- u_char *ptr; |
|
- } host_buffer; |
|
- querybuf *orig_host_buffer; |
|
char qbuf[MAXDNAME+1], *qp = NULL; |
|
size_t size; |
|
int n, status; |
|
int olderr = errno; |
|
|
|
- uintptr_t pad = -(uintptr_t) buffer % __alignof__ (struct host_data); |
|
- buffer += pad; |
|
- buflen = buflen > pad ? buflen - pad : 0; |
|
- |
|
- if (__glibc_unlikely (buflen < sizeof (struct host_data))) |
|
- { |
|
- *errnop = ERANGE; |
|
- *h_errnop = NETDB_INTERNAL; |
|
- return NSS_STATUS_TRYAGAIN; |
|
- } |
|
- |
|
- host_data = (struct host_data *) buffer; |
|
+ /* Prepare the allocation buffer. Store the pointer array first, to |
|
+ benefit from buffer alignment. */ |
|
+ struct alloc_buffer abuf = alloc_buffer_create (buffer, buflen); |
|
+ char **address_array = alloc_buffer_alloc_array (&abuf, char *, 2); |
|
+ if (address_array == NULL) |
|
+ { |
|
+ *errnop = ERANGE; |
|
+ *h_errnop = NETDB_INTERNAL; |
|
+ return NSS_STATUS_TRYAGAIN; |
|
+ } |
|
|
|
struct resolv_context *ctx = __resolv_context_get (); |
|
if (ctx == NULL) |
|
@@ -530,8 +515,6 @@ _nss_dns_gethostbyaddr2_r (const void *addr, socklen_t len, int af, |
|
return NSS_STATUS_UNAVAIL; |
|
} |
|
|
|
- host_buffer.buf = orig_host_buffer = (querybuf *) alloca (1024); |
|
- |
|
switch (af) |
|
{ |
|
case AF_INET: |
|
@@ -555,35 +538,52 @@ _nss_dns_gethostbyaddr2_r (const void *addr, socklen_t len, int af, |
|
break; |
|
} |
|
|
|
- n = __res_context_query (ctx, qbuf, C_IN, T_PTR, host_buffer.buf->buf, |
|
- 1024, &host_buffer.ptr, NULL, NULL, NULL, NULL); |
|
+ unsigned char dns_packet_buffer[1024]; |
|
+ unsigned char *alt_dns_packet_buffer = dns_packet_buffer; |
|
+ n = __res_context_query (ctx, qbuf, C_IN, T_PTR, |
|
+ dns_packet_buffer, sizeof (dns_packet_buffer), |
|
+ &alt_dns_packet_buffer, |
|
+ NULL, NULL, NULL, NULL); |
|
if (n < 0) |
|
{ |
|
*h_errnop = h_errno; |
|
__set_errno (olderr); |
|
- 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 errno == ECONNREFUSED ? NSS_STATUS_UNAVAIL : NSS_STATUS_NOTFOUND; |
|
} |
|
|
|
- status = getanswer_ptr (host_buffer.buf, n, qbuf, result, |
|
- buffer, buflen, errnop, h_errnop, ttlp); |
|
- if (host_buffer.buf != orig_host_buffer) |
|
- free (host_buffer.buf); |
|
+ status = getanswer_ptr (alt_dns_packet_buffer, n, |
|
+ &abuf, &result->h_name, errnop, h_errnop, ttlp); |
|
+ |
|
+ if (alt_dns_packet_buffer != dns_packet_buffer) |
|
+ free (alt_dns_packet_buffer); |
|
+ __resolv_context_put (ctx); |
|
+ |
|
if (status != NSS_STATUS_SUCCESS) |
|
- { |
|
- __resolv_context_put (ctx); |
|
- return status; |
|
- } |
|
+ return status; |
|
|
|
+ /* result->h_name has already been set by getanswer_ptr. */ |
|
result->h_addrtype = af; |
|
result->h_length = len; |
|
- memcpy (host_data->host_addr, addr, len); |
|
- host_data->h_addr_ptrs[0] = (char *) host_data->host_addr; |
|
- host_data->h_addr_ptrs[1] = NULL; |
|
+ /* Increase the alignment to 4, in case there are applications out |
|
+ there that expect at least this level of address alignment. */ |
|
+ address_array[0] = (char *) alloc_buffer_next (&abuf, uint32_t); |
|
+ alloc_buffer_copy_bytes (&abuf, uaddr, len); |
|
+ address_array[1] = NULL; |
|
+ |
|
+ /* This check also covers allocation failure in getanswer_ptr. */ |
|
+ if (alloc_buffer_has_failed (&abuf)) |
|
+ { |
|
+ *errnop = ERANGE; |
|
+ *h_errnop = NETDB_INTERNAL; |
|
+ return NSS_STATUS_TRYAGAIN; |
|
+ } |
|
+ result->h_addr_list = address_array; |
|
+ result->h_aliases = &address_array[1]; /* Points to NULL. */ |
|
+ |
|
*h_errnop = NETDB_SUCCESS; |
|
- __resolv_context_put (ctx); |
|
return NSS_STATUS_SUCCESS; |
|
} |
|
libc_hidden_def (_nss_dns_gethostbyaddr2_r) |
|
@@ -962,287 +962,86 @@ getanswer_r (struct resolv_context *ctx, |
|
} |
|
|
|
static enum nss_status |
|
-getanswer_ptr (const querybuf *answer, int anslen, const char *qname, |
|
- struct hostent *result, char *buffer, size_t buflen, |
|
+getanswer_ptr (unsigned char *packet, size_t packetlen, |
|
+ struct alloc_buffer *abuf, char **hnamep, |
|
int *errnop, int *h_errnop, int32_t *ttlp) |
|
{ |
|
- struct host_data |
|
- { |
|
- char *aliases[MAX_NR_ALIASES]; |
|
- unsigned char host_addr[16]; /* IPv4 or IPv6 */ |
|
- char *h_addr_ptrs[0]; |
|
- } *host_data; |
|
- int linebuflen; |
|
- const HEADER *hp; |
|
- const u_char *end_of_message, *cp; |
|
- int n, ancount, qdcount; |
|
- int haveanswer, had_error; |
|
- char *bp, **ap, **hap; |
|
- char tbuf[MAXDNAME]; |
|
- const char *tname; |
|
- u_char packtmp[NS_MAXCDNAME]; |
|
- uintptr_t pad = -(uintptr_t) buffer % __alignof__ (struct host_data); |
|
- buffer += pad; |
|
- buflen = buflen > pad ? buflen - pad : 0; |
|
- if (__glibc_unlikely (buflen < sizeof (struct host_data))) |
|
- { |
|
- /* The buffer is too small. */ |
|
- too_small: |
|
- *errnop = ERANGE; |
|
- *h_errnop = NETDB_INTERNAL; |
|
- return NSS_STATUS_TRYAGAIN; |
|
- } |
|
- host_data = (struct host_data *) buffer; |
|
- linebuflen = buflen - sizeof (struct host_data); |
|
- if (buflen - sizeof (struct host_data) != linebuflen) |
|
- linebuflen = INT_MAX; |
|
- |
|
- tname = qname; |
|
- result->h_name = NULL; |
|
- end_of_message = answer->buf + anslen; |
|
- |
|
- /* |
|
- * find first satisfactory answer |
|
- */ |
|
- hp = &answer->hdr; |
|
- ancount = ntohs (hp->ancount); |
|
- qdcount = ntohs (hp->qdcount); |
|
- cp = answer->buf + HFIXEDSZ; |
|
- if (__glibc_unlikely (qdcount != 1)) |
|
- { |
|
- *h_errnop = NO_RECOVERY; |
|
- return NSS_STATUS_UNAVAIL; |
|
- } |
|
- if (sizeof (struct host_data) + (ancount + 1) * sizeof (char *) >= buflen) |
|
- goto too_small; |
|
- bp = (char *) &host_data->h_addr_ptrs[ancount + 1]; |
|
- linebuflen -= (ancount + 1) * sizeof (char *); |
|
- |
|
- n = __ns_name_unpack (answer->buf, end_of_message, cp, |
|
- packtmp, sizeof packtmp); |
|
- if (n != -1 && __ns_name_ntop (packtmp, bp, linebuflen) == -1) |
|
+ struct ns_rr_cursor c; |
|
+ if (!__ns_rr_cursor_init (&c, packet, packetlen)) |
|
{ |
|
- if (__glibc_unlikely (errno == EMSGSIZE)) |
|
- goto too_small; |
|
- |
|
- n = -1; |
|
- } |
|
- |
|
- if (__glibc_unlikely (n < 0)) |
|
- { |
|
- *errnop = errno; |
|
- *h_errnop = NO_RECOVERY; |
|
- return NSS_STATUS_UNAVAIL; |
|
- } |
|
- if (__glibc_unlikely (__libc_res_dnok (bp) == 0)) |
|
- { |
|
- 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; |
|
+ 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]; |
|
|
|
- ap = host_data->aliases; |
|
- *ap = NULL; |
|
- result->h_aliases = host_data->aliases; |
|
- hap = host_data->h_addr_ptrs; |
|
- *hap = NULL; |
|
- result->h_addr_list = host_data->h_addr_ptrs; |
|
- haveanswer = 0; |
|
- had_error = 0; |
|
- |
|
- while (ancount-- > 0 && cp < end_of_message && had_error == 0) |
|
+ while (ancount > 0) |
|
{ |
|
- int type, class; |
|
- |
|
- n = __ns_name_unpack (answer->buf, end_of_message, cp, |
|
- packtmp, sizeof packtmp); |
|
- if (n != -1 && __ns_name_ntop (packtmp, bp, linebuflen) == -1) |
|
+ struct ns_rr_wire rr; |
|
+ if (!__ns_rr_cursor_next (&c, &rr)) |
|
{ |
|
- if (__glibc_unlikely (errno == EMSGSIZE)) |
|
- goto too_small; |
|
- |
|
- n = -1; |
|
- } |
|
- |
|
- if (__glibc_unlikely (n < 0 || __libc_res_dnok (bp) == 0)) |
|
- { |
|
- ++had_error; |
|
- continue; |
|
- } |
|
- cp += n; /* name */ |
|
- |
|
- if (__glibc_unlikely (cp + 10 > end_of_message)) |
|
- { |
|
- ++had_error; |
|
- continue; |
|
+ *h_errnop = NO_RECOVERY; |
|
+ return NSS_STATUS_UNAVAIL; |
|
} |
|
|
|
- NS_GET16 (type, cp); |
|
- NS_GET16 (class, cp); |
|
- int32_t ttl; |
|
- NS_GET32 (ttl, cp); |
|
- NS_GET16 (n, cp); /* RDATA length. */ |
|
+ /* Skip over records with the wrong class. */ |
|
+ if (rr.rclass != C_IN) |
|
+ continue; |
|
|
|
- if (end_of_message - cp < n) |
|
- { |
|
- /* RDATA extends beyond the end of the packet. */ |
|
- ++had_error; |
|
- continue; |
|
- } |
|
- |
|
- if (__glibc_unlikely (class != C_IN)) |
|
- { |
|
- /* XXX - debug? syslog? */ |
|
- cp += n; |
|
- continue; /* XXX - had_error++ ? */ |
|
- } |
|
+ /* Update TTL for known record types. */ |
|
+ if ((rr.rtype == T_CNAME || rr.rtype == T_PTR) |
|
+ && ttlp != NULL && *ttlp > rr.ttl) |
|
+ *ttlp = rr.ttl; |
|
|
|
- if (type == T_CNAME) |
|
+ if (rr.rtype == T_CNAME) |
|
{ |
|
- /* 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 || __libc_res_dnok (tbuf) == 0)) |
|
- { |
|
- ++had_error; |
|
- continue; |
|
- } |
|
- cp += n; |
|
- /* Get canonical name. */ |
|
- n = strlen (tbuf) + 1; /* For the \0. */ |
|
- if (__glibc_unlikely (n > linebuflen)) |
|
- goto too_small; |
|
- if (__glibc_unlikely (n >= MAXHOSTNAMELEN)) |
|
+ /* 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) |
|
{ |
|
- ++had_error; |
|
- continue; |
|
+ *h_errnop = NO_RECOVERY; |
|
+ return NSS_STATUS_UNAVAIL; |
|
} |
|
- tname = bp; |
|
- bp = __mempcpy (bp, tbuf, n); /* Cannot overflow. */ |
|
- linebuflen -= n; |
|
- continue; |
|
+ expected_name = name_buffer; |
|
} |
|
- |
|
- switch (type) |
|
+ else if (rr.rtype == T_PTR |
|
+ && __ns_samebinaryname (rr.rname, expected_name)) |
|
{ |
|
- case T_PTR: |
|
- if (__glibc_unlikely (__strcasecmp (tname, bp) != 0)) |
|
- { |
|
- cp += n; |
|
- continue; /* XXX - had_error++ ? */ |
|
- } |
|
- |
|
- n = __ns_name_unpack (answer->buf, end_of_message, cp, |
|
- packtmp, sizeof packtmp); |
|
- if (n != -1 && __ns_name_ntop (packtmp, bp, linebuflen) == -1) |
|
- { |
|
- if (__glibc_unlikely (errno == EMSGSIZE)) |
|
- goto too_small; |
|
- |
|
- n = -1; |
|
- } |
|
- |
|
- if (__glibc_unlikely (n < 0 || __libc_res_hnok (bp) == 0)) |
|
+ /* Decompress the target of the PTR record. This is the |
|
+ host name we are looking for. We can only use it if it |
|
+ is syntactically valid. Historically, only one host name |
|
+ is returned here. If the recursive resolver performs DNS |
|
+ record rotation, the returned host name is essentially |
|
+ random, which is why multiple PTR records are rarely |
|
+ used. Use MAXHOSTNAMELEN instead of NS_MAXCDNAME for |
|
+ additional length checking. */ |
|
+ char hname[MAXHOSTNAMELEN + 1]; |
|
+ if (__ns_name_unpack (c.begin, c.end, rr.rdata, |
|
+ name_buffer, sizeof (name_buffer)) < 0 |
|
+ || !__res_binary_hnok (expected_name) |
|
+ || __ns_name_ntop (name_buffer, hname, sizeof (hname)) < 0) |
|
{ |
|
- ++had_error; |
|
- break; |
|
+ *h_errnop = NO_RECOVERY; |
|
+ return NSS_STATUS_UNAVAIL; |
|
} |
|
- if (ttlp != NULL && ttl < *ttlp) |
|
- *ttlp = ttl; |
|
- /* bind would put multiple PTR records as aliases, but we don't do |
|
- that. */ |
|
- result->h_name = bp; |
|
- *h_errnop = NETDB_SUCCESS; |
|
+ /* Successful allocation is checked by the caller. */ |
|
+ *hnamep = alloc_buffer_copy_string (abuf, hname); |
|
return NSS_STATUS_SUCCESS; |
|
- case T_A: |
|
- case T_AAAA: |
|
- if (__glibc_unlikely (__strcasecmp (result->h_name, bp) != 0)) |
|
- { |
|
- cp += n; |
|
- continue; /* XXX - had_error++ ? */ |
|
- } |
|
- |
|
- /* Stop parsing at a record whose length is incorrect. */ |
|
- if (n != rrtype_to_rdata_length (type)) |
|
- { |
|
- ++had_error; |
|
- break; |
|
- } |
|
- |
|
- /* Skip records of the wrong type. */ |
|
- if (n != result->h_length) |
|
- { |
|
- cp += n; |
|
- continue; |
|
- } |
|
- if (!haveanswer) |
|
- { |
|
- int nn; |
|
- |
|
- /* 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; |
|
- result->h_name = bp; |
|
- nn = strlen (bp) + 1; /* for the \0 */ |
|
- bp += nn; |
|
- linebuflen -= nn; |
|
- } |
|
- |
|
- /* Provide sufficient alignment for both address |
|
- families. */ |
|
- enum { align = 4 }; |
|
- _Static_assert ((align % __alignof__ (struct in_addr)) == 0, |
|
- "struct in_addr alignment"); |
|
- _Static_assert ((align % __alignof__ (struct in6_addr)) == 0, |
|
- "struct in6_addr alignment"); |
|
- { |
|
- char *new_bp = PTR_ALIGN_UP (bp, align); |
|
- linebuflen -= new_bp - bp; |
|
- bp = new_bp; |
|
- } |
|
- |
|
- if (__glibc_unlikely (n > linebuflen)) |
|
- goto too_small; |
|
- bp = __mempcpy (*hap++ = bp, cp, n); |
|
- cp += n; |
|
- linebuflen -= n; |
|
- break; |
|
- default: |
|
- cp += n; |
|
- continue; /* XXX - had_error++ ? */ |
|
} |
|
- if (had_error == 0) |
|
- ++haveanswer; |
|
} |
|
|
|
- if (haveanswer > 0) |
|
- { |
|
- *ap = NULL; |
|
- *hap = NULL; |
|
- |
|
- if (result->h_name == NULL) |
|
- { |
|
- n = strlen (qname) + 1; /* For the \0. */ |
|
- if (n > linebuflen) |
|
- goto too_small; |
|
- if (n >= MAXHOSTNAMELEN) |
|
- goto no_recovery; |
|
- result->h_name = bp; |
|
- bp = __mempcpy (bp, qname, n); /* Cannot overflow. */ |
|
- linebuflen -= n; |
|
- } |
|
+ /* No PTR record found. */ |
|
+ if (ttlp != NULL) |
|
+ /* No caching of negative responses. */ |
|
+ *ttlp = 0; |
|
|
|
- *h_errnop = NETDB_SUCCESS; |
|
- return NSS_STATUS_SUCCESS; |
|
- } |
|
- no_recovery: |
|
*h_errnop = NO_RECOVERY; |
|
*errnop = ENOENT; |
|
return NSS_STATUS_TRYAGAIN;
|
|
|