nss_dns: Rewrite _nss_dns_gethostbyname4_r using current interfaces

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.

Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
This commit is contained in:
Florian Weimer 2022-08-30 10:02:49 +02:00
parent 9caf782276
commit 1d495912a7

View File

@ -100,13 +100,6 @@
#endif #endif
#define MAXHOSTNAMELEN 256 #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 /* For historic reasons, pointers to IP addresses are char *, so use a
single list type for addresses and host names. */ single list type for addresses and host names. */
#define DYNARRAY_STRUCT ptrlist #define DYNARRAY_STRUCT ptrlist
@ -125,18 +118,18 @@ static enum nss_status getanswer_ptr (unsigned char *packet, size_t packetlen,
char **hnamep, int *errnop, char **hnamep, int *errnop,
int *h_errnop, int32_t *ttlp); int *h_errnop, int32_t *ttlp);
static enum nss_status gaih_getanswer (const querybuf *answer1, int anslen1, static enum nss_status gaih_getanswer (unsigned char *packet1,
const querybuf *answer2, int anslen2, size_t packet1len,
const char *qname, unsigned char *packet2,
size_t packet2len,
struct alloc_buffer *abuf,
struct gaih_addrtuple **pat, struct gaih_addrtuple **pat,
char *buffer, size_t buflen,
int *errnop, int *h_errnop, int *errnop, int *h_errnop,
int32_t *ttlp); int32_t *ttlp);
static enum nss_status gaih_getanswer_noaaaa (const querybuf *answer1, static enum nss_status gaih_getanswer_noaaaa (unsigned char *packet,
int anslen1, size_t packetlen,
const char *qname, struct alloc_buffer *abuf,
struct gaih_addrtuple **pat, struct gaih_addrtuple **pat,
char *buffer, size_t buflen,
int *errnop, int *h_errnop, int *errnop, int *h_errnop,
int32_t *ttlp); int32_t *ttlp);
@ -408,17 +401,13 @@ _nss_dns_gethostbyname4_r (const char *name, struct gaih_addrtuple **pat,
name = cp; name = cp;
} }
union unsigned char dns_packet_buffer[2048];
{ unsigned char *alt_dns_packet_buffer = dns_packet_buffer;
querybuf *buf;
u_char *ptr;
} host_buffer;
querybuf *orig_host_buffer;
host_buffer.buf = orig_host_buffer = (querybuf *) alloca (2048);
u_char *ans2p = NULL; u_char *ans2p = NULL;
int nans2p = 0; int nans2p = 0;
int resplen2 = 0; int resplen2 = 0;
int ans2p_malloced = 0; int ans2p_malloced = 0;
struct alloc_buffer abuf = alloc_buffer_create (buffer, buflen);
int olderr = errno; 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) if ((ctx->resp->options & RES_NOAAAA) == 0)
{ {
n = __res_context_search (ctx, name, C_IN, T_QUERY_A_AND_AAAA, n = __res_context_search (ctx, name, C_IN, T_QUERY_A_AND_AAAA,
host_buffer.buf->buf, 2048, &host_buffer.ptr, dns_packet_buffer, sizeof (dns_packet_buffer),
&ans2p, &nans2p, &resplen2, &ans2p_malloced); &alt_dns_packet_buffer, &ans2p, &nans2p,
&resplen2, &ans2p_malloced);
if (n >= 0) if (n >= 0)
status = gaih_getanswer (host_buffer.buf, n, (const querybuf *) ans2p, status = gaih_getanswer (alt_dns_packet_buffer, n, ans2p, resplen2,
resplen2, name, pat, buffer, buflen, &abuf, pat, errnop, herrnop, ttlp);
errnop, herrnop, ttlp);
} }
else else
{ {
n = __res_context_search (ctx, name, C_IN, T_A, n = __res_context_search (ctx, name, C_IN, T_A,
host_buffer.buf->buf, 2048, NULL, dns_packet_buffer, sizeof (dns_packet_buffer),
NULL, NULL, NULL, NULL); NULL, NULL, NULL, NULL, NULL);
if (n >= 0) if (n >= 0)
status = gaih_getanswer_noaaaa (host_buffer.buf, n, status = gaih_getanswer_noaaaa (alt_dns_packet_buffer, n,
name, pat, buffer, buflen, &abuf, pat, errnop, herrnop, ttlp);
errnop, herrnop, ttlp);
} }
if (n < 0) if (n < 0)
{ {
@ -473,12 +461,20 @@ _nss_dns_gethostbyname4_r (const char *name, struct gaih_addrtuple **pat,
__set_errno (olderr); __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. */ /* Check whether ans2p was separately allocated. */
if (ans2p_malloced) if (ans2p_malloced)
free (ans2p); free (ans2p);
if (host_buffer.buf != orig_host_buffer) if (alt_dns_packet_buffer != dns_packet_buffer)
free (host_buffer.buf); free (alt_dns_packet_buffer);
__resolv_context_put (ctx); __resolv_context_put (ctx);
return status; return status;
@ -892,259 +888,152 @@ getanswer_ptr (unsigned char *packet, size_t packetlen,
return NSS_STATUS_TRYAGAIN; 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 static enum nss_status
gaih_getanswer_slice (const querybuf *answer, int anslen, const char *qname, gaih_getanswer_slice (unsigned char *packet, size_t packetlen,
struct gaih_addrtuple ***patp, struct alloc_buffer *abuf,
char **bufferp, size_t *buflenp, struct gaih_addrtuple ***tailp,
int *errnop, int *h_errnop, int32_t *ttlp, int *firstp) int *errnop, int *h_errnop, int32_t *ttlp,
bool store_canon)
{ {
char *buffer = *bufferp; struct ns_rr_cursor c;
size_t buflen = *buflenp; if (!__ns_rr_cursor_init (&c, packet, packetlen))
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))
{ {
/* This should not happen because __res_context_query already
perfroms response validation. */
*h_errnop = NO_RECOVERY; *h_errnop = NO_RECOVERY;
return NSS_STATUS_UNAVAIL; return NSS_STATUS_UNAVAIL;
} }
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];
u_char packtmp[NS_MAXCDNAME]; /* This is a pointer to a possibly-compressed name in the packet.
int n = __ns_name_unpack (answer->buf, end_of_message, cp, Eventually it is equivalent to the canonical name. If needed, it
packtmp, sizeof packtmp); is uncompressed and translated to text form when the first
/* We unpack the name to check it for validity. But we do not need address tuple is encountered. */
it later. */ const unsigned char *compressed_alias_name = expected_name;
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 (ancount == 0 || !__res_binary_hnok (compressed_alias_name))
}
if (__glibc_unlikely (n < 0))
{
*errnop = errno;
*h_errnop = NO_RECOVERY;
return NSS_STATUS_UNAVAIL;
}
if (__glibc_unlikely (__libc_res_hnok (buffer) == 0))
{
errno = EBADMSG;
*errnop = EBADMSG;
*h_errnop = NO_RECOVERY;
return NSS_STATUS_UNAVAIL;
}
cp += n + QFIXEDSZ;
int haveanswer = 0;
int had_error = 0;
char *canon = NULL;
char *h_name = NULL;
int h_namelen = 0;
if (ancount == 0)
{ {
*h_errnop = HOST_NOT_FOUND; *h_errnop = HOST_NOT_FOUND;
return NSS_STATUS_NOTFOUND; 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, struct ns_rr_wire rr;
packtmp, sizeof packtmp); if (!__ns_rr_cursor_next (&c, &rr))
if (n != -1 &&
(h_namelen = __ns_name_ntop (packtmp, buffer, buflen)) == -1)
{ {
if (__glibc_unlikely (errno == EMSGSIZE)) *h_errnop = NO_RECOVERY;
goto too_small; return NSS_STATUS_UNAVAIL;
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 */ /* Update TTL for known record types. */
if ((rr.rtype == T_CNAME || rr.rtype == qtype)
&& ttlp != NULL && *ttlp > rr.ttl)
*ttlp = rr.ttl;
if (__glibc_unlikely (cp + 10 > end_of_message)) if (rr.rtype == T_CNAME)
{ {
++had_error; /* NB: No check for owner name match, based on historic
continue; precedent. Record the CNAME target as the new expected
} name. */
int n = __ns_name_unpack (c.begin, c.end, rr.rdata,
uint16_t type; name_buffer, sizeof (name_buffer));
NS_GET16 (type, cp); if (n < 0)
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)
{
/* RDATA extends beyond the end of the packet. */
++had_error;
continue;
}
if (class != C_IN)
{
cp += n;
continue;
}
if (type == 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; *h_errnop = NO_RECOVERY;
continue; return NSS_STATUS_UNAVAIL;
} }
cp += n; expected_name = name_buffer;
if (store_canon && __res_binary_hnok (name_buffer))
if (*firstp && __libc_res_hnok (tbuf)) /* 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;
}
else if (rr.rtype == qtype
&& __ns_samebinaryname (rr.rname, expected_name)
&& rr.rdlength == rrtype_to_rdata_length (qtype))
{
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)
{ {
/* Reclaim buffer space. */ ntup->next = NULL;
if (h_name + h_namelen == buffer) if (store_canon && compressed_alias_name != NULL)
{ {
buffer = h_name; /* This assumes that all the CNAME records come
buflen += h_namelen; 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;
n = strlen (tbuf) + 1; /* Link in the new tuple, and update the tail pointer to
if (__glibc_unlikely (n > buflen)) point to its next field. */
goto too_small; **tailp = ntup;
if (__glibc_unlikely (n >= MAXHOSTNAMELEN)) *tailp = &ntup->next;
{
++had_error;
continue;
}
canon = buffer; haveanswer = true;
buffer = __mempcpy (buffer, tbuf, n);
buflen -= n;
h_namelen = 0;
}
continue;
}
/* Stop parsing if we encounter a record with incorrect RDATA
length. */
if (type == T_A || type == T_AAAA)
{
if (n != rrtype_to_rdata_length (type))
{
++had_error;
continue;
} }
} }
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) if (haveanswer)
{ {
*patp = pat;
*bufferp = buffer;
*buflenp = buflen;
*h_errnop = NETDB_SUCCESS; *h_errnop = NETDB_SUCCESS;
return NSS_STATUS_SUCCESS; return NSS_STATUS_SUCCESS;
} }
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 instead of TRYAGAIN. */
if (canon != NULL)
{ {
/* 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; *h_errnop = HOST_NOT_FOUND;
return NSS_STATUS_NOTFOUND; return NSS_STATUS_NOTFOUND;
} }
*h_errnop = NETDB_INTERNAL;
return NSS_STATUS_TRYAGAIN;
} }
static enum nss_status static enum nss_status
gaih_getanswer (const querybuf *answer1, int anslen1, const querybuf *answer2, gaih_getanswer (unsigned char *packet1, size_t packet1len,
int anslen2, const char *qname, unsigned char *packet2, size_t packet2len,
struct gaih_addrtuple **pat, char *buffer, size_t buflen, struct alloc_buffer *abuf, struct gaih_addrtuple **pat,
int *errnop, int *h_errnop, int32_t *ttlp) int *errnop, int *h_errnop, int32_t *ttlp)
{ {
int first = 1;
enum nss_status status = NSS_STATUS_NOTFOUND; enum nss_status status = NSS_STATUS_NOTFOUND;
/* Combining the NSS status of two distinct queries requires some /* Combining the NSS status of two distinct queries requires some
@ -1156,7 +1045,10 @@ gaih_getanswer (const querybuf *answer1, int anslen1, const querybuf *answer2,
between TRYAGAIN (recoverable) and TRYAGAIN' (not-recoverable). between TRYAGAIN (recoverable) and TRYAGAIN' (not-recoverable).
A recoverable TRYAGAIN is almost always due to buffer size issues A recoverable TRYAGAIN is almost always due to buffer size issues
and returns ERANGE in errno and the caller is expected to retry 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 Lastly, you may be tempted to make significant changes to the
conditions in this code to bring about symmetry between responses. conditions in this code to bring about symmetry between responses.
@ -1236,36 +1128,30 @@ gaih_getanswer (const querybuf *answer1, int anslen1, const querybuf *answer2,
is a recoverable error we now return TRYAGIN even if the first is a recoverable error we now return TRYAGIN even if the first
response was SUCCESS. */ response was SUCCESS. */
if (anslen1 > 0) if (packet1len > 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)
{ {
enum nss_status status2 = gaih_getanswer_slice(answer2, anslen2, qname, status = gaih_getanswer_slice (packet1, packet1len,
&pat, &buffer, &buflen, abuf, &pat, errnop, h_errnop, ttlp, true);
errnop, h_errnop, ttlp, if (alloc_buffer_has_failed (abuf))
&first); /* 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. */ /* Use the second response status in some cases. */
if (status != NSS_STATUS_SUCCESS && status2 != NSS_STATUS_NOTFOUND) if (status != NSS_STATUS_SUCCESS && status2 != NSS_STATUS_NOTFOUND)
status = status2; 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; 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. */ /* Variant of gaih_getanswer without a second (AAAA) response. */
static enum nss_status static enum nss_status
gaih_getanswer_noaaaa (const querybuf *answer1, int anslen1, const char *qname, gaih_getanswer_noaaaa (unsigned char *packet, size_t packetlen,
struct gaih_addrtuple **pat, struct alloc_buffer *abuf, struct gaih_addrtuple **pat,
char *buffer, size_t buflen,
int *errnop, int *h_errnop, int32_t *ttlp) int *errnop, int *h_errnop, int32_t *ttlp)
{ {
int first = 1;
enum nss_status status = NSS_STATUS_NOTFOUND; enum nss_status status = NSS_STATUS_NOTFOUND;
if (anslen1 > 0) if (packetlen > 0)
status = gaih_getanswer_slice (answer1, anslen1, qname, status = gaih_getanswer_slice (packet, packetlen,
&pat, &buffer, &buflen, abuf, &pat, errnop, h_errnop, ttlp, true);
errnop, h_errnop, ttlp,
&first);
return status; return status;
} }