xfrm: Add Direction to the SA in or out
authorAntony Antony <antony.antony@secunet.com>
Tue, 30 Apr 2024 07:08:52 +0000 (09:08 +0200)
committerSteffen Klassert <steffen.klassert@secunet.com>
Wed, 1 May 2024 08:05:11 +0000 (10:05 +0200)
This patch introduces the 'dir' attribute, 'in' or 'out', to the
xfrm_state, SA, enhancing usability by delineating the scope of values
based on direction. An input SA will restrict values pertinent to input,
effectively segregating them from output-related values.
And an output SA will restrict attributes for output. This change aims
to streamline the configuration process and improve the overall
consistency of SA attributes during configuration.

This feature sets the groundwork for future patches, including
the upcoming IP-TFS patch.

Signed-off-by: Antony Antony <antony.antony@secunet.com>
Reviewed-by: Sabrina Dubroca <sd@queasysnail.net>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
include/net/xfrm.h
include/uapi/linux/xfrm.h
net/xfrm/xfrm_compat.c
net/xfrm/xfrm_device.c
net/xfrm/xfrm_replay.c
net/xfrm/xfrm_state.c
net/xfrm/xfrm_user.c

index 57c743b7e4fef1146ff49d23b38f54c0c7b99c1a..7c9be06f830216ecd6f5d2942c2380c52fd5cc89 100644 (file)
@@ -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)
index 6a77328be114525b5eabd8138c25fc1b99d517b0..18ceaba8486e4eaea295a6dea9ce885896b92092 100644 (file)
@@ -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 */
index 655fe4ff862122738eb4ab8089ed3a1e45f4cdf1..703d4172c7d730ed6d8ccef85eb9e8bf04e08671 100644 (file)
@@ -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;
        }
index 6346690d5c699d4d9f9b46aeeacaa923c0c32256..2455a76a1cffb4560f7fba34bdd2de03a492fb35 100644 (file)
@@ -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. */
index ce56d659c55a692ed6ecf2d1fd4c31e6eff03449..bc56c6305725275ea2286373543af5d9333e34f4 100644 (file)
@@ -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;
                        }
index 0c306473a79d13321ae8ab93b97fe717c00cdab3..649bb739df0dd672322f09d272d4ba19faf12216 100644 (file)
@@ -1292,6 +1292,7 @@ found:
                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;
 
index 810b520493f329bff19ded6407d7968f57887c16..f5eb3af4fb8111ba0c0141995f229c03f8817e8f 100644 (file)
@@ -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);
 
@@ -3189,8 +3305,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 +3334,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 +3447,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;
 }