This function allows netlink attributes to be parsed more strictly,
to check for additional constraints beyond those that nla_parse() checks
for. Passing flags=0 to nla_parse_strict() implies the same behaviour as
nla_parse().

Signed-off-by: Joe Stringer <joestrin...@nicira.com>
---
v2: Change nla_parse_strict() interface to take strictness flags
---
 acinclude.m4                                  |    2 +
 datapath/linux/Modules.mk                     |    1 +
 datapath/linux/compat/include/linux/netlink.h |   16 +++
 datapath/linux/compat/nlattr.c                |  185 +++++++++++++++++++++++++
 4 files changed, 204 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..df61273 100644
--- a/datapath/linux/compat/include/linux/netlink.h
+++ b/datapath/linux/compat/include/linux/netlink.h
@@ -16,4 +16,20 @@
 #define NLMSG_DEFAULT_SIZE (NLMSG_GOODSIZE - NLMSG_HDRLEN)
 #endif
 
+#ifndef HAVE_NLA_PARSE_STRICT
+
+#define NLA_PARSE_F_NOINIT     (1<<0)  /* Don't initialize the tb to zero. */
+#define NLA_PARSE_F_UNKNOWN    (1<<1)  /* Disallow unknown attributes. */
+#define NLA_PARSE_F_TRAILING   (1<<2)  /* Disallow trailing attributes. */
+#define NLA_PARSE_F_DUPLICATE  (1<<3)  /* Disallow duplicate attributes. */
+#define NLA_PARSE_F_EXACT_LEN  (1<<4)  /* Lengths specified in the policy
+                                          specify both min and max length. */
+#define NLA_PARSE_F_NONZERO    (1<<5)  /* Only store pointers for attributes
+                                          with nonzero values. */
+
+int nla_parse_strict(const struct nlattr **tb, int maxtype,
+                    const struct nlattr *head, int len,
+                    const struct nla_policy *policy, u8 flags);
+#endif
+
 #endif
diff --git a/datapath/linux/compat/nlattr.c b/datapath/linux/compat/nlattr.c
new file mode 100644
index 0000000..0f7dd11
--- /dev/null
+++ b/datapath/linux/compat/nlattr.c
@@ -0,0 +1,185 @@
+#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
+ * @flags: mask of NLA_PARSE_F_*
+ *
+ * Parses a stream of attributes and stores a pointer to each attribute in the
+ * tb array accessible via the attribute type. Unlike nla_parse(), this
+ * function requires the policy to be specified.
+ *
+ * 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, u8 flags)
+{
+       const struct nlattr *nla;
+       int rem, err;
+
+       BUG_ON(!policy && (flags & NLA_PARSE_F_NONZERO));
+       if (!(flags & NLA_PARSE_F_NOINIT))
+               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) {
+                       bool strict_len = flags & NLA_PARSE_F_EXACT_LEN;
+
+                       err = validate_nla(nla, maxtype, policy,
+                                          strict_len);
+                       if (err < 0)
+                               goto errout;
+
+                       if ((flags & NLA_PARSE_F_DUPLICATE) && tb[type]) {
+                               pr_warn_ratelimited("netlink: duplicate 
attribute "
+                                         "received in process `%s' 
(type=%d).\n",
+                                         current->comm, type);
+                               return -EINVAL;
+                       }
+
+                       if (!(flags & NLA_PARSE_F_NONZERO) ||
+                           !is_all_zero(nla_data(nla), nla_len(nla)))
+                               tb[type] = nla;
+               } else if (flags & NLA_PARSE_F_UNKNOWN) {
+                       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);
+               if (flags & NLA_PARSE_F_TRAILING)
+                       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