Commit dcf280ea authored by Steffen Klassert's avatar Steffen Klassert

Merge remote branch 'xfrm: Introduce direction attribute for SA'

Antony Antony says:

====================
Inspired by the upcoming IP-TFS patch set, and confusions experienced in
the past due to lack of direction attribute on SAs, add a new direction
"dir" attribute. It aims to streamline the SA configuration process and
enhance the clarity of existing SA attributes.

This patch set introduces the 'dir' attribute to SA, aka xfrm_state,
('in' for input or 'out' for output). Alsp add validations of existing
direction-specific SA attributes during configuration and in the data
path lookup.

This change would not affect any existing use case or way of configuring
SA. You will notice improvements when the new 'dir' attribute is set.
====================
Signed-off-by: default avatarSteffen Klassert <steffen.klassert@secunet.com>
parents aeb48a42 451b5096
......@@ -73,6 +73,9 @@ XfrmAcquireError:
XfrmFwdHdrError:
Forward routing of a packet is not allowed
XfrmInStateDirError:
State direction mismatch (lookup found an output state on the input path, expected input or no direction)
Outbound errors
~~~~~~~~~~~~~~~
XfrmOutError:
......@@ -111,3 +114,6 @@ XfrmOutPolError:
XfrmOutStateInvalid:
State is invalid, perhaps expired
XfrmOutStateDirError:
State direction mismatch (lookup found an input state on the output path, expected output or no direction)
......@@ -291,6 +291,7 @@ struct xfrm_state {
/* Private data of this transformer, format is opaque,
* interpreted by xfrm_type methods. */
void *data;
u8 dir;
};
static inline struct net *xs_net(struct xfrm_state *x)
......
......@@ -337,6 +337,8 @@ enum
LINUX_MIB_XFRMFWDHDRERROR, /* XfrmFwdHdrError*/
LINUX_MIB_XFRMOUTSTATEINVALID, /* XfrmOutStateInvalid */
LINUX_MIB_XFRMACQUIREERROR, /* XfrmAcquireError */
LINUX_MIB_XFRMOUTSTATEDIRERROR, /* XfrmOutStateDirError */
LINUX_MIB_XFRMINSTATEDIRERROR, /* XfrmInStateDirError */
__LINUX_MIB_XFRMMAX
};
......
......@@ -141,6 +141,11 @@ enum {
XFRM_POLICY_MAX = 3
};
enum xfrm_sa_dir {
XFRM_SA_DIR_IN = 1,
XFRM_SA_DIR_OUT = 2
};
enum {
XFRM_SHARE_ANY, /* No limitations */
XFRM_SHARE_SESSION, /* For this session only */
......@@ -315,6 +320,7 @@ enum xfrm_attr_type_t {
XFRMA_SET_MARK_MASK, /* __u32 */
XFRMA_IF_ID, /* __u32 */
XFRMA_MTIMER_THRESH, /* __u32 in seconds for input SA */
XFRMA_SA_DIR, /* __u8 */
__XFRMA_MAX
#define XFRMA_OUTPUT_MARK XFRMA_SET_MARK /* Compatibility */
......
......@@ -266,6 +266,13 @@ int xfrm6_input_addr(struct sk_buff *skb, xfrm_address_t *daddr,
if (!x)
continue;
if (unlikely(x->dir && x->dir != XFRM_SA_DIR_IN)) {
XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATEDIRERROR);
xfrm_state_put(x);
x = NULL;
continue;
}
spin_lock(&x->lock);
if ((!i || (x->props.flags & XFRM_STATE_WILDRECV)) &&
......
......@@ -98,6 +98,7 @@ static const int compat_msg_min[XFRM_NR_MSGTYPES] = {
};
static const struct nla_policy compat_policy[XFRMA_MAX+1] = {
[XFRMA_UNSPEC] = { .strict_start_type = XFRMA_SA_DIR },
[XFRMA_SA] = { .len = XMSGSIZE(compat_xfrm_usersa_info)},
[XFRMA_POLICY] = { .len = XMSGSIZE(compat_xfrm_userpolicy_info)},
[XFRMA_LASTUSED] = { .type = NLA_U64},
......@@ -129,6 +130,7 @@ static const struct nla_policy compat_policy[XFRMA_MAX+1] = {
[XFRMA_SET_MARK_MASK] = { .type = NLA_U32 },
[XFRMA_IF_ID] = { .type = NLA_U32 },
[XFRMA_MTIMER_THRESH] = { .type = NLA_U32 },
[XFRMA_SA_DIR] = NLA_POLICY_RANGE(NLA_U8, XFRM_SA_DIR_IN, XFRM_SA_DIR_OUT),
};
static struct nlmsghdr *xfrm_nlmsg_put_compat(struct sk_buff *skb,
......@@ -277,9 +279,10 @@ static int xfrm_xlate64_attr(struct sk_buff *dst, const struct nlattr *src)
case XFRMA_SET_MARK_MASK:
case XFRMA_IF_ID:
case XFRMA_MTIMER_THRESH:
case XFRMA_SA_DIR:
return xfrm_nla_cpy(dst, src, nla_len(src));
default:
BUILD_BUG_ON(XFRMA_MAX != XFRMA_MTIMER_THRESH);
BUILD_BUG_ON(XFRMA_MAX != XFRMA_SA_DIR);
pr_warn_once("unsupported nla_type %d\n", src->nla_type);
return -EOPNOTSUPP;
}
......@@ -434,7 +437,7 @@ static int xfrm_xlate32_attr(void *dst, const struct nlattr *nla,
int err;
if (type > XFRMA_MAX) {
BUILD_BUG_ON(XFRMA_MAX != XFRMA_MTIMER_THRESH);
BUILD_BUG_ON(XFRMA_MAX != XFRMA_SA_DIR);
NL_SET_ERR_MSG(extack, "Bad attribute");
return -EOPNOTSUPP;
}
......
......@@ -253,6 +253,12 @@ int xfrm_dev_state_add(struct net *net, struct xfrm_state *x,
return -EINVAL;
}
if ((xuo->flags & XFRM_OFFLOAD_INBOUND && x->dir == XFRM_SA_DIR_OUT) ||
(!(xuo->flags & XFRM_OFFLOAD_INBOUND) && x->dir == XFRM_SA_DIR_IN)) {
NL_SET_ERR_MSG(extack, "Mismatched SA and offload direction");
return -EINVAL;
}
is_packet_offload = xuo->flags & XFRM_OFFLOAD_PACKET;
/* We don't yet support UDP encapsulation and TFC padding. */
......
......@@ -466,6 +466,11 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
if (encap_type < 0 || (xo && xo->flags & XFRM_GRO)) {
x = xfrm_input_state(skb);
if (unlikely(x->dir && x->dir != XFRM_SA_DIR_IN)) {
XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATEDIRERROR);
goto drop;
}
if (unlikely(x->km.state != XFRM_STATE_VALID)) {
if (x->km.state == XFRM_STATE_ACQ)
XFRM_INC_STATS(net, LINUX_MIB_XFRMACQUIREERROR);
......@@ -571,6 +576,12 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
goto drop;
}
if (unlikely(x->dir && x->dir != XFRM_SA_DIR_IN)) {
XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATEDIRERROR);
xfrm_state_put(x);
goto drop;
}
skb->mark = xfrm_smark_get(skb->mark, x);
sp->xvec[sp->len++] = x;
......
......@@ -2489,6 +2489,12 @@ xfrm_tmpl_resolve_one(struct xfrm_policy *policy, const struct flowi *fl,
x = xfrm_state_find(remote, local, fl, tmpl, policy, &error,
family, policy->if_id);
if (x && x->dir && x->dir != XFRM_SA_DIR_OUT) {
XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTSTATEDIRERROR);
xfrm_state_put(x);
error = -EINVAL;
goto fail;
}
if (x && x->km.state == XFRM_STATE_VALID) {
xfrm[nx++] = x;
......
......@@ -41,6 +41,8 @@ static const struct snmp_mib xfrm_mib_list[] = {
SNMP_MIB_ITEM("XfrmFwdHdrError", LINUX_MIB_XFRMFWDHDRERROR),
SNMP_MIB_ITEM("XfrmOutStateInvalid", LINUX_MIB_XFRMOUTSTATEINVALID),
SNMP_MIB_ITEM("XfrmAcquireError", LINUX_MIB_XFRMACQUIREERROR),
SNMP_MIB_ITEM("XfrmOutStateDirError", LINUX_MIB_XFRMOUTSTATEDIRERROR),
SNMP_MIB_ITEM("XfrmInStateDirError", LINUX_MIB_XFRMINSTATEDIRERROR),
SNMP_MIB_SENTINEL
};
......
......@@ -778,7 +778,8 @@ int xfrm_init_replay(struct xfrm_state *x, struct netlink_ext_ack *extack)
}
if (x->props.flags & XFRM_STATE_ESN) {
if (replay_esn->replay_window == 0) {
if (replay_esn->replay_window == 0 &&
(!x->dir || x->dir == XFRM_SA_DIR_IN)) {
NL_SET_ERR_MSG(extack, "ESN replay window must be > 0");
return -EINVAL;
}
......
......@@ -1292,6 +1292,7 @@ xfrm_state_find(const xfrm_address_t *daddr, const xfrm_address_t *saddr,
if (km_query(x, tmpl, pol) == 0) {
spin_lock_bh(&net->xfrm.xfrm_state_lock);
x->km.state = XFRM_STATE_ACQ;
x->dir = XFRM_SA_DIR_OUT;
list_add(&x->km.all, &net->xfrm.state_all);
XFRM_STATE_INSERT(bydst, &x->bydst,
net->xfrm.state_bydst + h,
......@@ -1744,6 +1745,7 @@ static struct xfrm_state *xfrm_state_clone(struct xfrm_state *orig,
x->lastused = orig->lastused;
x->new_mapping = 0;
x->new_mapping_sport = 0;
x->dir = orig->dir;
return x;
......@@ -1864,8 +1866,14 @@ int xfrm_state_update(struct xfrm_state *x)
}
if (x1->km.state == XFRM_STATE_ACQ) {
if (x->dir && x1->dir != x->dir)
goto out;
__xfrm_state_insert(x);
x = NULL;
} else {
if (x1->dir != x->dir)
goto out;
}
err = 0;
......
......@@ -130,7 +130,7 @@ static inline int verify_sec_ctx_len(struct nlattr **attrs, struct netlink_ext_a
}
static inline int verify_replay(struct xfrm_usersa_info *p,
struct nlattr **attrs,
struct nlattr **attrs, u8 sa_dir,
struct netlink_ext_ack *extack)
{
struct nlattr *rt = attrs[XFRMA_REPLAY_ESN_VAL];
......@@ -168,6 +168,30 @@ static inline int verify_replay(struct xfrm_usersa_info *p,
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 (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;
}
}
return 0;
}
......@@ -176,6 +200,7 @@ static int verify_newsa_info(struct xfrm_usersa_info *p,
struct netlink_ext_ack *extack)
{
int err;
u8 sa_dir = attrs[XFRMA_SA_DIR] ? nla_get_u8(attrs[XFRMA_SA_DIR]) : 0;
err = -EINVAL;
switch (p->family) {
......@@ -334,7 +359,7 @@ static int verify_newsa_info(struct xfrm_usersa_info *p,
goto out;
if ((err = verify_sec_ctx_len(attrs, extack)))
goto out;
if ((err = verify_replay(p, attrs, extack)))
if ((err = verify_replay(p, attrs, sa_dir, extack)))
goto out;
err = -EINVAL;
......@@ -358,6 +383,77 @@ static int verify_newsa_info(struct xfrm_usersa_info *p,
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_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;
}
}
}
out:
......@@ -734,6 +830,9 @@ static struct xfrm_state *xfrm_state_construct(struct net *net,
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]);
err = __xfrm_init_state(x, false, attrs[XFRMA_OFFLOAD_DEV], extack);
if (err)
goto error;
......@@ -1182,8 +1281,13 @@ static int copy_to_user_state_extra(struct xfrm_state *x,
if (ret)
goto out;
}
if (x->mapping_maxage)
if (x->mapping_maxage) {
ret = nla_put_u32(skb, XFRMA_MTIMER_THRESH, x->mapping_maxage);
if (ret)
goto out;
}
if (x->dir)
ret = nla_put_u8(skb, XFRMA_SA_DIR, x->dir);
out:
return ret;
}
......@@ -1618,6 +1722,9 @@ static int xfrm_alloc_userspi(struct sk_buff *skb, struct nlmsghdr *nlh,
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);
......@@ -2402,7 +2509,8 @@ static inline unsigned int xfrm_aevent_msgsize(struct xfrm_state *x)
+ 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(4) /* XFRM_AE_ETHR */
+ nla_total_size(sizeof(x->dir)); /* XFRMA_SA_DIR */
}
static int build_aevent(struct sk_buff *skb, struct xfrm_state *x, const struct km_event *c)
......@@ -2459,6 +2567,12 @@ static int build_aevent(struct sk_buff *skb, struct xfrm_state *x, const struct
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;
......@@ -3018,6 +3132,7 @@ EXPORT_SYMBOL_GPL(xfrm_msg_min);
#undef XMSGSIZE
const struct nla_policy xfrma_policy[XFRMA_MAX+1] = {
[XFRMA_UNSPEC] = { .strict_start_type = XFRMA_SA_DIR },
[XFRMA_SA] = { .len = sizeof(struct xfrm_usersa_info)},
[XFRMA_POLICY] = { .len = sizeof(struct xfrm_userpolicy_info)},
[XFRMA_LASTUSED] = { .type = NLA_U64},
......@@ -3049,6 +3164,7 @@ const struct nla_policy xfrma_policy[XFRMA_MAX+1] = {
[XFRMA_SET_MARK_MASK] = { .type = NLA_U32 },
[XFRMA_IF_ID] = { .type = NLA_U32 },
[XFRMA_MTIMER_THRESH] = { .type = NLA_U32 },
[XFRMA_SA_DIR] = NLA_POLICY_RANGE(NLA_U8, XFRM_SA_DIR_IN, XFRM_SA_DIR_OUT),
};
EXPORT_SYMBOL_GPL(xfrma_policy);
......@@ -3097,6 +3213,24 @@ static const struct xfrm_link {
[XFRM_MSG_GETDEFAULT - XFRM_MSG_BASE] = { .doit = xfrm_get_default },
};
static int xfrm_reject_unused_attr(int type, struct nlattr **attrs,
struct netlink_ext_ack *extack)
{
if (attrs[XFRMA_SA_DIR]) {
switch (type) {
case XFRM_MSG_NEWSA:
case XFRM_MSG_UPDSA:
case XFRM_MSG_ALLOCSPI:
break;
default:
NL_SET_ERR_MSG(extack, "Invalid attribute SA_DIR");
return -EINVAL;
}
}
return 0;
}
static int xfrm_user_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh,
struct netlink_ext_ack *extack)
{
......@@ -3156,6 +3290,12 @@ static int xfrm_user_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh,
if (err < 0)
goto err;
if (!link->nla_pol || link->nla_pol == xfrma_policy) {
err = xfrm_reject_unused_attr((type + XFRM_MSG_BASE), attrs, extack);
if (err < 0)
goto err;
}
if (link->doit == NULL) {
err = -EINVAL;
goto err;
......@@ -3189,8 +3329,9 @@ static void xfrm_netlink_rcv(struct sk_buff *skb)
static inline unsigned int xfrm_expire_msgsize(void)
{
return NLMSG_ALIGN(sizeof(struct xfrm_user_expire))
+ nla_total_size(sizeof(struct xfrm_mark));
return NLMSG_ALIGN(sizeof(struct xfrm_user_expire)) +
nla_total_size(sizeof(struct xfrm_mark)) +
nla_total_size(sizeof_field(struct xfrm_state, dir));
}
static int build_expire(struct sk_buff *skb, struct xfrm_state *x, const struct km_event *c)
......@@ -3217,6 +3358,12 @@ static int build_expire(struct sk_buff *skb, struct xfrm_state *x, const struct
if (err)
return err;
if (x->dir) {
err = nla_put_u8(skb, XFRMA_SA_DIR, x->dir);
if (err)
return err;
}
nlmsg_end(skb, nlh);
return 0;
}
......@@ -3324,6 +3471,9 @@ static inline unsigned int xfrm_sa_len(struct xfrm_state *x)
if (x->mapping_maxage)
l += nla_total_size(sizeof(x->mapping_maxage));
if (x->dir)
l += nla_total_size(sizeof(x->dir));
return l;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment