[ovs-dev] [PATCH 1/2] datapath: Add nla_parse_strict().

Joe Stringer joestringer at nicira.com
Tue Aug 26 23:50:30 UTC 2014


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 <joestringer at 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




More information about the dev mailing list