sioc-if-addr-bounds.c   [plain text]


/*
 * Copyright (c) 2020 Apple Inc. All rights reserved.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. The rights granted to you under the License
 * may not be used to create, or enable the creation or redistribution of,
 * unlawful or unlicensed copies of an Apple operating system, or to
 * circumvent, violate, or enable the circumvention or violation of, any
 * terms of an Apple operating system software license agreement.
 *
 * Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
 */

/* -*- compile-command: "xcrun --sdk iphoneos.internal make sioc-if-addr-bounds" -*- */

#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/sockio.h>

#include <net/if.h>

#include <netinet/in.h>
#include <netinet/in_var.h>

#include <string.h>
#include <unistd.h>

#include <arpa/inet.h>

#include <darwintest.h>
#include <darwintest_utils.h>

#include <uuid/uuid.h>

#include <ifaddrs.h>

#include <arpa/inet.h>

#include "ioc_str.h"

T_GLOBAL_META(T_META_NAMESPACE("xnu.net"));

#ifndef STRINGIFY
#define __STR(x)        #x              /* just a helper macro */
#define STRINGIFY(x)    __STR(x)
#endif /* STRINGIFY */

#define IF_NAME       "bridge"

/* On some platforms with DEBUG kernel, we need to wait a while */
#define SIFCREATE_RETRY 600

#define PATTERN_SIZE 8

static int
ifnet_destroy(int s, const char * ifname, bool fail_on_error)
{
	int             err;
	struct ifreq    ifr;

	bzero(&ifr, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
	err = ioctl(s, SIOCIFDESTROY, &ifr);
	if (fail_on_error) {
		T_QUIET;
		T_ASSERT_POSIX_SUCCESS(err, "SIOCSIFDESTROY %s", ifr.ifr_name);
	}
	if (err < 0) {
		T_LOG("SIOCSIFDESTROY %s", ifr.ifr_name);
	}
	return err;
}

static int
ifnet_set_flags(int s, const char * ifname,
    uint16_t flags_set, uint16_t flags_clear)
{
	uint16_t        flags_after;
	uint16_t        flags_before;
	struct ifreq    ifr;
	int             ret;

	bzero(&ifr, sizeof(ifr));
	strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
	ret = ioctl(s, SIOCGIFFLAGS, (caddr_t)&ifr);
	if (ret != 0) {
		T_LOG("SIOCGIFFLAGS %s", ifr.ifr_name);
		return ret;
	}
	flags_before = (uint16_t)ifr.ifr_flags;
	ifr.ifr_flags |= flags_set;
	ifr.ifr_flags &= ~(flags_clear);
	flags_after = (uint16_t)ifr.ifr_flags;
	if (flags_before == flags_after) {
		/* nothing to do */
		ret = 0;
	} else {
		/* issue the ioctl */
		T_QUIET;
		T_ASSERT_POSIX_SUCCESS(ioctl(s, SIOCSIFFLAGS, &ifr),
		    "SIOCSIFFLAGS %s 0x%x",
		    ifr.ifr_name, (uint16_t)ifr.ifr_flags);
	}
	return ret;
}

static int
ifnet_create(int s, char * ifname, size_t ifname_size)
{
	int error = 0;
	struct ifreq ifr;

	bzero(&ifr, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));

	for (int i = 0; i < SIFCREATE_RETRY; i++) {
		if (ioctl(s, SIOCIFCREATE, &ifr) < 0) {
			error = errno;
			T_LOG("SIOCSIFCREATE %s: %s", ifname,
			    strerror(error));
			if (error == EBUSY) {
				/* interface is tearing down, try again */
				usleep(10000);
			} else if (error == EEXIST) {
				/* interface exists, try destroying it */
				(void)ifnet_destroy(s, ifname, false);
			} else {
				/* unexpected failure */
				break;
			}
		} else {
			error = 0;
			break;
		}
	}
	if (error == 0) {
		/* Copy back the interface name with unit number */
		strlcpy(ifname, ifr.ifr_name, ifname_size);
		error = ifnet_set_flags(s, ifname, IFF_UP, 0);
	}
	return error;
}

#define MAXBUF 32

static void
HexDump(void *data, size_t len)
{
	size_t i, j, k;
	unsigned char *ptr = (unsigned char *)data;
	unsigned char buf[3 * MAXBUF + 1];

	for (i = 0; i < len; i += MAXBUF) {
		for (j = i, k = 0; j < i + MAXBUF && j < len; j++) {
			unsigned char msnbl = ptr[j] >> 4;
			unsigned char lsnbl = ptr[j] & 0x0f;

			buf[k++] = msnbl < 10 ? msnbl + '0' : msnbl + 'a' - 10;
			buf[k++] = lsnbl < 10 ? lsnbl + '0' : lsnbl + 'a' - 10;
			if ((j % 2) == 1) {
				buf[k++] = ' ';
			}
			if ((j % MAXBUF) == MAXBUF - 1) {
				buf[k++] = ' ';
			}
		}
		buf[k] = 0;
		T_LOG("%5zd: %s\n", i, buf);
	}
}

static size_t
snprint_dottedhex(char *str, size_t strsize, const void *data, const size_t datasize)
{
	size_t is = 0, ip = 0;
	const unsigned char *ptr = (const unsigned char *)data;

	for (is = 0, ip = 0; is + 3 < strsize - 1 && ip < datasize; ip++) {
		unsigned char msnbl = ptr[ip] >> 4;
		unsigned char lsnbl = ptr[ip] & 0x0f;

		if (ip > 0) {
			str[is++] = '.';
		}
		str[is++] = (char)(msnbl + (msnbl < 10 ? '0' : 'a' - 10));
		str[is++] = (char)(lsnbl + (lsnbl < 10 ? '0' : 'a' - 10));
	}
	str[is] = 0;
	return is;
}

static void
print_sockaddr_dl(const char *pre, const struct sockaddr *sa, const char *post)
{
	char nbuffer[256];
	char abuffer[256];
	char sbuffer[256];
	struct sockaddr_dl sdl = {};

	if (sa == NULL) {
		return;
	}
	memcpy(&sdl, sa, MIN(sizeof(sdl), sa->sa_len));
	strlcpy(nbuffer, sdl.sdl_data, sdl.sdl_nlen);
	snprint_dottedhex(abuffer, sizeof(abuffer), sdl.sdl_data + sdl.sdl_nlen, sdl.sdl_alen);
	snprint_dottedhex(sbuffer, sizeof(sbuffer), sdl.sdl_data + sdl.sdl_nlen + sdl.sdl_alen, sdl.sdl_slen);

	T_LOG("%ssdl_len %u sdl_family %u sdl_index %u sdl_type %u sdl_nlen %u (%s) sdl_alen %u (%s) sdl_slen %u (%s)%s",
	    pre != NULL ? pre : "",
	    sdl.sdl_len, sdl.sdl_family, sdl.sdl_index, sdl.sdl_type,
	    sdl.sdl_nlen, nbuffer, sdl.sdl_alen, abuffer, sdl.sdl_slen, sbuffer,
	    post != NULL ? post : "");
}

static void
print_sockaddr_in(const char *pre, const struct sockaddr *sa, const char *post)
{
	char abuffer[256];
	char zbuffer[256];
	struct sockaddr_in sin = {};

	if (sa == NULL) {
		return;
	}

	memcpy(&sin, sa, MIN(sizeof(sin), sa->sa_len));
	inet_ntop(AF_INET, &sin.sin_addr, abuffer, sizeof(abuffer));
	snprint_dottedhex(zbuffer, sizeof(zbuffer), sin.sin_zero, sizeof(sin.sin_zero));

	T_LOG("%ssin_len %u sin_family %u sin_port %u sin_addr %s sin_zero %s%s",
	    pre != NULL ? pre : "",
	    sin.sin_len, sin.sin_family, htons(sin.sin_port), abuffer, zbuffer,
	    post != NULL ? post : "");
}

static void
print_sockaddr_in6(const char *pre, const struct sockaddr *sa, const char *post)
{
	char abuffer[256];
	struct sockaddr_in6 sin6 = {};

	if (sa == NULL) {
		return;
	}

	memcpy(&sin6, sa, MIN(sizeof(sin6), sa->sa_len));
	inet_ntop(AF_INET6, &sin6.sin6_addr, abuffer, sizeof(abuffer));

	T_LOG("%ssin6_len %u sin6_family %u sin6_port %u sin6_flowinfo %u sin6_addr %s sin6_scope_id %u%s",
	    pre != NULL ? pre : "",
	    sin6.sin6_len, sin6.sin6_family, htons(sin6.sin6_port), sin6.sin6_flowinfo, abuffer, sin6.sin6_scope_id,
	    post != NULL ? post : "");
}

static void
print_sockaddr(const char *pre, const struct sockaddr *sa, const char *post)
{
	char buffer[256];

	if (sa == NULL) {
		return;
	}

	snprint_dottedhex(buffer, sizeof(buffer), sa->sa_data, sa->sa_len - 2);

	T_LOG("%ssa_len %u sa_family %u sa_data %s%s",
	    pre != NULL ? pre : "",
	    sa->sa_len, sa->sa_family, buffer,
	    post != NULL ? post : "");
}


#define ROUNDUP(a, size) (((a) & ((size) - 1)) ? (1 + ((a)|(size - 1))) : (a))

#define NEXT_SA(p) (struct sockaddr *) \
    ((caddr_t)p + (p->sa_len ? ROUNDUP(p->sa_len, sizeof(u_int32_t)) : \
     sizeof(u_long)))

static size_t
get_rti_info(int addrs, struct sockaddr *sa, struct sockaddr **rti_info)
{
	int i;
	size_t len = 0;

	for (i = 0; i < RTAX_MAX; i++) {
		if (addrs & (1 << i)) {
			rti_info[i] = sa;
			if (sa->sa_len < sizeof(struct sockaddr)) {
				len += sizeof(struct sockaddr);
			} else {
				len += sa->sa_len;
			}
			sa = NEXT_SA(sa);
		} else {
			rti_info[i] = NULL;
		}
	}
	return len;
}

static void
print_address(const char *pre, const struct sockaddr *sa, const char *post, u_char asFamily)
{
	if (sa == NULL) {
		T_LOG("%s(NULL)%s",
		    pre != NULL ? pre : "",
		    post != NULL ? post : "");
		return;
	}
	if (sa->sa_len == 0) {
		T_LOG("%ssa_len 0%s",
		    pre != NULL ? pre : "",
		    post != NULL ? post : "");
		return;
	}
	if (sa->sa_len == 1) {
		T_LOG("%ssa_len 1%s",
		    pre != NULL ? pre : "",
		    post != NULL ? post : "");
		return;
	}

	// If not forced
	if (asFamily == AF_UNSPEC) {
		asFamily = sa->sa_family;
	}
	switch (asFamily) {
	case AF_INET: {
		print_sockaddr_in(pre, sa, post);
		break;
	}
	case AF_INET6: {
		print_sockaddr_in6(pre, sa, post);
		break;
	}
	case AF_LINK: {
		print_sockaddr_dl(pre, sa, post);
		break;
	}
	default:
		print_sockaddr(pre, sa, post);
		break;
	}
}

static void
print_rti_info(struct sockaddr *rti_info[])
{
	struct sockaddr *sa;
	u_char asFamily = 0;

	if ((sa = rti_info[RTAX_IFA])) {
		asFamily = sa->sa_family;
		print_address(" RTAX_IFA         ", sa, "\n", 0);
	}
	if ((sa = rti_info[RTAX_DST])) {
		asFamily = sa->sa_family;
		print_address(" RTAX_DST         ", sa, "\n", 0);
	}
	if ((sa = rti_info[RTAX_BRD])) {
		print_address(" RTAX_BRD         ", sa, "\n", asFamily);
	}

	if ((sa = rti_info[RTAX_NETMASK])) {
		print_address(" RTAX_NETMASK     ", sa, "\n", asFamily);
	}

	if ((sa = rti_info[RTAX_GATEWAY])) {
		print_address(" RTAX_GATEWAY     ", sa, "\n", 0);
	}

	if ((sa = rti_info[RTAX_GENMASK])) {
		print_address(" RTAX_GENMASK     ", sa, "\n", asFamily);
	}

	if ((sa = rti_info[RTAX_AUTHOR])) {
		print_address(" RTAX_AUTHOR      ", sa, "\n", asFamily);
	}

	if ((sa = rti_info[RTAX_IFP])) {
		print_address(" RTAX_IFP         ", sa, "\n", 0);
	}
}

static void
print_rt_iflist2(const char *label)
{
	size_t len;
	int mib[6] = { CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST2, 0 };
	unsigned char *buf = NULL;
	unsigned char *lim, *next;
	struct if_msghdr *ifmsg;

	T_LOG("interface address list for %s", label);

	T_QUIET; T_EXPECT_POSIX_SUCCESS(sysctl(mib, 6, NULL, &len, NULL, 0), "sysctl NET_RT_IFLIST2");

	T_QUIET; T_ASSERT_NOTNULL(buf = calloc(1, len), "rt_if_list_buf calloc(1, %zd)", len);

	T_QUIET; T_EXPECT_POSIX_SUCCESS(sysctl(mib, 6, buf, &len, NULL, 0), "sysctl NET_RT_IFLIST2");

	lim = buf + len;
	for (next = buf; next < lim; next += ifmsg->ifm_msglen) {
		ifmsg = (struct if_msghdr *)(void *)next;
		char ifname[IF_NAMESIZE + 1];

		if (ifmsg->ifm_type == RTM_IFINFO2) {
			struct if_msghdr2 *ifm = (struct if_msghdr2 *)ifmsg;
			struct sockaddr *sa = (struct sockaddr *)(ifm + 1);

			(void)if_indextoname(ifm->ifm_index, ifname);
			T_LOG("interface: %s", ifname);
			print_address(" PRIMARY          ", sa, "", 0);
		} else if (ifmsg->ifm_type == RTM_NEWADDR) {
			struct sockaddr *rti_info[RTAX_MAX];
			struct ifa_msghdr *ifam = (struct ifa_msghdr *)ifmsg;

			(void) get_rti_info(ifam->ifam_addrs, (struct sockaddr *)(ifam + 1), rti_info);

			print_rti_info(rti_info);
		}
	}
	free(buf);
}

static int
check_rt_if_list_for_pattern(const char *label, unsigned char pattern, size_t pattern_size)
{
	size_t i;
	size_t len;
	int mib[6] = { CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, 0 };
	unsigned char *rt_if_list_buf = NULL;
	int count = 0;
	unsigned char *pattern_buf = NULL;

	T_QUIET; T_ASSERT_NOTNULL(pattern_buf = calloc(1, pattern_size), "pattern_buf calloc(1, %zd)", pattern_size);
	memset(pattern_buf, pattern, pattern_size);

	T_QUIET; T_EXPECT_POSIX_SUCCESS(sysctl(mib, 6, NULL, &len, NULL, 0), "sysctl NET_RT_IFLIST");

	T_QUIET; T_ASSERT_NOTNULL(rt_if_list_buf = calloc(1, len), "rt_if_list_buf calloc(1, %zd)", len);

	T_QUIET; T_EXPECT_POSIX_SUCCESS(sysctl(mib, 6, rt_if_list_buf, &len, NULL, 0), "sysctl NET_RT_IFLIST");

	if (label != NULL) {
		T_LOG("%s sysctl NET_RT_IFLIST buffer length %zd\n", label, len);
	}

	count = 0;
	for (i = 0; i < len - pattern_size; i++) {
		if (memcmp(rt_if_list_buf + i, pattern_buf, pattern_size) == 0) {
			count++;
			i += pattern_size - 1;
		}
	}

	if (label != NULL) {
		if (label != NULL && count > 0) {
			T_LOG("%s found pattern at %zd count %d times\n", label, i, count);
			HexDump(rt_if_list_buf, len);
		}
	}

	free(rt_if_list_buf);
	free(pattern_buf);

	return count;
}

static bool
find_unused_pattern_in_rt_if_list(unsigned char *pattern, size_t pattern_size)
{
	bool found_pattern = false;
	unsigned char i;
	unsigned char *pattern_buf = NULL;

	T_QUIET; T_ASSERT_NOTNULL(pattern_buf = calloc(1, pattern_size), "pattern_buf calloc(1, %zd)", pattern_size);

	/* Try 10 times to find an unused pattern */
	for (i = 1; i < 255; i++) {
		if (check_rt_if_list_for_pattern(NULL, i, pattern_size) == 0) {
			found_pattern = true;
			*pattern = i;
			memset(pattern_buf, i, pattern_size);
			T_LOG("PATTERN: ");
			HexDump(pattern_buf, pattern_size);
			break;
		}
	}
	free(pattern_buf);

	return found_pattern;
}

static const char *
ioc_str(unsigned long cmd)
{
#define X(a) case a: return #a;

	switch (cmd) {
		SIOC_LIST

	default:
		break;
	}
	return "";
}

struct ioc_ifreq {
	unsigned long ioc_cmd;
	uint8_t salen;
	uint8_t safamily;
	const char *sastr;
	int error; // 0 means no error, -1, end of list, otherwise expected errno
};

static struct ioc_ifreq ioc_list[] = {
	{ SIOCSIFADDR, sizeof(struct sockaddr_in), AF_INET6, "10.2.3.1", EINVAL },
	{ SIOCDIFADDR, sizeof(struct sockaddr_in), AF_INET6, "10.2.3.1", EADDRNOTAVAIL },
	{ SIOCDIFADDR, sizeof(struct sockaddr_in6), AF_INET, "10.2.3.1", EADDRNOTAVAIL },
	{ SIOCDIFADDR, sizeof(struct sockaddr_in), AF_INET, "10.2.3.1", EADDRNOTAVAIL },

	{ SIOCSIFADDR, 0xf0, AF_INET6, "10.2.3.1", EINVAL },
	{ SIOCDIFADDR, 0xf0, AF_INET6, "10.2.3.1", EADDRNOTAVAIL },
	{ SIOCDIFADDR, 0xf0, AF_INET, "10.2.3.1", EADDRNOTAVAIL },
	{ SIOCDIFADDR, 0xf0, AF_INET, "10.2.3.1", EADDRNOTAVAIL },

	{ SIOCSIFADDR, 0, AF_INET6, "10.2.3.1", EINVAL },
	{ SIOCDIFADDR, 0, AF_INET6, "10.2.3.1", EADDRNOTAVAIL },
	{ SIOCDIFADDR, 0, AF_INET, "10.2.3.1", EADDRNOTAVAIL },
	{ SIOCDIFADDR, 0, AF_INET, "10.2.3.1", EADDRNOTAVAIL },

	{ SIOCSIFADDR, sizeof(struct sockaddr_in6), AF_INET, "10.2.3.2", 0 },
	{ SIOCDIFADDR, sizeof(struct sockaddr_in), AF_INET6, "10.2.3.2", 0 },

	{ SIOCSIFADDR, sizeof(struct sockaddr_in), AF_INET, "10.2.3.3", 0 },
	{ SIOCDIFADDR, sizeof(struct sockaddr_in6), AF_INET, "10.2.3.3", 0 },

	{ SIOCSIFADDR, sizeof(struct sockaddr_in6), AF_INET6, "10.2.3.4", EINVAL },
	{ SIOCDIFADDR, sizeof(struct sockaddr_in6), AF_INET, "10.2.3.4", EADDRNOTAVAIL },

	{ SIOCSIFADDR, sizeof(struct sockaddr_in), AF_INET, "10.2.3.5", 0 },
	{ SIOCDIFADDR, sizeof(struct sockaddr_in), AF_INET, "0.0.0.0", EADDRNOTAVAIL },
	{ SIOCDIFADDR, sizeof(struct sockaddr_in), AF_INET, "10.2.3.5", 0 },

	{ SIOCSIFADDR, sizeof(struct sockaddr_in), AF_INET, "10.2.3.6", 0 },

	{ SIOCSIFNETMASK, sizeof(struct sockaddr_in), 0, "ff.00.00.00", 0 },
	{ SIOCSIFNETMASK, sizeof(struct sockaddr_in), AF_INET, "ff.00.00.00", 0 },
	{ SIOCSIFNETMASK, sizeof(struct sockaddr_in), AF_INET6, "ff.f.00.00", 0 },

	{ SIOCSIFNETMASK, sizeof(struct sockaddr_in6), 0, "ff.ff.00.00", 0 },
	{ SIOCSIFNETMASK, sizeof(struct sockaddr_in6), AF_INET, "ff.ff.00.00", 0 },
	{ SIOCSIFNETMASK, sizeof(struct sockaddr_in6), AF_INET6, "ff.ff.f0.00", 0 },

	{ SIOCSIFNETMASK, 0, 0, "ff.ff.00.00", 0 },
	{ SIOCSIFNETMASK, 0, AF_INET, "ff.ff.00.00", 0 },
	{ SIOCSIFNETMASK, 0, AF_INET6, "ff.ff.f0.00", 0 },

	{ SIOCSIFNETMASK, 0xf0, 0, "ff.ff.00.00", 0 },
	{ SIOCSIFNETMASK, 0xf0, AF_INET, "ff.ff.00.00", 0 },
	{ SIOCSIFNETMASK, 0xf0, AF_INET6, "ff.ff.f0.00", 0 },

	{ SIOCSIFBRDADDR, sizeof(struct sockaddr_in), 0, "10.255.255.255", 0 },
	{ SIOCSIFBRDADDR, sizeof(struct sockaddr_in), AF_INET, "10.255.255.255", 0 },
	{ SIOCSIFBRDADDR, sizeof(struct sockaddr_in), AF_INET6, "10.255.255.255", 0 },
	{ SIOCSIFBRDADDR, sizeof(struct sockaddr_in6), AF_INET, "10.255.255.255", 0 },

	{ SIOCSIFBRDADDR, 0xf0, 0, "10.255.255.255", 0 },
	{ SIOCSIFBRDADDR, 0xf0, AF_INET, "10.255.255.255", 0 },
	{ SIOCSIFBRDADDR, 0xf0, AF_INET6, "10.255.255.255", 0 },
	{ SIOCSIFBRDADDR, 0xf0, AF_INET, "10.255.255.255", 0 },

	{ SIOCSIFBRDADDR, 0, 0, "10.255.255.255", 0 },
	{ SIOCSIFBRDADDR, 0, AF_INET, "10.255.255.255", 0 },
	{ SIOCSIFBRDADDR, 0, AF_INET6, "10.255.255.255", 0 },
	{ SIOCSIFBRDADDR, 0, AF_INET, "10.255.255.255", 0 },

	{ 0, 0, 0, "", -1 },
};

static void
test_sioc_ifr_bounds(struct ioc_ifreq *ioc_ifreq, int s, const char *ifname)
{
	struct ifreq ifr = {};
	unsigned char pattern;
	struct sockaddr_in *sin;

	T_LOG("");
	T_LOG("TEST CASE: %s ioctl(%s, sa_len %u, sa_family %u, %s) -> %d", __func__,
	    ioc_str(ioc_ifreq->ioc_cmd), ioc_ifreq->salen, ioc_ifreq->safamily, ioc_ifreq->sastr, ioc_ifreq->error);


	if (find_unused_pattern_in_rt_if_list(&pattern, PATTERN_SIZE) == false) {
		T_SKIP("Could not find unused pattern");
	}

	strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
	memset(&ifr.ifr_addr.sa_data, pattern, sizeof(ifr.ifr_dstaddr.sa_data));

	sin = (struct sockaddr_in *)(void *)&ifr.ifr_addr;
	sin->sin_len = ioc_ifreq->salen;
	sin->sin_family = ioc_ifreq->safamily;
	sin->sin_addr.s_addr = inet_addr(ioc_ifreq->sastr);

	int retval;
	if (ioc_ifreq->error == 0) {
		T_EXPECT_POSIX_SUCCESS(retval = ioctl(s, ioc_ifreq->ioc_cmd, &ifr),
		    "%s, %s: retval %d", ioc_str(ioc_ifreq->ioc_cmd), ioc_ifreq->sastr, retval);
	} else {
		T_EXPECT_POSIX_FAILURE(retval = ioctl(s, ioc_ifreq->ioc_cmd, &ifr), ioc_ifreq->error,
		    "%s, %s: retval %d errno %s", ioc_str(ioc_ifreq->ioc_cmd), ioc_ifreq->sastr, retval, strerror(errno));
	}

	T_EXPECT_EQ(check_rt_if_list_for_pattern("test_sioc_ifr_bounds", pattern, PATTERN_SIZE), 0, "pattern should not be found");


	fflush(stdout);
	fflush(stderr);
}

T_DECL(sioc_ifr_bounds, "test bound checks on struct ifreq addresses passed to interface ioctls",
    T_META_ASROOT(true))
{
	int s = -1;
	char ifname[IFNAMSIZ];

	T_LOG("%s", __func__);

	T_QUIET; T_EXPECT_POSIX_SUCCESS(s = socket(AF_INET, SOCK_DGRAM, 0), "socket");

	strlcpy(ifname, IF_NAME, sizeof(ifname));

	int error = 0;
	if ((error = ifnet_create(s, ifname, sizeof(ifname))) != 0) {
		if (error == EINVAL) {
			T_SKIP("The system does not support the %s cloning interface", IF_NAME);
		}
		T_SKIP("This test failed creating a %s cloning interface", IF_NAME);
	}
	T_LOG("created clone interface '%s'", ifname);

	struct ioc_ifreq *ioc_ifreq;
	for (ioc_ifreq = ioc_list; ioc_ifreq->error != -1; ioc_ifreq++) {
		test_sioc_ifr_bounds(ioc_ifreq, s, ifname);
	}
	print_rt_iflist2(__func__);
	(void)ifnet_destroy(s, ifname, true);

	close(s);
}

struct ioc_ifra {
	const char *description;

	uint8_t addr_len;
	uint8_t addr_fam;
	const char *addr_str;

	uint8_t broad_len;
	uint8_t broad_fam;
	const char *broad_str;

	uint8_t mask_len;
	uint8_t mask_fam;
	const char *mask_str;

	int error; // 0 means no error, -1, end of list, otherwise expected errno
};

static struct ioc_ifra ioc_ifra_list[] = {
	{
		.description = "fully formed",
		.addr_len = sizeof(struct sockaddr_in), .addr_fam = AF_INET, .addr_str = "10.1.1.1",
		.broad_len = sizeof(struct sockaddr_in), .broad_fam = AF_INET, .broad_str = "1.1.1.255",
		.mask_len = sizeof(struct sockaddr_in), .mask_fam = AF_INET, .mask_str = "255.255.0.0",
		.error = 0
	},
	{
		.description = "addr_len 0",
		.addr_len = 0, .addr_fam = AF_INET, .addr_str = "10.2.2.0",
		.broad_len = sizeof(struct sockaddr_in), .broad_fam = AF_INET, .broad_str = "10.2.2.255",
		.mask_len = sizeof(struct sockaddr_in), .mask_fam = AF_INET, .mask_str = "255.0.0.0",
		.error = 0
	},
	{
		.description = "addr_len 1",
		.addr_len = 1, .addr_fam = AF_INET, .addr_str = "10.2.2.1",
		.broad_len = sizeof(struct sockaddr_in), .broad_fam = AF_INET, .broad_str = "10.2.2.255",
		.mask_len = sizeof(struct sockaddr_in), .mask_fam = AF_INET, .mask_str = "255.0.0.0",
		.error = 0
	},
	{
		.description = "addr_len 250",
		.addr_len = 250, .addr_fam = AF_INET, .addr_str = "10.2.2.250",
		.broad_len = sizeof(struct sockaddr_in), .broad_fam = AF_INET, .broad_str = "10.2.2.255",
		.mask_len = sizeof(struct sockaddr_in), .mask_fam = AF_INET, .mask_str = "255.0.0.0",
		.error = 0
	},
	{
		.description = "addr_family AF_INET6",
		.addr_len = sizeof(struct sockaddr_in), .addr_fam = AF_INET6, .addr_str = "10.3.3.3",
		.broad_len = sizeof(struct sockaddr_in), .broad_fam = AF_INET, .broad_str = "10.3.255.255",
		.mask_len = sizeof(struct sockaddr_in), .mask_fam = AF_INET, .mask_str = "255.255.255.0",
		.error = EINVAL
	},
	{
		.description = "broadcast_len 0xf0",
		.addr_len = sizeof(struct sockaddr_in), .addr_fam = AF_INET, .addr_str = "10.4.4.4",
		.broad_len = 0xf0, .broad_fam = AF_INET, .broad_str = "10.4.4.255",
		.mask_len = sizeof(struct sockaddr_in), .mask_fam = AF_INET, .mask_str = "255.255.255.0",
		.error = 0
	},
	{
		.description = "broadcast_family AF_INET6",
		.addr_len = sizeof(struct sockaddr_in), .addr_fam = AF_INET, .addr_str = "10.5.5.5",
		.broad_len = sizeof(struct sockaddr_in), .broad_fam = AF_INET6, .broad_str = "10.5.5.255",
		.mask_len = sizeof(struct sockaddr_in), .mask_fam = AF_INET, .mask_str = "255.255.0.0",
		.error = 0
	},
	{
		.description = "mask_len 0xf0",
		.addr_len = sizeof(struct sockaddr_in), .addr_fam = AF_INET, .addr_str = "10.6.6.6",
		.broad_len = sizeof(struct sockaddr_in), .broad_fam = AF_INET, .broad_str = "1.6.6.255",
		.mask_len = 0xf0, .mask_fam = AF_INET, .mask_str = "255.255.0.0",
		.error = 0
	},
	{
		.description = "mask_family AF_INET6",
		.addr_len = sizeof(struct sockaddr_in), .addr_fam = AF_INET, .addr_str = "10.7.7.7",
		.broad_len = sizeof(struct sockaddr_in), .broad_fam = AF_INET, .broad_str = "10.7.7.255",
		.mask_len = sizeof(struct sockaddr_in), .mask_fam = AF_INET6, .mask_str = "255.255.0.0",
		.error = 0
	},
	{
		.description = "ifra address only",
		.addr_len = sizeof(struct sockaddr_in), .addr_fam = AF_INET, .addr_str = "10.8.8.8",
		.broad_len = 0, .broad_str = NULL,
		.mask_len = 0, .mask_str = NULL,
		.error = 0
	},
	{
		.description = "ifra mask len 1",
		.addr_len = sizeof(struct sockaddr_in), .addr_fam = AF_INET, .addr_str = "10.9.9.1",
		.broad_len = 0, .broad_str = NULL,
		.mask_len = 1, .mask_fam = AF_INET, .mask_str = "255.255.255.0",
		.error = 0
	},
	{
		.description = "ifra mask len 3",
		.addr_len = sizeof(struct sockaddr_in), .addr_fam = AF_INET, .addr_str = "10.9.9.3",
		.broad_len = 0, .broad_str = NULL,
		.mask_len = 1, .mask_fam = AF_INET, .mask_str = "255.255.255.0",
		.error = 0
	},
	{
		.description = "ifra mask len 5",
		.addr_len = sizeof(struct sockaddr_in), .addr_fam = AF_INET, .addr_str = "10.9.9.5",
		.broad_len = 0, .broad_str = NULL,
		.mask_len = 1, .mask_fam = AF_INET, .mask_str = "255.255.255.0",
		.error = 0
	},
	{
		.description = "ifra mask len 7",
		.addr_len = sizeof(struct sockaddr_in), .addr_fam = AF_INET, .addr_str = "10.9.9.7",
		.broad_len = 0, .broad_str = NULL,
		.mask_len = 1, .mask_fam = AF_INET, .mask_str = "255.255.255.0",
		.error = 0
	},
	{
		.description = "ifra mask len 9",
		.addr_len = sizeof(struct sockaddr_in), .addr_fam = AF_INET, .addr_str = "10.9.9.9",
		.broad_len = 0, .broad_str = NULL,
		.mask_len = 1, .mask_fam = AF_INET, .mask_str = "255.255.255.0",
		.error = 0
	},
	{
		.description = "ifra mask len 11",
		.addr_len = sizeof(struct sockaddr_in), .addr_fam = AF_INET, .addr_str = "10.9.9.11",
		.broad_len = 0, .broad_str = NULL,
		.mask_len = 1, .mask_fam = AF_INET, .mask_str = "255.255.255.0",
		.error = 0
	},
	{
		.description = "ifra mask len 13",
		.addr_len = sizeof(struct sockaddr_in), .addr_fam = AF_INET, .addr_str = "10.9.9.13",
		.broad_len = 0, .broad_str = NULL,
		.mask_len = 1, .mask_fam = AF_INET, .mask_str = "255.255.255.0",
		.error = 0
	},
	{
		.description = NULL,
		.error = -1
	}
};

T_DECL(sioc_ifra_addr_bounds, "test bound checks on socket address passed to interface ioctls",
    T_META_ASROOT(true))
{
	int s = -1;

	T_QUIET; T_EXPECT_POSIX_SUCCESS(s = socket(AF_INET, SOCK_DGRAM, 0), "socket");

	char ifname[IFNAMSIZ];
	strlcpy(ifname, IF_NAME, sizeof(ifname));
	int error = 0;
	if ((error = ifnet_create(s, ifname, sizeof(ifname))) != 0) {
		if (error == EINVAL) {
			T_SKIP("The system does not support the %s cloning interface", IF_NAME);
		}
		T_SKIP("This test failed creating a %s cloning interface", IF_NAME);
	}
	T_LOG("created clone interface '%s'", ifname);

	struct ioc_ifra *ioc_ifra;

	for (ioc_ifra = ioc_ifra_list; ioc_ifra->error != -1; ioc_ifra++) {
		struct in_aliasreq ifra = {};
		unsigned char pattern;
		int retval;

		T_LOG("");
		T_LOG("TEST CASE: %s, ioctl(SIOCAIFADDR, %s)", ioc_ifra->description, ioc_ifra->addr_str != NULL ? ioc_ifra->addr_str : "");

		if (find_unused_pattern_in_rt_if_list(&pattern, PATTERN_SIZE) == false) {
			T_SKIP("Could not find unused pattern in rt_if_list");
			return;
		}

		memset(&ifra, pattern, sizeof(ifra));

		strlcpy(ifra.ifra_name, ifname, sizeof(ifra.ifra_name));

		ifra.ifra_addr.sin_len = ioc_ifra->addr_len;
		ifra.ifra_addr.sin_family = ioc_ifra->addr_fam;
		if (ioc_ifra->addr_str != NULL) {
			ifra.ifra_addr.sin_addr.s_addr = inet_addr(ioc_ifra->addr_str);
		}

		ifra.ifra_broadaddr.sin_len = ioc_ifra->broad_len;
		ifra.ifra_broadaddr.sin_family = ioc_ifra->broad_fam;
		if (ioc_ifra->broad_str != NULL) {
			ifra.ifra_broadaddr.sin_addr.s_addr = inet_addr(ioc_ifra->broad_str);
		}

		ifra.ifra_mask.sin_len = ioc_ifra->mask_len;
		ifra.ifra_mask.sin_family = ioc_ifra->mask_fam;
		if (ioc_ifra->mask_str != NULL) {
			ifra.ifra_mask.sin_addr.s_addr = inet_addr(ioc_ifra->mask_str);
		}

		if (ioc_ifra->error == 0) {
			T_EXPECT_POSIX_SUCCESS(retval = ioctl(s, SIOCAIFADDR, &ifra), "SIOCAIFADDR retval %d", retval);
		} else {
			T_EXPECT_POSIX_FAILURE(retval = ioctl(s, SIOCAIFADDR, &ifra), EINVAL, "SIOCAIFADDR retval %d, %s", retval, strerror(errno));
		}

		T_EXPECT_EQ(check_rt_if_list_for_pattern("after ioctl SIOCAIFADDR", pattern, PATTERN_SIZE), 0, "pattern should not be found");
	}

	print_rt_iflist2(__func__);

	(void)ifnet_destroy(s, ifname, true);

	close(s);
}

T_DECL(sioc_ifr_dstaddr_leak, "test bound checks on socket address passed to interface ioctls",
    T_META_ASROOT(true))
{
	int s = -1;
	struct ifreq ifr = {};
	unsigned char pattern;
	struct ifaddrs *ifap = NULL, *ifa;
	bool found_gif0 = false;

	T_QUIET; T_EXPECT_POSIX_SUCCESS(getifaddrs(&ifap), "getifaddrs");
	for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
		if (strcmp(ifa->ifa_name, "gif0") == 0) {
			found_gif0 = true;
			break;
		}
	}
	freeifaddrs(ifap);
	ifap = NULL;
	if (found_gif0 == false) {
		T_SKIP("gif0 does not exists");
	}

	if (find_unused_pattern_in_rt_if_list(&pattern, PATTERN_SIZE) == false) {
		T_SKIP("Could not find unused pattern");
		return;
	}

	T_QUIET; T_EXPECT_POSIX_SUCCESS(s = socket(AF_INET, SOCK_DGRAM, 0), "socket");

	strlcpy(ifr.ifr_name, "gif0", sizeof(ifr.ifr_name));
	ifr.ifr_dstaddr.sa_family = AF_INET6;
	ifr.ifr_dstaddr.sa_len = 0xf0;
	memset(&ifr.ifr_dstaddr.sa_data, pattern, PATTERN_SIZE);

	T_EXPECT_POSIX_SUCCESS(ioctl(s, SIOCSIFDSTADDR, &ifr), "ioctl(SIOCSIFDSTADDR)");

	print_rt_iflist2(__func__);

	close(s);

	T_EXPECT_EQ(check_rt_if_list_for_pattern("AFTER", pattern, PATTERN_SIZE), 0, "pattern should not be found");
}