[BACK]Return to pfkeyv2_parsemessage.c CVS log [TXT][DIR] Up to [local] / src / sys / net

File: [local] / src / sys / net / pfkeyv2_parsemessage.c (download)

Revision 1.62, Fri Sep 29 18:45:42 2023 UTC (8 months, 1 week ago) by tobhe
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, HEAD
Changes since 1.61: +3 -11 lines

Make sure pfkeyv2_parsemessage() only returns 0 if the message was
successfully validated.  Decline all messages from userland that contain
errnos and remove unneeded special handling for type SADB_X_PROMISC.

ok bluhm@

/*	$OpenBSD: pfkeyv2_parsemessage.c,v 1.62 2023/09/29 18:45:42 tobhe Exp $	*/

/*
 *	@(#)COPYRIGHT	1.1 (NRL) 17 January 1995
 *
 * NRL grants permission for redistribution and use in source and binary
 * forms, with or without modification, of the software and documentation
 * created at NRL 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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgements:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 *	This product includes software developed at the Information
 *	Technology Division, US Naval Research Laboratory.
 * 4. Neither the name of the NRL nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THE SOFTWARE PROVIDED BY NRL IS PROVIDED BY NRL 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 NRL 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.
 *
 * The views and conclusions contained in the software and documentation
 * are those of the authors and should not be interpreted as representing
 * official policies, either expressed or implied, of the US Naval
 * Research Laboratory (NRL).
 */

/*
 * Copyright (c) 1995, 1996, 1997, 1998, 1999 Craig Metz. 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.
 * 3. Neither the name of the author nor the names of any contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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 "pf.h"

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/socket.h>
#include <sys/mbuf.h>
#include <sys/proc.h>
#include <netinet/ip_ipsp.h>
#include <net/pfkeyv2.h>

#if NPF > 0
#include <net/if.h>
#include <net/pfvar.h>
#endif

#ifdef ENCDEBUG
#define DPRINTF(fmt, args...)						\
	do {								\
		if (encdebug)						\
			printf("%s: " fmt "\n", __func__, ## args);	\
	} while (0)
#else
#define DPRINTF(fmt, args...)						\
	do { } while (0)
#endif

#define BITMAP_SA                      (1LL << SADB_EXT_SA)
#define BITMAP_LIFETIME_CURRENT        (1LL << SADB_EXT_LIFETIME_CURRENT)
#define BITMAP_LIFETIME_HARD           (1LL << SADB_EXT_LIFETIME_HARD)
#define BITMAP_LIFETIME_SOFT           (1LL << SADB_EXT_LIFETIME_SOFT)
#define BITMAP_ADDRESS_SRC             (1LL << SADB_EXT_ADDRESS_SRC)
#define BITMAP_ADDRESS_DST             (1LL << SADB_EXT_ADDRESS_DST)
#define BITMAP_ADDRESS_PROXY           (1LL << SADB_EXT_ADDRESS_PROXY)
#define BITMAP_KEY_AUTH                (1LL << SADB_EXT_KEY_AUTH)
#define BITMAP_KEY_ENCRYPT             (1LL << SADB_EXT_KEY_ENCRYPT)
#define BITMAP_IDENTITY_SRC            (1LL << SADB_EXT_IDENTITY_SRC)
#define BITMAP_IDENTITY_DST            (1LL << SADB_EXT_IDENTITY_DST)
#define BITMAP_SENSITIVITY             (1LL << SADB_EXT_SENSITIVITY)
#define BITMAP_PROPOSAL                (1LL << SADB_EXT_PROPOSAL)
#define BITMAP_SUPPORTED_AUTH          (1LL << SADB_EXT_SUPPORTED_AUTH)
#define BITMAP_SUPPORTED_ENCRYPT       (1LL << SADB_EXT_SUPPORTED_ENCRYPT)
#define BITMAP_SPIRANGE                (1LL << SADB_EXT_SPIRANGE)
#define BITMAP_LIFETIME (BITMAP_LIFETIME_CURRENT | BITMAP_LIFETIME_HARD | BITMAP_LIFETIME_SOFT)
#define BITMAP_ADDRESS (BITMAP_ADDRESS_SRC | BITMAP_ADDRESS_DST)
#define BITMAP_KEY      (BITMAP_KEY_AUTH | BITMAP_KEY_ENCRYPT)
#define BITMAP_IDENTITY (BITMAP_IDENTITY_SRC | BITMAP_IDENTITY_DST)
#define BITMAP_MSG                     1
#define BITMAP_X_SRC_MASK              (1LL << SADB_X_EXT_SRC_MASK)
#define BITMAP_X_DST_MASK              (1LL << SADB_X_EXT_DST_MASK)
#define BITMAP_X_PROTOCOL              (1LL << SADB_X_EXT_PROTOCOL)
#define BITMAP_X_SRC_FLOW              (1LL << SADB_X_EXT_SRC_FLOW)
#define BITMAP_X_DST_FLOW              (1LL << SADB_X_EXT_DST_FLOW)
#define BITMAP_X_FLOW_TYPE             (1LL << SADB_X_EXT_FLOW_TYPE)
#define BITMAP_X_SA2                   (1LL << SADB_X_EXT_SA2)
#define BITMAP_X_DST2                  (1LL << SADB_X_EXT_DST2)
#define BITMAP_X_POLICY                (1LL << SADB_X_EXT_POLICY)
#define BITMAP_X_FLOW                  (BITMAP_X_SRC_MASK | BITMAP_X_DST_MASK | BITMAP_X_PROTOCOL | BITMAP_X_SRC_FLOW | BITMAP_X_DST_FLOW | BITMAP_X_FLOW_TYPE)
#define BITMAP_X_SUPPORTED_COMP        (1LL << SADB_X_EXT_SUPPORTED_COMP)
#define BITMAP_X_UDPENCAP              (1LL << SADB_X_EXT_UDPENCAP)
#define BITMAP_X_LIFETIME_LASTUSE      (1LL << SADB_X_EXT_LIFETIME_LASTUSE)
#define BITMAP_X_TAG                   (1LL << SADB_X_EXT_TAG)
#define BITMAP_X_TAP                   (1LL << SADB_X_EXT_TAP)
#define BITMAP_X_SATYPE2               (1LL << SADB_X_EXT_SATYPE2)
#define BITMAP_X_RDOMAIN               (1LL << SADB_X_EXT_RDOMAIN)
#define BITMAP_X_COUNTER               (1LL << SADB_X_EXT_COUNTER)
#define BITMAP_X_MTU                   (1LL << SADB_X_EXT_MTU)
#define BITMAP_X_REPLAY                (1LL << SADB_X_EXT_REPLAY)
#define BITMAP_X_IFACE                 (1LL << SADB_X_EXT_IFACE)

uint64_t sadb_exts_allowed_in[SADB_MAX+1] =
{
	/* RESERVED */
	~0,
	/* GETSPI */
	BITMAP_ADDRESS_SRC | BITMAP_ADDRESS_DST | BITMAP_SPIRANGE,
	/* UPDATE */
	BITMAP_SA | BITMAP_LIFETIME | BITMAP_ADDRESS | BITMAP_ADDRESS_PROXY | BITMAP_KEY | BITMAP_IDENTITY | BITMAP_X_FLOW | BITMAP_X_UDPENCAP | BITMAP_X_TAG | BITMAP_X_TAP | BITMAP_X_RDOMAIN | BITMAP_X_IFACE,
	/* ADD */
	BITMAP_SA | BITMAP_LIFETIME | BITMAP_ADDRESS | BITMAP_KEY | BITMAP_IDENTITY | BITMAP_X_FLOW | BITMAP_X_UDPENCAP | BITMAP_X_LIFETIME_LASTUSE | BITMAP_X_TAG | BITMAP_X_TAP | BITMAP_X_RDOMAIN | BITMAP_X_IFACE,
	/* DELETE */
	BITMAP_SA | BITMAP_ADDRESS_SRC | BITMAP_ADDRESS_DST | BITMAP_X_RDOMAIN,
	/* GET */
	BITMAP_SA | BITMAP_ADDRESS_SRC | BITMAP_ADDRESS_DST | BITMAP_X_RDOMAIN,
	/* ACQUIRE */
	BITMAP_ADDRESS_SRC | BITMAP_ADDRESS_DST | BITMAP_IDENTITY | BITMAP_PROPOSAL,
	/* REGISTER */
	0,
	/* EXPIRE */
	BITMAP_SA | BITMAP_ADDRESS_SRC | BITMAP_ADDRESS_DST,
	/* FLUSH */
	0,
	/* DUMP */
	0,
	/* X_PROMISC */
	0,
	/* X_ADDFLOW */
	BITMAP_ADDRESS_SRC | BITMAP_ADDRESS_DST | BITMAP_IDENTITY_SRC | BITMAP_IDENTITY_DST | BITMAP_X_FLOW | BITMAP_X_RDOMAIN,
	/* X_DELFLOW */
	BITMAP_X_FLOW | BITMAP_X_RDOMAIN,
	/* X_GRPSPIS */
	BITMAP_SA | BITMAP_X_SA2 | BITMAP_X_DST2 | BITMAP_ADDRESS_DST | BITMAP_X_SATYPE2 | BITMAP_X_RDOMAIN,
	/* X_ASKPOLICY */
	BITMAP_X_POLICY,
};

uint64_t sadb_exts_required_in[SADB_MAX+1] =
{
	/* RESERVED */
	0,
	/* GETSPI */
	BITMAP_ADDRESS_SRC | BITMAP_ADDRESS_DST | BITMAP_SPIRANGE,
	/* UPDATE */
	BITMAP_SA | BITMAP_ADDRESS_SRC | BITMAP_ADDRESS_DST,
	/* ADD */
	BITMAP_SA | BITMAP_ADDRESS_DST,
	/* DELETE */
	BITMAP_SA | BITMAP_ADDRESS_DST,
	/* GET */
	BITMAP_SA | BITMAP_ADDRESS_DST,
	/* ACQUIRE */
	0,
	/* REGISTER */
	0,
	/* EXPIRE */
	BITMAP_SA | BITMAP_ADDRESS_SRC | BITMAP_ADDRESS_DST,
	/* FLUSH */
	0,
	/* DUMP */
	0,
	/* X_PROMISC */
	0,
	/* X_ADDFLOW */
	BITMAP_X_SRC_MASK | BITMAP_X_DST_MASK | BITMAP_X_SRC_FLOW | BITMAP_X_DST_FLOW | BITMAP_X_FLOW_TYPE,
	/* X_DELFLOW */
	BITMAP_X_SRC_MASK | BITMAP_X_DST_MASK | BITMAP_X_SRC_FLOW | BITMAP_X_DST_FLOW | BITMAP_X_FLOW_TYPE,
	/* X_GRPSPIS */
	BITMAP_SA | BITMAP_X_SA2 | BITMAP_X_DST2 | BITMAP_ADDRESS_DST | BITMAP_X_SATYPE2,
	/* X_ASKPOLICY */
	BITMAP_X_POLICY,
};

const uint64_t sadb_exts_allowed_out[SADB_MAX+1] =
{
	/* RESERVED */
	~0,
	/* GETSPI */
	BITMAP_SA | BITMAP_ADDRESS_SRC | BITMAP_ADDRESS_DST,
	/* UPDATE */
	BITMAP_SA | BITMAP_LIFETIME | BITMAP_ADDRESS | BITMAP_ADDRESS_PROXY | BITMAP_IDENTITY | BITMAP_X_FLOW | BITMAP_X_UDPENCAP | BITMAP_X_TAG | BITMAP_X_TAP | BITMAP_X_RDOMAIN | BITMAP_X_IFACE,
	/* ADD */
	BITMAP_SA | BITMAP_LIFETIME | BITMAP_ADDRESS | BITMAP_IDENTITY | BITMAP_X_FLOW | BITMAP_X_UDPENCAP | BITMAP_X_TAG | BITMAP_X_TAP | BITMAP_X_RDOMAIN | BITMAP_X_IFACE,
	/* DELETE */
	BITMAP_SA | BITMAP_ADDRESS_SRC | BITMAP_ADDRESS_DST | BITMAP_X_RDOMAIN,
	/* GET */
	BITMAP_SA | BITMAP_LIFETIME | BITMAP_ADDRESS | BITMAP_KEY | BITMAP_IDENTITY | BITMAP_X_UDPENCAP | BITMAP_X_LIFETIME_LASTUSE | BITMAP_X_SRC_MASK | BITMAP_X_DST_MASK | BITMAP_X_PROTOCOL | BITMAP_X_FLOW_TYPE | BITMAP_X_SRC_FLOW | BITMAP_X_DST_FLOW | BITMAP_X_TAG | BITMAP_X_TAP | BITMAP_X_COUNTER | BITMAP_X_RDOMAIN | BITMAP_X_MTU | BITMAP_X_REPLAY | BITMAP_X_IFACE,
	/* ACQUIRE */
	BITMAP_ADDRESS_SRC | BITMAP_ADDRESS_DST | BITMAP_IDENTITY | BITMAP_PROPOSAL,
	/* REGISTER */
	BITMAP_SUPPORTED_AUTH | BITMAP_SUPPORTED_ENCRYPT | BITMAP_X_SUPPORTED_COMP,
	/* EXPIRE */
	BITMAP_SA | BITMAP_LIFETIME | BITMAP_ADDRESS,
	/* FLUSH */
	0,
	/* DUMP */
	BITMAP_SA | BITMAP_LIFETIME | BITMAP_ADDRESS | BITMAP_IDENTITY,
	/* X_PROMISC */
	0,
	/* X_ADDFLOW */
	BITMAP_ADDRESS_SRC | BITMAP_ADDRESS_DST | BITMAP_X_SRC_MASK | BITMAP_X_DST_MASK | BITMAP_X_PROTOCOL | BITMAP_X_SRC_FLOW | BITMAP_X_DST_FLOW | BITMAP_X_FLOW_TYPE | BITMAP_IDENTITY_SRC | BITMAP_IDENTITY_DST | BITMAP_X_RDOMAIN,
	/* X_DELFLOW */
	BITMAP_X_SRC_MASK | BITMAP_X_DST_MASK | BITMAP_X_PROTOCOL | BITMAP_X_SRC_FLOW | BITMAP_X_DST_FLOW | BITMAP_X_FLOW_TYPE | BITMAP_X_RDOMAIN,
	/* X_GRPSPIS */
	BITMAP_SA | BITMAP_X_SA2 | BITMAP_X_DST2 | BITMAP_ADDRESS_DST | BITMAP_X_SATYPE2 | BITMAP_X_RDOMAIN,
	/* X_ASKPOLICY */
	BITMAP_X_SRC_FLOW | BITMAP_X_DST_FLOW | BITMAP_X_SRC_MASK | BITMAP_X_DST_MASK | BITMAP_X_FLOW_TYPE | BITMAP_X_POLICY,
};

const uint64_t sadb_exts_required_out[SADB_MAX+1] =
{
	/* RESERVED */
	0,
	/* GETSPI */
	BITMAP_SA | BITMAP_ADDRESS_DST,
	/* UPDATE */
	BITMAP_SA | BITMAP_ADDRESS_DST,
	/* ADD */
	BITMAP_SA | BITMAP_ADDRESS_DST,
	/* DELETE */
	BITMAP_SA | BITMAP_ADDRESS_DST,
	/* GET */
	BITMAP_SA | BITMAP_LIFETIME_CURRENT | BITMAP_ADDRESS_DST,
	/* ACQUIRE */
	0,
	/* REGISTER */
	BITMAP_SUPPORTED_AUTH | BITMAP_SUPPORTED_ENCRYPT | BITMAP_X_SUPPORTED_COMP,
	/* EXPIRE */
	BITMAP_SA | BITMAP_ADDRESS_DST,
	/* FLUSH */
	0,
	/* DUMP */
	0,
	/* X_PROMISC */
	0,
	/* X_ADDFLOW */
	BITMAP_X_SRC_MASK | BITMAP_X_DST_MASK | BITMAP_X_SRC_FLOW | BITMAP_X_DST_FLOW | BITMAP_X_FLOW_TYPE,
	/* X_DELFLOW */
	BITMAP_X_SRC_MASK | BITMAP_X_DST_MASK | BITMAP_X_SRC_FLOW | BITMAP_X_DST_FLOW | BITMAP_X_FLOW_TYPE,
	/* X_GRPSPIS */
	BITMAP_SA | BITMAP_X_SA2 | BITMAP_X_DST2 | BITMAP_ADDRESS_DST | BITMAP_X_SATYPE2,
	/* X_REPPOLICY */
	BITMAP_X_SRC_FLOW | BITMAP_X_DST_FLOW | BITMAP_X_SRC_MASK | BITMAP_X_DST_MASK | BITMAP_X_FLOW_TYPE,
};

int
pfkeyv2_parsemessage(void *p, int len, void **headers)
{
	struct sadb_ext *sadb_ext;
	int i, left = len;
	uint64_t allow, seen = 1;
	struct sadb_msg *sadb_msg = (struct sadb_msg *) p;

	bzero(headers, (SADB_EXT_MAX + 1) * sizeof(void *));

	if (left < sizeof(struct sadb_msg)) {
		DPRINTF("message too short");
		return (EINVAL);
	}

	headers[0] = p;

	if (sadb_msg->sadb_msg_len * sizeof(uint64_t) != left) {
		DPRINTF("length not a multiple of 64");
		return (EINVAL);
	}

	p += sizeof(struct sadb_msg);
	left -= sizeof(struct sadb_msg);

	if (sadb_msg->sadb_msg_reserved) {
		DPRINTF("message header reserved field set");
		return (EINVAL);
	}

	if (sadb_msg->sadb_msg_type > SADB_MAX) {
		DPRINTF("message type > %d", SADB_MAX);
		return (EINVAL);
	}

	if (!sadb_msg->sadb_msg_type) {
		DPRINTF("message type unset");
		return (EINVAL);
	}

	if (sadb_msg->sadb_msg_pid != curproc->p_p->ps_pid) {
		DPRINTF("bad PID value");
		return (EINVAL);
	}

	if (sadb_msg->sadb_msg_errno) {
		DPRINTF("errno set");
		return (EINVAL);
	}

	allow = sadb_exts_allowed_in[sadb_msg->sadb_msg_type];

	while (left > 0) {
		sadb_ext = (struct sadb_ext *)p;
		if (left < sizeof(struct sadb_ext)) {
			DPRINTF("extension header too short");
			return (EINVAL);
		}

		i = sadb_ext->sadb_ext_len * sizeof(uint64_t);
		if (left < i) {
			DPRINTF("extension header exceeds message length");
			return (EINVAL);
		}

		if (sadb_ext->sadb_ext_type > SADB_EXT_MAX) {
			DPRINTF("unknown extension header %d",
			    sadb_ext->sadb_ext_type);
			return (EINVAL);
		}

		if (!sadb_ext->sadb_ext_type) {
			DPRINTF("unset extension header");
			return (EINVAL);
		}

		if (!(allow & (1LL << sadb_ext->sadb_ext_type))) {
			DPRINTF("extension header %d not permitted on message "
			    "type %d",
			    sadb_ext->sadb_ext_type, sadb_msg->sadb_msg_type);
			return (EINVAL);
		}

		if (headers[sadb_ext->sadb_ext_type]) {
			DPRINTF("duplicate extension header %d",
			    sadb_ext->sadb_ext_type);
			return (EINVAL);
		}

		seen |= (1LL << sadb_ext->sadb_ext_type);

		switch (sadb_ext->sadb_ext_type) {
		case SADB_EXT_SA:
		case SADB_X_EXT_SA2:
		{
			struct sadb_sa *sadb_sa = (struct sadb_sa *)p;

			if (i != sizeof(struct sadb_sa)) {
				DPRINTF("bad header length for SA extension "
				    "header %d",
				    sadb_ext->sadb_ext_type);
				return (EINVAL);
			}

			if (sadb_sa->sadb_sa_state > SADB_SASTATE_MAX) {
				DPRINTF("unknown SA state %d in SA extension "
				    "header %d",
				    sadb_sa->sadb_sa_state,
				    sadb_ext->sadb_ext_type);
				return (EINVAL);
			}

			if (sadb_sa->sadb_sa_state == SADB_SASTATE_DEAD) {
				DPRINTF("cannot set SA state to dead, "
				    "SA extension header %d",
				    sadb_ext->sadb_ext_type);
				return (EINVAL);
			}

			if (sadb_sa->sadb_sa_encrypt > SADB_EALG_MAX) {
				DPRINTF("unknown encryption algorithm %d "
				    "in SA extension header %d",
				    sadb_sa->sadb_sa_encrypt,
				    sadb_ext->sadb_ext_type);
				return (EINVAL);
			}

			if (sadb_sa->sadb_sa_auth > SADB_AALG_MAX) {
				DPRINTF("unknown authentication algorithm %d "
				    "in SA extension header %d",
				    sadb_sa->sadb_sa_auth,
				    sadb_ext->sadb_ext_type);
				return (EINVAL);
			}

			if (sadb_sa->sadb_sa_replay > 64) {
				DPRINTF("unsupported replay window size %d "
				    "in SA extension header %d",
				    sadb_sa->sadb_sa_replay,
				    sadb_ext->sadb_ext_type);
				return (EINVAL);
			}
		}
		break;
		case SADB_X_EXT_PROTOCOL:
		case SADB_X_EXT_FLOW_TYPE:
		case SADB_X_EXT_SATYPE2:
			if (i != sizeof(struct sadb_protocol)) {
				DPRINTF("bad PROTOCOL/FLOW/SATYPE2 header "
				    "length in extension header %d",
				    sadb_ext->sadb_ext_type);
				return (EINVAL);
			}
			break;
		case SADB_X_EXT_POLICY:
			if (i != sizeof(struct sadb_x_policy)) {
				DPRINTF("bad POLICY header length");
				return (EINVAL);
			}
			break;
		case SADB_EXT_LIFETIME_CURRENT:
		case SADB_EXT_LIFETIME_HARD:
		case SADB_EXT_LIFETIME_SOFT:
		case SADB_X_EXT_LIFETIME_LASTUSE:
			if (i != sizeof(struct sadb_lifetime)) {
				DPRINTF("bad header length for LIFETIME "
				    "extension header %d",
				    sadb_ext->sadb_ext_type);
				return (EINVAL);
			}
			break;
		case SADB_EXT_ADDRESS_SRC:
		case SADB_EXT_ADDRESS_DST:
		case SADB_EXT_ADDRESS_PROXY:
		case SADB_X_EXT_SRC_MASK:
		case SADB_X_EXT_DST_MASK:
		case SADB_X_EXT_SRC_FLOW:
		case SADB_X_EXT_DST_FLOW:
		case SADB_X_EXT_DST2:
		{
			struct sadb_address *sadb_address =
			    (struct sadb_address *)p;
			struct sockaddr *sa = (struct sockaddr *)(p +
			    sizeof(struct sadb_address));

			if (i < sizeof(struct sadb_address) +
			    sizeof(struct sockaddr)) {
				DPRINTF("bad ADDRESS extension header %d "
				    "length",
				    sadb_ext->sadb_ext_type);
				return (EINVAL);
			}

			if (sadb_address->sadb_address_reserved) {
				DPRINTF("ADDRESS extension header %d reserved "
				    "field set",
				    sadb_ext->sadb_ext_type);
				return (EINVAL);
			}
			if (sa->sa_len &&
			    (i != sizeof(struct sadb_address) +
			    PADUP(sa->sa_len))) {
				DPRINTF("bad sockaddr length field in ADDRESS "
				    "extension header %d",
				    sadb_ext->sadb_ext_type);
				return (EINVAL);
			}

			switch (sa->sa_family) {
			case AF_INET:
				if (sizeof(struct sadb_address) +
				    PADUP(sizeof(struct sockaddr_in)) != i) {
					DPRINTF("invalid ADDRESS extension "
					    "header %d length",
					    sadb_ext->sadb_ext_type);
					return (EINVAL);
				}

				if (sa->sa_len != sizeof(struct sockaddr_in)) {
					DPRINTF("bad sockaddr_in length in "
					    "ADDRESS extension header %d",
					    sadb_ext->sadb_ext_type);
					return (EINVAL);
				}

				/* Only check the right pieces */
				switch (sadb_ext->sadb_ext_type)
				{
				case SADB_X_EXT_SRC_MASK:
				case SADB_X_EXT_DST_MASK:
				case SADB_X_EXT_SRC_FLOW:
				case SADB_X_EXT_DST_FLOW:
					break;

				default:
					if (((struct sockaddr_in *)sa)->sin_port) {
						DPRINTF("port field set in "
						    "sockaddr_in of ADDRESS "
						    "extension header %d",
						    sadb_ext->sadb_ext_type);
						return (EINVAL);
					}
					break;
				}

				{
					char zero[sizeof(((struct sockaddr_in *)sa)->sin_zero)];
					bzero(zero, sizeof(zero));

					if (bcmp(&((struct sockaddr_in *)sa)->sin_zero, zero, sizeof(zero))) {
						DPRINTF("reserved sockaddr_in "
						    "field non-zero'ed in "
						    "ADDRESS extension header "
						    "%d",
						    sadb_ext->sadb_ext_type);
						return (EINVAL);
					}
				}
				break;
#ifdef INET6
			case AF_INET6:
				if (i != sizeof(struct sadb_address) +
				    PADUP(sizeof(struct sockaddr_in6))) {
					DPRINTF("invalid sockaddr_in6 length "
					    "in ADDRESS extension header %d",
					    sadb_ext->sadb_ext_type);
					return (EINVAL);
				}

				if (sa->sa_len !=
				    sizeof(struct sockaddr_in6)) {
					DPRINTF("bad sockaddr_in6 length in "
					    "ADDRESS extension header %d",
					    sadb_ext->sadb_ext_type);
					return (EINVAL);
				}

				if (((struct sockaddr_in6 *)sa)->sin6_flowinfo) {
					DPRINTF("flowinfo field set in "
					    "sockaddr_in6 of ADDRESS "
					    "extension header %d",
					    sadb_ext->sadb_ext_type);
					return (EINVAL);
				}

				/* Only check the right pieces */
				switch (sadb_ext->sadb_ext_type)
				{
				case SADB_X_EXT_SRC_MASK:
				case SADB_X_EXT_DST_MASK:
				case SADB_X_EXT_SRC_FLOW:
				case SADB_X_EXT_DST_FLOW:
					break;

				default:
					if (((struct sockaddr_in6 *)sa)->sin6_port) {
						DPRINTF("port field set in "
						    "sockaddr_in6 of ADDRESS "
						    "extension header %d",
						    sadb_ext->sadb_ext_type);
						return (EINVAL);
					}
					break;
				}
				break;
#endif /* INET6 */
			default:
				if (sadb_msg->sadb_msg_satype ==
				    SADB_X_SATYPE_TCPSIGNATURE &&
				    sa->sa_family == 0)
					break;
				DPRINTF("unknown address family %d in ADDRESS "
				    "extension header %d",
				    sa->sa_family, sadb_ext->sadb_ext_type);
				return (EINVAL);
			}
		}
		break;
		case SADB_EXT_KEY_AUTH:
		case SADB_EXT_KEY_ENCRYPT:
		{
			struct sadb_key *sadb_key = (struct sadb_key *)p;

			if (i < sizeof(struct sadb_key)) {
				DPRINTF("bad header length in KEY extension "
				    "header %d",
				    sadb_ext->sadb_ext_type);
				return (EINVAL);
			}

			if (!sadb_key->sadb_key_bits) {
				DPRINTF("key length unset in KEY extension "
				    "header %d",
				    sadb_ext->sadb_ext_type);
				return (EINVAL);
			}

			if (((sadb_key->sadb_key_bits + 63) / 64) * sizeof(uint64_t) != i - sizeof(struct sadb_key)) {
				DPRINTF("invalid key length in KEY extension "
				    "header %d",
				    sadb_ext->sadb_ext_type);
				return (EINVAL);
			}

			if (sadb_key->sadb_key_reserved) {
				DPRINTF("reserved field set in KEY extension "
				    "header %d",
				    sadb_ext->sadb_ext_type);
				return (EINVAL);
			}
		}
		break;
		case SADB_EXT_IDENTITY_SRC:
		case SADB_EXT_IDENTITY_DST:
		{
			struct sadb_ident *sadb_ident = (struct sadb_ident *)p;

			if (i < sizeof(struct sadb_ident)) {
				DPRINTF("bad header length of IDENTITY "
				    "extension header %d",
				    sadb_ext->sadb_ext_type);
				return (EINVAL);
			}

			if (sadb_ident->sadb_ident_type > SADB_IDENTTYPE_MAX) {
				DPRINTF("unknown identity type %d in IDENTITY "
				    "extension header %d",
				    sadb_ident->sadb_ident_type,
				    sadb_ext->sadb_ext_type);
				return (EINVAL);
			}

			if (sadb_ident->sadb_ident_reserved) {
				DPRINTF("reserved field set in IDENTITY "
				    "extension header %d",
				    sadb_ext->sadb_ext_type);
				return (EINVAL);
			}

			if (i > sizeof(struct sadb_ident)) {
				char *c =
				    (char *)(p + sizeof(struct sadb_ident));
				int j;

				if (*(char *)(p + i - 1)) {
					DPRINTF("non NUL-terminated identity "
					    "in IDENTITY extension header %d",
					    sadb_ext->sadb_ext_type);
					return (EINVAL);
				}

				j = PADUP(strlen(c) + 1) +
				    sizeof(struct sadb_ident);

				if (i != j) {
					DPRINTF("actual identity length does "
					    "not match expected length in "
					    "identity extension header %d",
					    sadb_ext->sadb_ext_type);
					return (EINVAL);
				}
			}
		}
		break;
		case SADB_EXT_SENSITIVITY:
		{
			struct sadb_sens *sadb_sens = (struct sadb_sens *)p;

			if (i < sizeof(struct sadb_sens)) {
				DPRINTF("bad header length for SENSITIVITY "
				    "extension header");
				return (EINVAL);
			}

			if (i != (sadb_sens->sadb_sens_sens_len +
			    sadb_sens->sadb_sens_integ_len) *
			    sizeof(uint64_t) +
			    sizeof(struct sadb_sens)) {
				DPRINTF("bad payload length for SENSITIVITY "
				    "extension header");
				return (EINVAL);
			}
		}
		break;
		case SADB_EXT_PROPOSAL:
		{
			struct sadb_prop *sadb_prop = (struct sadb_prop *)p;

			if (i < sizeof(struct sadb_prop)) {
				DPRINTF("bad PROPOSAL header length");
				return (EINVAL);
			}

			if (sadb_prop->sadb_prop_reserved) {
				DPRINTF("reserved fieldset in PROPOSAL "
				    "extension header");
				return (EINVAL);
			}

			if ((i - sizeof(struct sadb_prop)) %
			    sizeof(struct sadb_comb)) {
				DPRINTF("bad proposal length");
				return (EINVAL);
			}

			{
				struct sadb_comb *sadb_comb =
				    (struct sadb_comb *)(p +
					sizeof(struct sadb_prop));
				int j;

				for (j = 0;
				    j < (i - sizeof(struct sadb_prop))/
				    sizeof(struct sadb_comb);
				    j++) {
					if (sadb_comb->sadb_comb_auth >
					    SADB_AALG_MAX) {
						DPRINTF("unknown "
						    "authentication algorithm "
						    "%d in PROPOSAL",
						    sadb_comb->sadb_comb_auth);
						return (EINVAL);
					}

					if (sadb_comb->sadb_comb_encrypt >
					    SADB_EALG_MAX) {
						DPRINTF("unknown encryption "
						    "algorithm %d in PROPOSAL",
						    sadb_comb->
						    sadb_comb_encrypt);
						return (EINVAL);
					}

					if (sadb_comb->sadb_comb_reserved) {
						DPRINTF("reserved field set "
						    "in COMB header");
						return (EINVAL);
					}
				}
			}
		}
		break;
		case SADB_EXT_SUPPORTED_AUTH:
		case SADB_EXT_SUPPORTED_ENCRYPT:
		case SADB_X_EXT_SUPPORTED_COMP:
		{
			struct sadb_supported *sadb_supported =
			    (struct sadb_supported *)p;
			int j;

			if (i < sizeof(struct sadb_supported)) {
				DPRINTF("bad header length for SUPPORTED "					    "extension header %d",
				    sadb_ext->sadb_ext_type);
				return (EINVAL);
			}

			if (sadb_supported->sadb_supported_reserved) {
				DPRINTF("reserved field set in SUPPORTED "
				    "extension header %d",
				    sadb_ext->sadb_ext_type);
				return (EINVAL);
			}

			{
				struct sadb_alg *sadb_alg =
				    (struct sadb_alg *)(p +
					sizeof(struct sadb_supported));
				int max_alg;

				max_alg = sadb_ext->sadb_ext_type ==
				    SADB_EXT_SUPPORTED_AUTH ?
				    SADB_AALG_MAX : SADB_EXT_SUPPORTED_ENCRYPT ?
				    SADB_EALG_MAX : SADB_X_CALG_MAX;

				for (j = 0;
				    j < sadb_supported->sadb_supported_len - 1;
				    j++) {
					if (sadb_alg->sadb_alg_id > max_alg) {
						DPRINTF("unknown algorithm %d "
						    "in SUPPORTED extension "
						    "header %d",
						    sadb_alg->sadb_alg_id,
						    sadb_ext->sadb_ext_type);
						return (EINVAL);
					}

					if (sadb_alg->sadb_alg_reserved) {
						DPRINTF("reserved field set "
						    "in supported algorithms "
						    "header inside SUPPORTED "
						    "extension header %d",
						    sadb_ext->sadb_ext_type);
						return (EINVAL);
					}

					sadb_alg++;
				}
			}
		}
		break;
		case SADB_EXT_SPIRANGE:
		{
			struct sadb_spirange *sadb_spirange =
			    (struct sadb_spirange *)p;

			if (i != sizeof(struct sadb_spirange)) {
				DPRINTF("bad header length of SPIRANGE "
				    "extension header");
				return (EINVAL);
			}

			if (sadb_spirange->sadb_spirange_min >
			    sadb_spirange->sadb_spirange_max) {
				DPRINTF("bad SPI range");
				return (EINVAL);
			}
		}
		break;
		case SADB_X_EXT_UDPENCAP:
			if (i != sizeof(struct sadb_x_udpencap)) {
				DPRINTF("bad UDPENCAP header length");
				return (EINVAL);
			}
			break;
		case SADB_X_EXT_RDOMAIN:
			if (i != sizeof(struct sadb_x_rdomain)) {
				DPRINTF("bad RDOMAIN header length");
				return (EINVAL);
			}
			break;
#if NPF > 0
		case SADB_X_EXT_TAG:
			if (i < sizeof(struct sadb_x_tag)) {
				DPRINTF("TAG extension header too small");
				return (EINVAL);
			}
			if (i > (sizeof(struct sadb_x_tag) +
			    PF_TAG_NAME_SIZE)) {
				DPRINTF("TAG extension header too long");
				return (EINVAL);
			}
			break;
		case SADB_X_EXT_TAP:
			if (i < sizeof(struct sadb_x_tap)) {
				DPRINTF("TAP extension header too small");
				return (EINVAL);
			}
			if (i > sizeof(struct sadb_x_tap)) {
				DPRINTF("TAP extension header too long");
				return (EINVAL);
			}
			break;
#endif
		case SADB_X_EXT_IFACE:
			if (i != sizeof(struct sadb_x_iface)) {
				DPRINTF("bad IFACE header length");
				return (EINVAL);
			}
			break;
		default:
			DPRINTF("unknown extension header type %d",
			    sadb_ext->sadb_ext_type);
			return (EINVAL);
		}

		headers[sadb_ext->sadb_ext_type] = p;
		p += i;
		left -= i;
	}

	if (left) {
		DPRINTF("message too long");
		return (EINVAL);
	}

	{
		uint64_t required;

		required = sadb_exts_required_in[sadb_msg->sadb_msg_type];

		if ((seen & required) != required) {
			DPRINTF("required fields missing");
			return (EINVAL);
		}
	}

	switch (((struct sadb_msg *)headers[0])->sadb_msg_type) {
	case SADB_UPDATE:
		if (((struct sadb_sa *)headers[SADB_EXT_SA])->sadb_sa_state !=
		    SADB_SASTATE_MATURE) {
			DPRINTF("updating non-mature SA prohibited");
			return (EINVAL);
		}
		break;
	case SADB_ADD:
		if (((struct sadb_sa *)headers[SADB_EXT_SA])->sadb_sa_state !=
		    SADB_SASTATE_MATURE) {
			DPRINTF("adding non-mature SA prohibited");
			return (EINVAL);
		}
		break;
	}

	return (0);
}