view src/privsep-root.c @ 5231:a2c342295221 draft

privsep: Enable Capsicum for all processes. Except for the priviledged process. This is quite an in-depth change: * ARP is now one process per address * BPF flags are now returned via privsep * BPF write filters are locked when supported * The root process sends to the network The last step is done by opening RAW sockets and then sending a UDP header (where applicable) to avoid binding to an address which is already in use by the reader sockets. This is slightly wasteful for OS's without sandboxing but does have the very nice side effect of not needing a source address to unicast DHCPs replies from which makes the code smaller.
author Roy Marples <roy@marples.name>
date Tue, 19 May 2020 16:19:05 +0100
parents 2b18af138e24
children 0dd9b7f7cf6b
line wrap: on
line source

/* SPDX-License-Identifier: BSD-2-Clause */
/*
 * Privilege Separation for dhcpcd, privileged actioneer
 * Copyright (c) 2006-2020 Roy Marples <roy@marples.name>
 * All rights reserved

 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <pwd.h>
#include <signal.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "common.h"
#include "dhcpcd.h"
#include "dhcp6.h"
#include "eloop.h"
#include "if.h"
#include "ipv6nd.h"
#include "logerr.h"
#include "privsep.h"
#include "sa.h"
#include "script.h"

__CTASSERT(sizeof(ioctl_request_t) <= sizeof(unsigned long));

struct psr_error
{
	ssize_t psr_result;
	int psr_errno;
	char psr_pad[sizeof(ssize_t) - sizeof(int)];
	size_t psr_datalen;
};

struct psr_ctx {
	struct dhcpcd_ctx *psr_ctx;
	struct psr_error psr_error;
	size_t psr_datalen;
	void *psr_data;
};

static void
ps_root_readerrorsig(__unused int sig, void *arg)
{
	struct dhcpcd_ctx *ctx = arg;

	eloop_exit(ctx->ps_eloop, EXIT_FAILURE);
}

static void
ps_root_readerrorcb(void *arg)
{
	struct psr_ctx *psr_ctx = arg;
	struct dhcpcd_ctx *ctx = psr_ctx->psr_ctx;
	struct psr_error *psr_error = &psr_ctx->psr_error;
	struct iovec iov[] = {
		{ .iov_base = psr_error, .iov_len = sizeof(*psr_error) },
		{ .iov_base = psr_ctx->psr_data,
		  .iov_len = psr_ctx->psr_datalen },
	};
	ssize_t len;
	int exit_code = EXIT_FAILURE;

#define PSR_ERROR(e)				\
	do {					\
		psr_error->psr_result = -1;	\
		psr_error->psr_errno = (e);	\
		goto out;			\
	} while (0 /* CONSTCOND */)

	len = readv(ctx->ps_root_fd, iov, __arraycount(iov));
	if (len == -1)
		PSR_ERROR(errno);
	else if ((size_t)len < sizeof(*psr_error))
		PSR_ERROR(EINVAL);
	exit_code = EXIT_SUCCESS;

out:
	eloop_exit(ctx->ps_eloop, exit_code);
}

ssize_t
ps_root_readerror(struct dhcpcd_ctx *ctx, void *data, size_t len)
{
	struct psr_ctx psr_ctx = {
	    .psr_ctx = ctx,
	    .psr_data = data, .psr_datalen = len,
	};

	if (eloop_event_add(ctx->ps_eloop, ctx->ps_root_fd,
	    ps_root_readerrorcb, &psr_ctx) == -1)
		return -1;

	eloop_start(ctx->ps_eloop, &ctx->sigset);

	errno = psr_ctx.psr_error.psr_errno;
	return psr_ctx.psr_error.psr_result;
}

#ifdef HAVE_CAPSICUM
static void
ps_root_mreaderrorcb(void *arg)
{
	struct psr_ctx *psr_ctx = arg;
	struct dhcpcd_ctx *ctx = psr_ctx->psr_ctx;
	struct psr_error *psr_error = &psr_ctx->psr_error;
	struct iovec iov[] = {
		{ .iov_base = psr_error, .iov_len = sizeof(*psr_error) },
		{ .iov_base = NULL, .iov_len = 0 },
	};
	ssize_t len;
	int exit_code = EXIT_FAILURE;

	len = recv(ctx->ps_root_fd, psr_error, sizeof(*psr_error), MSG_PEEK);
	if (len == -1)
		PSR_ERROR(errno);
	else if ((size_t)len < sizeof(*psr_error))
		PSR_ERROR(EINVAL);

	if (psr_error->psr_datalen != 0) {
		psr_ctx->psr_data = malloc(psr_error->psr_datalen);
		if (psr_ctx->psr_data == NULL)
			PSR_ERROR(errno);
		psr_ctx->psr_datalen = psr_error->psr_datalen;
		iov[1].iov_base = psr_ctx->psr_data;
		iov[1].iov_len = psr_ctx->psr_datalen;
	}

	len = readv(ctx->ps_root_fd, iov, __arraycount(iov));
	if (len == -1)
		PSR_ERROR(errno);
	else if ((size_t)len != sizeof(*psr_error) + psr_ctx->psr_datalen)
		PSR_ERROR(EINVAL);
	exit_code = EXIT_SUCCESS;

out:
	eloop_exit(ctx->ps_eloop, exit_code);
}

ssize_t
ps_root_mreaderror(struct dhcpcd_ctx *ctx, void **data, size_t *len)
{
	struct psr_ctx psr_ctx = {
	    .psr_ctx = ctx,
	};

	if (eloop_event_add(ctx->ps_eloop, ctx->ps_root_fd,
	    ps_root_mreaderrorcb, &psr_ctx) == -1)
		return -1;

	eloop_start(ctx->ps_eloop, &ctx->sigset);

	errno = psr_ctx.psr_error.psr_errno;
	*data = psr_ctx.psr_data;
	*len = psr_ctx.psr_datalen;
	return psr_ctx.psr_error.psr_result;
}
#endif

static ssize_t
ps_root_writeerror(struct dhcpcd_ctx *ctx, ssize_t result,
    void *data, size_t len)
{
	struct psr_error psr = {
		.psr_result = result,
		.psr_errno = errno,
		.psr_datalen = len,
	};
	struct iovec iov[] = {
		{ .iov_base = &psr, .iov_len = sizeof(psr) },
		{ .iov_base = data, .iov_len = len },
	};

#ifdef PRIVSEP_DEBUG
	logdebugx("%s: result %zd errno %d", __func__, result, errno);
#endif

	return writev(ctx->ps_root_fd, iov, __arraycount(iov));
}

static ssize_t
ps_root_doioctl(unsigned long req, void *data, size_t len)
{
	int s, err;

	s = socket(PF_INET, SOCK_DGRAM, 0);
	if (s != -1)
#ifdef IOCTL_REQUEST_TYPE
	{
		ioctl_request_t reqt;

		memcpy(&reqt, &req, sizeof(reqt));
		err = ioctl(s, reqt, data, len);
	}
#else
		err = ioctl(s, req, data, len);
#endif
	else
		err = -1;
	if (s != -1)
		close(s);
	return err;
}

static ssize_t
ps_root_run_script(struct dhcpcd_ctx *ctx, const void *data, size_t len)
{
	const char *envbuf = data;
	char * const argv[] = { UNCONST(data), NULL };
	pid_t pid;
	int status;

#ifdef PRIVSEP_DEBUG
	logdebugx("%s: IN %zu", __func__, len);
#endif

	if (len == 0)
		return 0;

	/* Script is the first one, find the environment buffer. */
	while (*envbuf != '\0') {
		if (len == 0)
			return EINVAL;
		envbuf++;
		len--;
	}

	if (len != 0) {
		envbuf++;
		len--;
	}

#ifdef PRIVSEP_DEBUG
	logdebugx("%s: run script: %s", __func__, argv[0]);
#endif

	if (script_buftoenv(ctx, UNCONST(envbuf), len) == NULL)
		return -1;

	pid = script_exec(argv, ctx->script_env);
	if (pid == -1)
		return -1;
	/* Wait for the script to finish */
	while (waitpid(pid, &status, 0) == -1) {
		if (errno != EINTR) {
			logerr(__func__);
			status = 0;
			break;
		}
	}
	return status;
}

static ssize_t
ps_root_dowritefile(mode_t mode, void *data, size_t len)
{
	char *file = data, *nc;

	nc = memchr(file, '\0', len);
	if (nc == NULL) {
		errno = EINVAL;
		return -1;
	}
	nc++;
	return writefile(file, mode, nc, len - (size_t)(nc - file));
}

#ifdef HAVE_CAPSICUM
#define	IFA_NADDRS	3
static ssize_t
ps_root_dogetifaddrs(void **rdata, size_t *rlen)
{
	struct ifaddrs *ifaddrs, *ifa;
	size_t len;
	uint8_t *buf, *sap;
	socklen_t salen;
	void *ifdata;

	if (getifaddrs(&ifaddrs) == -1)
		return -1;
	if (ifaddrs == NULL) {
		*rdata = NULL;
		*rlen = 0;
		return 0;
	}

	/* Work out the buffer length required.
	 * Ensure everything is aligned correctly, which does
	 * create a larger buffer than what is needed to send,
	 * but makes creating the same structure in the client
	 * much easier. */
	len = 0;
	for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) {
		len += ALIGN(sizeof(*ifa));
		len += ALIGN(IFNAMSIZ);
		len += ALIGN(sizeof(salen) * IFA_NADDRS);
		if (ifa->ifa_addr != NULL)
			len += ALIGN(sa_len(ifa->ifa_addr));
		if (ifa->ifa_netmask != NULL)
			len += ALIGN(sa_len(ifa->ifa_netmask));
		if (ifa->ifa_broadaddr != NULL)
			len += ALIGN(sa_len(ifa->ifa_broadaddr));
	}

	/* Use calloc to set everything to zero.
	 * This satisfies memory sanitizers because don't write
	 * where we don't need to. */
	buf = calloc(1, len);
	if (buf == NULL) {
		freeifaddrs(ifaddrs);
		return -1;
	}
	*rdata = buf;
	*rlen = len;

	for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) {
		/* Don't carry ifa_data. */
		ifdata = ifa->ifa_data;
		ifa->ifa_data = NULL;
		memcpy(buf, ifa, sizeof(*ifa));
		buf += ALIGN(sizeof(*ifa));
		ifa->ifa_data = ifdata;

		strlcpy((char *)buf, ifa->ifa_name, IFNAMSIZ);
		buf += ALIGN(IFNAMSIZ);
		sap = buf;
		buf += ALIGN(sizeof(salen) * IFA_NADDRS);

#define	COPYINSA(addr)						\
	do {							\
		salen = sa_len((addr));				\
		if (salen != 0) {				\
			memcpy(sap, &salen, sizeof(salen));	\
			memcpy(buf, (addr), salen);		\
			buf += ALIGN(salen);			\
		}						\
		sap += sizeof(salen);				\
	} while (0 /*CONSTCOND */)

		if (ifa->ifa_addr != NULL)
			COPYINSA(ifa->ifa_addr);
		if (ifa->ifa_netmask != NULL)
			COPYINSA(ifa->ifa_netmask);
		if (ifa->ifa_broadaddr != NULL)
			COPYINSA(ifa->ifa_broadaddr);
	}

	freeifaddrs(ifaddrs);
	return 0;
}
#endif

static ssize_t
ps_root_recvmsgcb(void *arg, struct ps_msghdr *psm, struct msghdr *msg)
{
	struct dhcpcd_ctx *ctx = arg;
	uint16_t cmd;
	struct ps_process *psp;
	struct iovec *iov = msg->msg_iov;
	void *data = iov->iov_base, *rdata = NULL;
	size_t len = iov->iov_len, rlen = 0;
	uint8_t buf[PS_BUFLEN];
	time_t mtime;
	ssize_t err;
	bool free_rdata= false;

	cmd = (uint16_t)(psm->ps_cmd & ~(PS_START | PS_STOP));
	psp = ps_findprocess(ctx, &psm->ps_id);

#ifdef PRIVSEP_DEBUG
	logerrx("%s: IN cmd %x, psp %p", __func__, psm->ps_cmd, psp);
#endif

	if (psp != NULL) {
		if (psm->ps_cmd & PS_STOP) {
			int ret = ps_dostop(ctx, &psp->psp_pid, &psp->psp_fd);

			ps_freeprocess(psp);
			return ret;
		}
		return ps_sendpsmmsg(ctx, psp->psp_fd, psm, msg);
	}

	if (psm->ps_cmd & PS_STOP && psp == NULL)
		return 0;

	/* All these should just be PS_START */
	switch (cmd) {
#ifdef INET
#ifdef ARP
	case PS_BPF_ARP:	/* FALLTHROUGH */
#endif
	case PS_BPF_BOOTP:
		return ps_bpf_cmd(ctx, psm, msg);
#endif
#ifdef INET
	case PS_BOOTP:
		return ps_inet_cmd(ctx, psm, msg);
#endif
#ifdef INET6
#ifdef DHCP6
	case PS_DHCP6:	/* FALLTHROUGH */
#endif
	case PS_ND:
		return ps_inet_cmd(ctx, psm, msg);
#endif
	default:
		break;
	}

	assert(msg->msg_iovlen == 0 || msg->msg_iovlen == 1);

	/* Reset errno */
	errno = 0;

	switch (psm->ps_cmd) {
	case PS_IOCTL:
		err = ps_root_doioctl(psm->ps_flags, data, len);
		if (err != -1) {
			rdata = data;
			rlen = len;
		}
		break;
	case PS_SCRIPT:
		err = ps_root_run_script(ctx, data, len);
		break;
	case PS_UNLINK:
		err = unlink(data);
		break;
	case PS_READFILE:
		err = readfile(data, buf, sizeof(buf));
		if (err != -1) {
			rdata = buf;
			rlen = (size_t)err;
		}
		break;
	case PS_WRITEFILE:
		err = ps_root_dowritefile((mode_t)psm->ps_flags, data, len);
		break;
	case PS_FILEMTIME:
		err = filemtime(data, &mtime);
		if (err != -1) {
			rdata = &mtime;
			rlen = sizeof(mtime);
		}
		break;
#ifdef HAVE_CAPSICUM
	case PS_GETIFADDRS:
		err = ps_root_dogetifaddrs(&rdata, &rlen);
		free_rdata = true;
		break;
#endif
	default:
		err = ps_root_os(psm, msg);
		break;
	}

	err = ps_root_writeerror(ctx, err, rlen != 0 ? rdata : 0, rlen);
	if (free_rdata)
		free(rdata);
	return err;
}

/* Receive from state engine, do an action. */
static void
ps_root_recvmsg(void *arg)
{
	struct dhcpcd_ctx *ctx = arg;

	if (ps_recvpsmsg(ctx, ctx->ps_root_fd, ps_root_recvmsgcb, ctx) == -1 &&
	    errno != ECONNRESET)
		logerr(__func__);
}

static int
ps_root_startcb(void *arg)
{
	struct dhcpcd_ctx *ctx = arg;

	if (ctx->options & DHCPCD_MASTER)
		setproctitle("[privileged actioneer]");
	else
		setproctitle("[privileged actioneer] %s%s%s",
		    ctx->ifv[0],
		    ctx->options & DHCPCD_IPV4 ? " [ip4]" : "",
		    ctx->options & DHCPCD_IPV6 ? " [ip6]" : "");
	ctx->ps_root_pid = getpid();
	ctx->options |= DHCPCD_PRIVSEPROOT;

	/* Open network sockets for sending.
	 * This is a small bit wasteful for non sandboxed OS's
	 * but makes life very easy for unicasting DHCPv6 in non master
	 * mode as we no longer care about address selection. */
#ifdef INET
	ctx->udp_wfd = xsocket(PF_INET, SOCK_RAW | SOCK_CXNB, IPPROTO_UDP);
	if (ctx->udp_wfd == -1)
		return -1;
#endif
#ifdef INET6
	ctx->nd_fd = ipv6nd_open(false);
	if (ctx->nd_fd == -1)
		return -1;
#endif
#ifdef DHCP6
	ctx->dhcp6_wfd = dhcp6_openraw();
	if (ctx->dhcp6_wfd == -1)
		return -1;
#endif

	return 0;
}

static void
ps_root_signalcb(int sig, void *arg)
{
	struct dhcpcd_ctx *ctx = arg;

	/* Ignore SIGINT, respect PS_STOP command or SIGTERM. */
	if (sig == SIGINT)
		return;

	logerrx("process %d unexpectedly terminating on signal %d",
	    getpid(), sig);
	if (ctx->ps_root_pid == getpid()) {
		shutdown(ctx->ps_root_fd, SHUT_RDWR);
		shutdown(ctx->ps_data_fd, SHUT_RDWR);
	}
	eloop_exit(ctx->eloop, sig == SIGTERM ? EXIT_SUCCESS : EXIT_FAILURE);
}

static ssize_t
ps_root_dispatchcb(void *arg, struct ps_msghdr *psm, struct msghdr *msg)
{
	struct dhcpcd_ctx *ctx = arg;
	ssize_t err;

#ifdef INET
	err = ps_bpf_dispatch(ctx, psm, msg);
	if (err == -1 && errno == ENOTSUP)
#endif
		err = ps_inet_dispatch(ctx, psm, msg);
	return err;
}

static void
ps_root_dispatch(void *arg)
{
	struct dhcpcd_ctx *ctx = arg;

	if (ps_recvpsmsg(ctx, ctx->ps_data_fd, ps_root_dispatchcb, ctx) == -1)
		logerr(__func__);
}

pid_t
ps_root_start(struct dhcpcd_ctx *ctx)
{
	int fd[2];
	pid_t pid;

	if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CXNB, 0, fd) == -1)
		return -1;

	pid = ps_dostart(ctx, &ctx->ps_root_pid, &ctx->ps_root_fd,
	    ps_root_recvmsg, NULL, ctx,
	    ps_root_startcb, ps_root_signalcb, 0);

	if (pid == 0) {
		ctx->ps_data_fd = fd[1];
		close(fd[0]);
		return 0;
	} else if (pid == -1)
		return -1;

	ctx->ps_data_fd = fd[0];
	close(fd[1]);
	if (eloop_event_add(ctx->eloop, ctx->ps_data_fd,
	    ps_root_dispatch, ctx) == -1)
		return -1;

	if ((ctx->ps_eloop = eloop_new()) == NULL)
		return -1;

	if (eloop_signal_set_cb(ctx->ps_eloop,
	    dhcpcd_signals, dhcpcd_signals_len,
	    ps_root_readerrorsig, ctx) == -1)
		return -1;

	return pid;
}

int
ps_root_stop(struct dhcpcd_ctx *ctx)
{

	return ps_dostop(ctx, &ctx->ps_root_pid, &ctx->ps_root_fd);
}

ssize_t
ps_root_script(const struct interface *ifp, const void *data, size_t len)
{
	char buf[PS_BUFLEN], *p = buf;
	size_t blen = PS_BUFLEN, slen = strlen(ifp->options->script) + 1;

#ifdef PRIVSEP_DEBUG
	logdebugx("%s: sending script: %zu %s len %zu",
	    __func__, slen, ifp->options->script, len);
#endif

	if (slen > blen) {
		errno = ENOBUFS;
		return -1;
	}
	memcpy(p, ifp->options->script, slen);
	p += slen;
	blen -= slen;

	if (len > blen) {
		errno = ENOBUFS;
		return -1;
	}
	memcpy(p, data, len);

#ifdef PRIVSEP_DEBUG
	logdebugx("%s: sending script data: %zu", __func__, slen + len);
#endif

	if (ps_sendcmd(ifp->ctx, ifp->ctx->ps_root_fd, PS_SCRIPT, 0,
	    buf, slen + len) == -1)
		return -1;

	return ps_root_readerror(ifp->ctx, NULL, 0);
}

ssize_t
ps_root_ioctl(struct dhcpcd_ctx *ctx, ioctl_request_t req, void *data,
    size_t len)
{
#ifdef IOCTL_REQUEST_TYPE
	unsigned long ulreq = 0;

	memcpy(&ulreq, &req, sizeof(req));
	if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_IOCTL, ulreq, data, len) == -1)
		return -1;
#else
	if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_IOCTL, req, data, len) == -1)
		return -1;
#endif
	return ps_root_readerror(ctx, data, len);
}

ssize_t
ps_root_unlink(struct dhcpcd_ctx *ctx, const char *file)
{

	if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_UNLINK, 0,
	    file, strlen(file) + 1) == -1)
		return -1;
	return ps_root_readerror(ctx, NULL, 0);
}

ssize_t
ps_root_readfile(struct dhcpcd_ctx *ctx, const char *file,
    void *data, size_t len)
{
	if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_READFILE, 0,
	    file, strlen(file) + 1) == -1)
		return -1;
	return ps_root_readerror(ctx, data, len);
}

ssize_t
ps_root_writefile(struct dhcpcd_ctx *ctx, const char *file, mode_t mode,
    const void *data, size_t len)
{
	char buf[PS_BUFLEN];
	size_t flen;

	flen = strlcpy(buf, file, sizeof(buf));
	flen += 1;
	if (flen > sizeof(buf) || flen + len > sizeof(buf)) {
		errno = ENOBUFS;
		return -1;
	}
	memcpy(buf + flen, data, len);

	if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_WRITEFILE, mode,
	    buf, flen + len) == -1)
		return -1;
	return ps_root_readerror(ctx, NULL, 0);
}

ssize_t
ps_root_filemtime(struct dhcpcd_ctx *ctx, const char *file, time_t *time)
{

	if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_FILEMTIME, 0,
	    file, strlen(file) + 1) == -1)
		return -1;
	return ps_root_readerror(ctx, time, sizeof(*time));
}

#ifdef HAVE_CAPSICUM
int
ps_root_getifaddrs(struct dhcpcd_ctx *ctx, struct ifaddrs **ifahead)
{
	struct ifaddrs *ifa;
	void *buf = NULL;
	char *bp, *sap;
	socklen_t salen;
	size_t len;
	ssize_t err;

	if (ps_sendcmd(ctx, ctx->ps_root_fd,
	    PS_GETIFADDRS, 0, NULL, 0) == -1)
		return -1;
	err = ps_root_mreaderror(ctx, &buf, &len);

	if (err == -1)
		return -1;

	/* Should be impossible - lo0 will always exist. */
	if (len == 0) {
		*ifahead = NULL;
		return 0;
	}

	bp = buf;
	*ifahead = (struct ifaddrs *)(void *)bp;
	for (ifa = *ifahead; len != 0; ifa = ifa->ifa_next) {
		if (len < ALIGN(sizeof(*ifa)) +
		    ALIGN(IFNAMSIZ) + ALIGN(sizeof(salen) * IFA_NADDRS))
			goto err;
		bp += ALIGN(sizeof(*ifa));
		ifa->ifa_name = bp;
		bp += ALIGN(IFNAMSIZ);
		sap = bp;
		bp += ALIGN(sizeof(salen) * IFA_NADDRS);
		len -= ALIGN(sizeof(*ifa)) +
		    ALIGN(IFNAMSIZ) + ALIGN(sizeof(salen) * IFA_NADDRS);

#define	COPYOUTSA(addr)						\
	do {							\
		memcpy(&salen, sap, sizeof(salen));		\
		if (len < salen)				\
			goto err;				\
		if (salen != 0) {				\
			(addr) = (struct sockaddr *)bp;		\
			bp += ALIGN(salen);			\
			len -= ALIGN(salen);			\
		}						\
		sap += sizeof(salen);				\
	} while (0 /* CONSTCOND */)

		COPYOUTSA(ifa->ifa_addr);
		COPYOUTSA(ifa->ifa_netmask);
		COPYOUTSA(ifa->ifa_broadaddr);
		ifa->ifa_next = (struct ifaddrs *)(void *)bp;
	}
	ifa->ifa_next = NULL;
	return 0;

err:
	free(buf);
	*ifahead = NULL;
	errno = EINVAL;
	return -1;
}
#endif