xdp: Support specifying expected existing program when attaching XDP
authorToke Høiland-Jørgensen <toke@redhat.com>
Wed, 25 Mar 2020 17:23:26 +0000 (18:23 +0100)
committerAlexei Starovoitov <ast@kernel.org>
Sat, 28 Mar 2020 21:24:41 +0000 (14:24 -0700)
While it is currently possible for userspace to specify that an existing
XDP program should not be replaced when attaching to an interface, there is
no mechanism to safely replace a specific XDP program with another.

This patch adds a new netlink attribute, IFLA_XDP_EXPECTED_FD, which can be
set along with IFLA_XDP_FD. If set, the kernel will check that the program
currently loaded on the interface matches the expected one, and fail the
operation if it does not. This corresponds to a 'cmpxchg' memory operation.
Setting the new attribute with a negative value means that no program is
expected to be attached, which corresponds to setting the UPDATE_IF_NOEXIST
flag.

A new companion flag, XDP_FLAGS_REPLACE, is also added to explicitly
request checking of the EXPECTED_FD attribute. This is needed for userspace
to discover whether the kernel supports the new attribute.

Signed-off-by: Toke Høiland-Jørgensen <toke@redhat.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Reviewed-by: Jakub Kicinski <kuba@kernel.org>
Link: https://lore.kernel.org/bpf/158515700640.92963.3551295145441017022.stgit@toke.dk
include/linux/netdevice.h
include/uapi/linux/if_link.h
net/core/dev.c
net/core/rtnetlink.c

index 654808bfad83d458781c2dc59d2ca80b34055af2..b503d468f0df565b2a8ec00b3a574561daa28016 100644 (file)
@@ -3768,7 +3768,7 @@ struct sk_buff *dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev,
 
 typedef int (*bpf_op_t)(struct net_device *dev, struct netdev_bpf *bpf);
 int dev_change_xdp_fd(struct net_device *dev, struct netlink_ext_ack *extack,
-                     int fd, u32 flags);
+                     int fd, int expected_fd, u32 flags);
 u32 __dev_xdp_query(struct net_device *dev, bpf_op_t xdp_op,
                    enum bpf_netdev_command cmd);
 int xdp_umem_query(struct net_device *dev, u16 queue_id);
index 61e0801c82dfa20fca018254c95dd9abeb9a75fb..c2f768c8d65b5086f40856de6ae89ed9f9f9719d 100644 (file)
@@ -972,11 +972,12 @@ enum {
 #define XDP_FLAGS_SKB_MODE             (1U << 1)
 #define XDP_FLAGS_DRV_MODE             (1U << 2)
 #define XDP_FLAGS_HW_MODE              (1U << 3)
+#define XDP_FLAGS_REPLACE              (1U << 4)
 #define XDP_FLAGS_MODES                        (XDP_FLAGS_SKB_MODE | \
                                         XDP_FLAGS_DRV_MODE | \
                                         XDP_FLAGS_HW_MODE)
 #define XDP_FLAGS_MASK                 (XDP_FLAGS_UPDATE_IF_NOEXIST | \
-                                        XDP_FLAGS_MODES)
+                                        XDP_FLAGS_MODES | XDP_FLAGS_REPLACE)
 
 /* These are stored into IFLA_XDP_ATTACHED on dump. */
 enum {
@@ -996,6 +997,7 @@ enum {
        IFLA_XDP_DRV_PROG_ID,
        IFLA_XDP_SKB_PROG_ID,
        IFLA_XDP_HW_PROG_ID,
+       IFLA_XDP_EXPECTED_FD,
        __IFLA_XDP_MAX,
 };
 
index d84541c2444640cc9ef2de40421212c13e360784..651a3c28d33aa36a8f660af7ff46e9d0fbf39e74 100644 (file)
@@ -8655,15 +8655,17 @@ static void dev_xdp_uninstall(struct net_device *dev)
  *     @dev: device
  *     @extack: netlink extended ack
  *     @fd: new program fd or negative value to clear
+ *     @expected_fd: old program fd that userspace expects to replace or clear
  *     @flags: xdp-related flags
  *
  *     Set or clear a bpf program for a device
  */
 int dev_change_xdp_fd(struct net_device *dev, struct netlink_ext_ack *extack,
-                     int fd, u32 flags)
+                     int fd, int expected_fd, u32 flags)
 {
        const struct net_device_ops *ops = dev->netdev_ops;
        enum bpf_netdev_command query;
+       u32 prog_id, expected_id = 0;
        struct bpf_prog *prog = NULL;
        bpf_op_t bpf_op, bpf_chk;
        bool offload;
@@ -8684,15 +8686,29 @@ int dev_change_xdp_fd(struct net_device *dev, struct netlink_ext_ack *extack,
        if (bpf_op == bpf_chk)
                bpf_chk = generic_xdp_install;
 
-       if (fd >= 0) {
-               u32 prog_id;
+       prog_id = __dev_xdp_query(dev, bpf_op, query);
+       if (flags & XDP_FLAGS_REPLACE) {
+               if (expected_fd >= 0) {
+                       prog = bpf_prog_get_type_dev(expected_fd,
+                                                    BPF_PROG_TYPE_XDP,
+                                                    bpf_op == ops->ndo_bpf);
+                       if (IS_ERR(prog))
+                               return PTR_ERR(prog);
+                       expected_id = prog->aux->id;
+                       bpf_prog_put(prog);
+               }
 
+               if (prog_id != expected_id) {
+                       NL_SET_ERR_MSG(extack, "Active program does not match expected");
+                       return -EEXIST;
+               }
+       }
+       if (fd >= 0) {
                if (!offload && __dev_xdp_query(dev, bpf_chk, XDP_QUERY_PROG)) {
                        NL_SET_ERR_MSG(extack, "native and generic XDP can't be active at the same time");
                        return -EEXIST;
                }
 
-               prog_id = __dev_xdp_query(dev, bpf_op, query);
                if ((flags & XDP_FLAGS_UPDATE_IF_NOEXIST) && prog_id) {
                        NL_SET_ERR_MSG(extack, "XDP program already attached");
                        return -EBUSY;
@@ -8715,7 +8731,7 @@ int dev_change_xdp_fd(struct net_device *dev, struct netlink_ext_ack *extack,
                        return 0;
                }
        } else {
-               if (!__dev_xdp_query(dev, bpf_op, query))
+               if (!prog_id)
                        return 0;
        }
 
index 14e6ea21c3781469673938c57a6df6f053d874a1..709ebbf8ab5bf1e2216d220fe9f8e3003dd3d117 100644 (file)
@@ -1872,7 +1872,9 @@ static const struct nla_policy ifla_port_policy[IFLA_PORT_MAX+1] = {
 };
 
 static const struct nla_policy ifla_xdp_policy[IFLA_XDP_MAX + 1] = {
+       [IFLA_XDP_UNSPEC]       = { .strict_start_type = IFLA_XDP_EXPECTED_FD },
        [IFLA_XDP_FD]           = { .type = NLA_S32 },
+       [IFLA_XDP_EXPECTED_FD]  = { .type = NLA_S32 },
        [IFLA_XDP_ATTACHED]     = { .type = NLA_U8 },
        [IFLA_XDP_FLAGS]        = { .type = NLA_U32 },
        [IFLA_XDP_PROG_ID]      = { .type = NLA_U32 },
@@ -2799,8 +2801,20 @@ static int do_setlink(const struct sk_buff *skb,
                }
 
                if (xdp[IFLA_XDP_FD]) {
+                       int expected_fd = -1;
+
+                       if (xdp_flags & XDP_FLAGS_REPLACE) {
+                               if (!xdp[IFLA_XDP_EXPECTED_FD]) {
+                                       err = -EINVAL;
+                                       goto errout;
+                               }
+                               expected_fd =
+                                       nla_get_s32(xdp[IFLA_XDP_EXPECTED_FD]);
+                       }
+
                        err = dev_change_xdp_fd(dev, extack,
                                                nla_get_s32(xdp[IFLA_XDP_FD]),
+                                               expected_fd,
                                                xdp_flags);
                        if (err)
                                goto errout;