Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/net/xfrm/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 102 kB image not shown  

Quelle  xfrm_user.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-only
/* xfrm_user.c: User interface to configure xfrm engine.
 *
 * Copyright (C) 2002 David S. Miller (davem@redhat.com)
 *
 * Changes:
 * Mitsuru KANDA @USAGI
 *  Kazunori MIYAZAWA @USAGI
 *  Kunihiro Ishiguro <kunihiro@ipinfusion.com>
 *  IPv6 support
 *
 */


#include <linux/compat.h>
#include <linux/crypto.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/socket.h>
#include <linux/string.h>
#include <linux/net.h>
#include <linux/skbuff.h>
#include <linux/pfkeyv2.h>
#include <linux/ipsec.h>
#include <linux/init.h>
#include <linux/security.h>
#include <net/sock.h>
#include <net/xfrm.h>
#include <net/netlink.h>
#include <net/ah.h>
#include <linux/uaccess.h>
#if IS_ENABLED(CONFIG_IPV6)
#include <linux/in6.h>
#endif
#include <linux/unaligned.h>

static int verify_one_alg(struct nlattr **attrs, enum xfrm_attr_type_t type,
     struct netlink_ext_ack *extack)
{
 struct nlattr *rt = attrs[type];
 struct xfrm_algo *algp;

 if (!rt)
  return 0;

 algp = nla_data(rt);
 if (nla_len(rt) < (int)xfrm_alg_len(algp)) {
  NL_SET_ERR_MSG(extack, "Invalid AUTH/CRYPT/COMP attribute length");
  return -EINVAL;
 }

 switch (type) {
 case XFRMA_ALG_AUTH:
 case XFRMA_ALG_CRYPT:
 case XFRMA_ALG_COMP:
  break;

 default:
  NL_SET_ERR_MSG(extack, "Invalid algorithm attribute type");
  return -EINVAL;
 }

 algp->alg_name[sizeof(algp->alg_name) - 1] = '\0';
 return 0;
}

static int verify_auth_trunc(struct nlattr **attrs,
        struct netlink_ext_ack *extack)
{
 struct nlattr *rt = attrs[XFRMA_ALG_AUTH_TRUNC];
 struct xfrm_algo_auth *algp;

 if (!rt)
  return 0;

 algp = nla_data(rt);
 if (nla_len(rt) < (int)xfrm_alg_auth_len(algp)) {
  NL_SET_ERR_MSG(extack, "Invalid AUTH_TRUNC attribute length");
  return -EINVAL;
 }

 algp->alg_name[sizeof(algp->alg_name) - 1] = '\0';
 return 0;
}

static int verify_aead(struct nlattr **attrs, struct netlink_ext_ack *extack)
{
 struct nlattr *rt = attrs[XFRMA_ALG_AEAD];
 struct xfrm_algo_aead *algp;

 if (!rt)
  return 0;

 algp = nla_data(rt);
 if (nla_len(rt) < (int)aead_len(algp)) {
  NL_SET_ERR_MSG(extack, "Invalid AEAD attribute length");
  return -EINVAL;
 }

 algp->alg_name[sizeof(algp->alg_name) - 1] = '\0';
 return 0;
}

static void verify_one_addr(struct nlattr **attrs, enum xfrm_attr_type_t type,
      xfrm_address_t **addrp)
{
 struct nlattr *rt = attrs[type];

 if (rt && addrp)
  *addrp = nla_data(rt);
}

static inline int verify_sec_ctx_len(struct nlattr **attrs, struct netlink_ext_ack *extack)
{
 struct nlattr *rt = attrs[XFRMA_SEC_CTX];
 struct xfrm_user_sec_ctx *uctx;

 if (!rt)
  return 0;

 uctx = nla_data(rt);
 if (uctx->len > nla_len(rt) ||
     uctx->len != (sizeof(struct xfrm_user_sec_ctx) + uctx->ctx_len)) {
  NL_SET_ERR_MSG(extack, "Invalid security context length");
  return -EINVAL;
 }

 return 0;
}

static inline int verify_replay(struct xfrm_usersa_info *p,
    struct nlattr **attrs, u8 sa_dir,
    struct netlink_ext_ack *extack)
{
 struct nlattr *rt = attrs[XFRMA_REPLAY_ESN_VAL];
 struct xfrm_replay_state_esn *rs;

 if (!rt) {
  if (p->flags & XFRM_STATE_ESN) {
   NL_SET_ERR_MSG(extack, "Missing required attribute for ESN");
   return -EINVAL;
  }
  return 0;
 }

 rs = nla_data(rt);

 if (rs->bmp_len > XFRMA_REPLAY_ESN_MAX / sizeof(rs->bmp[0]) / 8) {
  NL_SET_ERR_MSG(extack, "ESN bitmap length must be <= 128");
  return -EINVAL;
 }

 if (nla_len(rt) < (int)xfrm_replay_state_esn_len(rs) &&
     nla_len(rt) != sizeof(*rs)) {
  NL_SET_ERR_MSG(extack, "ESN attribute is too short to fit the full bitmap length");
  return -EINVAL;
 }

 /* As only ESP and AH support ESN feature. */
 if ((p->id.proto != IPPROTO_ESP) && (p->id.proto != IPPROTO_AH)) {
  NL_SET_ERR_MSG(extack, "ESN only supported for ESP and AH");
  return -EINVAL;
 }

 if (p->replay_window != 0) {
  NL_SET_ERR_MSG(extack, "ESN not compatible with legacy replay_window");
  return -EINVAL;
 }

 if (sa_dir == XFRM_SA_DIR_OUT)  {
  if (rs->replay_window) {
   NL_SET_ERR_MSG(extack, "Replay window should be 0 for output SA");
   return -EINVAL;
  }
  if (rs->seq || rs->seq_hi) {
   NL_SET_ERR_MSG(extack,
           "Replay seq and seq_hi should be 0 for output SA");
   return -EINVAL;
  }

  if (!(p->flags & XFRM_STATE_ESN)) {
   if (rs->oseq_hi) {
    NL_SET_ERR_MSG(
     extack,
     "Replay oseq_hi should be 0 in non-ESN mode for output SA");
    return -EINVAL;
   }
   if (rs->oseq == U32_MAX) {
    NL_SET_ERR_MSG(
     extack,
     "Replay oseq should be less than 0xFFFFFFFF in non-ESN mode for output SA");
    return -EINVAL;
   }
  } else {
   if (rs->oseq == U32_MAX && rs->oseq_hi == U32_MAX) {
    NL_SET_ERR_MSG(
     extack,
     "Replay oseq and oseq_hi should be less than 0xFFFFFFFF for output SA");
    return -EINVAL;
   }
  }
  if (rs->bmp_len) {
   NL_SET_ERR_MSG(extack, "Replay bmp_len should 0 for output SA");
   return -EINVAL;
  }
 }

 if (sa_dir == XFRM_SA_DIR_IN)  {
  if (rs->oseq || rs->oseq_hi) {
   NL_SET_ERR_MSG(extack,
           "Replay oseq and oseq_hi should be 0 for input SA");
   return -EINVAL;
  }
  if (!(p->flags & XFRM_STATE_ESN)) {
   if (rs->seq_hi) {
    NL_SET_ERR_MSG(
     extack,
     "Replay seq_hi should be 0 in non-ESN mode for input SA");
    return -EINVAL;
   }

   if (rs->seq == U32_MAX) {
    NL_SET_ERR_MSG(
     extack,
     "Replay seq should be less than 0xFFFFFFFF in non-ESN mode for input SA");
    return -EINVAL;
   }
  } else {
   if (rs->seq == U32_MAX && rs->seq_hi == U32_MAX) {
    NL_SET_ERR_MSG(
     extack,
     "Replay seq and seq_hi should be less than 0xFFFFFFFF for input SA");
    return -EINVAL;
   }
  }
 }

 return 0;
}

static int verify_newsa_info(struct xfrm_usersa_info *p,
        struct nlattr **attrs,
        struct netlink_ext_ack *extack)
{
 int err;
 u8 sa_dir = nla_get_u8_default(attrs[XFRMA_SA_DIR], 0);
 u16 family = p->sel.family;

 err = -EINVAL;
 switch (p->family) {
 case AF_INET:
  break;

 case AF_INET6:
#if IS_ENABLED(CONFIG_IPV6)
  break;
#else
  err = -EAFNOSUPPORT;
  NL_SET_ERR_MSG(extack, "IPv6 support disabled");
  goto out;
#endif

 default:
  NL_SET_ERR_MSG(extack, "Invalid address family");
  goto out;
 }

 if (!family && !(p->flags & XFRM_STATE_AF_UNSPEC))
  family = p->family;

 switch (family) {
 case AF_UNSPEC:
  break;

 case AF_INET:
  if (p->sel.prefixlen_d > 32 || p->sel.prefixlen_s > 32) {
   NL_SET_ERR_MSG(extack, "Invalid prefix length in selector (must be <= 32 for IPv4)");
   goto out;
  }

  break;

 case AF_INET6:
#if IS_ENABLED(CONFIG_IPV6)
  if (p->sel.prefixlen_d > 128 || p->sel.prefixlen_s > 128) {
   NL_SET_ERR_MSG(extack, "Invalid prefix length in selector (must be <= 128 for IPv6)");
   goto out;
  }

  break;
#else
  NL_SET_ERR_MSG(extack, "IPv6 support disabled");
  err = -EAFNOSUPPORT;
  goto out;
#endif

 default:
  NL_SET_ERR_MSG(extack, "Invalid address family in selector");
  goto out;
 }

 err = -EINVAL;
 switch (p->id.proto) {
 case IPPROTO_AH:
  if (!attrs[XFRMA_ALG_AUTH] &&
      !attrs[XFRMA_ALG_AUTH_TRUNC]) {
   NL_SET_ERR_MSG(extack, "Missing required attribute for AH: AUTH_TRUNC or AUTH");
   goto out;
  }

  if (attrs[XFRMA_ALG_AEAD] ||
      attrs[XFRMA_ALG_CRYPT] ||
      attrs[XFRMA_ALG_COMP] ||
      attrs[XFRMA_TFCPAD]) {
   NL_SET_ERR_MSG(extack, "Invalid attributes for AH: AEAD, CRYPT, COMP, TFCPAD");
   goto out;
  }
  break;

 case IPPROTO_ESP:
  if (attrs[XFRMA_ALG_COMP]) {
   NL_SET_ERR_MSG(extack, "Invalid attribute for ESP: COMP");
   goto out;
  }

  if (!attrs[XFRMA_ALG_AUTH] &&
      !attrs[XFRMA_ALG_AUTH_TRUNC] &&
      !attrs[XFRMA_ALG_CRYPT] &&
      !attrs[XFRMA_ALG_AEAD]) {
   NL_SET_ERR_MSG(extack, "Missing required attribute for ESP: at least one of AUTH, AUTH_TRUNC, CRYPT, AEAD");
   goto out;
  }

  if ((attrs[XFRMA_ALG_AUTH] ||
       attrs[XFRMA_ALG_AUTH_TRUNC] ||
       attrs[XFRMA_ALG_CRYPT]) &&
      attrs[XFRMA_ALG_AEAD]) {
   NL_SET_ERR_MSG(extack, "Invalid attribute combination for ESP: AEAD can't be used with AUTH, AUTH_TRUNC, CRYPT");
   goto out;
  }

  if (attrs[XFRMA_TFCPAD] &&
      p->mode != XFRM_MODE_TUNNEL) {
   NL_SET_ERR_MSG(extack, "TFC padding can only be used in tunnel mode");
   goto out;
  }
  if ((attrs[XFRMA_IPTFS_DROP_TIME] ||
       attrs[XFRMA_IPTFS_REORDER_WINDOW] ||
       attrs[XFRMA_IPTFS_DONT_FRAG] ||
       attrs[XFRMA_IPTFS_INIT_DELAY] ||
       attrs[XFRMA_IPTFS_MAX_QSIZE] ||
       attrs[XFRMA_IPTFS_PKT_SIZE]) &&
      p->mode != XFRM_MODE_IPTFS) {
   NL_SET_ERR_MSG(extack, "IP-TFS options can only be used in IP-TFS mode");
   goto out;
  }
  break;

 case IPPROTO_COMP:
  if (!attrs[XFRMA_ALG_COMP]) {
   NL_SET_ERR_MSG(extack, "Missing required attribute for COMP: COMP");
   goto out;
  }

  if (attrs[XFRMA_ALG_AEAD] ||
      attrs[XFRMA_ALG_AUTH] ||
      attrs[XFRMA_ALG_AUTH_TRUNC] ||
      attrs[XFRMA_ALG_CRYPT] ||
      attrs[XFRMA_TFCPAD]) {
   NL_SET_ERR_MSG(extack, "Invalid attributes for COMP: AEAD, AUTH, AUTH_TRUNC, CRYPT, TFCPAD");
   goto out;
  }

  if (ntohl(p->id.spi) >= 0x10000) {
   NL_SET_ERR_MSG(extack, "SPI is too large for COMP (must be < 0x10000)");
   goto out;
  }
  break;

#if IS_ENABLED(CONFIG_IPV6)
 case IPPROTO_DSTOPTS:
 case IPPROTO_ROUTING:
  if (attrs[XFRMA_ALG_COMP] ||
      attrs[XFRMA_ALG_AUTH] ||
      attrs[XFRMA_ALG_AUTH_TRUNC] ||
      attrs[XFRMA_ALG_AEAD] ||
      attrs[XFRMA_ALG_CRYPT] ||
      attrs[XFRMA_ENCAP]  ||
      attrs[XFRMA_SEC_CTX] ||
      attrs[XFRMA_TFCPAD]) {
   NL_SET_ERR_MSG(extack, "Invalid attributes for DSTOPTS/ROUTING");
   goto out;
  }

  if (!attrs[XFRMA_COADDR]) {
   NL_SET_ERR_MSG(extack, "Missing required COADDR attribute for DSTOPTS/ROUTING");
   goto out;
  }
  break;
#endif

 default:
  NL_SET_ERR_MSG(extack, "Unsupported protocol");
  goto out;
 }

 if ((err = verify_aead(attrs, extack)))
  goto out;
 if ((err = verify_auth_trunc(attrs, extack)))
  goto out;
 if ((err = verify_one_alg(attrs, XFRMA_ALG_AUTH, extack)))
  goto out;
 if ((err = verify_one_alg(attrs, XFRMA_ALG_CRYPT, extack)))
  goto out;
 if ((err = verify_one_alg(attrs, XFRMA_ALG_COMP, extack)))
  goto out;
 if ((err = verify_sec_ctx_len(attrs, extack)))
  goto out;
 if ((err = verify_replay(p, attrs, sa_dir, extack)))
  goto out;

 err = -EINVAL;
 switch (p->mode) {
 case XFRM_MODE_TRANSPORT:
 case XFRM_MODE_TUNNEL:
 case XFRM_MODE_ROUTEOPTIMIZATION:
 case XFRM_MODE_BEET:
  break;
 case XFRM_MODE_IPTFS:
  if (p->id.proto != IPPROTO_ESP) {
   NL_SET_ERR_MSG(extack, "IP-TFS mode only supported with ESP");
   goto out;
  }
  if (sa_dir == 0) {
   NL_SET_ERR_MSG(extack, "IP-TFS mode requires in or out direction attribute");
   goto out;
  }
  break;

 default:
  NL_SET_ERR_MSG(extack, "Unsupported mode");
  goto out;
 }

 err = 0;

 if (attrs[XFRMA_MTIMER_THRESH]) {
  if (!attrs[XFRMA_ENCAP]) {
   NL_SET_ERR_MSG(extack, "MTIMER_THRESH attribute can only be set on ENCAP states");
   err = -EINVAL;
   goto out;
  }

  if (sa_dir == XFRM_SA_DIR_OUT) {
   NL_SET_ERR_MSG(extack,
           "MTIMER_THRESH attribute should not be set on output SA");
   err = -EINVAL;
   goto out;
  }
 }

 if (sa_dir == XFRM_SA_DIR_OUT) {
  if (p->flags & XFRM_STATE_DECAP_DSCP) {
   NL_SET_ERR_MSG(extack, "Flag DECAP_DSCP should not be set for output SA");
   err = -EINVAL;
   goto out;
  }

  if (p->flags & XFRM_STATE_ICMP) {
   NL_SET_ERR_MSG(extack, "Flag ICMP should not be set for output SA");
   err = -EINVAL;
   goto out;
  }

  if (p->flags & XFRM_STATE_WILDRECV) {
   NL_SET_ERR_MSG(extack, "Flag WILDRECV should not be set for output SA");
   err = -EINVAL;
   goto out;
  }

  if (p->replay_window) {
   NL_SET_ERR_MSG(extack, "Replay window should be 0 for output SA");
   err = -EINVAL;
   goto out;
  }

  if (attrs[XFRMA_IPTFS_DROP_TIME]) {
   NL_SET_ERR_MSG(extack, "IP-TFS drop time should not be set for output SA");
   err = -EINVAL;
   goto out;
  }

  if (attrs[XFRMA_IPTFS_REORDER_WINDOW]) {
   NL_SET_ERR_MSG(extack, "IP-TFS reorder window should not be set for output SA");
   err = -EINVAL;
   goto out;
  }

  if (attrs[XFRMA_REPLAY_VAL]) {
   struct xfrm_replay_state *replay;

   replay = nla_data(attrs[XFRMA_REPLAY_VAL]);

   if (replay->seq || replay->bitmap) {
    NL_SET_ERR_MSG(extack,
            "Replay seq and bitmap should be 0 for output SA");
    err = -EINVAL;
    goto out;
   }
  }
 }

 if (sa_dir == XFRM_SA_DIR_IN) {
  if (p->flags & XFRM_STATE_NOPMTUDISC) {
   NL_SET_ERR_MSG(extack, "Flag NOPMTUDISC should not be set for input SA");
   err = -EINVAL;
   goto out;
  }

  if (attrs[XFRMA_SA_EXTRA_FLAGS]) {
   u32 xflags = nla_get_u32(attrs[XFRMA_SA_EXTRA_FLAGS]);

   if (xflags & XFRM_SA_XFLAG_DONT_ENCAP_DSCP) {
    NL_SET_ERR_MSG(extack, "Flag DONT_ENCAP_DSCP should not be set for input SA");
    err = -EINVAL;
    goto out;
   }

   if (xflags & XFRM_SA_XFLAG_OSEQ_MAY_WRAP) {
    NL_SET_ERR_MSG(extack, "Flag OSEQ_MAY_WRAP should not be set for input SA");
    err = -EINVAL;
    goto out;
   }

  }

  if (attrs[XFRMA_IPTFS_DONT_FRAG]) {
   NL_SET_ERR_MSG(extack, "IP-TFS don't fragment should not be set for input SA");
   err = -EINVAL;
   goto out;
  }

  if (attrs[XFRMA_IPTFS_INIT_DELAY]) {
   NL_SET_ERR_MSG(extack, "IP-TFS initial delay should not be set for input SA");
   err = -EINVAL;
   goto out;
  }

  if (attrs[XFRMA_IPTFS_MAX_QSIZE]) {
   NL_SET_ERR_MSG(extack, "IP-TFS max queue size should not be set for input SA");
   err = -EINVAL;
   goto out;
  }

  if (attrs[XFRMA_IPTFS_PKT_SIZE]) {
   NL_SET_ERR_MSG(extack, "IP-TFS packet size should not be set for input SA");
   err = -EINVAL;
   goto out;
  }
 }

 if (!sa_dir && attrs[XFRMA_SA_PCPU]) {
  NL_SET_ERR_MSG(extack, "SA_PCPU only supported with SA_DIR");
  err = -EINVAL;
  goto out;
 }

out:
 return err;
}

static int attach_one_algo(struct xfrm_algo **algpp, u8 *props,
      struct xfrm_algo_desc *(*get_byname)(const char *, int),
      struct nlattr *rta, struct netlink_ext_ack *extack)
{
 struct xfrm_algo *p, *ualg;
 struct xfrm_algo_desc *algo;

 if (!rta)
  return 0;

 ualg = nla_data(rta);

 algo = get_byname(ualg->alg_name, 1);
 if (!algo) {
  NL_SET_ERR_MSG(extack, "Requested COMP algorithm not found");
  return -ENOSYS;
 }
 *props = algo->desc.sadb_alg_id;

 p = kmemdup(ualg, xfrm_alg_len(ualg), GFP_KERNEL);
 if (!p)
  return -ENOMEM;

 strcpy(p->alg_name, algo->name);
 *algpp = p;
 return 0;
}

static int attach_crypt(struct xfrm_state *x, struct nlattr *rta,
   struct netlink_ext_ack *extack)
{
 struct xfrm_algo *p, *ualg;
 struct xfrm_algo_desc *algo;

 if (!rta)
  return 0;

 ualg = nla_data(rta);

 algo = xfrm_ealg_get_byname(ualg->alg_name, 1);
 if (!algo) {
  NL_SET_ERR_MSG(extack, "Requested CRYPT algorithm not found");
  return -ENOSYS;
 }
 x->props.ealgo = algo->desc.sadb_alg_id;

 p = kmemdup(ualg, xfrm_alg_len(ualg), GFP_KERNEL);
 if (!p)
  return -ENOMEM;

 strcpy(p->alg_name, algo->name);
 x->ealg = p;
 x->geniv = algo->uinfo.encr.geniv;
 return 0;
}

static int attach_auth(struct xfrm_algo_auth **algpp, u8 *props,
         struct nlattr *rta, struct netlink_ext_ack *extack)
{
 struct xfrm_algo *ualg;
 struct xfrm_algo_auth *p;
 struct xfrm_algo_desc *algo;

 if (!rta)
  return 0;

 ualg = nla_data(rta);

 algo = xfrm_aalg_get_byname(ualg->alg_name, 1);
 if (!algo) {
  NL_SET_ERR_MSG(extack, "Requested AUTH algorithm not found");
  return -ENOSYS;
 }
 *props = algo->desc.sadb_alg_id;

 p = kmalloc(sizeof(*p) + (ualg->alg_key_len + 7) / 8, GFP_KERNEL);
 if (!p)
  return -ENOMEM;

 strcpy(p->alg_name, algo->name);
 p->alg_key_len = ualg->alg_key_len;
 p->alg_trunc_len = algo->uinfo.auth.icv_truncbits;
 memcpy(p->alg_key, ualg->alg_key, (ualg->alg_key_len + 7) / 8);

 *algpp = p;
 return 0;
}

static int attach_auth_trunc(struct xfrm_algo_auth **algpp, u8 *props,
        struct nlattr *rta, struct netlink_ext_ack *extack)
{
 struct xfrm_algo_auth *p, *ualg;
 struct xfrm_algo_desc *algo;

 if (!rta)
  return 0;

 ualg = nla_data(rta);

 algo = xfrm_aalg_get_byname(ualg->alg_name, 1);
 if (!algo) {
  NL_SET_ERR_MSG(extack, "Requested AUTH_TRUNC algorithm not found");
  return -ENOSYS;
 }
 if (ualg->alg_trunc_len > algo->uinfo.auth.icv_fullbits) {
  NL_SET_ERR_MSG(extack, "Invalid length requested for truncated ICV");
  return -EINVAL;
 }
 *props = algo->desc.sadb_alg_id;

 p = kmemdup(ualg, xfrm_alg_auth_len(ualg), GFP_KERNEL);
 if (!p)
  return -ENOMEM;

 strcpy(p->alg_name, algo->name);
 if (!p->alg_trunc_len)
  p->alg_trunc_len = algo->uinfo.auth.icv_truncbits;

 *algpp = p;
 return 0;
}

static int attach_aead(struct xfrm_state *x, struct nlattr *rta,
         struct netlink_ext_ack *extack)
{
 struct xfrm_algo_aead *p, *ualg;
 struct xfrm_algo_desc *algo;

 if (!rta)
  return 0;

 ualg = nla_data(rta);

 algo = xfrm_aead_get_byname(ualg->alg_name, ualg->alg_icv_len, 1);
 if (!algo) {
  NL_SET_ERR_MSG(extack, "Requested AEAD algorithm not found");
  return -ENOSYS;
 }
 x->props.ealgo = algo->desc.sadb_alg_id;

 p = kmemdup(ualg, aead_len(ualg), GFP_KERNEL);
 if (!p)
  return -ENOMEM;

 strcpy(p->alg_name, algo->name);
 x->aead = p;
 x->geniv = algo->uinfo.aead.geniv;
 return 0;
}

static inline int xfrm_replay_verify_len(struct xfrm_replay_state_esn *replay_esn,
      struct nlattr *rp,
      struct netlink_ext_ack *extack)
{
 struct xfrm_replay_state_esn *up;
 unsigned int ulen;

 if (!replay_esn || !rp)
  return 0;

 up = nla_data(rp);
 ulen = xfrm_replay_state_esn_len(up);

 /* Check the overall length and the internal bitmap length to avoid
 * potential overflow. */

 if (nla_len(rp) < (int)ulen) {
  NL_SET_ERR_MSG(extack, "ESN attribute is too short");
  return -EINVAL;
 }

 if (xfrm_replay_state_esn_len(replay_esn) != ulen) {
  NL_SET_ERR_MSG(extack, "New ESN size doesn't match the existing SA's ESN size");
  return -EINVAL;
 }

 if (replay_esn->bmp_len != up->bmp_len) {
  NL_SET_ERR_MSG(extack, "New ESN bitmap size doesn't match the existing SA's ESN bitmap");
  return -EINVAL;
 }

 if (up->replay_window > up->bmp_len * sizeof(__u32) * 8) {
  NL_SET_ERR_MSG(extack, "ESN replay window is longer than the bitmap");
  return -EINVAL;
 }

 return 0;
}

static int xfrm_alloc_replay_state_esn(struct xfrm_replay_state_esn **replay_esn,
           struct xfrm_replay_state_esn **preplay_esn,
           struct nlattr *rta)
{
 struct xfrm_replay_state_esn *p, *pp, *up;
 unsigned int klen, ulen;

 if (!rta)
  return 0;

 up = nla_data(rta);
 klen = xfrm_replay_state_esn_len(up);
 ulen = nla_len(rta) >= (int)klen ? klen : sizeof(*up);

 p = kzalloc(klen, GFP_KERNEL);
 if (!p)
  return -ENOMEM;

 pp = kzalloc(klen, GFP_KERNEL);
 if (!pp) {
  kfree(p);
  return -ENOMEM;
 }

 memcpy(p, up, ulen);
 memcpy(pp, up, ulen);

 *replay_esn = p;
 *preplay_esn = pp;

 return 0;
}

static inline unsigned int xfrm_user_sec_ctx_size(struct xfrm_sec_ctx *xfrm_ctx)
{
 unsigned int len = 0;

 if (xfrm_ctx) {
  len += sizeof(struct xfrm_user_sec_ctx);
  len += xfrm_ctx->ctx_len;
 }
 return len;
}

static void copy_from_user_state(struct xfrm_state *x, struct xfrm_usersa_info *p)
{
 memcpy(&x->id, &p->id, sizeof(x->id));
 memcpy(&x->sel, &p->sel, sizeof(x->sel));
 memcpy(&x->lft, &p->lft, sizeof(x->lft));
 x->props.mode = p->mode;
 x->props.replay_window = min_t(unsigned int, p->replay_window,
     sizeof(x->replay.bitmap) * 8);
 x->props.reqid = p->reqid;
 x->props.family = p->family;
 memcpy(&x->props.saddr, &p->saddr, sizeof(x->props.saddr));
 x->props.flags = p->flags;

 if (!x->sel.family && !(p->flags & XFRM_STATE_AF_UNSPEC))
  x->sel.family = p->family;
}

/*
 * someday when pfkey also has support, we could have the code
 * somehow made shareable and move it to xfrm_state.c - JHS
 *
*/

static void xfrm_update_ae_params(struct xfrm_state *x, struct nlattr **attrs,
      int update_esn)
{
 struct nlattr *rp = attrs[XFRMA_REPLAY_VAL];
 struct nlattr *re = update_esn ? attrs[XFRMA_REPLAY_ESN_VAL] : NULL;
 struct nlattr *lt = attrs[XFRMA_LTIME_VAL];
 struct nlattr *et = attrs[XFRMA_ETIMER_THRESH];
 struct nlattr *rt = attrs[XFRMA_REPLAY_THRESH];
 struct nlattr *mt = attrs[XFRMA_MTIMER_THRESH];

 if (re && x->replay_esn && x->preplay_esn) {
  struct xfrm_replay_state_esn *replay_esn;
  replay_esn = nla_data(re);
  memcpy(x->replay_esn, replay_esn,
         xfrm_replay_state_esn_len(replay_esn));
  memcpy(x->preplay_esn, replay_esn,
         xfrm_replay_state_esn_len(replay_esn));
 }

 if (rp) {
  struct xfrm_replay_state *replay;
  replay = nla_data(rp);
  memcpy(&x->replay, replay, sizeof(*replay));
  memcpy(&x->preplay, replay, sizeof(*replay));
 }

 if (lt) {
  struct xfrm_lifetime_cur *ltime;
  ltime = nla_data(lt);
  x->curlft.bytes = ltime->bytes;
  x->curlft.packets = ltime->packets;
  x->curlft.add_time = ltime->add_time;
  x->curlft.use_time = ltime->use_time;
 }

 if (et)
  x->replay_maxage = nla_get_u32(et);

 if (rt)
  x->replay_maxdiff = nla_get_u32(rt);

 if (mt)
  x->mapping_maxage = nla_get_u32(mt);
}

static void xfrm_smark_init(struct nlattr **attrs, struct xfrm_mark *m)
{
 if (attrs[XFRMA_SET_MARK]) {
  m->v = nla_get_u32(attrs[XFRMA_SET_MARK]);
  m->m = nla_get_u32_default(attrs[XFRMA_SET_MARK_MASK],
        0xffffffff);
 } else {
  m->v = m->m = 0;
 }
}

static struct xfrm_state *xfrm_state_construct(struct net *net,
            struct xfrm_usersa_info *p,
            struct nlattr **attrs,
            int *errp,
            struct netlink_ext_ack *extack)
{
 struct xfrm_state *x = xfrm_state_alloc(net);
 int err = -ENOMEM;

 if (!x)
  goto error_no_put;

 copy_from_user_state(x, p);

 if (attrs[XFRMA_ENCAP]) {
  x->encap = kmemdup(nla_data(attrs[XFRMA_ENCAP]),
       sizeof(*x->encap), GFP_KERNEL);
  if (x->encap == NULL)
   goto error;
 }

 if (attrs[XFRMA_COADDR]) {
  x->coaddr = kmemdup(nla_data(attrs[XFRMA_COADDR]),
        sizeof(*x->coaddr), GFP_KERNEL);
  if (x->coaddr == NULL)
   goto error;
 }

 if (attrs[XFRMA_SA_EXTRA_FLAGS])
  x->props.extra_flags = nla_get_u32(attrs[XFRMA_SA_EXTRA_FLAGS]);

 if ((err = attach_aead(x, attrs[XFRMA_ALG_AEAD], extack)))
  goto error;
 if ((err = attach_auth_trunc(&x->aalg, &x->props.aalgo,
         attrs[XFRMA_ALG_AUTH_TRUNC], extack)))
  goto error;
 if (!x->props.aalgo) {
  if ((err = attach_auth(&x->aalg, &x->props.aalgo,
           attrs[XFRMA_ALG_AUTH], extack)))
   goto error;
 }
 if ((err = attach_crypt(x, attrs[XFRMA_ALG_CRYPT], extack)))
  goto error;
 if ((err = attach_one_algo(&x->calg, &x->props.calgo,
       xfrm_calg_get_byname,
       attrs[XFRMA_ALG_COMP], extack)))
  goto error;

 if (attrs[XFRMA_TFCPAD])
  x->tfcpad = nla_get_u32(attrs[XFRMA_TFCPAD]);

 xfrm_mark_get(attrs, &x->mark);

 xfrm_smark_init(attrs, &x->props.smark);

 if (attrs[XFRMA_IF_ID])
  x->if_id = nla_get_u32(attrs[XFRMA_IF_ID]);

 if (attrs[XFRMA_SA_DIR])
  x->dir = nla_get_u8(attrs[XFRMA_SA_DIR]);

 if (attrs[XFRMA_NAT_KEEPALIVE_INTERVAL])
  x->nat_keepalive_interval =
   nla_get_u32(attrs[XFRMA_NAT_KEEPALIVE_INTERVAL]);

 if (attrs[XFRMA_SA_PCPU]) {
  x->pcpu_num = nla_get_u32(attrs[XFRMA_SA_PCPU]);
  if (x->pcpu_num >= num_possible_cpus())
   goto error;
 }

 err = __xfrm_init_state(x, extack);
 if (err)
  goto error;

 if (attrs[XFRMA_SEC_CTX]) {
  err = security_xfrm_state_alloc(x,
      nla_data(attrs[XFRMA_SEC_CTX]));
  if (err)
   goto error;
 }

 if ((err = xfrm_alloc_replay_state_esn(&x->replay_esn, &x->preplay_esn,
            attrs[XFRMA_REPLAY_ESN_VAL])))
  goto error;

 x->km.seq = p->seq;
 x->replay_maxdiff = net->xfrm.sysctl_aevent_rseqth;
 /* sysctl_xfrm_aevent_etime is in 100ms units */
 x->replay_maxage = (net->xfrm.sysctl_aevent_etime*HZ)/XFRM_AE_ETH_M;

 if ((err = xfrm_init_replay(x, extack)))
  goto error;

 /* override default values from above */
 xfrm_update_ae_params(x, attrs, 0);

 xfrm_set_type_offload(x, attrs[XFRMA_OFFLOAD_DEV]);
 /* configure the hardware if offload is requested */
 if (attrs[XFRMA_OFFLOAD_DEV]) {
  err = xfrm_dev_state_add(net, x,
      nla_data(attrs[XFRMA_OFFLOAD_DEV]),
      extack);
  if (err)
   goto error;
 }

 if (x->mode_cbs && x->mode_cbs->user_init) {
  err = x->mode_cbs->user_init(net, x, attrs, extack);
  if (err)
   goto error;
 }

 return x;

error:
 x->km.state = XFRM_STATE_DEAD;
 xfrm_state_put(x);
error_no_put:
 *errp = err;
 return NULL;
}

static int xfrm_add_sa(struct sk_buff *skb, struct nlmsghdr *nlh,
         struct nlattr **attrs, struct netlink_ext_ack *extack)
{
 struct net *net = sock_net(skb->sk);
 struct xfrm_usersa_info *p = nlmsg_data(nlh);
 struct xfrm_state *x;
 int err;
 struct km_event c;

 err = verify_newsa_info(p, attrs, extack);
 if (err)
  return err;

 x = xfrm_state_construct(net, p, attrs, &err, extack);
 if (!x)
  return err;

 xfrm_state_hold(x);
 if (nlh->nlmsg_type == XFRM_MSG_NEWSA)
  err = xfrm_state_add(x);
 else
  err = xfrm_state_update(x);

 xfrm_audit_state_add(x, err ? 0 : 1, true);

 if (err < 0) {
  x->km.state = XFRM_STATE_DEAD;
  xfrm_dev_state_delete(x);
  __xfrm_state_put(x);
  goto out;
 }

 if (x->km.state == XFRM_STATE_VOID)
  x->km.state = XFRM_STATE_VALID;

 c.seq = nlh->nlmsg_seq;
 c.portid = nlh->nlmsg_pid;
 c.event = nlh->nlmsg_type;

 km_state_notify(x, &c);
out:
 xfrm_state_put(x);
 return err;
}

static struct xfrm_state *xfrm_user_state_lookup(struct net *net,
       struct xfrm_usersa_id *p,
       struct nlattr **attrs,
       int *errp)
{
 struct xfrm_state *x = NULL;
 struct xfrm_mark m;
 int err;
 u32 mark = xfrm_mark_get(attrs, &m);

 if (xfrm_id_proto_match(p->proto, IPSEC_PROTO_ANY)) {
  err = -ESRCH;
  x = xfrm_state_lookup(net, mark, &p->daddr, p->spi, p->proto, p->family);
 } else {
  xfrm_address_t *saddr = NULL;

  verify_one_addr(attrs, XFRMA_SRCADDR, &saddr);
  if (!saddr) {
   err = -EINVAL;
   goto out;
  }

  err = -ESRCH;
  x = xfrm_state_lookup_byaddr(net, mark,
          &p->daddr, saddr,
          p->proto, p->family);
 }

 out:
 if (!x && errp)
  *errp = err;
 return x;
}

static int xfrm_del_sa(struct sk_buff *skb, struct nlmsghdr *nlh,
         struct nlattr **attrs, struct netlink_ext_ack *extack)
{
 struct net *net = sock_net(skb->sk);
 struct xfrm_state *x;
 int err = -ESRCH;
 struct km_event c;
 struct xfrm_usersa_id *p = nlmsg_data(nlh);

 x = xfrm_user_state_lookup(net, p, attrs, &err);
 if (x == NULL)
  return err;

 if ((err = security_xfrm_state_delete(x)) != 0)
  goto out;

 if (xfrm_state_kern(x)) {
  NL_SET_ERR_MSG(extack, "SA is in use by tunnels");
  err = -EPERM;
  goto out;
 }

 err = xfrm_state_delete(x);
 if (err < 0)
  goto out;

 c.seq = nlh->nlmsg_seq;
 c.portid = nlh->nlmsg_pid;
 c.event = nlh->nlmsg_type;
 km_state_notify(x, &c);

out:
 xfrm_audit_state_delete(x, err ? 0 : 1, true);
 xfrm_state_put(x);
 return err;
}

static void copy_to_user_state(struct xfrm_state *x, struct xfrm_usersa_info *p)
{
 memset(p, 0, sizeof(*p));
 memcpy(&p->id, &x->id, sizeof(p->id));
 memcpy(&p->sel, &x->sel, sizeof(p->sel));
 memcpy(&p->lft, &x->lft, sizeof(p->lft));
 if (x->xso.dev)
  xfrm_dev_state_update_stats(x);
 memcpy(&p->curlft, &x->curlft, sizeof(p->curlft));
 put_unaligned(x->stats.replay_window, &p->stats.replay_window);
 put_unaligned(x->stats.replay, &p->stats.replay);
 put_unaligned(x->stats.integrity_failed, &p->stats.integrity_failed);
 memcpy(&p->saddr, &x->props.saddr, sizeof(p->saddr));
 p->mode = x->props.mode;
 p->replay_window = x->props.replay_window;
 p->reqid = x->props.reqid;
 p->family = x->props.family;
 p->flags = x->props.flags;
 p->seq = x->km.seq;
}

struct xfrm_dump_info {
 struct sk_buff *in_skb;
 struct sk_buff *out_skb;
 u32 nlmsg_seq;
 u16 nlmsg_flags;
};

static int copy_sec_ctx(struct xfrm_sec_ctx *s, struct sk_buff *skb)
{
 struct xfrm_user_sec_ctx *uctx;
 struct nlattr *attr;
 int ctx_size = sizeof(*uctx) + s->ctx_len;

 attr = nla_reserve(skb, XFRMA_SEC_CTX, ctx_size);
 if (attr == NULL)
  return -EMSGSIZE;

 uctx = nla_data(attr);
 uctx->exttype = XFRMA_SEC_CTX;
 uctx->len = ctx_size;
 uctx->ctx_doi = s->ctx_doi;
 uctx->ctx_alg = s->ctx_alg;
 uctx->ctx_len = s->ctx_len;
 memcpy(uctx + 1, s->ctx_str, s->ctx_len);

 return 0;
}

static int copy_user_offload(struct xfrm_dev_offload *xso, struct sk_buff *skb)
{
 struct xfrm_user_offload *xuo;
 struct nlattr *attr;

 attr = nla_reserve(skb, XFRMA_OFFLOAD_DEV, sizeof(*xuo));
 if (attr == NULL)
  return -EMSGSIZE;

 xuo = nla_data(attr);
 memset(xuo, 0, sizeof(*xuo));
 xuo->ifindex = xso->dev->ifindex;
 if (xso->dir == XFRM_DEV_OFFLOAD_IN)
  xuo->flags = XFRM_OFFLOAD_INBOUND;
 if (xso->type == XFRM_DEV_OFFLOAD_PACKET)
  xuo->flags |= XFRM_OFFLOAD_PACKET;

 return 0;
}

static bool xfrm_redact(void)
{
 return IS_ENABLED(CONFIG_SECURITY) &&
  security_locked_down(LOCKDOWN_XFRM_SECRET);
}

static int copy_to_user_auth(struct xfrm_algo_auth *auth, struct sk_buff *skb)
{
 struct xfrm_algo *algo;
 struct xfrm_algo_auth *ap;
 struct nlattr *nla;
 bool redact_secret = xfrm_redact();

 nla = nla_reserve(skb, XFRMA_ALG_AUTH,
     sizeof(*algo) + (auth->alg_key_len + 7) / 8);
 if (!nla)
  return -EMSGSIZE;
 algo = nla_data(nla);
 strscpy_pad(algo->alg_name, auth->alg_name);

 if (redact_secret && auth->alg_key_len)
  memset(algo->alg_key, 0, (auth->alg_key_len + 7) / 8);
 else
  memcpy(algo->alg_key, auth->alg_key,
         (auth->alg_key_len + 7) / 8);
 algo->alg_key_len = auth->alg_key_len;

 nla = nla_reserve(skb, XFRMA_ALG_AUTH_TRUNC, xfrm_alg_auth_len(auth));
 if (!nla)
  return -EMSGSIZE;
 ap = nla_data(nla);
 strscpy_pad(ap->alg_name, auth->alg_name);
 ap->alg_key_len = auth->alg_key_len;
 ap->alg_trunc_len = auth->alg_trunc_len;
 if (redact_secret && auth->alg_key_len)
  memset(ap->alg_key, 0, (auth->alg_key_len + 7) / 8);
 else
  memcpy(ap->alg_key, auth->alg_key,
         (auth->alg_key_len + 7) / 8);
 return 0;
}

static int copy_to_user_aead(struct xfrm_algo_aead *aead, struct sk_buff *skb)
{
 struct nlattr *nla = nla_reserve(skb, XFRMA_ALG_AEAD, aead_len(aead));
 struct xfrm_algo_aead *ap;
 bool redact_secret = xfrm_redact();

 if (!nla)
  return -EMSGSIZE;

 ap = nla_data(nla);
 strscpy_pad(ap->alg_name, aead->alg_name);
 ap->alg_key_len = aead->alg_key_len;
 ap->alg_icv_len = aead->alg_icv_len;

 if (redact_secret && aead->alg_key_len)
  memset(ap->alg_key, 0, (aead->alg_key_len + 7) / 8);
 else
  memcpy(ap->alg_key, aead->alg_key,
         (aead->alg_key_len + 7) / 8);
 return 0;
}

static int copy_to_user_ealg(struct xfrm_algo *ealg, struct sk_buff *skb)
{
 struct xfrm_algo *ap;
 bool redact_secret = xfrm_redact();
 struct nlattr *nla = nla_reserve(skb, XFRMA_ALG_CRYPT,
      xfrm_alg_len(ealg));
 if (!nla)
  return -EMSGSIZE;

 ap = nla_data(nla);
 strscpy_pad(ap->alg_name, ealg->alg_name);
 ap->alg_key_len = ealg->alg_key_len;

 if (redact_secret && ealg->alg_key_len)
  memset(ap->alg_key, 0, (ealg->alg_key_len + 7) / 8);
 else
  memcpy(ap->alg_key, ealg->alg_key,
         (ealg->alg_key_len + 7) / 8);

 return 0;
}

static int copy_to_user_calg(struct xfrm_algo *calg, struct sk_buff *skb)
{
 struct nlattr *nla = nla_reserve(skb, XFRMA_ALG_COMP, sizeof(*calg));
 struct xfrm_algo *ap;

 if (!nla)
  return -EMSGSIZE;

 ap = nla_data(nla);
 strscpy_pad(ap->alg_name, calg->alg_name);
 ap->alg_key_len = 0;

 return 0;
}

static int copy_to_user_encap(struct xfrm_encap_tmpl *ep, struct sk_buff *skb)
{
 struct nlattr *nla = nla_reserve(skb, XFRMA_ENCAP, sizeof(*ep));
 struct xfrm_encap_tmpl *uep;

 if (!nla)
  return -EMSGSIZE;

 uep = nla_data(nla);
 memset(uep, 0, sizeof(*uep));

 uep->encap_type = ep->encap_type;
 uep->encap_sport = ep->encap_sport;
 uep->encap_dport = ep->encap_dport;
 uep->encap_oa = ep->encap_oa;

 return 0;
}

static int xfrm_smark_put(struct sk_buff *skb, struct xfrm_mark *m)
{
 int ret = 0;

 if (m->v | m->m) {
  ret = nla_put_u32(skb, XFRMA_SET_MARK, m->v);
  if (!ret)
   ret = nla_put_u32(skb, XFRMA_SET_MARK_MASK, m->m);
 }
 return ret;
}

/* Don't change this without updating xfrm_sa_len! */
static int copy_to_user_state_extra(struct xfrm_state *x,
        struct xfrm_usersa_info *p,
        struct sk_buff *skb)
{
 int ret = 0;

 copy_to_user_state(x, p);

 if (x->props.extra_flags) {
  ret = nla_put_u32(skb, XFRMA_SA_EXTRA_FLAGS,
      x->props.extra_flags);
  if (ret)
   goto out;
 }

 if (x->coaddr) {
  ret = nla_put(skb, XFRMA_COADDR, sizeof(*x->coaddr), x->coaddr);
  if (ret)
   goto out;
 }
 if (x->lastused) {
  ret = nla_put_u64_64bit(skb, XFRMA_LASTUSED, x->lastused,
     XFRMA_PAD);
  if (ret)
   goto out;
 }
 if (x->aead) {
  ret = copy_to_user_aead(x->aead, skb);
  if (ret)
   goto out;
 }
 if (x->aalg) {
  ret = copy_to_user_auth(x->aalg, skb);
  if (ret)
   goto out;
 }
 if (x->ealg) {
  ret = copy_to_user_ealg(x->ealg, skb);
  if (ret)
   goto out;
 }
 if (x->calg) {
  ret = copy_to_user_calg(x->calg, skb);
  if (ret)
   goto out;
 }
 if (x->encap) {
  ret = copy_to_user_encap(x->encap, skb);
  if (ret)
   goto out;
 }
 if (x->tfcpad) {
  ret = nla_put_u32(skb, XFRMA_TFCPAD, x->tfcpad);
  if (ret)
   goto out;
 }
 ret = xfrm_mark_put(skb, &x->mark);
 if (ret)
  goto out;

 ret = xfrm_smark_put(skb, &x->props.smark);
 if (ret)
  goto out;

 if (x->replay_esn)
  ret = nla_put(skb, XFRMA_REPLAY_ESN_VAL,
         xfrm_replay_state_esn_len(x->replay_esn),
         x->replay_esn);
 else
  ret = nla_put(skb, XFRMA_REPLAY_VAL, sizeof(x->replay),
         &x->replay);
 if (ret)
  goto out;
 if(x->xso.dev)
  ret = copy_user_offload(&x->xso, skb);
 if (ret)
  goto out;
 if (x->if_id) {
  ret = nla_put_u32(skb, XFRMA_IF_ID, x->if_id);
  if (ret)
   goto out;
 }
 if (x->security) {
  ret = copy_sec_ctx(x->security, skb);
  if (ret)
   goto out;
 }
 if (x->mode_cbs && x->mode_cbs->copy_to_user)
  ret = x->mode_cbs->copy_to_user(x, skb);
 if (ret)
  goto out;
 if (x->mapping_maxage) {
  ret = nla_put_u32(skb, XFRMA_MTIMER_THRESH, x->mapping_maxage);
  if (ret)
   goto out;
 }
 if (x->pcpu_num != UINT_MAX) {
  ret = nla_put_u32(skb, XFRMA_SA_PCPU, x->pcpu_num);
  if (ret)
   goto out;
 }
 if (x->dir)
  ret = nla_put_u8(skb, XFRMA_SA_DIR, x->dir);

 if (x->nat_keepalive_interval) {
  ret = nla_put_u32(skb, XFRMA_NAT_KEEPALIVE_INTERVAL,
      x->nat_keepalive_interval);
  if (ret)
   goto out;
 }
out:
 return ret;
}

static int dump_one_state(struct xfrm_state *x, int count, void *ptr)
{
 struct xfrm_dump_info *sp = ptr;
 struct sk_buff *in_skb = sp->in_skb;
 struct sk_buff *skb = sp->out_skb;
 struct xfrm_translator *xtr;
 struct xfrm_usersa_info *p;
 struct nlmsghdr *nlh;
 int err;

 nlh = nlmsg_put(skb, NETLINK_CB(in_skb).portid, sp->nlmsg_seq,
   XFRM_MSG_NEWSA, sizeof(*p), sp->nlmsg_flags);
 if (nlh == NULL)
  return -EMSGSIZE;

 p = nlmsg_data(nlh);

 err = copy_to_user_state_extra(x, p, skb);
 if (err) {
  nlmsg_cancel(skb, nlh);
  return err;
 }
 nlmsg_end(skb, nlh);

 xtr = xfrm_get_translator();
 if (xtr) {
  err = xtr->alloc_compat(skb, nlh);

  xfrm_put_translator(xtr);
  if (err) {
   nlmsg_cancel(skb, nlh);
   return err;
  }
 }

 return 0;
}

static int xfrm_dump_sa_done(struct netlink_callback *cb)
{
 struct xfrm_state_walk *walk = (struct xfrm_state_walk *) &cb->args[1];
 struct sock *sk = cb->skb->sk;
 struct net *net = sock_net(sk);

 if (cb->args[0])
  xfrm_state_walk_done(walk, net);
 return 0;
}

static int xfrm_dump_sa(struct sk_buff *skb, struct netlink_callback *cb)
{
 struct net *net = sock_net(skb->sk);
 struct xfrm_state_walk *walk = (struct xfrm_state_walk *) &cb->args[1];
 struct xfrm_dump_info info;

 BUILD_BUG_ON(sizeof(struct xfrm_state_walk) >
       sizeof(cb->args) - sizeof(cb->args[0]));

 info.in_skb = cb->skb;
 info.out_skb = skb;
 info.nlmsg_seq = cb->nlh->nlmsg_seq;
 info.nlmsg_flags = NLM_F_MULTI;

 if (!cb->args[0]) {
  struct nlattr *attrs[XFRMA_MAX+1];
  struct xfrm_address_filter *filter = NULL;
  u8 proto = 0;
  int err;

  err = nlmsg_parse_deprecated(cb->nlh, 0, attrs, XFRMA_MAX,
          xfrma_policy, cb->extack);
  if (err < 0)
   return err;

  if (attrs[XFRMA_ADDRESS_FILTER]) {
   filter = kmemdup(nla_data(attrs[XFRMA_ADDRESS_FILTER]),
      sizeof(*filter), GFP_KERNEL);
   if (filter == NULL)
    return -ENOMEM;

   /* see addr_match(), (prefix length >> 5) << 2
 * will be used to compare xfrm_address_t
 */

   if (filter->splen > (sizeof(xfrm_address_t) << 3) ||
       filter->dplen > (sizeof(xfrm_address_t) << 3)) {
    kfree(filter);
    return -EINVAL;
   }
  }

  if (attrs[XFRMA_PROTO])
   proto = nla_get_u8(attrs[XFRMA_PROTO]);

  xfrm_state_walk_init(walk, proto, filter);
  cb->args[0] = 1;
 }

 (void) xfrm_state_walk(net, walk, dump_one_state, &info);

 return skb->len;
}

static struct sk_buff *xfrm_state_netlink(struct sk_buff *in_skb,
       struct xfrm_state *x, u32 seq)
{
 struct xfrm_dump_info info;
 struct sk_buff *skb;
 int err;

 skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC);
 if (!skb)
  return ERR_PTR(-ENOMEM);

 info.in_skb = in_skb;
 info.out_skb = skb;
 info.nlmsg_seq = seq;
 info.nlmsg_flags = 0;

 err = dump_one_state(x, 0, &info);
 if (err) {
  kfree_skb(skb);
  return ERR_PTR(err);
 }

 return skb;
}

/* A wrapper for nlmsg_multicast() checking that nlsk is still available.
 * Must be called with RCU read lock.
 */

static inline int xfrm_nlmsg_multicast(struct net *net, struct sk_buff *skb,
           u32 pid, unsigned int group)
{
 struct sock *nlsk = rcu_dereference(net->xfrm.nlsk);
 struct xfrm_translator *xtr;

 if (!nlsk) {
  kfree_skb(skb);
  return -EPIPE;
 }

 xtr = xfrm_get_translator();
 if (xtr) {
  int err = xtr->alloc_compat(skb, nlmsg_hdr(skb));

  xfrm_put_translator(xtr);
  if (err) {
   kfree_skb(skb);
   return err;
  }
 }

 return nlmsg_multicast(nlsk, skb, pid, group, GFP_ATOMIC);
}

static inline unsigned int xfrm_spdinfo_msgsize(void)
{
 return NLMSG_ALIGN(4)
        + nla_total_size(sizeof(struct xfrmu_spdinfo))
        + nla_total_size(sizeof(struct xfrmu_spdhinfo))
        + nla_total_size(sizeof(struct xfrmu_spdhthresh))
        + nla_total_size(sizeof(struct xfrmu_spdhthresh));
}

static int build_spdinfo(struct sk_buff *skb, struct net *net,
    u32 portid, u32 seq, u32 flags)
{
 struct xfrmk_spdinfo si;
 struct xfrmu_spdinfo spc;
 struct xfrmu_spdhinfo sph;
 struct xfrmu_spdhthresh spt4, spt6;
 struct nlmsghdr *nlh;
 int err;
 u32 *f;
 unsigned lseq;

 nlh = nlmsg_put(skb, portid, seq, XFRM_MSG_NEWSPDINFO, sizeof(u32), 0);
 if (nlh == NULL) /* shouldn't really happen ... */
  return -EMSGSIZE;

 f = nlmsg_data(nlh);
 *f = flags;
 xfrm_spd_getinfo(net, &si);
 spc.incnt = si.incnt;
 spc.outcnt = si.outcnt;
 spc.fwdcnt = si.fwdcnt;
 spc.inscnt = si.inscnt;
 spc.outscnt = si.outscnt;
 spc.fwdscnt = si.fwdscnt;
 sph.spdhcnt = si.spdhcnt;
 sph.spdhmcnt = si.spdhmcnt;

 do {
  lseq = read_seqbegin(&net->xfrm.policy_hthresh.lock);

  spt4.lbits = net->xfrm.policy_hthresh.lbits4;
  spt4.rbits = net->xfrm.policy_hthresh.rbits4;
  spt6.lbits = net->xfrm.policy_hthresh.lbits6;
  spt6.rbits = net->xfrm.policy_hthresh.rbits6;
 } while (read_seqretry(&net->xfrm.policy_hthresh.lock, lseq));

 err = nla_put(skb, XFRMA_SPD_INFO, sizeof(spc), &spc);
 if (!err)
  err = nla_put(skb, XFRMA_SPD_HINFO, sizeof(sph), &sph);
 if (!err)
  err = nla_put(skb, XFRMA_SPD_IPV4_HTHRESH, sizeof(spt4), &spt4);
 if (!err)
  err = nla_put(skb, XFRMA_SPD_IPV6_HTHRESH, sizeof(spt6), &spt6);
 if (err) {
  nlmsg_cancel(skb, nlh);
  return err;
 }

 nlmsg_end(skb, nlh);
 return 0;
}

static int xfrm_set_spdinfo(struct sk_buff *skb, struct nlmsghdr *nlh,
       struct nlattr **attrs,
       struct netlink_ext_ack *extack)
{
 struct net *net = sock_net(skb->sk);
 struct xfrmu_spdhthresh *thresh4 = NULL;
 struct xfrmu_spdhthresh *thresh6 = NULL;

 /* selector prefixlen thresholds to hash policies */
 if (attrs[XFRMA_SPD_IPV4_HTHRESH]) {
  struct nlattr *rta = attrs[XFRMA_SPD_IPV4_HTHRESH];

  if (nla_len(rta) < sizeof(*thresh4)) {
   NL_SET_ERR_MSG(extack, "Invalid SPD_IPV4_HTHRESH attribute length");
   return -EINVAL;
  }
  thresh4 = nla_data(rta);
  if (thresh4->lbits > 32 || thresh4->rbits > 32) {
   NL_SET_ERR_MSG(extack, "Invalid hash threshold (must be <= 32 for IPv4)");
   return -EINVAL;
  }
 }
 if (attrs[XFRMA_SPD_IPV6_HTHRESH]) {
  struct nlattr *rta = attrs[XFRMA_SPD_IPV6_HTHRESH];

  if (nla_len(rta) < sizeof(*thresh6)) {
   NL_SET_ERR_MSG(extack, "Invalid SPD_IPV6_HTHRESH attribute length");
   return -EINVAL;
  }
  thresh6 = nla_data(rta);
  if (thresh6->lbits > 128 || thresh6->rbits > 128) {
   NL_SET_ERR_MSG(extack, "Invalid hash threshold (must be <= 128 for IPv6)");
   return -EINVAL;
  }
 }

 if (thresh4 || thresh6) {
  write_seqlock(&net->xfrm.policy_hthresh.lock);
  if (thresh4) {
   net->xfrm.policy_hthresh.lbits4 = thresh4->lbits;
   net->xfrm.policy_hthresh.rbits4 = thresh4->rbits;
  }
  if (thresh6) {
   net->xfrm.policy_hthresh.lbits6 = thresh6->lbits;
   net->xfrm.policy_hthresh.rbits6 = thresh6->rbits;
  }
  write_sequnlock(&net->xfrm.policy_hthresh.lock);

  xfrm_policy_hash_rebuild(net);
 }

 return 0;
}

static int xfrm_get_spdinfo(struct sk_buff *skb, struct nlmsghdr *nlh,
       struct nlattr **attrs,
       struct netlink_ext_ack *extack)
{
 struct net *net = sock_net(skb->sk);
 struct sk_buff *r_skb;
 u32 *flags = nlmsg_data(nlh);
 u32 sportid = NETLINK_CB(skb).portid;
 u32 seq = nlh->nlmsg_seq;
 int err;

 r_skb = nlmsg_new(xfrm_spdinfo_msgsize(), GFP_ATOMIC);
 if (r_skb == NULL)
  return -ENOMEM;

 err = build_spdinfo(r_skb, net, sportid, seq, *flags);
 BUG_ON(err < 0);

 return nlmsg_unicast(net->xfrm.nlsk, r_skb, sportid);
}

static inline unsigned int xfrm_sadinfo_msgsize(void)
{
 return NLMSG_ALIGN(4)
        + nla_total_size(sizeof(struct xfrmu_sadhinfo))
        + nla_total_size(4); /* XFRMA_SAD_CNT */
}

static int build_sadinfo(struct sk_buff *skb, struct net *net,
    u32 portid, u32 seq, u32 flags)
{
 struct xfrmk_sadinfo si;
 struct xfrmu_sadhinfo sh;
 struct nlmsghdr *nlh;
 int err;
 u32 *f;

 nlh = nlmsg_put(skb, portid, seq, XFRM_MSG_NEWSADINFO, sizeof(u32), 0);
 if (nlh == NULL) /* shouldn't really happen ... */
  return -EMSGSIZE;

 f = nlmsg_data(nlh);
 *f = flags;
 xfrm_sad_getinfo(net, &si);

 sh.sadhmcnt = si.sadhmcnt;
 sh.sadhcnt = si.sadhcnt;

 err = nla_put_u32(skb, XFRMA_SAD_CNT, si.sadcnt);
 if (!err)
  err = nla_put(skb, XFRMA_SAD_HINFO, sizeof(sh), &sh);
 if (err) {
  nlmsg_cancel(skb, nlh);
  return err;
 }

 nlmsg_end(skb, nlh);
 return 0;
}

static int xfrm_get_sadinfo(struct sk_buff *skb, struct nlmsghdr *nlh,
       struct nlattr **attrs,
       struct netlink_ext_ack *extack)
{
 struct net *net = sock_net(skb->sk);
 struct sk_buff *r_skb;
 u32 *flags = nlmsg_data(nlh);
 u32 sportid = NETLINK_CB(skb).portid;
 u32 seq = nlh->nlmsg_seq;
 int err;

 r_skb = nlmsg_new(xfrm_sadinfo_msgsize(), GFP_ATOMIC);
 if (r_skb == NULL)
  return -ENOMEM;

 err = build_sadinfo(r_skb, net, sportid, seq, *flags);
 BUG_ON(err < 0);

 return nlmsg_unicast(net->xfrm.nlsk, r_skb, sportid);
}

static int xfrm_get_sa(struct sk_buff *skb, struct nlmsghdr *nlh,
         struct nlattr **attrs, struct netlink_ext_ack *extack)
{
 struct net *net = sock_net(skb->sk);
 struct xfrm_usersa_id *p = nlmsg_data(nlh);
 struct xfrm_state *x;
 struct sk_buff *resp_skb;
 int err = -ESRCH;

 x = xfrm_user_state_lookup(net, p, attrs, &err);
 if (x == NULL)
  goto out_noput;

 resp_skb = xfrm_state_netlink(skb, x, nlh->nlmsg_seq);
 if (IS_ERR(resp_skb)) {
  err = PTR_ERR(resp_skb);
 } else {
  err = nlmsg_unicast(net->xfrm.nlsk, resp_skb, NETLINK_CB(skb).portid);
 }
 xfrm_state_put(x);
out_noput:
 return err;
}

static int xfrm_alloc_userspi(struct sk_buff *skb, struct nlmsghdr *nlh,
         struct nlattr **attrs,
         struct netlink_ext_ack *extack)
{
 struct net *net = sock_net(skb->sk);
 struct xfrm_state *x;
 struct xfrm_userspi_info *p;
 struct xfrm_translator *xtr;
 struct sk_buff *resp_skb;
 xfrm_address_t *daddr;
 int family;
 int err;
 u32 mark;
 struct xfrm_mark m;
 u32 if_id = 0;
 u32 pcpu_num = UINT_MAX;

 p = nlmsg_data(nlh);
 err = verify_spi_info(p->info.id.proto, p->min, p->max, extack);
 if (err)
  goto out_noput;

 family = p->info.family;
 daddr = &p->info.id.daddr;

 x = NULL;

 mark = xfrm_mark_get(attrs, &m);

 if (attrs[XFRMA_IF_ID])
  if_id = nla_get_u32(attrs[XFRMA_IF_ID]);

 if (attrs[XFRMA_SA_PCPU]) {
  pcpu_num = nla_get_u32(attrs[XFRMA_SA_PCPU]);
  if (pcpu_num >= num_possible_cpus()) {
   err = -EINVAL;
   goto out_noput;
  }
 }

 if (p->info.seq) {
  x = xfrm_find_acq_byseq(net, mark, p->info.seq, pcpu_num);
  if (x && !xfrm_addr_equal(&x->id.daddr, daddr, family)) {
   xfrm_state_put(x);
   x = NULL;
  }
 }

 if (!x)
  x = xfrm_find_acq(net, &m, p->info.mode, p->info.reqid,
      if_id, pcpu_num, p->info.id.proto, daddr,
      &p->info.saddr, 1,
      family);
 err = -ENOENT;
 if (!x) {
  NL_SET_ERR_MSG(extack, "Target ACQUIRE not found");
  goto out_noput;
 }

 err = xfrm_alloc_spi(x, p->min, p->max, extack);
 if (err)
  goto out;

 if (attrs[XFRMA_SA_DIR])
  x->dir = nla_get_u8(attrs[XFRMA_SA_DIR]);

 resp_skb = xfrm_state_netlink(skb, x, nlh->nlmsg_seq);
 if (IS_ERR(resp_skb)) {
  err = PTR_ERR(resp_skb);
  goto out;
 }

 xtr = xfrm_get_translator();
 if (xtr) {
  err = xtr->alloc_compat(skb, nlmsg_hdr(skb));

  xfrm_put_translator(xtr);
  if (err) {
   kfree_skb(resp_skb);
   goto out;
  }
 }

 err = nlmsg_unicast(net->xfrm.nlsk, resp_skb, NETLINK_CB(skb).portid);

out:
 xfrm_state_put(x);
out_noput:
 return err;
}

static int verify_policy_dir(u8 dir, struct netlink_ext_ack *extack)
{
 switch (dir) {
 case XFRM_POLICY_IN:
 case XFRM_POLICY_OUT:
 case XFRM_POLICY_FWD:
  break;

 default:
  NL_SET_ERR_MSG(extack, "Invalid policy direction");
  return -EINVAL;
 }

 return 0;
}

static int verify_policy_type(u8 type, struct netlink_ext_ack *extack)
{
 switch (type) {
 case XFRM_POLICY_TYPE_MAIN:
#ifdef CONFIG_XFRM_SUB_POLICY
 case XFRM_POLICY_TYPE_SUB:
#endif
  break;

 default:
  NL_SET_ERR_MSG(extack, "Invalid policy type");
  return -EINVAL;
 }

 return 0;
}

static int verify_newpolicy_info(struct xfrm_userpolicy_info *p,
     struct netlink_ext_ack *extack)
{
 int ret;

 switch (p->share) {
 case XFRM_SHARE_ANY:
 case XFRM_SHARE_SESSION:
 case XFRM_SHARE_USER:
 case XFRM_SHARE_UNIQUE:
  break;

 default:
  NL_SET_ERR_MSG(extack, "Invalid policy share");
  return -EINVAL;
 }

 switch (p->action) {
 case XFRM_POLICY_ALLOW:
 case XFRM_POLICY_BLOCK:
  break;

 default:
  NL_SET_ERR_MSG(extack, "Invalid policy action");
  return -EINVAL;
 }

 switch (p->sel.family) {
 case AF_INET:
  if (p->sel.prefixlen_d > 32 || p->sel.prefixlen_s > 32) {
   NL_SET_ERR_MSG(extack, "Invalid prefix length in selector (must be <= 32 for IPv4)");
   return -EINVAL;
  }

  break;

 case AF_INET6:
#if IS_ENABLED(CONFIG_IPV6)
  if (p->sel.prefixlen_d > 128 || p->sel.prefixlen_s > 128) {
   NL_SET_ERR_MSG(extack, "Invalid prefix length in selector (must be <= 128 for IPv6)");
   return -EINVAL;
  }

  break;
#else
  NL_SET_ERR_MSG(extack, "IPv6 support disabled");
  return  -EAFNOSUPPORT;
#endif

 default:
  NL_SET_ERR_MSG(extack, "Invalid selector family");
  return -EINVAL;
 }

 ret = verify_policy_dir(p->dir, extack);
 if (ret)
  return ret;
 if (p->index && (xfrm_policy_id2dir(p->index) != p->dir)) {
  NL_SET_ERR_MSG(extack, "Policy index doesn't match direction");
  return -EINVAL;
 }

 return 0;
}

static int copy_from_user_sec_ctx(struct xfrm_policy *pol, struct nlattr **attrs)
{
 struct nlattr *rt = attrs[XFRMA_SEC_CTX];
 struct xfrm_user_sec_ctx *uctx;

 if (!rt)
  return 0;

 uctx = nla_data(rt);
 return security_xfrm_policy_alloc(&pol->security, uctx, GFP_KERNEL);
}

static void copy_templates(struct xfrm_policy *xp, struct xfrm_user_tmpl *ut,
      int nr)
{
 int i;

 xp->xfrm_nr = nr;
 for (i = 0; i < nr; i++, ut++) {
  struct xfrm_tmpl *t = &xp->xfrm_vec[i];

  memcpy(&t->id, &ut->id, sizeof(struct xfrm_id));
  memcpy(&t->saddr, &ut->saddr,
         sizeof(xfrm_address_t));
  t->reqid = ut->reqid;
  t->mode = ut->mode;
  t->share = ut->share;
  t->optional = ut->optional;
  t->aalgos = ut->aalgos;
  t->ealgos = ut->ealgos;
  t->calgos = ut->calgos;
  /* If all masks are ~0, then we allow all algorithms. */
  t->allalgs = !~(t->aalgos & t->ealgos & t->calgos);
  t->encap_family = ut->family;
 }
}

static int validate_tmpl(int nr, struct xfrm_user_tmpl *ut, u16 family,
    int dir, struct netlink_ext_ack *extack)
{
 u16 prev_family;
 int i;

 if (nr > XFRM_MAX_DEPTH) {
  NL_SET_ERR_MSG(extack, "Template count must be <= XFRM_MAX_DEPTH (" __stringify(XFRM_MAX_DEPTH) ")");
  return -EINVAL;
 }

 prev_family = family;

 for (i = 0; i < nr; i++) {
  /* We never validated the ut->family value, so many
 * applications simply leave it at zero.  The check was
 * never made and ut->family was ignored because all
 * templates could be assumed to have the same family as
 * the policy itself.  Now that we will have ipv4-in-ipv6
 * and ipv6-in-ipv4 tunnels, this is no longer true.
 */

  if (!ut[i].family)
   ut[i].family = family;

  switch (ut[i].mode) {
  case XFRM_MODE_TUNNEL:
  case XFRM_MODE_BEET:
   if (ut[i].optional && dir == XFRM_POLICY_OUT) {
    NL_SET_ERR_MSG(extack, "Mode in optional template not allowed in outbound policy");
    return -EINVAL;
   }
   break;
  case XFRM_MODE_IPTFS:
   break;
  default:
   if (ut[i].family != prev_family) {
    NL_SET_ERR_MSG(extack, "Mode in template doesn't support a family change");
    return -EINVAL;
   }
   break;
  }
  if (ut[i].mode >= XFRM_MODE_MAX) {
   NL_SET_ERR_MSG(extack, "Mode in template must be < XFRM_MODE_MAX (" __stringify(XFRM_MODE_MAX) ")");
   return -EINVAL;
  }

  prev_family = ut[i].family;

  switch (ut[i].family) {
  case AF_INET:
   break;
#if IS_ENABLED(CONFIG_IPV6)
  case AF_INET6:
   break;
#endif
  default:
   NL_SET_ERR_MSG(extack, "Invalid family in template");
   return -EINVAL;
  }

  if (!xfrm_id_proto_valid(ut[i].id.proto)) {
   NL_SET_ERR_MSG(extack, "Invalid XFRM protocol in template");
   return -EINVAL;
  }
 }

 return 0;
}

static int copy_from_user_tmpl(struct xfrm_policy *pol, struct nlattr **attrs,
          int dir, struct netlink_ext_ack *extack)
{
 struct nlattr *rt = attrs[XFRMA_TMPL];

 if (!rt) {
  pol->xfrm_nr = 0;
 } else {
  struct xfrm_user_tmpl *utmpl = nla_data(rt);
  int nr = nla_len(rt) / sizeof(*utmpl);
  int err;

  err = validate_tmpl(nr, utmpl, pol->family, dir, extack);
  if (err)
   return err;

  copy_templates(pol, utmpl, nr);
 }
 return 0;
}

static int copy_from_user_policy_type(u8 *tp, struct nlattr **attrs,
          struct netlink_ext_ack *extack)
{
 struct nlattr *rt = attrs[XFRMA_POLICY_TYPE];
 struct xfrm_userpolicy_type *upt;
 u8 type = XFRM_POLICY_TYPE_MAIN;
 int err;

 if (rt) {
  upt = nla_data(rt);
  type = upt->type;
 }

 err = verify_policy_type(type, extack);
 if (err)
  return err;

 *tp = type;
 return 0;
}

static void copy_from_user_policy(struct xfrm_policy *xp, struct xfrm_userpolicy_info *p)
{
 xp->priority = p->priority;
 xp->index = p->index;
 memcpy(&xp->selector, &p->sel, sizeof(xp->selector));
 memcpy(&xp->lft, &p->lft, sizeof(xp->lft));
 xp->action = p->action;
 xp->flags = p->flags;
 xp->family = p->sel.family;
 /* XXX xp->share = p->share; */
}

static void copy_to_user_policy(struct xfrm_policy *xp, struct xfrm_userpolicy_info *p, int dir)
{
 memset(p, 0, sizeof(*p));
 memcpy(&p->sel, &xp->selector, sizeof(p->sel));
 memcpy(&p->lft, &xp->lft, sizeof(p->lft));
 memcpy(&p->curlft, &xp->curlft, sizeof(p->curlft));
 p->priority = xp->priority;
 p->index = xp->index;
 p->sel.family = xp->family;
 p->dir = dir;
 p->action = xp->action;
 p->flags = xp->flags;
 p->share = XFRM_SHARE_ANY; /* XXX xp->share */
}

static struct xfrm_policy *xfrm_policy_construct(struct net *net,
       struct xfrm_userpolicy_info *p,
       struct nlattr **attrs,
       int *errp,
       struct netlink_ext_ack *extack)
{
 struct xfrm_policy *xp = xfrm_policy_alloc(net, GFP_KERNEL);
 int err;

 if (!xp) {
  *errp = -ENOMEM;
  return NULL;
 }

 copy_from_user_policy(xp, p);

 err = copy_from_user_policy_type(&xp->type, attrs, extack);
 if (err)
  goto error;

 if (!(err = copy_from_user_tmpl(xp, attrs, p->dir, extack)))
  err = copy_from_user_sec_ctx(xp, attrs);
 if (err)
  goto error;

 xfrm_mark_get(attrs, &xp->mark);

 if (attrs[XFRMA_IF_ID])
  xp->if_id = nla_get_u32(attrs[XFRMA_IF_ID]);

 /* configure the hardware if offload is requested */
 if (attrs[XFRMA_OFFLOAD_DEV]) {
  err = xfrm_dev_policy_add(net, xp,
       nla_data(attrs[XFRMA_OFFLOAD_DEV]),
       p->dir, extack);
  if (err)
   goto error;
 }

 return xp;
 error:
 *errp = err;
 xp->walk.dead = 1;
 xfrm_policy_destroy(xp);
 return NULL;
}

static int xfrm_add_policy(struct sk_buff *skb, struct nlmsghdr *nlh,
      struct nlattr **attrs,
      struct netlink_ext_ack *extack)
{
 struct net *net = sock_net(skb->sk);
 struct xfrm_userpolicy_info *p = nlmsg_data(nlh);
 struct xfrm_policy *xp;
 struct km_event c;
 int err;
 int excl;

 err = verify_newpolicy_info(p, extack);
 if (err)
  return err;
 err = verify_sec_ctx_len(attrs, extack);
 if (err)
  return err;

 xp = xfrm_policy_construct(net, p, attrs, &err, extack);
 if (!xp)
  return err;

 /* shouldn't excl be based on nlh flags??
 * Aha! this is anti-netlink really i.e  more pfkey derived
 * in netlink excl is a flag and you wouldn't need
 * a type XFRM_MSG_UPDPOLICY - JHS */

 excl = nlh->nlmsg_type == XFRM_MSG_NEWPOLICY;
 err = xfrm_policy_insert(p->dir, xp, excl);
 xfrm_audit_policy_add(xp, err ? 0 : 1, true);

 if (err) {
  xfrm_dev_policy_delete(xp);
  xfrm_dev_policy_free(xp);
  security_xfrm_policy_free(xp->security);
  kfree(xp);
  return err;
 }

 c.event = nlh->nlmsg_type;
 c.seq = nlh->nlmsg_seq;
 c.portid = nlh->nlmsg_pid;
 km_policy_notify(xp, p->dir, &c);

 xfrm_pol_put(xp);

 return 0;
}

static int copy_to_user_tmpl(struct xfrm_policy *xp, struct sk_buff *skb)
{
 struct xfrm_user_tmpl vec[XFRM_MAX_DEPTH];
 int i;

 if (xp->xfrm_nr == 0)
  return 0;

 if (xp->xfrm_nr > XFRM_MAX_DEPTH)
  return -ENOBUFS;

 for (i = 0; i < xp->xfrm_nr; i++) {
  struct xfrm_user_tmpl *up = &vec[i];
  struct xfrm_tmpl *kp = &xp->xfrm_vec[i];

  memset(up, 0, sizeof(*up));
  memcpy(&up->id, &kp->id, sizeof(up->id));
  up->family = kp->encap_family;
  memcpy(&up->saddr, &kp->saddr, sizeof(up->saddr));
  up->reqid = kp->reqid;
  up->mode = kp->mode;
  up->share = kp->share;
  up->optional = kp->optional;
  up->aalgos = kp->aalgos;
  up->ealgos = kp->ealgos;
  up->calgos = kp->calgos;
 }

 return nla_put(skb, XFRMA_TMPL,
         sizeof(struct xfrm_user_tmpl) * xp->xfrm_nr, vec);
}

static inline int copy_to_user_state_sec_ctx(struct xfrm_state *x, struct sk_buff *skb)
{
 if (x->security) {
  return copy_sec_ctx(x->security, skb);
 }
 return 0;
}

static inline int copy_to_user_sec_ctx(struct xfrm_policy *xp, struct sk_buff *skb)
{
 if (xp->security)
  return copy_sec_ctx(xp->security, skb);
 return 0;
}
static inline unsigned int userpolicy_type_attrsize(void)
{
#ifdef CONFIG_XFRM_SUB_POLICY
 return nla_total_size(sizeof(struct xfrm_userpolicy_type));
#else
 return 0;
#endif
}

#ifdef CONFIG_XFRM_SUB_POLICY
static int copy_to_user_policy_type(u8 type, struct sk_buff *skb)
{
 struct xfrm_userpolicy_type upt;

 /* Sadly there are two holes in struct xfrm_userpolicy_type */
 memset(&upt, 0, sizeof(upt));
 upt.type = type;

 return nla_put(skb, XFRMA_POLICY_TYPE, sizeof(upt), &upt);
}

#else
static inline int copy_to_user_policy_type(u8 type, struct sk_buff *skb)
{
 return 0;
}
#endif

static int dump_one_policy(struct xfrm_policy *xp, int dir, int count, void *ptr)
{
 struct xfrm_dump_info *sp = ptr;
 struct xfrm_userpolicy_info *p;
 struct sk_buff *in_skb = sp->in_skb;
 struct sk_buff *skb = sp->out_skb;
 struct xfrm_translator *xtr;
 struct nlmsghdr *nlh;
 int err;

 nlh = nlmsg_put(skb, NETLINK_CB(in_skb).portid, sp->nlmsg_seq,
   XFRM_MSG_NEWPOLICY, sizeof(*p), sp->nlmsg_flags);
 if (nlh == NULL)
  return -EMSGSIZE;

 p = nlmsg_data(nlh);
 copy_to_user_policy(xp, p, dir);
 err = copy_to_user_tmpl(xp, skb);
 if (!err)
  err = copy_to_user_sec_ctx(xp, skb);
 if (!err)
  err = copy_to_user_policy_type(xp->type, skb);
 if (!err)
  err = xfrm_mark_put(skb, &xp->mark);
 if (!err)
  err = xfrm_if_id_put(skb, xp->if_id);
 if (!err && xp->xdo.dev)
  err = copy_user_offload(&xp->xdo, skb);
 if (err) {
  nlmsg_cancel(skb, nlh);
  return err;
 }
 nlmsg_end(skb, nlh);

 xtr = xfrm_get_translator();
 if (xtr) {
  err = xtr->alloc_compat(skb, nlh);

  xfrm_put_translator(xtr);
  if (err) {
   nlmsg_cancel(skb, nlh);
   return err;
  }
 }

 return 0;
}

static int xfrm_dump_policy_done(struct netlink_callback *cb)
{
 struct xfrm_policy_walk *walk = (struct xfrm_policy_walk *)cb->args;
 struct net *net = sock_net(cb->skb->sk);

 xfrm_policy_walk_done(walk, net);
 return 0;
}

static int xfrm_dump_policy_start(struct netlink_callback *cb)
{
 struct xfrm_policy_walk *walk = (struct xfrm_policy_walk *)cb->args;

 BUILD_BUG_ON(sizeof(*walk) > sizeof(cb->args));

 xfrm_policy_walk_init(walk, XFRM_POLICY_TYPE_ANY);
 return 0;
}

static int xfrm_dump_policy(struct sk_buff *skb, struct netlink_callback *cb)
{
 struct net *net = sock_net(skb->sk);
 struct xfrm_policy_walk *walk = (struct xfrm_policy_walk *)cb->args;
 struct xfrm_dump_info info;

 info.in_skb = cb->skb;
 info.out_skb = skb;
 info.nlmsg_seq = cb->nlh->nlmsg_seq;
 info.nlmsg_flags = NLM_F_MULTI;

 (void) xfrm_policy_walk(net, walk, dump_one_policy, &info);

 return skb->len;
}

static struct sk_buff *xfrm_policy_netlink(struct sk_buff *in_skb,
       struct xfrm_policy *xp,
       int dir, u32 seq)
{
 struct xfrm_dump_info info;
 struct sk_buff *skb;
 int err;

 skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
 if (!skb)
  return ERR_PTR(-ENOMEM);

 info.in_skb = in_skb;
 info.out_skb = skb;
 info.nlmsg_seq = seq;
 info.nlmsg_flags = 0;

 err = dump_one_policy(xp, dir, 0, &info);
 if (err) {
  kfree_skb(skb);
  return ERR_PTR(err);
 }

 return skb;
}

static int xfrm_notify_userpolicy(struct net *net)
{
 struct xfrm_userpolicy_default *up;
 int len = NLMSG_ALIGN(sizeof(*up));
 struct nlmsghdr *nlh;
 struct sk_buff *skb;
 int err;

 skb = nlmsg_new(len, GFP_ATOMIC);
 if (skb == NULL)
  return -ENOMEM;

 nlh = nlmsg_put(skb, 0, 0, XFRM_MSG_GETDEFAULT, sizeof(*up), 0);
 if (nlh == NULL) {
  kfree_skb(skb);
  return -EMSGSIZE;
 }

 up = nlmsg_data(nlh);
 up->in = net->xfrm.policy_default[XFRM_POLICY_IN];
 up->fwd = net->xfrm.policy_default[XFRM_POLICY_FWD];
 up->out = net->xfrm.policy_default[XFRM_POLICY_OUT];

 nlmsg_end(skb, nlh);

 rcu_read_lock();
 err = xfrm_nlmsg_multicast(net, skb, 0, XFRMNLGRP_POLICY);
 rcu_read_unlock();

 return err;
}

static bool xfrm_userpolicy_is_valid(__u8 policy)
{
 return policy == XFRM_USERPOLICY_BLOCK ||
        policy == XFRM_USERPOLICY_ACCEPT;
}

static int xfrm_set_default(struct sk_buff *skb, struct nlmsghdr *nlh,
       struct nlattr **attrs, struct netlink_ext_ack *extack)
{
 struct net *net = sock_net(skb->sk);
 struct xfrm_userpolicy_default *up = nlmsg_data(nlh);

 if (xfrm_userpolicy_is_valid(up->in))
  net->xfrm.policy_default[XFRM_POLICY_IN] = up->in;

 if (xfrm_userpolicy_is_valid(up->fwd))
  net->xfrm.policy_default[XFRM_POLICY_FWD] = up->fwd;

 if (xfrm_userpolicy_is_valid(up->out))
  net->xfrm.policy_default[XFRM_POLICY_OUT] = up->out;

 rt_genid_bump_all(net);

 xfrm_notify_userpolicy(net);
 return 0;
}

static int xfrm_get_default(struct sk_buff *skb, struct nlmsghdr *nlh,
       struct nlattr **attrs, struct netlink_ext_ack *extack)
{
 struct sk_buff *r_skb;
 struct nlmsghdr *r_nlh;
 struct net *net = sock_net(skb->sk);
 struct xfrm_userpolicy_default *r_up;
 int len = NLMSG_ALIGN(sizeof(struct xfrm_userpolicy_default));
 u32 portid = NETLINK_CB(skb).portid;
 u32 seq = nlh->nlmsg_seq;

 r_skb = nlmsg_new(len, GFP_ATOMIC);
 if (!r_skb)
  return -ENOMEM;

 r_nlh = nlmsg_put(r_skb, portid, seq, XFRM_MSG_GETDEFAULT, sizeof(*r_up), 0);
 if (!r_nlh) {
  kfree_skb(r_skb);
  return -EMSGSIZE;
 }

 r_up = nlmsg_data(r_nlh);
 r_up->in = net->xfrm.policy_default[XFRM_POLICY_IN];
 r_up->fwd = net->xfrm.policy_default[XFRM_POLICY_FWD];
 r_up->out = net->xfrm.policy_default[XFRM_POLICY_OUT];
 nlmsg_end(r_skb, r_nlh);

 return nlmsg_unicast(net->xfrm.nlsk, r_skb, portid);
}

static int xfrm_get_policy(struct sk_buff *skb, struct nlmsghdr *nlh,
      struct nlattr **attrs,
      struct netlink_ext_ack *extack)
{
 struct net *net = sock_net(skb->sk);
 struct xfrm_policy *xp;
 struct xfrm_userpolicy_id *p;
 u8 type = XFRM_POLICY_TYPE_MAIN;
 int err;
 struct km_event c;
 int delete;
 struct xfrm_mark m;
 u32 if_id = 0;

 p = nlmsg_data(nlh);
 delete = nlh->nlmsg_type == XFRM_MSG_DELPOLICY;

 err = copy_from_user_policy_type(&type, attrs, extack);
 if (err)
  return err;

 err = verify_policy_dir(p->dir, extack);
 if (err)
  return err;

 if (attrs[XFRMA_IF_ID])
  if_id = nla_get_u32(attrs[XFRMA_IF_ID]);

 xfrm_mark_get(attrs, &m);

 if (p->index)
  xp = xfrm_policy_byid(net, &m, if_id, type, p->dir,
          p->index, delete, &err);
 else {
  struct nlattr *rt = attrs[XFRMA_SEC_CTX];
  struct xfrm_sec_ctx *ctx;

  err = verify_sec_ctx_len(attrs, extack);
  if (err)
   return err;

  ctx = NULL;
  if (rt) {
   struct xfrm_user_sec_ctx *uctx = nla_data(rt);

   err = security_xfrm_policy_alloc(&ctx, uctx, GFP_KERNEL);
   if (err)
    return err;
  }
  xp = xfrm_policy_bysel_ctx(net, &m, if_id, type, p->dir,
        &p->sel, ctx, delete, &err);
  security_xfrm_policy_free(ctx);
 }
 if (xp == NULL)
  return -ENOENT;

 if (!delete) {
  struct sk_buff *resp_skb;

  resp_skb = xfrm_policy_netlink(skb, xp, p->dir, nlh->nlmsg_seq);
  if (IS_ERR(resp_skb)) {
   err = PTR_ERR(resp_skb);
  } else {
   err = nlmsg_unicast(net->xfrm.nlsk, resp_skb,
         NETLINK_CB(skb).portid);
  }
 } else {
  xfrm_audit_policy_delete(xp, err ? 0 : 1, true);

  if (err != 0)
   goto out;

  c.data.byid = p->index;
  c.event = nlh->nlmsg_type;
  c.seq = nlh->nlmsg_seq;
  c.portid = nlh->nlmsg_pid;
  km_policy_notify(xp, p->dir, &c);
 }

out:
 xfrm_pol_put(xp);
 return err;
}

static int xfrm_flush_sa(struct sk_buff *skb, struct nlmsghdr *nlh,
    struct nlattr **attrs,
    struct netlink_ext_ack *extack)
{
 struct net *net = sock_net(skb->sk);
 struct km_event c;
 struct xfrm_usersa_flush *p = nlmsg_data(nlh);
 int err;

 err = xfrm_state_flush(net, p->proto, true);
 if (err) {
  if (err == -ESRCH) /* empty table */
   return 0;
  return err;
 }
 c.data.proto = p->proto;
 c.event = nlh->nlmsg_type;
 c.seq = nlh->nlmsg_seq;
 c.portid = nlh->nlmsg_pid;
 c.net = net;
 km_state_notify(NULL, &c);

 return 0;
}

static inline unsigned int xfrm_aevent_msgsize(struct xfrm_state *x)
{
 unsigned int replay_size = x->replay_esn ?
         xfrm_replay_state_esn_len(x->replay_esn) :
         sizeof(struct xfrm_replay_state);

 return NLMSG_ALIGN(sizeof(struct xfrm_aevent_id))
        + nla_total_size(replay_size)
        + nla_total_size_64bit(sizeof(struct xfrm_lifetime_cur))
        + nla_total_size(sizeof(struct xfrm_mark))
        + nla_total_size(4) /* XFRM_AE_RTHR */
        + nla_total_size(4) /* XFRM_AE_ETHR */
        + nla_total_size(sizeof(x->dir)) /* XFRMA_SA_DIR */
        + nla_total_size(4); /* XFRMA_SA_PCPU */
}

static int build_aevent(struct sk_buff *skb, struct xfrm_state *x, const struct km_event *c)
{
 struct xfrm_aevent_id *id;
 struct nlmsghdr *nlh;
 int err;

 nlh = nlmsg_put(skb, c->portid, c->seq, XFRM_MSG_NEWAE, sizeof(*id), 0);
 if (nlh == NULL)
  return -EMSGSIZE;

 id = nlmsg_data(nlh);
 memset(&id->sa_id, 0, sizeof(id->sa_id));
 memcpy(&id->sa_id.daddr, &x->id.daddr, sizeof(x->id.daddr));
 id->sa_id.spi = x->id.spi;
 id->sa_id.family = x->props.family;
 id->sa_id.proto = x->id.proto;
 memcpy(&id->saddr, &x->props.saddr, sizeof(x->props.saddr));
 id->reqid = x->props.reqid;
 id->flags = c->data.aevent;

 if (x->replay_esn) {
  err = nla_put(skb, XFRMA_REPLAY_ESN_VAL,
         xfrm_replay_state_esn_len(x->replay_esn),
         x->replay_esn);
 } else {
  err = nla_put(skb, XFRMA_REPLAY_VAL, sizeof(x->replay),
         &x->replay);
 }
 if (err)
  goto out_cancel;
 err = nla_put_64bit(skb, XFRMA_LTIME_VAL, sizeof(x->curlft), &x->curlft,
       XFRMA_PAD);
 if (err)
  goto out_cancel;

 if (id->flags & XFRM_AE_RTHR) {
  err = nla_put_u32(skb, XFRMA_REPLAY_THRESH, x->replay_maxdiff);
  if (err)
   goto out_cancel;
 }
 if (id->flags & XFRM_AE_ETHR) {
  err = nla_put_u32(skb, XFRMA_ETIMER_THRESH,
      x->replay_maxage * 10 / HZ);
  if (err)
   goto out_cancel;
 }
 err = xfrm_mark_put(skb, &x->mark);
 if (err)
  goto out_cancel;

 err = xfrm_if_id_put(skb, x->if_id);
 if (err)
  goto out_cancel;
 if (x->pcpu_num != UINT_MAX) {
  err = nla_put_u32(skb, XFRMA_SA_PCPU, x->pcpu_num);
  if (err)
   goto out_cancel;
 }

 if (x->dir) {
  err = nla_put_u8(skb, XFRMA_SA_DIR, x->dir);
  if (err)
   goto out_cancel;
 }

 nlmsg_end(skb, nlh);
 return 0;

out_cancel:
 nlmsg_cancel(skb, nlh);
 return err;
}

static int xfrm_get_ae(struct sk_buff *skb, struct nlmsghdr *nlh,
         struct nlattr **attrs, struct netlink_ext_ack *extack)
{
 struct net *net = sock_net(skb->sk);
 struct xfrm_state *x;
 struct sk_buff *r_skb;
 int err;
 struct km_event c;
 u32 mark;
 struct xfrm_mark m;
 struct xfrm_aevent_id *p = nlmsg_data(nlh);
 struct xfrm_usersa_id *id = &p->sa_id;

 mark = xfrm_mark_get(attrs, &m);

 x = xfrm_state_lookup(net, mark, &id->daddr, id->spi, id->proto, id->family);
 if (x == NULL)
  return -ESRCH;

 r_skb = nlmsg_new(xfrm_aevent_msgsize(x), GFP_ATOMIC);
 if (r_skb == NULL) {
  xfrm_state_put(x);
  return -ENOMEM;
 }

 /*
 * XXX: is this lock really needed - none of the other
 * gets lock (the concern is things getting updated
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=95 H=95 G=94

¤ Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.0.22Bemerkung:  ¤

*Bot Zugriff






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.