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