]> Pileus Git - ~andy/linux/blobdiff - net/core/rtnetlink.c
rtnetlink: fix oops in rtnl_link_get_slave_info_data_size
[~andy/linux] / net / core / rtnetlink.c
index cf67144d3e3c28f5fa2ab9965fba2884b03130d1..048dc8d183aa9f9f105c0d4615b03d8ebd75931b 100644 (file)
@@ -365,6 +365,22 @@ void rtnl_link_unregister(struct rtnl_link_ops *ops)
 }
 EXPORT_SYMBOL_GPL(rtnl_link_unregister);
 
+static size_t rtnl_link_get_slave_info_data_size(const struct net_device *dev)
+{
+       struct net_device *master_dev;
+       const struct rtnl_link_ops *ops;
+
+       master_dev = netdev_master_upper_dev_get((struct net_device *) dev);
+       if (!master_dev)
+               return 0;
+       ops = master_dev->rtnl_link_ops;
+       if (!ops || !ops->get_slave_size)
+               return 0;
+       /* IFLA_INFO_SLAVE_DATA + nested data */
+       return nla_total_size(sizeof(struct nlattr)) +
+              ops->get_slave_size(master_dev, dev);
+}
+
 static size_t rtnl_link_get_size(const struct net_device *dev)
 {
        const struct rtnl_link_ops *ops = dev->rtnl_link_ops;
@@ -385,6 +401,8 @@ static size_t rtnl_link_get_size(const struct net_device *dev)
                /* IFLA_INFO_XSTATS */
                size += nla_total_size(ops->get_xstats_size(dev));
 
+       size += rtnl_link_get_slave_info_data_size(dev);
+
        return size;
 }
 
@@ -402,35 +420,17 @@ static const struct rtnl_af_ops *rtnl_af_lookup(const int family)
        return NULL;
 }
 
-/**
- * __rtnl_af_register - Register rtnl_af_ops with rtnetlink.
- * @ops: struct rtnl_af_ops * to register
- *
- * The caller must hold the rtnl_mutex.
- *
- * Returns 0 on success or a negative error code.
- */
-int __rtnl_af_register(struct rtnl_af_ops *ops)
-{
-       list_add_tail(&ops->list, &rtnl_af_ops);
-       return 0;
-}
-EXPORT_SYMBOL_GPL(__rtnl_af_register);
-
 /**
  * rtnl_af_register - Register rtnl_af_ops with rtnetlink.
  * @ops: struct rtnl_af_ops * to register
  *
  * Returns 0 on success or a negative error code.
  */
-int rtnl_af_register(struct rtnl_af_ops *ops)
+void rtnl_af_register(struct rtnl_af_ops *ops)
 {
-       int err;
-
        rtnl_lock();
-       err = __rtnl_af_register(ops);
+       list_add_tail(&ops->list, &rtnl_af_ops);
        rtnl_unlock();
-       return err;
 }
 EXPORT_SYMBOL_GPL(rtnl_af_register);
 
@@ -477,40 +477,100 @@ static size_t rtnl_link_get_af_size(const struct net_device *dev)
        return size;
 }
 
-static int rtnl_link_fill(struct sk_buff *skb, const struct net_device *dev)
+static bool rtnl_have_link_slave_info(const struct net_device *dev)
 {
-       const struct rtnl_link_ops *ops = dev->rtnl_link_ops;
-       struct nlattr *linkinfo, *data;
-       int err = -EMSGSIZE;
+       struct net_device *master_dev;
 
-       linkinfo = nla_nest_start(skb, IFLA_LINKINFO);
-       if (linkinfo == NULL)
-               goto out;
+       master_dev = netdev_master_upper_dev_get((struct net_device *) dev);
+       if (master_dev && master_dev->rtnl_link_ops)
+               return true;
+       return false;
+}
+
+static int rtnl_link_slave_info_fill(struct sk_buff *skb,
+                                    const struct net_device *dev)
+{
+       struct net_device *master_dev;
+       const struct rtnl_link_ops *ops;
+       struct nlattr *slave_data;
+       int err;
+
+       master_dev = netdev_master_upper_dev_get((struct net_device *) dev);
+       if (!master_dev)
+               return 0;
+       ops = master_dev->rtnl_link_ops;
+       if (!ops)
+               return 0;
+       if (nla_put_string(skb, IFLA_INFO_SLAVE_KIND, ops->kind) < 0)
+               return -EMSGSIZE;
+       if (ops->fill_slave_info) {
+               slave_data = nla_nest_start(skb, IFLA_INFO_SLAVE_DATA);
+               if (!slave_data)
+                       return -EMSGSIZE;
+               err = ops->fill_slave_info(skb, master_dev, dev);
+               if (err < 0)
+                       goto err_cancel_slave_data;
+               nla_nest_end(skb, slave_data);
+       }
+       return 0;
+
+err_cancel_slave_data:
+       nla_nest_cancel(skb, slave_data);
+       return err;
+}
+
+static int rtnl_link_info_fill(struct sk_buff *skb,
+                              const struct net_device *dev)
+{
+       const struct rtnl_link_ops *ops = dev->rtnl_link_ops;
+       struct nlattr *data;
+       int err;
 
+       if (!ops)
+               return 0;
        if (nla_put_string(skb, IFLA_INFO_KIND, ops->kind) < 0)
-               goto err_cancel_link;
+               return -EMSGSIZE;
        if (ops->fill_xstats) {
                err = ops->fill_xstats(skb, dev);
                if (err < 0)
-                       goto err_cancel_link;
+                       return err;
        }
        if (ops->fill_info) {
                data = nla_nest_start(skb, IFLA_INFO_DATA);
-               if (data == NULL) {
-                       err = -EMSGSIZE;
-                       goto err_cancel_link;
-               }
+               if (data == NULL)
+                       return -EMSGSIZE;
                err = ops->fill_info(skb, dev);
                if (err < 0)
                        goto err_cancel_data;
                nla_nest_end(skb, data);
        }
-
-       nla_nest_end(skb, linkinfo);
        return 0;
 
 err_cancel_data:
        nla_nest_cancel(skb, data);
+       return err;
+}
+
+static int rtnl_link_fill(struct sk_buff *skb, const struct net_device *dev)
+{
+       struct nlattr *linkinfo;
+       int err = -EMSGSIZE;
+
+       linkinfo = nla_nest_start(skb, IFLA_LINKINFO);
+       if (linkinfo == NULL)
+               goto out;
+
+       err = rtnl_link_info_fill(skb, dev);
+       if (err < 0)
+               goto err_cancel_link;
+
+       err = rtnl_link_slave_info_fill(skb, dev);
+       if (err < 0)
+               goto err_cancel_link;
+
+       nla_nest_end(skb, linkinfo);
+       return 0;
+
 err_cancel_link:
        nla_nest_cancel(skb, linkinfo);
 out:
@@ -1019,7 +1079,7 @@ static int rtnl_fill_ifinfo(struct sk_buff *skb, struct net_device *dev,
        if (rtnl_port_fill(skb, dev))
                goto nla_put_failure;
 
-       if (dev->rtnl_link_ops) {
+       if (dev->rtnl_link_ops || rtnl_have_link_slave_info(dev)) {
                if (rtnl_link_fill(skb, dev) < 0)
                        goto nla_put_failure;
        }
@@ -1142,6 +1202,8 @@ EXPORT_SYMBOL(ifla_policy);
 static const struct nla_policy ifla_info_policy[IFLA_INFO_MAX+1] = {
        [IFLA_INFO_KIND]        = { .type = NLA_STRING },
        [IFLA_INFO_DATA]        = { .type = NLA_NESTED },
+       [IFLA_INFO_SLAVE_KIND]  = { .type = NLA_STRING },
+       [IFLA_INFO_SLAVE_DATA]  = { .type = NLA_NESTED },
 };
 
 static const struct nla_policy ifla_vfinfo_policy[IFLA_VF_INFO_MAX+1] = {
@@ -1729,7 +1791,9 @@ static int rtnl_newlink(struct sk_buff *skb, struct nlmsghdr *nlh)
 {
        struct net *net = sock_net(skb->sk);
        const struct rtnl_link_ops *ops;
+       const struct rtnl_link_ops *m_ops = NULL;
        struct net_device *dev;
+       struct net_device *master_dev = NULL;
        struct ifinfomsg *ifm;
        char kind[MODULE_NAME_LEN];
        char ifname[IFNAMSIZ];
@@ -1759,6 +1823,12 @@ replay:
                        dev = NULL;
        }
 
+       if (dev) {
+               master_dev = netdev_master_upper_dev_get(dev);
+               if (master_dev)
+                       m_ops = master_dev->rtnl_link_ops;
+       }
+
        err = validate_linkmsg(dev, tb);
        if (err < 0)
                return err;
@@ -1780,7 +1850,10 @@ replay:
        }
 
        if (1) {
-               struct nlattr *attr[ops ? ops->maxtype + 1 : 0], **data = NULL;
+               struct nlattr *attr[ops ? ops->maxtype + 1 : 0];
+               struct nlattr *slave_attr[m_ops ? m_ops->slave_maxtype + 1 : 0];
+               struct nlattr **data = NULL;
+               struct nlattr **slave_data = NULL;
                struct net *dest_net;
 
                if (ops) {
@@ -1799,6 +1872,24 @@ replay:
                        }
                }
 
+               if (m_ops) {
+                       if (m_ops->slave_maxtype &&
+                           linkinfo[IFLA_INFO_SLAVE_DATA]) {
+                               err = nla_parse_nested(slave_attr,
+                                                      m_ops->slave_maxtype,
+                                                      linkinfo[IFLA_INFO_SLAVE_DATA],
+                                                      m_ops->slave_policy);
+                               if (err < 0)
+                                       return err;
+                               slave_data = slave_attr;
+                       }
+                       if (m_ops->slave_validate) {
+                               err = m_ops->slave_validate(tb, slave_data);
+                               if (err < 0)
+                                       return err;
+                       }
+               }
+
                if (dev) {
                        int modified = 0;
 
@@ -1818,6 +1909,17 @@ replay:
                                modified = 1;
                        }
 
+                       if (linkinfo[IFLA_INFO_SLAVE_DATA]) {
+                               if (!m_ops || !m_ops->slave_changelink)
+                                       return -EOPNOTSUPP;
+
+                               err = m_ops->slave_changelink(master_dev, dev,
+                                                             tb, slave_data);
+                               if (err < 0)
+                                       return err;
+                               modified = 1;
+                       }
+
                        return do_setlink(dev, ifm, tb, ifname, modified);
                }