Merge branch 'rtnetlink-Add-support-for-rigid-checking-of-data-in-dump-request'
authorDavid S. Miller <davem@davemloft.net>
Mon, 8 Oct 2018 17:39:06 +0000 (10:39 -0700)
committerDavid S. Miller <davem@davemloft.net>
Mon, 8 Oct 2018 17:39:06 +0000 (10:39 -0700)
David Ahern says:

====================
rtnetlink: Add support for rigid checking of data in dump request

There are many use cases where a user wants to influence what is
returned in a dump for some rtnetlink command: one is wanting data
for a different namespace than the one the request is received and
another is limiting the amount of data returned in the dump to a
specific set of interest to userspace, reducing the cpu overhead of
both kernel and userspace. Unfortunately, the kernel has historically
not been strict with checking for the proper header or checking the
values passed in the header. This lenient implementation has allowed
iproute2 and other packages to pass any struct or data in the dump
request as long as the family is the first byte. For example, ifinfomsg
struct is used by iproute2 for all generic dump requests - links,
addresses, routes and rules when it is really only valid for link
requests.

There is 1 is example where the kernel deals with the wrong struct: link
dumps after VF support was added. Older iproute2 was sending rtgenmsg as
the header instead of ifinfomsg so a patch was added to try and detect
old userspace vs new:
e5eca6d41f53 ("rtnetlink: fix userspace API breakage for iproute2 < v3.9.0")

The latest example is Christian's patch set wanting to return addresses for
a target namespace. It guesses the header struct is an ifaddrmsg and if it
guesses wrong a netlink warning is generated in the kernel log on every
address dump which is unacceptable.

Another example where the kernel is a bit lenient is route dumps: iproute2
can send either a request with either ifinfomsg or a rtmsg as the header
struct, yet the kernel always treats the header as an rtmsg (see
inet_dump_fib and rtm_flags check). The header inconsistency impacts the
ability to add kernel side filters for route dumps - a necessary feature
for scale setups with 100k+ routes.

How to resolve the problem of not breaking old userspace yet be able to
move forward with new features such as kernel side filtering which are
crucial for efficient operation at high scale?

This patch set addresses the problem by adding a new socket flag,
NETLINK_DUMP_STRICT_CHK, that userspace can use with setsockopt to
request strict checking of headers and attributes on dump requests and
hence unlock the ability to use kernel side filters as they are added.

Kernel side, the dump handlers are updated to verify the message contains
at least the expected header struct:
    RTM_GETLINK:       ifinfomsg
    RTM_GETADDR:       ifaddrmsg
    RTM_GETMULTICAST:  ifaddrmsg
    RTM_GETANYCAST:    ifaddrmsg
    RTM_GETADDRLABEL:  ifaddrlblmsg
    RTM_GETROUTE:      rtmsg
    RTM_GETSTATS:      if_stats_msg
    RTM_GETNEIGH:      ndmsg
    RTM_GETNEIGHTBL:   ndtmsg
    RTM_GETNSID:       rtgenmsg
    RTM_GETRULE:       fib_rule_hdr
    RTM_GETNETCONF:    netconfmsg
    RTM_GETMDB:        br_port_msg

And then every field in the header struct should be 0 with the exception
of the family. There are a few exceptions to this rule where the kernel
already influences the data returned by values in the struct. Next the
message should not contain attributes unless the kernel implements
filtering for it. Any unexpected data causes the dump to fail with EINVAL.
If the new flag is honored by the kernel and the dump contents adjusted
by any data passed in the request, the dump handler can set the
NLM_F_DUMP_FILTERED flag in the netlink message header.

For old userspace on new kernel there is no impact as all checks are
wrapped in a check on the new strict flag. For new userspace on old
kernel, the data in the headers and any appended attributes are
silently ignored though the setsockopt failing is the clue to userspace
the feature is not supported. New userspace on new kernel gets the
requested data dump.

iproute2 patches can be found here:
    https://github.com/dsahern/iproute2 dump-enhancements

Major changes since v1
- inner header is supposed to be 4-bytes aligned. So for dumps that
  should not have attributes appended changed the check to use:
        if (nlmsg_attrlen(nlh, sizeof(hdr)))
  Only impacts patches with headers that are not multiples of 4-bytes
  (rtgenmsg, netconfmsg), but applied the change to all patches not
  calling nlmsg_parse for consistency.

- Added nlmsg_parse_strict and nla_parse_strict for tighter control on
  attribute parsing. There should be no unknown attribute types or extra
  bytes.

- Moved validation to a helper in most cases

Changes since rfc-v2
- dropped the NLM_F_DUMP_FILTERED flag from target nsid dumps per
  Jiri's objections
- changed the opt-in uapi from a netlink message flag to a socket
  flag. setsockopt provides an api for userspace to definitively
  know if the kernel supports strict checking on dumps.
- re-ordered patches to peel off the extack on dumps if needed to
  keep this set size within limits
- misc cleanups in patches based on testing
====================

Acked-by: Christian Brauner <christian@brauner.io>
Signed-off-by: David S. Miller <davem@davemloft.net>
27 files changed:
include/linux/netlink.h
include/net/ip_fib.h
include/net/netlink.h
include/uapi/linux/netlink.h
lib/nlattr.c
net/bridge/br_mdb.c
net/core/devlink.c
net/core/fib_rules.c
net/core/neighbour.c
net/core/net_namespace.c
net/core/rtnetlink.c
net/ipv4/devinet.c
net/ipv4/fib_frontend.c
net/ipv4/ipmr.c
net/ipv6/addrconf.c
net/ipv6/addrlabel.c
net/ipv6/ip6_fib.c
net/ipv6/ip6mr.c
net/ipv6/route.c
net/mpls/af_mpls.c
net/netfilter/ipvs/ip_vs_ctl.c
net/netlink/af_netlink.c
net/netlink/af_netlink.h
net/sched/act_api.c
net/sched/cls_api.c
net/sched/sch_api.c
net/xfrm/xfrm_user.c

index 71f121b66ca896a455d4ebf310c94d4a0472eaff..72580f1a72a224cf5cf53c4b0111d3bf0a42e8a6 100644 (file)
@@ -176,8 +176,10 @@ struct netlink_callback {
        void                    *data;
        /* the module that dump function belong to */
        struct module           *module;
+       struct netlink_ext_ack  *extack;
        u16                     family;
        u16                     min_dump_alloc;
+       bool                    strict_check;
        unsigned int            prev_seq, seq;
        long                    args[6];
 };
index f7c109e372987c06f379c673a2e99635e2da477b..9846b79c9ee194e9e4242f5fbc56728c49eccfd5 100644 (file)
@@ -452,4 +452,6 @@ static inline void fib_proc_exit(struct net *net)
 
 u32 ip_mtu_from_fib_result(struct fib_result *res, __be32 daddr);
 
+int ip_valid_fib_dump_req(const struct nlmsghdr *nlh,
+                         struct netlink_ext_ack *extack);
 #endif  /* _NET_FIB_H */
index 589683091f16790290e368449349b9451bb09626..f1db8e594847a7503f083182ba4a717b1a628e40 100644 (file)
@@ -373,6 +373,9 @@ int nla_validate(const struct nlattr *head, int len, int maxtype,
 int nla_parse(struct nlattr **tb, int maxtype, const struct nlattr *head,
              int len, const struct nla_policy *policy,
              struct netlink_ext_ack *extack);
+int nla_parse_strict(struct nlattr **tb, int maxtype, const struct nlattr *head,
+                    int len, const struct nla_policy *policy,
+                    struct netlink_ext_ack *extack);
 int nla_policy_len(const struct nla_policy *, int);
 struct nlattr *nla_find(const struct nlattr *head, int len, int attrtype);
 size_t nla_strlcpy(char *dst, const struct nlattr *nla, size_t dstsize);
@@ -516,13 +519,29 @@ static inline int nlmsg_parse(const struct nlmsghdr *nlh, int hdrlen,
                              const struct nla_policy *policy,
                              struct netlink_ext_ack *extack)
 {
-       if (nlh->nlmsg_len < nlmsg_msg_size(hdrlen))
+       if (nlh->nlmsg_len < nlmsg_msg_size(hdrlen)) {
+               NL_SET_ERR_MSG(extack, "Invalid header length");
                return -EINVAL;
+       }
 
        return nla_parse(tb, maxtype, nlmsg_attrdata(nlh, hdrlen),
                         nlmsg_attrlen(nlh, hdrlen), policy, extack);
 }
 
+static inline int nlmsg_parse_strict(const struct nlmsghdr *nlh, int hdrlen,
+                                    struct nlattr *tb[], int maxtype,
+                                    const struct nla_policy *policy,
+                                    struct netlink_ext_ack *extack)
+{
+       if (nlh->nlmsg_len < nlmsg_msg_size(hdrlen)) {
+               NL_SET_ERR_MSG(extack, "Invalid header length");
+               return -EINVAL;
+       }
+
+       return nla_parse_strict(tb, maxtype, nlmsg_attrdata(nlh, hdrlen),
+                               nlmsg_attrlen(nlh, hdrlen), policy, extack);
+}
+
 /**
  * nlmsg_find_attr - find a specific attribute in a netlink message
  * @nlh: netlink message header
index 776bc92e91180725e75f0291b1635234d6b6875f..486ed1f0c0bc17f48dca895ebf9581aa7d69278d 100644 (file)
@@ -155,6 +155,7 @@ enum nlmsgerr_attrs {
 #define NETLINK_LIST_MEMBERSHIPS       9
 #define NETLINK_CAP_ACK                        10
 #define NETLINK_EXT_ACK                        11
+#define NETLINK_DUMP_STRICT_CHK                12
 
 struct nl_pktinfo {
        __u32   group;
index 1e900bb414ef60c911fffbab3ee47354be091d54..d26de6156b97db73803cb617bcd81fd7415ac7b1 100644 (file)
@@ -391,9 +391,10 @@ EXPORT_SYMBOL(nla_policy_len);
  *
  * Returns 0 on success or a negative error code.
  */
-int nla_parse(struct nlattr **tb, int maxtype, const struct nlattr *head,
-             int len, const struct nla_policy *policy,
-             struct netlink_ext_ack *extack)
+static int __nla_parse(struct nlattr **tb, int maxtype,
+                      const struct nlattr *head, int len,
+                      bool strict, const struct nla_policy *policy,
+                      struct netlink_ext_ack *extack)
 {
        const struct nlattr *nla;
        int rem;
@@ -403,27 +404,50 @@ int nla_parse(struct nlattr **tb, int maxtype, const struct nlattr *head,
        nla_for_each_attr(nla, head, len, rem) {
                u16 type = nla_type(nla);
 
-               if (type > 0 && type <= maxtype) {
-                       if (policy) {
-                               int err = validate_nla(nla, maxtype, policy,
-                                                      extack);
-
-                               if (err < 0)
-                                       return err;
+               if (type == 0 || type > maxtype) {
+                       if (strict) {
+                               NL_SET_ERR_MSG(extack, "Unknown attribute type");
+                               return -EINVAL;
                        }
+                       continue;
+               }
+               if (policy) {
+                       int err = validate_nla(nla, maxtype, policy, extack);
 
-                       tb[type] = (struct nlattr *)nla;
+                       if (err < 0)
+                               return err;
                }
+
+               tb[type] = (struct nlattr *)nla;
        }
 
-       if (unlikely(rem > 0))
+       if (unlikely(rem > 0)) {
                pr_warn_ratelimited("netlink: %d bytes leftover after parsing attributes in process `%s'.\n",
                                    rem, current->comm);
+               NL_SET_ERR_MSG(extack, "bytes leftover after parsing attributes");
+               if (strict)
+                       return -EINVAL;
+       }
 
        return 0;
 }
+
+int nla_parse(struct nlattr **tb, int maxtype, const struct nlattr *head,
+             int len, const struct nla_policy *policy,
+             struct netlink_ext_ack *extack)
+{
+       return __nla_parse(tb, maxtype, head, len, false, policy, extack);
+}
 EXPORT_SYMBOL(nla_parse);
 
+int nla_parse_strict(struct nlattr **tb, int maxtype, const struct nlattr *head,
+                    int len, const struct nla_policy *policy,
+                    struct netlink_ext_ack *extack)
+{
+       return __nla_parse(tb, maxtype, head, len, true, policy, extack);
+}
+EXPORT_SYMBOL(nla_parse_strict);
+
 /**
  * nla_find - Find a specific attribute in a stream of attributes
  * @head: head of attribute stream
index a4a848bf827b0e61fe722afccca3cde5d0062967..a7ea2d431714300bec42f4c28c6cdba46d22d2c6 100644 (file)
@@ -162,6 +162,29 @@ out:
        return err;
 }
 
+static int br_mdb_valid_dump_req(const struct nlmsghdr *nlh,
+                                struct netlink_ext_ack *extack)
+{
+       struct br_port_msg *bpm;
+
+       if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*bpm))) {
+               NL_SET_ERR_MSG_MOD(extack, "Invalid header for mdb dump request");
+               return -EINVAL;
+       }
+
+       bpm = nlmsg_data(nlh);
+       if (bpm->ifindex) {
+               NL_SET_ERR_MSG_MOD(extack, "Filtering by device index is not supported for mdb dump request");
+               return -EINVAL;
+       }
+       if (nlmsg_attrlen(nlh, sizeof(*bpm))) {
+               NL_SET_ERR_MSG(extack, "Invalid data after header in mdb dump request");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
 static int br_mdb_dump(struct sk_buff *skb, struct netlink_callback *cb)
 {
        struct net_device *dev;
@@ -169,6 +192,13 @@ static int br_mdb_dump(struct sk_buff *skb, struct netlink_callback *cb)
        struct nlmsghdr *nlh = NULL;
        int idx = 0, s_idx;
 
+       if (cb->strict_check) {
+               int err = br_mdb_valid_dump_req(cb->nlh, cb->extack);
+
+               if (err < 0)
+                       return err;
+       }
+
        s_idx = cb->args[0];
 
        rcu_read_lock();
index 938f68ee92f01c1e0571e8ad6dc1afb64887cd41..6dae81d65d5c4ed958a9044278f4c3b8b9281ca4 100644 (file)
@@ -3504,7 +3504,7 @@ static int devlink_nl_cmd_region_read_dumpit(struct sk_buff *skb,
        start_offset = *((u64 *)&cb->args[0]);
 
        err = nlmsg_parse(cb->nlh, GENL_HDRLEN + devlink_nl_family.hdrsize,
-                         attrs, DEVLINK_ATTR_MAX, ops->policy, NULL);
+                         attrs, DEVLINK_ATTR_MAX, ops->policy, cb->extack);
        if (err)
                goto out;
 
index 0ff3953f64aa7830a07dad9c0873813eb944fdd1..ffbb827723a236a6b187a10a33886561727ae0be 100644 (file)
@@ -1063,13 +1063,47 @@ skip:
        return err;
 }
 
+static int fib_valid_dumprule_req(const struct nlmsghdr *nlh,
+                                  struct netlink_ext_ack *extack)
+{
+       struct fib_rule_hdr *frh;
+
+       if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*frh))) {
+               NL_SET_ERR_MSG(extack, "Invalid header for fib rule dump request");
+               return -EINVAL;
+       }
+
+       frh = nlmsg_data(nlh);
+       if (frh->dst_len || frh->src_len || frh->tos || frh->table ||
+           frh->res1 || frh->res2 || frh->action || frh->flags) {
+               NL_SET_ERR_MSG(extack,
+                              "Invalid values in header for fib rule dump request");
+               return -EINVAL;
+       }
+
+       if (nlmsg_attrlen(nlh, sizeof(*frh))) {
+               NL_SET_ERR_MSG(extack, "Invalid data after header in fib rule dump request");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
 static int fib_nl_dumprule(struct sk_buff *skb, struct netlink_callback *cb)
 {
+       const struct nlmsghdr *nlh = cb->nlh;
        struct net *net = sock_net(skb->sk);
        struct fib_rules_ops *ops;
        int idx = 0, family;
 
-       family = rtnl_msg_family(cb->nlh);
+       if (cb->strict_check) {
+               int err = fib_valid_dumprule_req(nlh, cb->extack);
+
+               if (err < 0)
+                       return err;
+       }
+
+       family = rtnl_msg_family(nlh);
        if (family != AF_UNSPEC) {
                /* Protocol specific dump request */
                ops = lookup_rules_ops(net, family);
index fb023df48b8301fa3976fa76e5b11cdff206f0d5..dc1389b8beb120a90ba65c5294c051da3e15b909 100644 (file)
@@ -2164,15 +2164,47 @@ errout:
        return err;
 }
 
+static int neightbl_valid_dump_info(const struct nlmsghdr *nlh,
+                                   struct netlink_ext_ack *extack)
+{
+       struct ndtmsg *ndtm;
+
+       if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*ndtm))) {
+               NL_SET_ERR_MSG(extack, "Invalid header for neighbor table dump request");
+               return -EINVAL;
+       }
+
+       ndtm = nlmsg_data(nlh);
+       if (ndtm->ndtm_pad1  || ndtm->ndtm_pad2) {
+               NL_SET_ERR_MSG(extack, "Invalid values in header for neighbor table dump request");
+               return -EINVAL;
+       }
+
+       if (nlmsg_attrlen(nlh, sizeof(*ndtm))) {
+               NL_SET_ERR_MSG(extack, "Invalid data after header in neighbor table dump request");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
 static int neightbl_dump_info(struct sk_buff *skb, struct netlink_callback *cb)
 {
+       const struct nlmsghdr *nlh = cb->nlh;
        struct net *net = sock_net(skb->sk);
        int family, tidx, nidx = 0;
        int tbl_skip = cb->args[0];
        int neigh_skip = cb->args[1];
        struct neigh_table *tbl;
 
-       family = ((struct rtgenmsg *) nlmsg_data(cb->nlh))->rtgen_family;
+       if (cb->strict_check) {
+               int err = neightbl_valid_dump_info(nlh, cb->extack);
+
+               if (err < 0)
+                       return err;
+       }
+
+       family = ((struct rtgenmsg *)nlmsg_data(nlh))->rtgen_family;
 
        for (tidx = 0; tidx < NEIGH_NR_TABLES; tidx++) {
                struct neigh_parms *p;
@@ -2185,7 +2217,7 @@ static int neightbl_dump_info(struct sk_buff *skb, struct netlink_callback *cb)
                        continue;
 
                if (neightbl_fill_info(skb, tbl, NETLINK_CB(cb->skb).portid,
-                                      cb->nlh->nlmsg_seq, RTM_NEWNEIGHTBL,
+                                      nlh->nlmsg_seq, RTM_NEWNEIGHTBL,
                                       NLM_F_MULTI) < 0)
                        break;
 
@@ -2200,7 +2232,7 @@ static int neightbl_dump_info(struct sk_buff *skb, struct netlink_callback *cb)
 
                        if (neightbl_fill_param_info(skb, tbl, p,
                                                     NETLINK_CB(cb->skb).portid,
-                                                    cb->nlh->nlmsg_seq,
+                                                    nlh->nlmsg_seq,
                                                     RTM_NEWNEIGHTBL,
                                                     NLM_F_MULTI) < 0)
                                goto out;
@@ -2426,11 +2458,73 @@ out:
 
 }
 
+static int neigh_valid_dump_req(const struct nlmsghdr *nlh,
+                               bool strict_check,
+                               struct neigh_dump_filter *filter,
+                               struct netlink_ext_ack *extack)
+{
+       struct nlattr *tb[NDA_MAX + 1];
+       int err, i;
+
+       if (strict_check) {
+               struct ndmsg *ndm;
+
+               if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*ndm))) {
+                       NL_SET_ERR_MSG(extack, "Invalid header for neighbor dump request");
+                       return -EINVAL;
+               }
+
+               ndm = nlmsg_data(nlh);
+               if (ndm->ndm_pad1  || ndm->ndm_pad2  || ndm->ndm_ifindex ||
+                   ndm->ndm_state || ndm->ndm_flags || ndm->ndm_type) {
+                       NL_SET_ERR_MSG(extack, "Invalid values in header for neighbor dump request");
+                       return -EINVAL;
+               }
+
+               err = nlmsg_parse_strict(nlh, sizeof(struct ndmsg), tb, NDA_MAX,
+                                        NULL, extack);
+       } else {
+               err = nlmsg_parse(nlh, sizeof(struct ndmsg), tb, NDA_MAX,
+                                 NULL, extack);
+       }
+       if (err < 0)
+               return err;
+
+       for (i = 0; i <= NDA_MAX; ++i) {
+               if (!tb[i])
+                       continue;
+
+               /* all new attributes should require strict_check */
+               switch (i) {
+               case NDA_IFINDEX:
+                       if (nla_len(tb[i]) != sizeof(u32)) {
+                               NL_SET_ERR_MSG(extack, "Invalid IFINDEX attribute in neighbor dump request");
+                               return -EINVAL;
+                       }
+                       filter->dev_idx = nla_get_u32(tb[i]);
+                       break;
+               case NDA_MASTER:
+                       if (nla_len(tb[i]) != sizeof(u32)) {
+                               NL_SET_ERR_MSG(extack, "Invalid MASTER attribute in neighbor dump request");
+                               return -EINVAL;
+                       }
+                       filter->master_idx = nla_get_u32(tb[i]);
+                       break;
+               default:
+                       if (strict_check) {
+                               NL_SET_ERR_MSG(extack, "Unsupported attribute in neighbor dump request");
+                               return -EINVAL;
+                       }
+               }
+       }
+
+       return 0;
+}
+
 static int neigh_dump_info(struct sk_buff *skb, struct netlink_callback *cb)
 {
        const struct nlmsghdr *nlh = cb->nlh;
        struct neigh_dump_filter filter = {};
-       struct nlattr *tb[NDA_MAX + 1];
        struct neigh_table *tbl;
        int t, family, s_t;
        int proxy = 0;
@@ -2445,19 +2539,10 @@ static int neigh_dump_info(struct sk_buff *skb, struct netlink_callback *cb)
            ((struct ndmsg *)nlmsg_data(nlh))->ndm_flags == NTF_PROXY)
                proxy = 1;
 
-       err = nlmsg_parse(nlh, sizeof(struct ndmsg), tb, NDA_MAX, NULL, NULL);
-       if (!err) {
-               if (tb[NDA_IFINDEX]) {
-                       if (nla_len(tb[NDA_IFINDEX]) != sizeof(u32))
-                               return -EINVAL;
-                       filter.dev_idx = nla_get_u32(tb[NDA_IFINDEX]);
-               }
-               if (tb[NDA_MASTER]) {
-                       if (nla_len(tb[NDA_MASTER]) != sizeof(u32))
-                               return -EINVAL;
-                       filter.master_idx = nla_get_u32(tb[NDA_MASTER]);
-               }
-       }
+       err = neigh_valid_dump_req(nlh, cb->strict_check, &filter, cb->extack);
+       if (err < 0 && cb->strict_check)
+               return err;
+
        s_t = cb->args[0];
 
        for (t = 0; t < NEIGH_NR_TABLES; t++) {
index 670c84b1bfc23bbb9dde13b4df21a503350d390e..fefe72774aeb3a78d2bec6894cd9494741a78ee4 100644 (file)
@@ -853,6 +853,12 @@ static int rtnl_net_dumpid(struct sk_buff *skb, struct netlink_callback *cb)
                .s_idx = cb->args[0],
        };
 
+       if (cb->strict_check &&
+           nlmsg_attrlen(cb->nlh, sizeof(struct rtgenmsg))) {
+                       NL_SET_ERR_MSG(cb->extack, "Unknown data in network namespace id dump request");
+                       return -EINVAL;
+       }
+
        spin_lock_bh(&net->nsid_lock);
        idr_for_each(&net->netns_ids, rtnl_net_dumpid_one, &net_cb);
        spin_unlock_bh(&net->nsid_lock);
index 5564eee1e980dcbd155d4d1c2050bcc330fb66b9..c894c4af89817a780b7da6e2fbb03a7e0a6f7518 100644 (file)
@@ -1878,8 +1878,52 @@ struct net *rtnl_get_net_ns_capable(struct sock *sk, int netnsid)
 }
 EXPORT_SYMBOL_GPL(rtnl_get_net_ns_capable);
 
+static int rtnl_valid_dump_ifinfo_req(const struct nlmsghdr *nlh,
+                                     bool strict_check, struct nlattr **tb,
+                                     struct netlink_ext_ack *extack)
+{
+       int hdrlen;
+
+       if (strict_check) {
+               struct ifinfomsg *ifm;
+
+               if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*ifm))) {
+                       NL_SET_ERR_MSG(extack, "Invalid header for link dump");
+                       return -EINVAL;
+               }
+
+               ifm = nlmsg_data(nlh);
+               if (ifm->__ifi_pad || ifm->ifi_type || ifm->ifi_flags ||
+                   ifm->ifi_change) {
+                       NL_SET_ERR_MSG(extack, "Invalid values in header for link dump request");
+                       return -EINVAL;
+               }
+               if (ifm->ifi_index) {
+                       NL_SET_ERR_MSG(extack, "Filter by device index not supported for link dumps");
+                       return -EINVAL;
+               }
+
+               return nlmsg_parse_strict(nlh, sizeof(*ifm), tb, IFLA_MAX,
+                                         ifla_policy, extack);
+       }
+
+       /* A hack to preserve kernel<->userspace interface.
+        * The correct header is ifinfomsg. It is consistent with rtnl_getlink.
+        * However, before Linux v3.9 the code here assumed rtgenmsg and that's
+        * what iproute2 < v3.9.0 used.
+        * We can detect the old iproute2. Even including the IFLA_EXT_MASK
+        * attribute, its netlink message is shorter than struct ifinfomsg.
+        */
+       hdrlen = nlmsg_len(nlh) < sizeof(struct ifinfomsg) ?
+                sizeof(struct rtgenmsg) : sizeof(struct ifinfomsg);
+
+       return nlmsg_parse(nlh, hdrlen, tb, IFLA_MAX, ifla_policy, extack);
+}
+
 static int rtnl_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb)
 {
+       struct netlink_ext_ack *extack = cb->extack;
+       const struct nlmsghdr *nlh = cb->nlh;
        struct net *net = sock_net(skb->sk);
        struct net *tgt_net = net;
        int h, s_h;
@@ -1892,44 +1936,54 @@ static int rtnl_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb)
        unsigned int flags = NLM_F_MULTI;
        int master_idx = 0;
        int netnsid = -1;
-       int err;
-       int hdrlen;
+       int err, i;
 
        s_h = cb->args[0];
        s_idx = cb->args[1];
 
-       /* A hack to preserve kernel<->userspace interface.
-        * The correct header is ifinfomsg. It is consistent with rtnl_getlink.
-        * However, before Linux v3.9 the code here assumed rtgenmsg and that's
-        * what iproute2 < v3.9.0 used.
-        * We can detect the old iproute2. Even including the IFLA_EXT_MASK
-        * attribute, its netlink message is shorter than struct ifinfomsg.
-        */
-       hdrlen = nlmsg_len(cb->nlh) < sizeof(struct ifinfomsg) ?
-                sizeof(struct rtgenmsg) : sizeof(struct ifinfomsg);
+       err = rtnl_valid_dump_ifinfo_req(nlh, cb->strict_check, tb, extack);
+       if (err < 0) {
+               if (cb->strict_check)
+                       return err;
 
-       if (nlmsg_parse(cb->nlh, hdrlen, tb, IFLA_MAX,
-                       ifla_policy, NULL) >= 0) {
-               if (tb[IFLA_TARGET_NETNSID]) {
-                       netnsid = nla_get_s32(tb[IFLA_TARGET_NETNSID]);
+               goto walk_entries;
+       }
+
+       for (i = 0; i <= IFLA_MAX; ++i) {
+               if (!tb[i])
+                       continue;
+
+               /* new attributes should only be added with strict checking */
+               switch (i) {
+               case IFLA_TARGET_NETNSID:
+                       netnsid = nla_get_s32(tb[i]);
                        tgt_net = rtnl_get_net_ns_capable(skb->sk, netnsid);
-                       if (IS_ERR(tgt_net))
+                       if (IS_ERR(tgt_net)) {
+                               NL_SET_ERR_MSG(extack, "Invalid target network namespace id");
                                return PTR_ERR(tgt_net);
+                       }
+                       break;
+               case IFLA_EXT_MASK:
+                       ext_filter_mask = nla_get_u32(tb[i]);
+                       break;
+               case IFLA_MASTER:
+                       master_idx = nla_get_u32(tb[i]);
+                       break;
+               case IFLA_LINKINFO:
+                       kind_ops = linkinfo_to_kind_ops(tb[i]);
+                       break;
+               default:
+                       if (cb->strict_check) {
+                               NL_SET_ERR_MSG(extack, "Unsupported attribute in link dump request");
+                               return -EINVAL;
+                       }
                }
-
-               if (tb[IFLA_EXT_MASK])
-                       ext_filter_mask = nla_get_u32(tb[IFLA_EXT_MASK]);
-
-               if (tb[IFLA_MASTER])
-                       master_idx = nla_get_u32(tb[IFLA_MASTER]);
-
-               if (tb[IFLA_LINKINFO])
-                       kind_ops = linkinfo_to_kind_ops(tb[IFLA_LINKINFO]);
-
-               if (master_idx || kind_ops)
-                       flags |= NLM_F_DUMP_FILTERED;
        }
 
+       if (master_idx || kind_ops)
+               flags |= NLM_F_DUMP_FILTERED;
+
+walk_entries:
        for (h = s_h; h < NETDEV_HASHENTRIES; h++, s_idx = 0) {
                idx = 0;
                head = &tgt_net->dev_index_head[h];
@@ -1941,8 +1995,7 @@ static int rtnl_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb)
                        err = rtnl_fill_ifinfo(skb, dev, net,
                                               RTM_NEWLINK,
                                               NETLINK_CB(cb->skb).portid,
-                                              cb->nlh->nlmsg_seq, 0,
-                                              flags,
+                                              nlh->nlmsg_seq, 0, flags,
                                               ext_filter_mask, 0, NULL, 0,
                                               netnsid);
 
@@ -3746,22 +3799,67 @@ out:
 }
 EXPORT_SYMBOL(ndo_dflt_fdb_dump);
 
-static int rtnl_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb)
+static int valid_fdb_dump_strict(const struct nlmsghdr *nlh,
+                                int *br_idx, int *brport_idx,
+                                struct netlink_ext_ack *extack)
 {
-       struct net_device *dev;
+       struct nlattr *tb[NDA_MAX + 1];
+       struct ndmsg *ndm;
+       int err, i;
+
+       if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*ndm))) {
+               NL_SET_ERR_MSG(extack, "Invalid header for fdb dump request");
+               return -EINVAL;
+       }
+
+       ndm = nlmsg_data(nlh);
+       if (ndm->ndm_pad1  || ndm->ndm_pad2  || ndm->ndm_state ||
+           ndm->ndm_flags || ndm->ndm_type) {
+               NL_SET_ERR_MSG(extack, "Invalid values in header for fbd dump request");
+               return -EINVAL;
+       }
+
+       err = nlmsg_parse_strict(nlh, sizeof(struct ndmsg), tb, NDA_MAX,
+                                NULL, extack);
+       if (err < 0)
+               return err;
+
+       *brport_idx = ndm->ndm_ifindex;
+       for (i = 0; i <= NDA_MAX; ++i) {
+               if (!tb[i])
+                       continue;
+
+               switch (i) {
+               case NDA_IFINDEX:
+                       if (nla_len(tb[i]) != sizeof(u32)) {
+                               NL_SET_ERR_MSG(extack, "Invalid IFINDEX attribute in fdb dump request");
+                               return -EINVAL;
+                       }
+                       *brport_idx = nla_get_u32(tb[NDA_IFINDEX]);
+                       break;
+               case NDA_MASTER:
+                       if (nla_len(tb[i]) != sizeof(u32)) {
+                               NL_SET_ERR_MSG(extack, "Invalid MASTER attribute in fdb dump request");
+                               return -EINVAL;
+                       }
+                       *br_idx = nla_get_u32(tb[NDA_MASTER]);
+                       break;
+               default:
+                       NL_SET_ERR_MSG(extack, "Unsupported attribute in fdb dump request");
+                       return -EINVAL;
+               }
+       }
+
+       return 0;
+}
+
+static int valid_fdb_dump_legacy(const struct nlmsghdr *nlh,
+                                int *br_idx, int *brport_idx,
+                                struct netlink_ext_ack *extack)
+{
+       struct ifinfomsg *ifm = nlmsg_data(nlh);
        struct nlattr *tb[IFLA_MAX+1];
-       struct net_device *br_dev = NULL;
-       const struct net_device_ops *ops = NULL;
-       const struct net_device_ops *cops = NULL;
-       struct ifinfomsg *ifm = nlmsg_data(cb->nlh);
-       struct net *net = sock_net(skb->sk);
-       struct hlist_head *head;
-       int brport_idx = 0;
-       int br_idx = 0;
-       int h, s_h;
-       int idx = 0, s_idx;
-       int err = 0;
-       int fidx = 0;
+       int err;
 
        /* A hack to preserve kernel<->userspace interface.
         * Before Linux v4.12 this code accepted ndmsg since iproute2 v3.3.0.
@@ -3770,20 +3868,46 @@ static int rtnl_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb)
         * Fortunately these sizes don't conflict with the size of ifinfomsg
         * with an optional attribute.
         */
-       if (nlmsg_len(cb->nlh) != sizeof(struct ndmsg) &&
-           (nlmsg_len(cb->nlh) != sizeof(struct ndmsg) +
+       if (nlmsg_len(nlh) != sizeof(struct ndmsg) &&
+           (nlmsg_len(nlh) != sizeof(struct ndmsg) +
             nla_attr_size(sizeof(u32)))) {
-               err = nlmsg_parse(cb->nlh, sizeof(struct ifinfomsg), tb,
-                                 IFLA_MAX, ifla_policy, NULL);
+               err = nlmsg_parse(nlh, sizeof(struct ifinfomsg), tb, IFLA_MAX,
+                                 ifla_policy, extack);
                if (err < 0) {
                        return -EINVAL;
                } else if (err == 0) {
                        if (tb[IFLA_MASTER])
-                               br_idx = nla_get_u32(tb[IFLA_MASTER]);
+                               *br_idx = nla_get_u32(tb[IFLA_MASTER]);
                }
 
-               brport_idx = ifm->ifi_index;
+               *brport_idx = ifm->ifi_index;
        }
+       return 0;
+}
+
+static int rtnl_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb)
+{
+       struct net_device *dev;
+       struct net_device *br_dev = NULL;
+       const struct net_device_ops *ops = NULL;
+       const struct net_device_ops *cops = NULL;
+       struct net *net = sock_net(skb->sk);
+       struct hlist_head *head;
+       int brport_idx = 0;
+       int br_idx = 0;
+       int h, s_h;
+       int idx = 0, s_idx;
+       int err = 0;
+       int fidx = 0;
+
+       if (cb->strict_check)
+               err = valid_fdb_dump_strict(cb->nlh, &br_idx, &brport_idx,
+                                           cb->extack);
+       else
+               err = valid_fdb_dump_legacy(cb->nlh, &br_idx, &brport_idx,
+                                           cb->extack);
+       if (err < 0)
+               return err;
 
        if (br_idx) {
                br_dev = __dev_get_by_index(net, br_idx);
@@ -3968,28 +4092,72 @@ nla_put_failure:
 }
 EXPORT_SYMBOL_GPL(ndo_dflt_bridge_getlink);
 
+static int valid_bridge_getlink_req(const struct nlmsghdr *nlh,
+                                   bool strict_check, u32 *filter_mask,
+                                   struct netlink_ext_ack *extack)
+{
+       struct nlattr *tb[IFLA_MAX+1];
+       int err, i;
+
+       if (strict_check) {
+               struct ifinfomsg *ifm;
+
+               if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*ifm))) {
+                       NL_SET_ERR_MSG(extack, "Invalid header for bridge link dump");
+                       return -EINVAL;
+               }
+
+               ifm = nlmsg_data(nlh);
+               if (ifm->__ifi_pad || ifm->ifi_type || ifm->ifi_flags ||
+                   ifm->ifi_change || ifm->ifi_index) {
+                       NL_SET_ERR_MSG(extack, "Invalid values in header for bridge link dump request");
+                       return -EINVAL;
+               }
+
+               err = nlmsg_parse_strict(nlh, sizeof(struct ifinfomsg), tb,
+                                        IFLA_MAX, ifla_policy, extack);
+       } else {
+               err = nlmsg_parse(nlh, sizeof(struct ifinfomsg), tb,
+                                 IFLA_MAX, ifla_policy, extack);
+       }
+       if (err < 0)
+               return err;
+
+       /* new attributes should only be added with strict checking */
+       for (i = 0; i <= IFLA_MAX; ++i) {
+               if (!tb[i])
+                       continue;
+
+               switch (i) {
+               case IFLA_EXT_MASK:
+                       *filter_mask = nla_get_u32(tb[i]);
+                       break;
+               default:
+                       if (strict_check) {
+                               NL_SET_ERR_MSG(extack, "Unsupported attribute in bridge link dump request");
+                               return -EINVAL;
+                       }
+               }
+       }
+
+       return 0;
+}
+
 static int rtnl_bridge_getlink(struct sk_buff *skb, struct netlink_callback *cb)
 {
+       const struct nlmsghdr *nlh = cb->nlh;
        struct net *net = sock_net(skb->sk);
        struct net_device *dev;
        int idx = 0;
        u32 portid = NETLINK_CB(cb->skb).portid;
-       u32 seq = cb->nlh->nlmsg_seq;
+       u32 seq = nlh->nlmsg_seq;
        u32 filter_mask = 0;
        int err;
 
-       if (nlmsg_len(cb->nlh) > sizeof(struct ifinfomsg)) {
-               struct nlattr *extfilt;
-
-               extfilt = nlmsg_find_attr(cb->nlh, sizeof(struct ifinfomsg),
-                                         IFLA_EXT_MASK);
-               if (extfilt) {
-                       if (nla_len(extfilt) < sizeof(filter_mask))
-                               return -EINVAL;
-
-                       filter_mask = nla_get_u32(extfilt);
-               }
-       }
+       err = valid_bridge_getlink_req(nlh, cb->strict_check, &filter_mask,
+                                      cb->extack);
+       if (err < 0 && cb->strict_check)
+               return err;
 
        rcu_read_lock();
        for_each_netdev_rcu(net, dev) {
@@ -4583,6 +4751,7 @@ static int rtnl_stats_get(struct sk_buff *skb, struct nlmsghdr *nlh,
 
 static int rtnl_stats_dump(struct sk_buff *skb, struct netlink_callback *cb)
 {
+       struct netlink_ext_ack *extack = cb->extack;
        int h, s_h, err, s_idx, s_idxattr, s_prividx;
        struct net *net = sock_net(skb->sk);
        unsigned int flags = NLM_F_MULTI;
@@ -4599,13 +4768,32 @@ static int rtnl_stats_dump(struct sk_buff *skb, struct netlink_callback *cb)
 
        cb->seq = net->dev_base_seq;
 
-       if (nlmsg_len(cb->nlh) < sizeof(*ifsm))
+       if (nlmsg_len(cb->nlh) < sizeof(*ifsm)) {
+               NL_SET_ERR_MSG(extack, "Invalid header for stats dump");
                return -EINVAL;
+       }
 
        ifsm = nlmsg_data(cb->nlh);
+
+       /* only requests using NLM_F_DUMP_PROPER_HDR can pass data to
+        * influence the dump. The legacy exception is filter_mask.
+        */
+       if (cb->strict_check) {
+               if (ifsm->pad1 || ifsm->pad2 || ifsm->ifindex) {
+                       NL_SET_ERR_MSG(extack, "Invalid values in header for stats dump request");
+                       return -EINVAL;
+               }
+               if (nlmsg_attrlen(cb->nlh, sizeof(*ifsm))) {
+                       NL_SET_ERR_MSG(extack, "Invalid attributes after stats header");
+                       return -EINVAL;
+               }
+       }
+
        filter_mask = ifsm->filter_mask;
-       if (!filter_mask)
+       if (!filter_mask) {
+               NL_SET_ERR_MSG(extack, "Filter mask must be set for stats dump");
                return -EINVAL;
+       }
 
        for (h = s_h; h < NETDEV_HASHENTRIES; h++, s_idx = 0) {
                idx = 0;
index 44d931a3cd50c1b56cdd1365d84c5a828a9853c3..d122ebbe5980139f892431af0eb98fa514f0a448 100644 (file)
@@ -782,7 +782,8 @@ static void set_ifa_lifetime(struct in_ifaddr *ifa, __u32 valid_lft,
 }
 
 static struct in_ifaddr *rtm_to_ifaddr(struct net *net, struct nlmsghdr *nlh,
-                                      __u32 *pvalid_lft, __u32 *pprefered_lft)
+                                      __u32 *pvalid_lft, __u32 *pprefered_lft,
+                                      struct netlink_ext_ack *extack)
 {
        struct nlattr *tb[IFA_MAX+1];
        struct in_ifaddr *ifa;
@@ -792,7 +793,7 @@ static struct in_ifaddr *rtm_to_ifaddr(struct net *net, struct nlmsghdr *nlh,
        int err;
 
        err = nlmsg_parse(nlh, sizeof(*ifm), tb, IFA_MAX, ifa_ipv4_policy,
-                         NULL);
+                         extack);
        if (err < 0)
                goto errout;
 
@@ -897,7 +898,7 @@ static int inet_rtm_newaddr(struct sk_buff *skb, struct nlmsghdr *nlh,
 
        ASSERT_RTNL();
 
-       ifa = rtm_to_ifaddr(net, nlh, &valid_lft, &prefered_lft);
+       ifa = rtm_to_ifaddr(net, nlh, &valid_lft, &prefered_lft, extack);
        if (IS_ERR(ifa))
                return PTR_ERR(ifa);
 
@@ -1659,17 +1660,70 @@ nla_put_failure:
        return -EMSGSIZE;
 }
 
+static int inet_valid_dump_ifaddr_req(const struct nlmsghdr *nlh,
+                                     struct inet_fill_args *fillargs,
+                                     struct net **tgt_net, struct sock *sk,
+                                     struct netlink_ext_ack *extack)
+{
+       struct nlattr *tb[IFA_MAX+1];
+       struct ifaddrmsg *ifm;
+       int err, i;
+
+       if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*ifm))) {
+               NL_SET_ERR_MSG(extack, "ipv4: Invalid header for address dump request");
+               return -EINVAL;
+       }
+
+       ifm = nlmsg_data(nlh);
+       if (ifm->ifa_prefixlen || ifm->ifa_flags || ifm->ifa_scope) {
+               NL_SET_ERR_MSG(extack, "ipv4: Invalid values in header for address dump request");
+               return -EINVAL;
+       }
+       if (ifm->ifa_index) {
+               NL_SET_ERR_MSG(extack, "ipv4: Filter by device index not supported for address dump");
+               return -EINVAL;
+       }
+
+       err = nlmsg_parse_strict(nlh, sizeof(*ifm), tb, IFA_MAX,
+                                ifa_ipv4_policy, extack);
+       if (err < 0)
+               return err;
+
+       for (i = 0; i <= IFA_MAX; ++i) {
+               if (!tb[i])
+                       continue;
+
+               if (i == IFA_TARGET_NETNSID) {
+                       struct net *net;
+
+                       fillargs->netnsid = nla_get_s32(tb[i]);
+
+                       net = rtnl_get_net_ns_capable(sk, fillargs->netnsid);
+                       if (IS_ERR(net)) {
+                               NL_SET_ERR_MSG(extack, "ipv4: Invalid target network namespace id");
+                               return PTR_ERR(net);
+                       }
+                       *tgt_net = net;
+               } else {
+                       NL_SET_ERR_MSG(extack, "ipv4: Unsupported attribute in dump request");
+                       return -EINVAL;
+               }
+       }
+
+       return 0;
+}
+
 static int inet_dump_ifaddr(struct sk_buff *skb, struct netlink_callback *cb)
 {
+       const struct nlmsghdr *nlh = cb->nlh;
        struct inet_fill_args fillargs = {
                .portid = NETLINK_CB(cb->skb).portid,
-               .seq = cb->nlh->nlmsg_seq,
+               .seq = nlh->nlmsg_seq,
                .event = RTM_NEWADDR,
                .flags = NLM_F_MULTI,
                .netnsid = -1,
        };
        struct net *net = sock_net(skb->sk);
-       struct nlattr *tb[IFA_MAX+1];
        struct net *tgt_net = net;
        int h, s_h;
        int idx, s_idx;
@@ -1683,16 +1737,13 @@ static int inet_dump_ifaddr(struct sk_buff *skb, struct netlink_callback *cb)
        s_idx = idx = cb->args[1];
        s_ip_idx = ip_idx = cb->args[2];
 
-       if (nlmsg_parse(cb->nlh, sizeof(struct ifaddrmsg), tb, IFA_MAX,
-                       ifa_ipv4_policy, NULL) >= 0) {
-               if (tb[IFA_TARGET_NETNSID]) {
-                       fillargs.netnsid = nla_get_s32(tb[IFA_TARGET_NETNSID]);
+       if (cb->strict_check) {
+               int err;
 
-                       tgt_net = rtnl_get_net_ns_capable(skb->sk,
-                                                         fillargs.netnsid);
-                       if (IS_ERR(tgt_net))
-                               return PTR_ERR(tgt_net);
-               }
+               err = inet_valid_dump_ifaddr_req(nlh, &fillargs, &tgt_net,
+                                                skb->sk, cb->extack);
+               if (err < 0)
+                       return err;
        }
 
        for (h = s_h; h < NETDEV_HASHENTRIES; h++, s_idx = 0) {
@@ -2035,6 +2086,7 @@ errout:
 static int inet_netconf_dump_devconf(struct sk_buff *skb,
                                     struct netlink_callback *cb)
 {
+       const struct nlmsghdr *nlh = cb->nlh;
        struct net *net = sock_net(skb->sk);
        int h, s_h;
        int idx, s_idx;
@@ -2042,6 +2094,21 @@ static int inet_netconf_dump_devconf(struct sk_buff *skb,
        struct in_device *in_dev;
        struct hlist_head *head;
 
+       if (cb->strict_check) {
+               struct netlink_ext_ack *extack = cb->extack;
+               struct netconfmsg *ncm;
+
+               if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*ncm))) {
+                       NL_SET_ERR_MSG(extack, "ipv4: Invalid header for netconf dump request");
+                       return -EINVAL;
+               }
+
+               if (nlmsg_attrlen(nlh, sizeof(*ncm))) {
+                       NL_SET_ERR_MSG(extack, "ipv4: Invalid data after header in netconf dump request");
+                       return -EINVAL;
+               }
+       }
+
        s_h = cb->args[0];
        s_idx = idx = cb->args[1];
 
@@ -2061,7 +2128,7 @@ static int inet_netconf_dump_devconf(struct sk_buff *skb,
                        if (inet_netconf_fill_devconf(skb, dev->ifindex,
                                                      &in_dev->cnf,
                                                      NETLINK_CB(cb->skb).portid,
-                                                     cb->nlh->nlmsg_seq,
+                                                     nlh->nlmsg_seq,
                                                      RTM_NEWNETCONF,
                                                      NLM_F_MULTI,
                                                      NETCONFA_ALL) < 0) {
@@ -2078,7 +2145,7 @@ cont:
                if (inet_netconf_fill_devconf(skb, NETCONFA_IFINDEX_ALL,
                                              net->ipv4.devconf_all,
                                              NETLINK_CB(cb->skb).portid,
-                                             cb->nlh->nlmsg_seq,
+                                             nlh->nlmsg_seq,
                                              RTM_NEWNETCONF, NLM_F_MULTI,
                                              NETCONFA_ALL) < 0)
                        goto done;
@@ -2089,7 +2156,7 @@ cont:
                if (inet_netconf_fill_devconf(skb, NETCONFA_IFINDEX_DEFAULT,
                                              net->ipv4.devconf_dflt,
                                              NETLINK_CB(cb->skb).portid,
-                                             cb->nlh->nlmsg_seq,
+                                             nlh->nlmsg_seq,
                                              RTM_NEWNETCONF, NLM_F_MULTI,
                                              NETCONFA_ALL) < 0)
                        goto done;
index 30e2bcc3ef2a293568076228fecf3cf07ba01f20..038f511c73fa176ebe075ebfa8175aeac5dba285 100644 (file)
@@ -802,8 +802,40 @@ errout:
        return err;
 }
 
+int ip_valid_fib_dump_req(const struct nlmsghdr *nlh,
+                         struct netlink_ext_ack *extack)
+{
+       struct rtmsg *rtm;
+
+       if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*rtm))) {
+               NL_SET_ERR_MSG(extack, "Invalid header for FIB dump request");
+               return -EINVAL;
+       }
+
+       rtm = nlmsg_data(nlh);
+       if (rtm->rtm_dst_len || rtm->rtm_src_len  || rtm->rtm_tos   ||
+           rtm->rtm_table   || rtm->rtm_protocol || rtm->rtm_scope ||
+           rtm->rtm_type) {
+               NL_SET_ERR_MSG(extack, "Invalid values in header for FIB dump request");
+               return -EINVAL;
+       }
+       if (rtm->rtm_flags & ~(RTM_F_CLONED | RTM_F_PREFIX)) {
+               NL_SET_ERR_MSG(extack, "Invalid flags for FIB dump request");
+               return -EINVAL;
+       }
+
+       if (nlmsg_attrlen(nlh, sizeof(*rtm))) {
+               NL_SET_ERR_MSG(extack, "Invalid data after header in FIB dump request");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(ip_valid_fib_dump_req);
+
 static int inet_dump_fib(struct sk_buff *skb, struct netlink_callback *cb)
 {
+       const struct nlmsghdr *nlh = cb->nlh;
        struct net *net = sock_net(skb->sk);
        unsigned int h, s_h;
        unsigned int e = 0, s_e;
@@ -811,8 +843,14 @@ static int inet_dump_fib(struct sk_buff *skb, struct netlink_callback *cb)
        struct hlist_head *head;
        int dumped = 0, err;
 
-       if (nlmsg_len(cb->nlh) >= sizeof(struct rtmsg) &&
-           ((struct rtmsg *) nlmsg_data(cb->nlh))->rtm_flags & RTM_F_CLONED)
+       if (cb->strict_check) {
+               err = ip_valid_fib_dump_req(nlh, cb->extack);
+               if (err < 0)
+                       return err;
+       }
+
+       if (nlmsg_len(nlh) >= sizeof(struct rtmsg) &&
+           ((struct rtmsg *)nlmsg_data(nlh))->rtm_flags & RTM_F_CLONED)
                return skb->len;
 
        s_h = cb->args[0];
index 5660adcf7a042ba675026a8397759618fd2a56b3..91b0d5671649c30b95fbb02a1b0a20636435a263 100644 (file)
@@ -2527,6 +2527,13 @@ errout_free:
 
 static int ipmr_rtm_dumproute(struct sk_buff *skb, struct netlink_callback *cb)
 {
+       if (cb->strict_check) {
+               int err = ip_valid_fib_dump_req(cb->nlh, cb->extack);
+
+               if (err < 0)
+                       return err;
+       }
+
        return mr_rtm_dumproute(skb, cb, ipmr_mr_table_iter,
                                _ipmr_fill_mroute, &mfc_unres_lock);
 }
@@ -2710,6 +2717,31 @@ static bool ipmr_fill_vif(struct mr_table *mrt, u32 vifid, struct sk_buff *skb)
        return true;
 }
 
+static int ipmr_valid_dumplink(const struct nlmsghdr *nlh,
+                              struct netlink_ext_ack *extack)
+{
+       struct ifinfomsg *ifm;
+
+       if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*ifm))) {
+               NL_SET_ERR_MSG(extack, "ipv4: Invalid header for ipmr link dump");
+               return -EINVAL;
+       }
+
+       if (nlmsg_attrlen(nlh, sizeof(*ifm))) {
+               NL_SET_ERR_MSG(extack, "Invalid data after header in ipmr link dump");
+               return -EINVAL;
+       }
+
+       ifm = nlmsg_data(nlh);
+       if (ifm->__ifi_pad || ifm->ifi_type || ifm->ifi_flags ||
+           ifm->ifi_change || ifm->ifi_index) {
+               NL_SET_ERR_MSG(extack, "Invalid values in header for ipmr link dump request");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
 static int ipmr_rtm_dumplink(struct sk_buff *skb, struct netlink_callback *cb)
 {
        struct net *net = sock_net(skb->sk);
@@ -2718,6 +2750,13 @@ static int ipmr_rtm_dumplink(struct sk_buff *skb, struct netlink_callback *cb)
        unsigned int e = 0, s_e;
        struct mr_table *mrt;
 
+       if (cb->strict_check) {
+               int err = ipmr_valid_dumplink(cb->nlh, cb->extack);
+
+               if (err < 0)
+                       return err;
+       }
+
        s_t = cb->args[0];
        s_e = cb->args[1];
 
index a9a317322388632b326a40d056c7338a8c3cc328..2496b12bf721e78aae9ca1fb20b72806bf01005a 100644 (file)
@@ -666,6 +666,7 @@ errout:
 static int inet6_netconf_dump_devconf(struct sk_buff *skb,
                                      struct netlink_callback *cb)
 {
+       const struct nlmsghdr *nlh = cb->nlh;
        struct net *net = sock_net(skb->sk);
        int h, s_h;
        int idx, s_idx;
@@ -673,6 +674,21 @@ static int inet6_netconf_dump_devconf(struct sk_buff *skb,
        struct inet6_dev *idev;
        struct hlist_head *head;
 
+       if (cb->strict_check) {
+               struct netlink_ext_ack *extack = cb->extack;
+               struct netconfmsg *ncm;
+
+               if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*ncm))) {
+                       NL_SET_ERR_MSG_MOD(extack, "Invalid header for netconf dump request");
+                       return -EINVAL;
+               }
+
+               if (nlmsg_attrlen(nlh, sizeof(*ncm))) {
+                       NL_SET_ERR_MSG_MOD(extack, "Invalid data after header in netconf dump request");
+                       return -EINVAL;
+               }
+       }
+
        s_h = cb->args[0];
        s_idx = idx = cb->args[1];
 
@@ -692,7 +708,7 @@ static int inet6_netconf_dump_devconf(struct sk_buff *skb,
                        if (inet6_netconf_fill_devconf(skb, dev->ifindex,
                                                       &idev->cnf,
                                                       NETLINK_CB(cb->skb).portid,
-                                                      cb->nlh->nlmsg_seq,
+                                                      nlh->nlmsg_seq,
                                                       RTM_NEWNETCONF,
                                                       NLM_F_MULTI,
                                                       NETCONFA_ALL) < 0) {
@@ -709,7 +725,7 @@ cont:
                if (inet6_netconf_fill_devconf(skb, NETCONFA_IFINDEX_ALL,
                                               net->ipv6.devconf_all,
                                               NETLINK_CB(cb->skb).portid,
-                                              cb->nlh->nlmsg_seq,
+                                              nlh->nlmsg_seq,
                                               RTM_NEWNETCONF, NLM_F_MULTI,
                                               NETCONFA_ALL) < 0)
                        goto done;
@@ -720,7 +736,7 @@ cont:
                if (inet6_netconf_fill_devconf(skb, NETCONFA_IFINDEX_DEFAULT,
                                               net->ipv6.devconf_dflt,
                                               NETLINK_CB(cb->skb).portid,
-                                              cb->nlh->nlmsg_seq,
+                                              nlh->nlmsg_seq,
                                               RTM_NEWNETCONF, NLM_F_MULTI,
                                               NETCONFA_ALL) < 0)
                        goto done;
@@ -4793,12 +4809,19 @@ static inline int inet6_ifaddr_msgsize(void)
               + nla_total_size(4)  /* IFA_RT_PRIORITY */;
 }
 
+enum addr_type_t {
+       UNICAST_ADDR,
+       MULTICAST_ADDR,
+       ANYCAST_ADDR,
+};
+
 struct inet6_fill_args {
        u32 portid;
        u32 seq;
        int event;
        unsigned int flags;
        int netnsid;
+       enum addr_type_t type;
 };
 
 static int inet6_fill_ifaddr(struct sk_buff *skb, struct inet6_ifaddr *ifa,
@@ -4930,39 +4953,28 @@ static int inet6_fill_ifacaddr(struct sk_buff *skb, struct ifacaddr6 *ifaca,
        return 0;
 }
 
-enum addr_type_t {
-       UNICAST_ADDR,
-       MULTICAST_ADDR,
-       ANYCAST_ADDR,
-};
-
 /* called with rcu_read_lock() */
 static int in6_dump_addrs(struct inet6_dev *idev, struct sk_buff *skb,
-                         struct netlink_callback *cb, enum addr_type_t type,
-                         int s_ip_idx, int *p_ip_idx, int netnsid)
+                         struct netlink_callback *cb,
+                         int s_ip_idx, int *p_ip_idx,
+                         struct inet6_fill_args *fillargs)
 {
-       struct inet6_fill_args fillargs = {
-               .portid = NETLINK_CB(cb->skb).portid,
-               .seq = cb->nlh->nlmsg_seq,
-               .flags = NLM_F_MULTI,
-               .netnsid = netnsid,
-       };
        struct ifmcaddr6 *ifmca;
        struct ifacaddr6 *ifaca;
        int err = 1;
        int ip_idx = *p_ip_idx;
 
        read_lock_bh(&idev->lock);
-       switch (type) {
+       switch (fillargs->type) {
        case UNICAST_ADDR: {
                struct inet6_ifaddr *ifa;
-               fillargs.event = RTM_NEWADDR;
+               fillargs->event = RTM_NEWADDR;
 
                /* unicast address incl. temp addr */
                list_for_each_entry(ifa, &idev->addr_list, if_list) {
                        if (++ip_idx < s_ip_idx)
                                continue;
-                       err = inet6_fill_ifaddr(skb, ifa, &fillargs);
+                       err = inet6_fill_ifaddr(skb, ifa, fillargs);
                        if (err < 0)
                                break;
                        nl_dump_check_consistent(cb, nlmsg_hdr(skb));
@@ -4970,26 +4982,26 @@ static int in6_dump_addrs(struct inet6_dev *idev, struct sk_buff *skb,
                break;
        }
        case MULTICAST_ADDR:
-               fillargs.event = RTM_GETMULTICAST;
+               fillargs->event = RTM_GETMULTICAST;
 
                /* multicast address */
                for (ifmca = idev->mc_list; ifmca;
                     ifmca = ifmca->next, ip_idx++) {
                        if (ip_idx < s_ip_idx)
                                continue;
-                       err = inet6_fill_ifmcaddr(skb, ifmca, &fillargs);
+                       err = inet6_fill_ifmcaddr(skb, ifmca, fillargs);
                        if (err < 0)
                                break;
                }
                break;
        case ANYCAST_ADDR:
-               fillargs.event = RTM_GETANYCAST;
+               fillargs->event = RTM_GETANYCAST;
                /* anycast address */
                for (ifaca = idev->ac_list; ifaca;
                     ifaca = ifaca->aca_next, ip_idx++) {
                        if (ip_idx < s_ip_idx)
                                continue;
-                       err = inet6_fill_ifacaddr(skb, ifaca, &fillargs);
+                       err = inet6_fill_ifacaddr(skb, ifaca, fillargs);
                        if (err < 0)
                                break;
                }
@@ -5002,13 +5014,71 @@ static int in6_dump_addrs(struct inet6_dev *idev, struct sk_buff *skb,
        return err;
 }
 
+static int inet6_valid_dump_ifaddr_req(const struct nlmsghdr *nlh,
+                                      struct inet6_fill_args *fillargs,
+                                      struct net **tgt_net, struct sock *sk,
+                                      struct netlink_ext_ack *extack)
+{
+       struct nlattr *tb[IFA_MAX+1];
+       struct ifaddrmsg *ifm;
+       int err, i;
+
+       if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*ifm))) {
+               NL_SET_ERR_MSG_MOD(extack, "Invalid header for address dump request");
+               return -EINVAL;
+       }
+
+       ifm = nlmsg_data(nlh);
+       if (ifm->ifa_prefixlen || ifm->ifa_flags || ifm->ifa_scope) {
+               NL_SET_ERR_MSG_MOD(extack, "Invalid values in header for address dump request");
+               return -EINVAL;
+       }
+       if (ifm->ifa_index) {
+               NL_SET_ERR_MSG_MOD(extack, "Filter by device index not supported for address dump");
+               return -EINVAL;
+       }
+
+       err = nlmsg_parse_strict(nlh, sizeof(*ifm), tb, IFA_MAX,
+                                ifa_ipv6_policy, extack);
+       if (err < 0)
+               return err;
+
+       for (i = 0; i <= IFA_MAX; ++i) {
+               if (!tb[i])
+                       continue;
+
+               if (i == IFA_TARGET_NETNSID) {
+                       struct net *net;
+
+                       fillargs->netnsid = nla_get_s32(tb[i]);
+                       net = rtnl_get_net_ns_capable(sk, fillargs->netnsid);
+                       if (IS_ERR(net)) {
+                               NL_SET_ERR_MSG_MOD(extack, "Invalid target network namespace id");
+                               return PTR_ERR(net);
+                       }
+                       *tgt_net = net;
+               } else {
+                       NL_SET_ERR_MSG_MOD(extack, "Unsupported attribute in dump request");
+                       return -EINVAL;
+               }
+       }
+
+       return 0;
+}
+
 static int inet6_dump_addr(struct sk_buff *skb, struct netlink_callback *cb,
                           enum addr_type_t type)
 {
+       const struct nlmsghdr *nlh = cb->nlh;
+       struct inet6_fill_args fillargs = {
+               .portid = NETLINK_CB(cb->skb).portid,
+               .seq = cb->nlh->nlmsg_seq,
+               .flags = NLM_F_MULTI,
+               .netnsid = -1,
+               .type = type,
+       };
        struct net *net = sock_net(skb->sk);
-       struct nlattr *tb[IFA_MAX+1];
        struct net *tgt_net = net;
-       int netnsid = -1;
        int h, s_h;
        int idx, ip_idx;
        int s_idx, s_ip_idx;
@@ -5020,15 +5090,13 @@ static int inet6_dump_addr(struct sk_buff *skb, struct netlink_callback *cb,
        s_idx = idx = cb->args[1];
        s_ip_idx = ip_idx = cb->args[2];
 
-       if (nlmsg_parse(cb->nlh, sizeof(struct ifaddrmsg), tb, IFA_MAX,
-                       ifa_ipv6_policy, NULL) >= 0) {
-               if (tb[IFA_TARGET_NETNSID]) {
-                       netnsid = nla_get_s32(tb[IFA_TARGET_NETNSID]);
+       if (cb->strict_check) {
+               int err;
 
-                       tgt_net = rtnl_get_net_ns_capable(skb->sk, netnsid);
-                       if (IS_ERR(tgt_net))
-                               return PTR_ERR(tgt_net);
-               }
+               err = inet6_valid_dump_ifaddr_req(nlh, &fillargs, &tgt_net,
+                                                 skb->sk, cb->extack);
+               if (err < 0)
+                       return err;
        }
 
        rcu_read_lock();
@@ -5046,8 +5114,8 @@ static int inet6_dump_addr(struct sk_buff *skb, struct netlink_callback *cb,
                        if (!idev)
                                goto cont;
 
-                       if (in6_dump_addrs(idev, skb, cb, type,
-                                          s_ip_idx, &ip_idx, netnsid) < 0)
+                       if (in6_dump_addrs(idev, skb, cb, s_ip_idx, &ip_idx,
+                                          &fillargs) < 0)
                                goto done;
 cont:
                        idx++;
@@ -5058,7 +5126,7 @@ done:
        cb->args[0] = h;
        cb->args[1] = idx;
        cb->args[2] = ip_idx;
-       if (netnsid >= 0)
+       if (fillargs.netnsid >= 0)
                put_net(tgt_net);
 
        return skb->len;
@@ -5592,6 +5660,31 @@ nla_put_failure:
        return -EMSGSIZE;
 }
 
+static int inet6_valid_dump_ifinfo(const struct nlmsghdr *nlh,
+                                  struct netlink_ext_ack *extack)
+{
+       struct ifinfomsg *ifm;
+
+       if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*ifm))) {
+               NL_SET_ERR_MSG_MOD(extack, "Invalid header for link dump request");
+               return -EINVAL;
+       }
+
+       if (nlmsg_attrlen(nlh, sizeof(*ifm))) {
+               NL_SET_ERR_MSG_MOD(extack, "Invalid data after header");
+               return -EINVAL;
+       }
+
+       ifm = nlmsg_data(nlh);
+       if (ifm->__ifi_pad || ifm->ifi_type || ifm->ifi_flags ||
+           ifm->ifi_change || ifm->ifi_index) {
+               NL_SET_ERR_MSG_MOD(extack, "Invalid values in header for dump request");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
 static int inet6_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb)
 {
        struct net *net = sock_net(skb->sk);
@@ -5601,6 +5694,16 @@ static int inet6_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb)
        struct inet6_dev *idev;
        struct hlist_head *head;
 
+       /* only requests using strict checking can pass data to
+        * influence the dump
+        */
+       if (cb->strict_check) {
+               int err = inet6_valid_dump_ifinfo(cb->nlh, cb->extack);
+
+               if (err < 0)
+                       return err;
+       }
+
        s_h = cb->args[0];
        s_idx = cb->args[1];
 
index 1d6ced37ad718398c947cf49b3b486d4b88f3f6f..0d1ee82ee55b9ec64c3f5aa674c3f0aaa293a7e0 100644 (file)
@@ -458,20 +458,52 @@ static int ip6addrlbl_fill(struct sk_buff *skb,
        return 0;
 }
 
+static int ip6addrlbl_valid_dump_req(const struct nlmsghdr *nlh,
+                                    struct netlink_ext_ack *extack)
+{
+       struct ifaddrlblmsg *ifal;
+
+       if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*ifal))) {
+               NL_SET_ERR_MSG_MOD(extack, "Invalid header for address label dump request");
+               return -EINVAL;
+       }
+
+       ifal = nlmsg_data(nlh);
+       if (ifal->__ifal_reserved || ifal->ifal_prefixlen ||
+           ifal->ifal_flags || ifal->ifal_index || ifal->ifal_seq) {
+               NL_SET_ERR_MSG_MOD(extack, "Invalid values in header for address label dump request");
+               return -EINVAL;
+       }
+
+       if (nlmsg_attrlen(nlh, sizeof(*ifal))) {
+               NL_SET_ERR_MSG_MOD(extack, "Invalid data after header for address label dump requewst");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
 static int ip6addrlbl_dump(struct sk_buff *skb, struct netlink_callback *cb)
 {
+       const struct nlmsghdr *nlh = cb->nlh;
        struct net *net = sock_net(skb->sk);
        struct ip6addrlbl_entry *p;
        int idx = 0, s_idx = cb->args[0];
        int err;
 
+       if (cb->strict_check) {
+               err = ip6addrlbl_valid_dump_req(nlh, cb->extack);
+               if (err < 0)
+                       return err;
+       }
+
        rcu_read_lock();
        hlist_for_each_entry_rcu(p, &net->ipv6.ip6addrlbl_table.head, list) {
                if (idx >= s_idx) {
                        err = ip6addrlbl_fill(skb, p,
                                              net->ipv6.ip6addrlbl_table.seq,
                                              NETLINK_CB(cb->skb).portid,
-                                             cb->nlh->nlmsg_seq,
+                                             nlh->nlmsg_seq,
                                              RTM_NEWADDRLABEL,
                                              NLM_F_MULTI);
                        if (err < 0)
index cf709eadc932b7eeb1450cb8c1ba3c65e0848954..e14d244c551f3670f412ebf180e3bd5dddc9eabe 100644 (file)
@@ -564,6 +564,7 @@ static int fib6_dump_table(struct fib6_table *table, struct sk_buff *skb,
 
 static int inet6_dump_fib(struct sk_buff *skb, struct netlink_callback *cb)
 {
+       const struct nlmsghdr *nlh = cb->nlh;
        struct net *net = sock_net(skb->sk);
        unsigned int h, s_h;
        unsigned int e = 0, s_e;
@@ -573,6 +574,13 @@ static int inet6_dump_fib(struct sk_buff *skb, struct netlink_callback *cb)
        struct hlist_head *head;
        int res = 0;
 
+       if (cb->strict_check) {
+               int err = ip_valid_fib_dump_req(nlh, cb->extack);
+
+               if (err < 0)
+                       return err;
+       }
+
        s_h = cb->args[0];
        s_e = cb->args[1];
 
index 6f07b838042500832b718433c5091ca4cdc5cd0c..d7563ef76518482dfac68386b6946e9677f1708e 100644 (file)
@@ -2457,6 +2457,15 @@ errout:
 
 static int ip6mr_rtm_dumproute(struct sk_buff *skb, struct netlink_callback *cb)
 {
+       const struct nlmsghdr *nlh = cb->nlh;
+
+       if (cb->strict_check) {
+               int err = ip_valid_fib_dump_req(nlh, cb->extack);
+
+               if (err < 0)
+                       return err;
+       }
+
        return mr_rtm_dumproute(skb, cb, ip6mr_mr_table_iter,
                                _ip6mr_fill_mroute, &mfc_unres_lock);
 }
index 74d97addf1af20dda0c2b6a2018e88696f9f7d5a..7c38e0e058aeafc065f8790e9d381c2d13b70f47 100644 (file)
@@ -4117,7 +4117,7 @@ static int rtm_to_fib6_config(struct sk_buff *skb, struct nlmsghdr *nlh,
        int err;
 
        err = nlmsg_parse(nlh, sizeof(*rtm), tb, RTA_MAX, rtm_ipv6_policy,
-                         NULL);
+                         extack);
        if (err < 0)
                goto errout;
 
index 8fbe6cdbe255d4d32b790baa22e1431d240f6e7f..7f891ffffc052bc432d0537dd821d34667a55152 100644 (file)
@@ -1223,7 +1223,7 @@ static int mpls_netconf_get_devconf(struct sk_buff *in_skb,
        int err;
 
        err = nlmsg_parse(nlh, sizeof(*ncm), tb, NETCONFA_MAX,
-                         devconf_mpls_policy, NULL);
+                         devconf_mpls_policy, extack);
        if (err < 0)
                goto errout;
 
@@ -1263,6 +1263,7 @@ errout:
 static int mpls_netconf_dump_devconf(struct sk_buff *skb,
                                     struct netlink_callback *cb)
 {
+       const struct nlmsghdr *nlh = cb->nlh;
        struct net *net = sock_net(skb->sk);
        struct hlist_head *head;
        struct net_device *dev;
@@ -1270,6 +1271,21 @@ static int mpls_netconf_dump_devconf(struct sk_buff *skb,
        int idx, s_idx;
        int h, s_h;
 
+       if (cb->strict_check) {
+               struct netlink_ext_ack *extack = cb->extack;
+               struct netconfmsg *ncm;
+
+               if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*ncm))) {
+                       NL_SET_ERR_MSG_MOD(extack, "Invalid header for netconf dump request");
+                       return -EINVAL;
+               }
+
+               if (nlmsg_attrlen(nlh, sizeof(*ncm))) {
+                       NL_SET_ERR_MSG_MOD(extack, "Invalid data after header in netconf dump request");
+                       return -EINVAL;
+               }
+       }
+
        s_h = cb->args[0];
        s_idx = idx = cb->args[1];
 
@@ -1286,7 +1302,7 @@ static int mpls_netconf_dump_devconf(struct sk_buff *skb,
                                goto cont;
                        if (mpls_netconf_fill_devconf(skb, mdev,
                                                      NETLINK_CB(cb->skb).portid,
-                                                     cb->nlh->nlmsg_seq,
+                                                     nlh->nlmsg_seq,
                                                      RTM_NEWNETCONF,
                                                      NLM_F_MULTI,
                                                      NETCONFA_ALL) < 0) {
@@ -2017,6 +2033,7 @@ nla_put_failure:
 
 static int mpls_dump_routes(struct sk_buff *skb, struct netlink_callback *cb)
 {
+       const struct nlmsghdr *nlh = cb->nlh;
        struct net *net = sock_net(skb->sk);
        struct mpls_route __rcu **platform_label;
        size_t platform_labels;
@@ -2024,6 +2041,13 @@ static int mpls_dump_routes(struct sk_buff *skb, struct netlink_callback *cb)
 
        ASSERT_RTNL();
 
+       if (cb->strict_check) {
+               int err = ip_valid_fib_dump_req(nlh, cb->extack);
+
+               if (err < 0)
+                       return err;
+       }
+
        index = cb->args[0];
        if (index < MPLS_LABEL_FIRST_UNRESERVED)
                index = MPLS_LABEL_FIRST_UNRESERVED;
index 62eefea489732d6d11195e98388ded730d963283..83395bf6dc35e2a3ea486246e98de99b6e1094da 100644 (file)
@@ -3234,7 +3234,7 @@ static int ip_vs_genl_dump_dests(struct sk_buff *skb,
 
        /* Try to find the service for which to dump destinations */
        if (nlmsg_parse(cb->nlh, GENL_HDRLEN, attrs, IPVS_CMD_ATTR_MAX,
-                       ip_vs_cmd_policy, NULL))
+                       ip_vs_cmd_policy, cb->extack))
                goto out_err;
 
 
index e3a0538ec0bed789a245a1f50e121f9fb2f2a836..e613a9f896004ec1c4ca7e1b6b047259b622ebd1 100644 (file)
@@ -1706,6 +1706,13 @@ static int netlink_setsockopt(struct socket *sock, int level, int optname,
                        nlk->flags &= ~NETLINK_F_EXT_ACK;
                err = 0;
                break;
+       case NETLINK_DUMP_STRICT_CHK:
+               if (val)
+                       nlk->flags |= NETLINK_F_STRICT_CHK;
+               else
+                       nlk->flags &= ~NETLINK_F_STRICT_CHK;
+               err = 0;
+               break;
        default:
                err = -ENOPROTOOPT;
        }
@@ -1799,6 +1806,15 @@ static int netlink_getsockopt(struct socket *sock, int level, int optname,
                        return -EFAULT;
                err = 0;
                break;
+       case NETLINK_DUMP_STRICT_CHK:
+               if (len < sizeof(int))
+                       return -EINVAL;
+               len = sizeof(int);
+               val = nlk->flags & NETLINK_F_STRICT_CHK ? 1 : 0;
+               if (put_user(len, optlen) || put_user(val, optval))
+                       return -EFAULT;
+               err = 0;
+               break;
        default:
                err = -ENOPROTOOPT;
        }
@@ -2171,6 +2187,7 @@ EXPORT_SYMBOL(__nlmsg_put);
 static int netlink_dump(struct sock *sk)
 {
        struct netlink_sock *nlk = nlk_sk(sk);
+       struct netlink_ext_ack extack = {};
        struct netlink_callback *cb;
        struct sk_buff *skb = NULL;
        struct nlmsghdr *nlh;
@@ -2222,8 +2239,11 @@ static int netlink_dump(struct sock *sk)
        skb_reserve(skb, skb_tailroom(skb) - alloc_size);
        netlink_skb_set_owner_r(skb, sk);
 
-       if (nlk->dump_done_errno > 0)
+       if (nlk->dump_done_errno > 0) {
+               cb->extack = &extack;
                nlk->dump_done_errno = cb->dump(skb, cb);
+               cb->extack = NULL;
+       }
 
        if (nlk->dump_done_errno > 0 ||
            skb_tailroom(skb) < nlmsg_total_size(sizeof(nlk->dump_done_errno))) {
@@ -2246,6 +2266,12 @@ static int netlink_dump(struct sock *sk)
        memcpy(nlmsg_data(nlh), &nlk->dump_done_errno,
               sizeof(nlk->dump_done_errno));
 
+       if (extack._msg && nlk->flags & NETLINK_F_EXT_ACK) {
+               nlh->nlmsg_flags |= NLM_F_ACK_TLVS;
+               if (!nla_put_string(skb, NLMSGERR_ATTR_MSG, extack._msg))
+                       nlmsg_end(skb, nlh);
+       }
+
        if (sk_filter(sk, skb))
                kfree_skb(skb);
        else
@@ -2272,9 +2298,9 @@ int __netlink_dump_start(struct sock *ssk, struct sk_buff *skb,
                         const struct nlmsghdr *nlh,
                         struct netlink_dump_control *control)
 {
+       struct netlink_sock *nlk, *nlk2;
        struct netlink_callback *cb;
        struct sock *sk;
-       struct netlink_sock *nlk;
        int ret;
 
        refcount_inc(&skb->users);
@@ -2308,6 +2334,9 @@ int __netlink_dump_start(struct sock *ssk, struct sk_buff *skb,
        cb->min_dump_alloc = control->min_dump_alloc;
        cb->skb = skb;
 
+       nlk2 = nlk_sk(NETLINK_CB(skb).sk);
+       cb->strict_check = !!(nlk2->flags & NETLINK_F_STRICT_CHK);
+
        if (control->start) {
                ret = control->start(cb);
                if (ret)
index 962de7b3c023d44e5ab5bc5a62544c181963d9cd..5f454c8de6a4de07996578538d98bfd8ad45b950 100644 (file)
@@ -15,6 +15,7 @@
 #define NETLINK_F_LISTEN_ALL_NSID      0x10
 #define NETLINK_F_CAP_ACK              0x20
 #define NETLINK_F_EXT_ACK              0x40
+#define NETLINK_F_STRICT_CHK           0x80
 
 #define NLGRPSZ(x)     (ALIGN(x, sizeof(unsigned long) * 8) / 8)
 #define NLGRPLONGS(x)  (NLGRPSZ(x)/sizeof(unsigned long))
index 55153da0027862ab6354ff35bf50455109aa498a..9c1b0729aebf7146db03f534b433e6e871093ca8 100644 (file)
@@ -1452,7 +1452,7 @@ static int tc_dump_action(struct sk_buff *skb, struct netlink_callback *cb)
        u32 act_count = 0;
 
        ret = nlmsg_parse(cb->nlh, sizeof(struct tcamsg), tb, TCA_ROOT_MAX,
-                         tcaa_policy, NULL);
+                         tcaa_policy, cb->extack);
        if (ret < 0)
                return ret;
 
index d670d3066ebd6c2f3cd1220b5447f31d252d97b4..43c8559aca563bbd3b18504c7587c89e877a2779 100644 (file)
@@ -1727,7 +1727,8 @@ static int tc_dump_tfilter(struct sk_buff *skb, struct netlink_callback *cb)
        if (nlmsg_len(cb->nlh) < sizeof(*tcm))
                return skb->len;
 
-       err = nlmsg_parse(cb->nlh, sizeof(*tcm), tca, TCA_MAX, NULL, NULL);
+       err = nlmsg_parse(cb->nlh, sizeof(*tcm), tca, TCA_MAX, NULL,
+                         cb->extack);
        if (err)
                return err;
 
@@ -2054,7 +2055,8 @@ static int tc_dump_chain(struct sk_buff *skb, struct netlink_callback *cb)
        if (nlmsg_len(cb->nlh) < sizeof(*tcm))
                return skb->len;
 
-       err = nlmsg_parse(cb->nlh, sizeof(*tcm), tca, TCA_MAX, NULL, NULL);
+       err = nlmsg_parse(cb->nlh, sizeof(*tcm), tca, TCA_MAX, NULL,
+                         cb->extack);
        if (err)
                return err;
 
index da1963b19dec68d792f0059537094390ff765552..cf5c714ae786c376259a44f8de6f561e848383dc 100644 (file)
@@ -1671,7 +1671,7 @@ static int tc_dump_qdisc(struct sk_buff *skb, struct netlink_callback *cb)
        ASSERT_RTNL();
 
        err = nlmsg_parse(nlh, sizeof(struct tcmsg), tca, TCA_MAX,
-                         rtm_tca_policy, NULL);
+                         rtm_tca_policy, cb->extack);
        if (err < 0)
                return err;
 
index df7ca2dabc48881eb607089bb17a19b1bc6028b1..ca7a207b81a9587c942dd8763e4444cc5675f1ee 100644 (file)
@@ -1007,7 +1007,7 @@ static int xfrm_dump_sa(struct sk_buff *skb, struct netlink_callback *cb)
                int err;
 
                err = nlmsg_parse(cb->nlh, 0, attrs, XFRMA_MAX, xfrma_policy,
-                                 NULL);
+                                 cb->extack);
                if (err < 0)
                        return err;