This function allows netlink attributes to be parsed more strictly,
to check for duplicate attributes, attribute max lengths, and detecting
attributes with only non-zero values.

Signed-off-by: Joe Stringer <joestrin...@nicira.com>
---
 acinclude.m4                                  |    2 +
 datapath/linux/Modules.mk                     |    1 +
 datapath/linux/compat/include/linux/netlink.h |    6 +
 datapath/linux/compat/nlattr.c                |  186 +++++++++++++++++++++++++
 4 files changed, 195 insertions(+)
 create mode 100644 datapath/linux/compat/nlattr.c

diff --git a/acinclude.m4 b/acinclude.m4
index 7e036e5..a47906a 100644
--- a/acinclude.m4
+++ b/acinclude.m4
@@ -351,6 +351,8 @@ AC_DEFUN([OVS_CHECK_LINUX_COMPAT], [
   OVS_GREP_IFELSE([$KSRC/include/net/netlink.h], [nla_put_be32])
   OVS_GREP_IFELSE([$KSRC/include/net/netlink.h], [nla_put_be64])
   OVS_GREP_IFELSE([$KSRC/include/net/netlink.h], [nla_find_nested])
+  OVS_GREP_IFELSE([$KSRC/include/net/netlink.h], [nla_parse_strict],
+                  [OVS_DEFINE([HAVE_NLA_PARSE_STRICT])])
 
   OVS_GREP_IFELSE([$KSRC/include/net/sctp/checksum.h], [sctp_compute_cksum])
 
diff --git a/datapath/linux/Modules.mk b/datapath/linux/Modules.mk
index 0b9fffa..eabe2a8 100644
--- a/datapath/linux/Modules.mk
+++ b/datapath/linux/Modules.mk
@@ -11,6 +11,7 @@ openvswitch_sources += \
        linux/compat/ip_tunnels_core.c \
        linux/compat/netdevice.c \
        linux/compat/net_namespace.c \
+       linux/compat/nlattr.c \
        linux/compat/reciprocal_div.c \
        linux/compat/skbuff-openvswitch.c \
        linux/compat/vxlan.c    \
diff --git a/datapath/linux/compat/include/linux/netlink.h 
b/datapath/linux/compat/include/linux/netlink.h
index a64de4f..f172590 100644
--- a/datapath/linux/compat/include/linux/netlink.h
+++ b/datapath/linux/compat/include/linux/netlink.h
@@ -16,4 +16,10 @@
 #define NLMSG_DEFAULT_SIZE (NLMSG_GOODSIZE - NLMSG_HDRLEN)
 #endif
 
+#ifndef HAVE_NLA_PARSE_STRICT
+int nla_parse_strict(const struct nlattr **tb, int maxtype,
+                    const struct nlattr *head, int len,
+                    const struct nla_policy *policy, bool dup, bool nz);
+#endif
+
 #endif
diff --git a/datapath/linux/compat/nlattr.c b/datapath/linux/compat/nlattr.c
new file mode 100644
index 0000000..64d0b78
--- /dev/null
+++ b/datapath/linux/compat/nlattr.c
@@ -0,0 +1,186 @@
+#include <linux/errno.h>
+#include <linux/netdevice.h>
+#include <linux/netlink.h>
+#include <linux/ratelimit.h>
+#include <linux/types.h>
+
+#ifndef HAVE_NLA_PARSE_STRICT
+
+static const u16 nla_attr_minlen[NLA_TYPE_MAX+1] = {
+       [NLA_U8]        = sizeof(u8),
+       [NLA_U16]       = sizeof(u16),
+       [NLA_U32]       = sizeof(u32),
+       [NLA_U64]       = sizeof(u64),
+       [NLA_MSECS]     = sizeof(u64),
+       [NLA_NESTED]    = NLA_HDRLEN,
+};
+
+static int validate_nla(const struct nlattr *nla, int maxtype,
+                       const struct nla_policy *policy, bool strict)
+{
+       const struct nla_policy *pt;
+       int minlen = 0, maxlen = 0, attrlen = nla_len(nla), type = 
nla_type(nla);
+
+       if (type <= 0 || type > maxtype)
+               return 0;
+
+       pt = &policy[type];
+
+       BUG_ON(pt->type > NLA_TYPE_MAX);
+
+       switch (pt->type) {
+       case NLA_FLAG:
+               if (attrlen > 0)
+                       return -ERANGE;
+               break;
+
+       case NLA_NUL_STRING:
+               if (pt->len)
+                       minlen = min_t(int, attrlen, pt->len + 1);
+               else
+                       minlen = attrlen;
+
+               if (!minlen || memchr(nla_data(nla), '\0', minlen) == NULL)
+                       return -EINVAL;
+               /* fall through */
+
+       case NLA_STRING:
+               if (attrlen < 1)
+                       return -ERANGE;
+
+               if (pt->len) {
+                       char *buf = nla_data(nla);
+
+                       if (buf[attrlen - 1] == '\0')
+                               attrlen--;
+
+                       if (attrlen > pt->len)
+                               return -ERANGE;
+               }
+               break;
+
+       case NLA_BINARY:
+               if (pt->len && attrlen > pt->len)
+                       return -ERANGE;
+               break;
+
+       case NLA_NESTED_COMPAT:
+               if (attrlen < pt->len)
+                       return -ERANGE;
+               if (attrlen < NLA_ALIGN(pt->len))
+                       break;
+               if (attrlen < NLA_ALIGN(pt->len) + NLA_HDRLEN)
+                       return -ERANGE;
+               nla = nla_data(nla) + NLA_ALIGN(pt->len);
+               if (attrlen < NLA_ALIGN(pt->len) + NLA_HDRLEN + nla_len(nla))
+                       return -ERANGE;
+               break;
+       case NLA_NESTED:
+               /* a nested attributes is allowed to be empty; if its not,
+                * it must have a size of at least NLA_HDRLEN.
+                */
+               if (attrlen == 0)
+                       break;
+       default:
+               if (pt->len) {
+                       minlen = pt->len;
+                       if (strict)
+                               maxlen = pt->len;
+               } else if (pt->type != NLA_UNSPEC)
+                       minlen = nla_attr_minlen[pt->type];
+
+               if (attrlen < minlen || (maxlen && attrlen > maxlen)) {
+                       pr_warn_ratelimited("netlink: unexpected attribute "
+                                 "length in process `%s' (type=%d, length=%d,"
+                                 " expected length=%d).\n", current->comm,
+                                 type, nla_len(nla), minlen);
+                       return -ERANGE;
+               }
+       }
+
+       return 0;
+}
+
+static bool is_all_zero(const u8 *fp, size_t size)
+{
+       int i;
+
+       if (!fp)
+               return false;
+
+       for (i = 0; i < size; i++)
+               if (fp[i])
+                       return false;
+
+       return true;
+}
+
+/**
+ * nla_parse_strict - Parse a stream of attributes into a tb buffer
+ * @tb: destination array with maxtype+1 elements
+ * @maxtype: maximum attribute type to be expected
+ * @head: head of attribute stream
+ * @len: length of attribute stream
+ * @policy: validation policy
+ * @dup: allow duplicate attributes (later attributes override earlier)
+ * @nz: only store pointers for attributes with nonzero values
+ *
+ * Parses a stream of attributes and stores a pointer to each attribute in
+ * the tb array accessible via the attribute type. Attributes with unknown
+ * types or invalid lengths, duplicate attributes, or unexpected trailing
+ * bytes will cause parsing to fail. Unlike nla_parse(), this function
+ * requires the policy to be specified. Lengths specified by the policy
+ * indicate the exact length of the attribute, any variation will cause
+ * parsing to fail.
+ *
+ * Returns 0 on success or a negative error code.
+ */
+int nla_parse_strict(const struct nlattr **tb, int maxtype,
+                    const struct nlattr *head, int len,
+                    const struct nla_policy *policy,
+                    bool dup, bool nz)
+{
+       const struct nlattr *nla;
+       int rem, err;
+
+       BUG_ON(!policy && nz);
+       memset(tb, 0, sizeof(struct nlattr *) * (maxtype + 1));
+
+       nla_for_each_attr(nla, head, len, rem) {
+               u16 type = nla_type(nla);
+
+               if (type > 0 && type <= maxtype) {
+                       err = validate_nla(nla, maxtype, policy,
+                                          true);
+                       if (err < 0)
+                               goto errout;
+
+                       if (!dup && tb[type]) {
+                               pr_warn_ratelimited("netlink: duplicate 
attribute "
+                                         "received in process `%s' 
(type=%d).\n",
+                                         current->comm, type);
+                               return -EINVAL;
+                       }
+
+                       if (!nz || !is_all_zero(nla_data(nla), nla_len(nla)))
+                               tb[type] = nla;
+               } else {
+                       pr_warn_ratelimited("netlink: unknown attribute 
received "
+                                 "in process `%s' (type=%d, max=%d).\n",
+                                 current->comm, type, maxtype);
+                       return -EINVAL;
+               }
+       }
+
+       if (unlikely(rem > 0)) {
+               pr_warn_ratelimited("netlink: %d bytes leftover after parsing "
+                         "attributes in process `%s'.\n", rem, current->comm);
+               return -EINVAL;
+       }
+
+       err = 0;
+errout:
+       return err;
+}
+
+#endif /* HAVE_NLA_PARSE_STRICT */
-- 
1.7.10.4

_______________________________________________
dev mailing list
dev@openvswitch.org
http://openvswitch.org/mailman/listinfo/dev

Reply via email to