Use AI_NUMERICSERV if block_dns, and use local getaddrinfo to audit.
This change has two parts: 1. Provide our own local implementation of numeric-only getaddrinfo in auditdns.c used to audit for DNS leaks, rather than deferring to dlsym(RTLD_NEXT, "getaddrinfo"), in terms of inet_pton. To keep review and implementation simple, this is limited to AI_NUMERICHOST _and_ AI_NUMERICSERV -- this requires that we arrange to pass AI_NUMERICSERV in callers too. 2. Wherever we implement block_dns, set AI_NUMERICSERV in addition to AI_NUMERICHOST as needed by the new auditdns.c getaddrinfo. (In principle this might also avoid other network leaks -- POSIX guarantees no name resolution service will be invoked, and gives NIS+ as an example.) One tiny semantic change to avoid tripping over the auditor: kadmin(8) now uses the string "749" rather than the string "kerberos-adm". (Currently we don't audit kadmin(8) for DNS leaks but let's avoid leaving a rake to step on.) Every other caller I found is already guaranteed to pass a numeric service rather than named service to getaddrinfo. fix https://github.com/heimdal/heimdal/issues/1212
This commit is contained in:

committed by
Nico Williams

parent
1d8f4347bb
commit
e75e549252
@ -28,8 +28,11 @@
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
@ -65,32 +68,308 @@ gethostbyname2(const char *name, int af)
|
||||
|
||||
#ifdef HAVE_GETADDRINFO
|
||||
|
||||
typedef int getaddrinfo_fn_t(const char *, const char *,
|
||||
const struct addrinfo *restrict,
|
||||
struct addrinfo **restrict);
|
||||
getaddrinfo_fn_t getaddrinfo;
|
||||
void
|
||||
freeaddrinfo(struct addrinfo *ai)
|
||||
{
|
||||
|
||||
free(ai->ai_addr);
|
||||
free(ai);
|
||||
}
|
||||
|
||||
int
|
||||
getaddrinfo(const char *hostname, const char *servname,
|
||||
const struct addrinfo *restrict hints,
|
||||
struct addrinfo **restrict res)
|
||||
{
|
||||
void *sym;
|
||||
char *servend;
|
||||
unsigned long port;
|
||||
union {
|
||||
struct sockaddr sa;
|
||||
struct sockaddr_in sin;
|
||||
struct sockaddr_in6 sin6;
|
||||
} *addr = NULL;
|
||||
int af[2] = {AF_INET, AF_INET6};
|
||||
socklen_t addrlen[2] = {sizeof(addr->sin), sizeof(addr->sin6)};
|
||||
int socktype[2] = {SOCK_DGRAM, SOCK_STREAM};
|
||||
int proto[2] = {IPPROTO_UDP, IPPROTO_TCP};
|
||||
size_t i, j, naddr, nproto;
|
||||
struct addrinfo *ai = NULL;
|
||||
int error;
|
||||
|
||||
/*
|
||||
* DNS audit: Abort unless the user specified hints with
|
||||
* AI_NUMERICHOST, AI_NUMERICSERV, and no AI_CANONNAME.
|
||||
*/
|
||||
if (hints == NULL ||
|
||||
(hints->ai_flags & AI_NUMERICHOST) == 0 ||
|
||||
(hints->ai_flags & AI_NUMERICSERV) == 0 ||
|
||||
(hints->ai_flags & AI_CANONNAME) != 0) {
|
||||
fprintf(stderr, "DNS leak: %s %s:%s\n",
|
||||
__func__, hostname, servname);
|
||||
abort();
|
||||
}
|
||||
|
||||
if ((sym = dlsym(RTLD_NEXT, __func__)) == NULL) {
|
||||
fprintf(stderr, "dlsym(RTLD_NEXT, \"%s\") failed: %s\n",
|
||||
__func__, dlerror());
|
||||
return EAI_FAIL;
|
||||
/*
|
||||
* Check hints for address family. If unspecified, use the default
|
||||
* set of address families: {AF_INET, AF_INET6}.
|
||||
*/
|
||||
switch (hints->ai_family) {
|
||||
case AF_UNSPEC:
|
||||
naddr = 2;
|
||||
break;
|
||||
case AF_INET:
|
||||
naddr = 1;
|
||||
af[0] = AF_INET;
|
||||
addrlen[0] = sizeof(addr->sin);
|
||||
break;
|
||||
case AF_INET6:
|
||||
naddr = 1;
|
||||
af[0] = AF_INET6;
|
||||
addrlen[0] = sizeof(addr->sin6);
|
||||
break;
|
||||
default:
|
||||
error = EAI_FAMILY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
return (*(getaddrinfo_fn_t *)sym)(hostname, servname, hints, res);
|
||||
/*
|
||||
* Check hints for socket type and protocol. If both are zero, we
|
||||
* use the default set of socktype/proto pairs. If one is
|
||||
* specified but not the other, use the default. If both are
|
||||
* specified, make sure they match.
|
||||
*/
|
||||
switch (hints->ai_socktype) {
|
||||
case 0:
|
||||
if (hints->ai_protocol == 0)
|
||||
nproto = sizeof(proto)/sizeof(proto[0]);
|
||||
else
|
||||
nproto = 1;
|
||||
break;
|
||||
case SOCK_DGRAM: /* datagram <-> UDP */
|
||||
if (hints->ai_protocol != 0 && hints->ai_protocol != IPPROTO_UDP) {
|
||||
error = EAI_SOCKTYPE;;
|
||||
goto out;
|
||||
}
|
||||
socktype[0] = SOCK_DGRAM;
|
||||
proto[0] = IPPROTO_UDP;
|
||||
nproto = 1;
|
||||
break;
|
||||
case SOCK_STREAM: /* stream <-> TCP */
|
||||
if (hints->ai_protocol != 0 && hints->ai_protocol != IPPROTO_TCP) {
|
||||
error = EAI_SOCKTYPE;
|
||||
goto out;
|
||||
}
|
||||
socktype[0] = SOCK_STREAM;
|
||||
proto[0] = IPPROTO_TCP;
|
||||
nproto = 1;
|
||||
break;
|
||||
default:
|
||||
error = EAI_SOCKTYPE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check whether a service is specified at all.
|
||||
*/
|
||||
if (servname == NULL) {
|
||||
/*
|
||||
* No service specified. Use the wildcard port 0.
|
||||
*/
|
||||
port = 0;
|
||||
} else {
|
||||
/*
|
||||
* Service specified. Parse it as a nonnegative integer, at
|
||||
* most 65535.
|
||||
*/
|
||||
errno = 0;
|
||||
port = strtoul(servname, &servend, 10);
|
||||
if (servend == servname ||
|
||||
*servend != '\0' ||
|
||||
errno != 0 ||
|
||||
port > 65535) {
|
||||
error = EAI_NONAME;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check whether a hostname is specified at all.
|
||||
*/
|
||||
if (hostname == NULL) {
|
||||
/*
|
||||
* No hostname. This only makes sense if we're going to bind
|
||||
* to a socket and receive incoming packets or listen and
|
||||
* accept incoming connections, i.e., only if AI_PASSIVE is
|
||||
* set. Otherwise, fail with EAI_NONAME.
|
||||
*/
|
||||
if ((hints->ai_flags & AI_PASSIVE) == 0) {
|
||||
error = EAI_NONAME;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate an array of as many addresses as the hints allow.
|
||||
*/
|
||||
if ((addr = calloc(naddr, sizeof(*addr))) == NULL) {
|
||||
error = EAI_MEMORY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fill the addresses with the ANY wildcard address, IPv4
|
||||
* 0.0.0.0 or IPv6 `::' (i.e., 0000:0000:....:0000).
|
||||
*/
|
||||
switch (hints->ai_family) {
|
||||
case AF_UNSPEC:
|
||||
assert(naddr == 2);
|
||||
addr[0].sin.sin_family = AF_INET;
|
||||
addr[0].sin.sin_port = htons(port);
|
||||
addr[0].sin.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
addr[1].sin6.sin6_family = AF_INET6;
|
||||
addr[1].sin6.sin6_port = htons(port);
|
||||
addr[1].sin6.sin6_addr = in6addr_any;
|
||||
break;
|
||||
case AF_INET:
|
||||
assert(naddr == 1);
|
||||
addr[0].sin.sin_family = AF_INET;
|
||||
addr[0].sin.sin_port = htons(port);
|
||||
addr[0].sin.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
break;
|
||||
case AF_INET6:
|
||||
assert(naddr == 1);
|
||||
addr[0].sin6.sin6_family = AF_INET6;
|
||||
addr[0].sin6.sin6_port = htons(port);
|
||||
addr[0].sin6.sin6_addr = in6addr_any;
|
||||
break;
|
||||
default:
|
||||
error = EAI_FAIL; /* XXX unreachable */
|
||||
goto out;
|
||||
}
|
||||
goto have_addr;
|
||||
} else {
|
||||
/*
|
||||
* Allocate a single socket address record. Since we have
|
||||
* AI_NUMERICHOST, the hostname can be parsed as only one
|
||||
* address and won't be resolved to an array of possibly >1
|
||||
* addresses.
|
||||
*/
|
||||
naddr = 1;
|
||||
if ((addr = calloc(naddr, sizeof(*addr))) == NULL) {
|
||||
error = EAI_MEMORY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the hints specify AF_INET, or don't specify anything, try
|
||||
* to parse it as an IPv4 address. If this fails, it will fall
|
||||
* through.
|
||||
*/
|
||||
if (hints->ai_family == AF_UNSPEC || hints->ai_family == AF_INET) {
|
||||
switch (inet_pton(AF_INET, hostname, &addr->sin.sin_addr)) {
|
||||
case -1: /* system error */
|
||||
error = EAI_SYSTEM;
|
||||
goto out;
|
||||
case 0: /* failure */
|
||||
break;
|
||||
case 1: /* success */
|
||||
addr->sin.sin_family = AF_INET;
|
||||
addr->sin.sin_port = htons(port);
|
||||
af[0] = AF_INET;
|
||||
addrlen[0] = sizeof(addr->sin);
|
||||
goto have_addr;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If the hints specify AF_INET6, or don't specify anything,
|
||||
* try to parse it as an IPv6 address. If this fails, it will
|
||||
* fall through.
|
||||
*/
|
||||
if (hints->ai_family == AF_UNSPEC || hints->ai_family == AF_INET6) {
|
||||
switch (inet_pton(AF_INET6, hostname, &addr->sin6.sin6_addr)) {
|
||||
case -1: /* system error */
|
||||
error = EAI_SYSTEM;
|
||||
goto out;
|
||||
case 0: /* failure */
|
||||
break;
|
||||
case 1: /* success */
|
||||
addr->sin6.sin6_family = AF_INET6;
|
||||
addr->sin6.sin6_port = htons(port);
|
||||
af[0] = AF_INET6;
|
||||
addrlen[0] = sizeof(addr->sin6);
|
||||
goto have_addr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* No hostname, or hostname can't be parsed.
|
||||
*/
|
||||
error = EAI_NONAME;
|
||||
goto out;
|
||||
|
||||
have_addr:
|
||||
/*
|
||||
* We have an address, or multiple possible addresses. Allocate an
|
||||
* array of addrinfo records to store the result.
|
||||
*/
|
||||
if ((ai = calloc(naddr * nproto, sizeof(*ai))) == NULL) {
|
||||
error = EAI_MEMORY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fill in the addrinfo records with the cartesian product of
|
||||
* matching address families and matching socktype/protocol pairs.
|
||||
*
|
||||
* XXX Consider randomizing the output for fun!
|
||||
*/
|
||||
for (i = 0; i < naddr; i++) {
|
||||
for (j = 0; j < nproto; j++) {
|
||||
ai[i*nproto + j] = (struct addrinfo) {
|
||||
.ai_flags = 0, /* input flags, unused on output */
|
||||
.ai_family = af[i],
|
||||
.ai_addrlen = addrlen[i],
|
||||
.ai_addr = &addr[i].sa,
|
||||
.ai_socktype = socktype[j],
|
||||
.ai_protocol = proto[j],
|
||||
.ai_canonname = NULL,
|
||||
.ai_next = &ai[i*nproto + j + 1],
|
||||
};
|
||||
}
|
||||
}
|
||||
addr = NULL; /* reference consumed by ai[...].ai_addr */
|
||||
|
||||
/*
|
||||
* Null out the last addrinfo's next pointer.
|
||||
*/
|
||||
ai[naddr*nproto - 1].ai_next = NULL;
|
||||
|
||||
/*
|
||||
* Success!
|
||||
*/
|
||||
error = 0;
|
||||
|
||||
out:
|
||||
/*
|
||||
* In the event of error, free whatever we've allocated so far.
|
||||
* Make sure to save and restore errno in case free touches it,
|
||||
* because EAI_SYSTEM requires errno to report the system error.
|
||||
*/
|
||||
if (error) {
|
||||
int errno_save = errno;
|
||||
|
||||
if (addr)
|
||||
free(addr);
|
||||
addr = NULL;
|
||||
if (ai)
|
||||
freeaddrinfo(ai);
|
||||
ai = NULL;
|
||||
|
||||
errno = errno_save;
|
||||
}
|
||||
*res = ai;
|
||||
return error;
|
||||
}
|
||||
|
||||
#endif /* HAVE_GETADDRINFO */
|
||||
|
@ -65,7 +65,11 @@ add_kadm_port(krb5_context contextp, const char *service, unsigned int port)
|
||||
static void
|
||||
add_standard_ports (krb5_context contextp)
|
||||
{
|
||||
add_kadm_port(contextp, "kerberos-adm", 749);
|
||||
if (krb5_config_get_bool(context, NULL, "libdefaults", "block_dns",
|
||||
NULL))
|
||||
add_kadm_port(contextp, "749", 749);
|
||||
else
|
||||
add_kadm_port(contextp, "kerberos-adm", 749);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -574,7 +574,7 @@ kadm_connect(kadm5_client_context *ctx)
|
||||
if (krb5_config_get_bool(context, NULL, "libdefaults", "block_dns",
|
||||
NULL)) {
|
||||
hints.ai_flags &= ~AI_CANONNAME;
|
||||
hints.ai_flags |= AI_NUMERICHOST;
|
||||
hints.ai_flags |= AI_NUMERICHOST|AI_NUMERICSERV;
|
||||
}
|
||||
error = getaddrinfo(hostname, portstr, &hints, &ai);
|
||||
if (error) {
|
||||
|
@ -73,7 +73,7 @@ connect_to_master (krb5_context context, const char *master,
|
||||
if (krb5_config_get_bool(context, NULL, "libdefaults", "block_dns",
|
||||
NULL)) {
|
||||
hints.ai_flags &= ~AI_CANONNAME;
|
||||
hints.ai_flags |= AI_NUMERICHOST;
|
||||
hints.ai_flags |= AI_NUMERICHOST|AI_NUMERICSERV;
|
||||
}
|
||||
error = getaddrinfo(master, port_str, &hints, &ai);
|
||||
if (error) {
|
||||
|
@ -2720,7 +2720,7 @@ kadm5_log_signal_socket_info(krb5_context context,
|
||||
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
|
||||
hints.ai_flags = AI_NUMERICHOST;
|
||||
hints.ai_flags = AI_NUMERICHOST|AI_NUMERICSERV;
|
||||
if (server_end)
|
||||
hints.ai_flags |= AI_PASSIVE;
|
||||
hints.ai_family = AF_INET;
|
||||
|
@ -1213,7 +1213,7 @@ krb5_parse_address(krb5_context context,
|
||||
if (krb5_config_get_bool(context, NULL, "libdefaults", "block_dns",
|
||||
NULL)) {
|
||||
hint.ai_flags &= ~AI_CANONNAME;
|
||||
hint.ai_flags |= AI_NUMERICHOST;
|
||||
hint.ai_flags |= AI_NUMERICHOST|AI_NUMERICSERV;
|
||||
}
|
||||
error = getaddrinfo (string, NULL, &hint, &ai);
|
||||
if (error) {
|
||||
|
@ -353,7 +353,7 @@ get_addresses(krb5_context context,
|
||||
if (krb5_config_get_bool(context, NULL, "libdefaults", "block_dns",
|
||||
NULL)) {
|
||||
hints.ai_flags &= ~AI_CANONNAME;
|
||||
hints.ai_flags |= AI_NUMERICHOST;
|
||||
hints.ai_flags |= AI_NUMERICHOST|AI_NUMERICSERV;
|
||||
}
|
||||
eai = getaddrinfo(hostname, NULL, &hints, &ai);
|
||||
if (eai) {
|
||||
|
@ -433,7 +433,7 @@ krb5_krbhst_get_addrinfo(krb5_context context, krb5_krbhst_info *host,
|
||||
if (krb5_config_get_bool(context, NULL, "libdefaults", "block_dns",
|
||||
NULL)) {
|
||||
hints.ai_flags &= ~AI_CANONNAME;
|
||||
hints.ai_flags |= AI_NUMERICHOST;
|
||||
hints.ai_flags |= AI_NUMERICHOST|AI_NUMERICSERV;
|
||||
}
|
||||
ret = getaddrinfo(host->hostname, portstr, &hints, &host->ai);
|
||||
if (ret) {
|
||||
@ -558,7 +558,7 @@ fallback_get_hosts(krb5_context context, struct krb5_krbhst_data *kd,
|
||||
if (krb5_config_get_bool(context, NULL, "libdefaults", "block_dns",
|
||||
NULL)) {
|
||||
hints.ai_flags &= ~AI_CANONNAME;
|
||||
hints.ai_flags |= AI_NUMERICHOST;
|
||||
hints.ai_flags |= AI_NUMERICHOST|AI_NUMERICSERV;
|
||||
}
|
||||
ret = getaddrinfo(host, portstr, &hints, &ai);
|
||||
if (ret) {
|
||||
|
@ -860,7 +860,7 @@ submit_request(krb5_context context, krb5_sendto_ctx ctx, krb5_krbhst_info *hi)
|
||||
if (krb5_config_get_bool(context, NULL, "libdefaults", "block_dns",
|
||||
NULL)) {
|
||||
hints.ai_flags &= ~AI_CANONNAME;
|
||||
hints.ai_flags |= AI_NUMERICHOST;
|
||||
hints.ai_flags |= AI_NUMERICHOST|AI_NUMERICSERV;
|
||||
}
|
||||
ret = getaddrinfo(proxy, portstr, &hints, &ai);
|
||||
free(proxy2);
|
||||
|
@ -205,7 +205,7 @@ check_host(krb5_context context, const char *path, char *data)
|
||||
if (krb5_config_get_bool(context, NULL, "libdefaults", "block_dns",
|
||||
NULL)) {
|
||||
hints.ai_flags &= ~AI_CANONNAME;
|
||||
hints.ai_flags |= AI_NUMERICHOST;
|
||||
hints.ai_flags |= AI_NUMERICHOST|AI_NUMERICSERV;
|
||||
}
|
||||
ret = getaddrinfo(hostname, service, &hints, &ai);
|
||||
if (ret == EAI_SERVICE && !isdigit((unsigned char)service[0])) {
|
||||
|
@ -49,7 +49,7 @@ get_address(int flags, struct addrinfo ** ret)
|
||||
|
||||
memset(&ai, 0, sizeof(ai));
|
||||
|
||||
ai.ai_flags = flags | AI_NUMERICHOST;
|
||||
ai.ai_flags = flags | AI_NUMERICHOST | AI_NUMERICSERV;
|
||||
ai.ai_family = AF_INET;
|
||||
ai.ai_socktype = SOCK_STREAM;
|
||||
ai.ai_protocol = PF_UNSPEC;
|
||||
|
Reference in New Issue
Block a user