#include /*- * Copyright (c) 2016 Andrey V. Elsukov * 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 ``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 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MALLOC_DEFINE(M_IPSEC_INPCB, "inpcbpolicy", "inpcb-resident ipsec policy"); static void ipsec_setsockaddrs_inpcb(struct inpcb *inp, union sockaddr_union *src, union sockaddr_union *dst, u_int dir) { #ifdef INET6 if (inp->inp_vflag & INP_IPV6) { struct sockaddr_in6 *sin6; bzero(&src->sin6, sizeof(src->sin6)); bzero(&dst->sin6, sizeof(dst->sin6)); src->sin6.sin6_family = AF_INET6; src->sin6.sin6_len = sizeof(struct sockaddr_in6); dst->sin6.sin6_family = AF_INET6; dst->sin6.sin6_len = sizeof(struct sockaddr_in6); if (dir == IPSEC_DIR_OUTBOUND) sin6 = &src->sin6; else sin6 = &dst->sin6; sin6->sin6_addr = inp->in6p_laddr; sin6->sin6_port = inp->inp_lport; if (IN6_IS_SCOPE_LINKLOCAL(&inp->in6p_laddr)) { /* XXXAE: use in6p_zoneid */ sin6->sin6_addr.s6_addr16[1] = 0; sin6->sin6_scope_id = ntohs( inp->in6p_laddr.s6_addr16[1]); } if (dir == IPSEC_DIR_OUTBOUND) sin6 = &dst->sin6; else sin6 = &src->sin6; sin6->sin6_addr = inp->in6p_faddr; sin6->sin6_port = inp->inp_fport; if (IN6_IS_SCOPE_LINKLOCAL(&inp->in6p_faddr)) { /* XXXAE: use in6p_zoneid */ sin6->sin6_addr.s6_addr16[1] = 0; sin6->sin6_scope_id = ntohs( inp->in6p_faddr.s6_addr16[1]); } } #endif #ifdef INET if (inp->inp_vflag & INP_IPV4) { struct sockaddr_in *sin; bzero(&src->sin, sizeof(src->sin)); bzero(&dst->sin, sizeof(dst->sin)); src->sin.sin_family = AF_INET; src->sin.sin_len = sizeof(struct sockaddr_in); dst->sin.sin_family = AF_INET; dst->sin.sin_len = sizeof(struct sockaddr_in); if (dir == IPSEC_DIR_OUTBOUND) sin = &src->sin; else sin = &dst->sin; sin->sin_addr = inp->inp_laddr; sin->sin_port = inp->inp_lport; if (dir == IPSEC_DIR_OUTBOUND) sin = &dst->sin; else sin = &src->sin; sin->sin_addr = inp->inp_faddr; sin->sin_port = inp->inp_fport; } #endif } void ipsec_setspidx_inpcb(struct inpcb *inp, struct secpolicyindex *spidx, u_int dir) { ipsec_setsockaddrs_inpcb(inp, &spidx->src, &spidx->dst, dir); #ifdef INET6 if (inp->inp_vflag & INP_IPV6) { spidx->prefs = sizeof(struct in6_addr) << 3; spidx->prefd = sizeof(struct in6_addr) << 3; } #endif #ifdef INET if (inp->inp_vflag & INP_IPV4) { spidx->prefs = sizeof(struct in_addr) << 3; spidx->prefd = sizeof(struct in_addr) << 3; } #endif spidx->ul_proto = IPPROTO_TCP; /* XXX: currently only TCP uses this */ spidx->dir = dir; KEYDBG(IPSEC_DUMP, printf("%s: ", __func__); kdebug_secpolicyindex(spidx, NULL)); } /* Initialize PCB policy. */ int ipsec_init_pcbpolicy(struct inpcb *inp) { IPSEC_ASSERT(inp != NULL, ("null inp")); IPSEC_ASSERT(inp->inp_sp == NULL, ("inp_sp already initialized")); inp->inp_sp = malloc(sizeof(struct inpcbpolicy), M_IPSEC_INPCB, M_NOWAIT | M_ZERO); if (inp->inp_sp == NULL) return (ENOBUFS); return (0); } /* Delete PCB policy. */ int ipsec_delete_pcbpolicy(struct inpcb *inp) { if (inp->inp_sp == NULL) return (0); if (inp->inp_sp->sp_in != NULL) key_freesp(&inp->inp_sp->sp_in); if (inp->inp_sp->sp_out != NULL) key_freesp(&inp->inp_sp->sp_out); free(inp->inp_sp, M_IPSEC_INPCB); inp->inp_sp = NULL; return (0); } /* Deep-copy a policy in PCB. */ static struct secpolicy * ipsec_deepcopy_pcbpolicy(struct secpolicy *src) { struct secpolicy *dst; int i; if (src == NULL) return (NULL); IPSEC_ASSERT(src->state == IPSEC_SPSTATE_PCB, ("SP isn't PCB")); dst = key_newsp(); if (dst == NULL) return (NULL); /* spidx is not copied here */ dst->policy = src->policy; dst->state = src->state; dst->priority = src->priority; /* Do not touch the refcnt field. */ /* Copy IPsec request chain. */ for (i = 0; i < src->tcount; i++) { dst->req[i] = ipsec_newisr(); if (dst->req[i] == NULL) { key_freesp(&dst); return (NULL); } bcopy(src->req[i], dst->req[i], sizeof(struct ipsecrequest)); dst->tcount++; } KEYDBG(IPSEC_DUMP, printf("%s: copied SP(%p) -> SP(%p)\n", __func__, src, dst); kdebug_secpolicy(dst)); return (dst); } /* * Copy IPsec policy from old INPCB into new. * It is expected that new INPCB has not configured policies. */ int ipsec_copy_pcbpolicy(struct inpcb *old, struct inpcb *new) { struct secpolicy *sp; /* * old->inp_sp can be NULL if PCB was created when an IPsec * support was unavailable. This is not an error, we don't have * policies in this PCB, so nothing to copy. */ if (old->inp_sp == NULL) return (0); IPSEC_ASSERT(new->inp_sp != NULL, ("new inp_sp is NULL")); IPSEC_ASSERT((new->inp_sp->flags & ( INP_INBOUND_POLICY | INP_OUTBOUND_POLICY)) == 0, ("new PCB already has configured policies")); INP_WLOCK_ASSERT(new); INP_LOCK_ASSERT(old); if (old->inp_sp->flags & INP_INBOUND_POLICY) { sp = ipsec_deepcopy_pcbpolicy(old->inp_sp->sp_in); if (sp == NULL) return (ENOBUFS); ipsec_setspidx_inpcb(new, &sp->spidx, IPSEC_DIR_INBOUND); if (new->inp_sp->sp_in != NULL) key_freesp(&new->inp_sp->sp_in); new->inp_sp->sp_in = sp; new->inp_sp->flags |= INP_INBOUND_POLICY; } if (old->inp_sp->flags & INP_OUTBOUND_POLICY) { sp = ipsec_deepcopy_pcbpolicy(old->inp_sp->sp_out); if (sp == NULL) return (ENOBUFS); ipsec_setspidx_inpcb(new, &sp->spidx, IPSEC_DIR_OUTBOUND); if (new->inp_sp->sp_out != NULL) key_freesp(&new->inp_sp->sp_out); new->inp_sp->sp_out = sp; new->inp_sp->flags |= INP_OUTBOUND_POLICY; } return (0); } static int ipsec_set_pcbpolicy(struct inpcb *inp, struct ucred *cred, void *request, size_t len) { struct sadb_x_policy *xpl; struct secpolicy **spp, *newsp; int error, flags; xpl = (struct sadb_x_policy *)request; /* Select direction. */ switch (xpl->sadb_x_policy_dir) { case IPSEC_DIR_INBOUND: case IPSEC_DIR_OUTBOUND: break; default: ipseclog((LOG_ERR, "%s: invalid direction=%u\n", __func__, xpl->sadb_x_policy_dir)); return (EINVAL); } /* * Privileged sockets are allowed to set own security policy * and configure IPsec bypass. Unprivileged sockets only can * have ENTRUST policy. */ switch (xpl->sadb_x_policy_type) { case IPSEC_POLICY_IPSEC: case IPSEC_POLICY_BYPASS: if (cred != NULL && priv_check_cred(cred, PRIV_NETINET_IPSEC) != 0) return (EACCES); /* Allocate new SP entry. */ newsp = key_msg2sp(xpl, len, &error); if (newsp == NULL) return (error); newsp->state = IPSEC_SPSTATE_PCB; newsp->spidx.ul_proto = IPSEC_ULPROTO_ANY; #ifdef INET if (inp->inp_vflag & INP_IPV4) { newsp->spidx.src.sin.sin_family = newsp->spidx.dst.sin.sin_family = AF_INET; newsp->spidx.src.sin.sin_len = newsp->spidx.dst.sin.sin_len = sizeof(struct sockaddr_in); } #endif #ifdef INET6 if (inp->inp_vflag & INP_IPV6) { newsp->spidx.src.sin6.sin6_family = newsp->spidx.dst.sin6.sin6_family = AF_INET6; newsp->spidx.src.sin6.sin6_len = newsp->spidx.dst.sin6.sin6_len = sizeof(struct sockaddr_in6); } #endif break; case IPSEC_POLICY_ENTRUST: /* We just use NULL pointer for ENTRUST policy */ newsp = NULL; break; default: /* Other security policy types aren't allowed for PCB */ return (EINVAL); } INP_WLOCK(inp); if (xpl->sadb_x_policy_dir == IPSEC_DIR_INBOUND) { spp = &inp->inp_sp->sp_in; flags = INP_INBOUND_POLICY; } else { spp = &inp->inp_sp->sp_out; flags = INP_OUTBOUND_POLICY; } /* Clear old SP and set new SP. */ if (*spp != NULL) key_freesp(spp); *spp = newsp; KEYDBG(IPSEC_DUMP, printf("%s: new SP(%p)\n", __func__, newsp)); if (newsp == NULL) inp->inp_sp->flags &= ~flags; else { inp->inp_sp->flags |= flags; KEYDBG(IPSEC_DUMP, kdebug_secpolicy(newsp)); } INP_WUNLOCK(inp); return (0); } static int ipsec_get_pcbpolicy(struct inpcb *inp, void *request, size_t *len) { struct sadb_x_policy *xpl; struct secpolicy *sp; int error, flags; xpl = (struct sadb_x_policy *)request; INP_RLOCK(inp); flags = inp->inp_sp->flags; /* Select direction. */ switch (xpl->sadb_x_policy_dir) { case IPSEC_DIR_INBOUND: sp = inp->inp_sp->sp_in; flags &= INP_INBOUND_POLICY; break; case IPSEC_DIR_OUTBOUND: sp = inp->inp_sp->sp_out; flags &= INP_OUTBOUND_POLICY; break; default: INP_RUNLOCK(inp); ipseclog((LOG_ERR, "%s: invalid direction=%u\n", __func__, xpl->sadb_x_policy_dir)); return (EINVAL); } if (flags == 0) { /* Return ENTRUST policy */ INP_RUNLOCK(inp); xpl->sadb_x_policy_exttype = SADB_X_EXT_POLICY; xpl->sadb_x_policy_type = IPSEC_POLICY_ENTRUST; xpl->sadb_x_policy_id = 0; xpl->sadb_x_policy_priority = 0; xpl->sadb_x_policy_len = PFKEY_UNIT64(sizeof(*xpl)); *len = sizeof(*xpl); return (0); } IPSEC_ASSERT(sp != NULL, ("sp is NULL, but flags is 0x%04x", inp->inp_sp->flags)); key_addref(sp); INP_RUNLOCK(inp); error = key_sp2msg(sp, request, len); key_freesp(&sp); if (error == EINVAL) return (error); /* * We return "success", but user should check *len. * *len will be set to size of valid data and * sadb_x_policy_len will contain needed size. */ return (0); } /* Handle socket option control request for PCB */ static int ipsec_control_pcbpolicy(struct inpcb *inp, struct sockopt *sopt) { void *optdata; size_t optlen; int error; if (inp->inp_sp == NULL) return (ENOPROTOOPT); /* Limit maximum request size to PAGE_SIZE */ optlen = sopt->sopt_valsize; if (optlen < sizeof(struct sadb_x_policy) || optlen > PAGE_SIZE) return (EINVAL); optdata = malloc(optlen, M_TEMP, sopt->sopt_td ? M_WAITOK: M_NOWAIT); if (optdata == NULL) return (ENOBUFS); /* * We need a hint from the user, what policy is requested - input * or output? User should specify it in the buffer, even for * setsockopt(). */ error = sooptcopyin(sopt, optdata, optlen, optlen); if (error == 0) { if (sopt->sopt_dir == SOPT_SET) error = ipsec_set_pcbpolicy(inp, sopt->sopt_td ? sopt->sopt_td->td_ucred: NULL, optdata, optlen); else { error = ipsec_get_pcbpolicy(inp, optdata, &optlen); if (error == 0) error = sooptcopyout(sopt, optdata, optlen); } } free(optdata, M_TEMP); return (error); } #ifdef INET /* * IPSEC_PCBCTL() method implementation for IPv4. */ int ipsec4_pcbctl(struct inpcb *inp, struct sockopt *sopt) { if (sopt->sopt_name != IP_IPSEC_POLICY) return (ENOPROTOOPT); return (ipsec_control_pcbpolicy(inp, sopt)); } #endif #ifdef INET6 /* * IPSEC_PCBCTL() method implementation for IPv6. */ int ipsec6_pcbctl(struct inpcb *inp, struct sockopt *sopt) { if (sopt->sopt_name != IPV6_IPSEC_POLICY) return (ENOPROTOOPT); return (ipsec_control_pcbpolicy(inp, sopt)); } #endif