2020-04-29 13:50:59

by Johannes Berg

[permalink] [raw]
Subject: [PATCH 0/7] netlink validation improvements/refactoring

Hi,

Sorry - again, I got distracted/interrupted before I could send this.
I made this a little more than a year ago, and then forgot it. Antonio
asked me something a couple of weeks ago, and that reminded me of this
so I'm finally sending it out now (rebased & adjusted).

Basically this just does some refactoring & improvements for range
validation, leading up to a patch to expose the policy to userspace,
which I'll send separately as RFC for now.

johannes



2020-04-29 13:51:08

by Johannes Berg

[permalink] [raw]
Subject: [RFC PATCH] netlink: add infrastructure to expose policies to userspace

From: Johannes Berg <[email protected]>

Add, and use in generic netlink, helpers to dump out a netlink
policy to userspace, including all the range validation data,
nested policies etc.

This lets userspace discover what the kernel understands.

For families/commands other than generic netlink, the helpers
need to be used directly in an appropriate command, or we can
add some infrastructure (a new netlink family) that those can
register their policies with for introspection. I'm not that
familiar with non-generic netlink, so that's left out for now.

The data exposed to userspace also includes min and max length
for binary/string data, I've done that instead of letting the
userspace tools figure out whether min/max is intended based
on the type so that we can extend this later in the kernel, we
might want to just use the range data for example.

Because of this, I opted to not directly expose the NLA_*
values, even if some of them are already exposed via BPF, as
with min/max length we don't need to have different types here
for NLA_BINARY/NLA_MIN_LEN/NLA_EXACT_LEN, we just make them
all NL_ATTR_TYPE_BINARY with min/max length optionally set.

Similarly, we don't really need NLA_MSECS, and perhaps can
remove it in the future - but not if we encode it into the
userspace API now. It gets mapped to NL_ATTR_TYPE_U64 here.

Note that the exposing here corresponds to the strict policy
interpretation, and NLA_UNSPEC items are omitted entirely.
To get those, change them to NLA_MIN_LEN which behaves in
exactly the same way, but is exposed.

Signed-off-by: Johannes Berg <[email protected]>
---
include/net/netlink.h | 6 +
include/uapi/linux/genetlink.h | 2 +
include/uapi/linux/netlink.h | 103 +++++++++++
net/netlink/Makefile | 2 +-
net/netlink/genetlink.c | 78 +++++++++
net/netlink/policy.c | 308 +++++++++++++++++++++++++++++++++
6 files changed, 498 insertions(+), 1 deletion(-)
create mode 100644 net/netlink/policy.c

diff --git a/include/net/netlink.h b/include/net/netlink.h
index 557b67f1db99..c0411f14fb53 100644
--- a/include/net/netlink.h
+++ b/include/net/netlink.h
@@ -1933,4 +1933,10 @@ void nla_get_range_unsigned(const struct nla_policy *pt,
void nla_get_range_signed(const struct nla_policy *pt,
struct netlink_range_validation_signed *range);

+int netlink_policy_dump_start(const struct nla_policy *policy,
+ unsigned int maxtype,
+ unsigned long *state);
+bool netlink_policy_dump_loop(unsigned long *state);
+int netlink_policy_dump_write(struct sk_buff *skb, unsigned long state);
+
#endif
diff --git a/include/uapi/linux/genetlink.h b/include/uapi/linux/genetlink.h
index 877f7fa95466..9c0636ec2286 100644
--- a/include/uapi/linux/genetlink.h
+++ b/include/uapi/linux/genetlink.h
@@ -48,6 +48,7 @@ enum {
CTRL_CMD_NEWMCAST_GRP,
CTRL_CMD_DELMCAST_GRP,
CTRL_CMD_GETMCAST_GRP, /* unused */
+ CTRL_CMD_GETPOLICY,
__CTRL_CMD_MAX,
};

@@ -62,6 +63,7 @@ enum {
CTRL_ATTR_MAXATTR,
CTRL_ATTR_OPS,
CTRL_ATTR_MCAST_GROUPS,
+ CTRL_ATTR_POLICY,
__CTRL_ATTR_MAX,
};

diff --git a/include/uapi/linux/netlink.h b/include/uapi/linux/netlink.h
index 0a4d73317759..eac8a6a648ea 100644
--- a/include/uapi/linux/netlink.h
+++ b/include/uapi/linux/netlink.h
@@ -249,4 +249,107 @@ struct nla_bitfield32 {
__u32 selector;
};

+/*
+ * policy descriptions - it's specific to each family how this is used
+ * Normally, it should be retrieved via a dump inside another attribute
+ * specifying where it applies.
+ */
+
+/**
+ * enum netlink_attribute_type - type of an attribute
+ * @NL_ATTR_TYPE_INVALID: unused
+ * @NL_ATTR_TYPE_FLAG: flag attribute (present/not present)
+ * @NL_ATTR_TYPE_U8: 8-bit unsigned attribute
+ * @NL_ATTR_TYPE_U16: 16-bit unsigned attribute
+ * @NL_ATTR_TYPE_U32: 32-bit unsigned attribute
+ * @NL_ATTR_TYPE_U64: 64-bit unsigned attribute
+ * @NL_ATTR_TYPE_S8: 8-bit signed attribute
+ * @NL_ATTR_TYPE_S16: 16-bit signed attribute
+ * @NL_ATTR_TYPE_S32: 32-bit signed attribute
+ * @NL_ATTR_TYPE_S64: 64-bit signed attribute
+ * @NL_ATTR_TYPE_BINARY: binary data, min/max length may be specified
+ * @NL_ATTR_TYPE_STRING: string, min/max length may be specified
+ * @NL_ATTR_TYPE_NUL_STRING: NUL-terminated string,
+ * min/max length may be specified
+ * @NL_ATTR_TYPE_NESTED: nested, i.e. the content of this attribute
+ * consists of sub-attributes. The nested policy and maxtype
+ * inside may be specified.
+ * @NL_ATTR_TYPE_NESTED_ARRAY: nested array, i.e. the content of this
+ * attribute contains sub-attributes whose type is irrelevant
+ * (just used to separate the array entries) and each such array
+ * entry has attributes again, the policy for those inner ones
+ * and the corresponding maxtype may be specified.
+ * @NL_ATTR_TYPE_BITFIELD32: &struct nla_bitfield32 attribute
+ */
+enum netlink_attribute_type {
+ NL_ATTR_TYPE_INVALID,
+
+ NL_ATTR_TYPE_FLAG,
+
+ NL_ATTR_TYPE_U8,
+ NL_ATTR_TYPE_U16,
+ NL_ATTR_TYPE_U32,
+ NL_ATTR_TYPE_U64,
+
+ NL_ATTR_TYPE_S8,
+ NL_ATTR_TYPE_S16,
+ NL_ATTR_TYPE_S32,
+ NL_ATTR_TYPE_S64,
+
+ NL_ATTR_TYPE_BINARY,
+ NL_ATTR_TYPE_STRING,
+ NL_ATTR_TYPE_NUL_STRING,
+
+ NL_ATTR_TYPE_NESTED,
+ NL_ATTR_TYPE_NESTED_ARRAY,
+
+ NL_ATTR_TYPE_BITFIELD32,
+};
+
+/**
+ * enum netlink_policy_type_attr - policy type attributes
+ * @NL_POLICY_TYPE_ATTR_UNSPEC: unused
+ * @NL_POLICY_TYPE_ATTR_TYPE: type of the attribute,
+ * &enum netlink_attribute_type (U32)
+ * @NL_POLICY_TYPE_ATTR_MIN_VALUE_S: minimum value for signed
+ * integers (S64)
+ * @NL_POLICY_TYPE_ATTR_MAX_VALUE_S: maximum value for signed
+ * integers (S64)
+ * @NL_POLICY_TYPE_ATTR_MIN_VALUE_U: minimum value for unsigned
+ * integers (U64)
+ * @NL_POLICY_TYPE_ATTR_MAX_VALUE_U: maximum value for unsigned
+ * integers (U64)
+ * @NL_POLICY_TYPE_ATTR_MIN_LENGTH: minimum length for binary
+ * attributes, no minimum if not given (U32)
+ * @NL_POLICY_TYPE_ATTR_MAX_LENGTH: maximum length for binary
+ * attributes, no maximum if not given (U32)
+ * @NL_POLICY_TYPE_ATTR_POLICY_IDX: sub policy for nested and
+ * nested array types (U32)
+ * @NL_POLICY_TYPE_ATTR_POLICY_MAXTYPE: maximum sub policy
+ * attribute for nested and nested array types, this can
+ * in theory be < the size of the policy pointed to by
+ * the index, if limited inside the nesting (U32)
+ * @NL_POLICY_TYPE_ATTR_BITFIELD32_MASK: valid mask for the
+ * bitfield32 type (U32)
+ * @NL_POLICY_TYPE_ATTR_PAD: pad attribute for 64-bit alignment
+ */
+enum netlink_policy_type_attr {
+ NL_POLICY_TYPE_ATTR_UNSPEC,
+ NL_POLICY_TYPE_ATTR_TYPE,
+ NL_POLICY_TYPE_ATTR_MIN_VALUE_S,
+ NL_POLICY_TYPE_ATTR_MAX_VALUE_S,
+ NL_POLICY_TYPE_ATTR_MIN_VALUE_U,
+ NL_POLICY_TYPE_ATTR_MAX_VALUE_U,
+ NL_POLICY_TYPE_ATTR_MIN_LENGTH,
+ NL_POLICY_TYPE_ATTR_MAX_LENGTH,
+ NL_POLICY_TYPE_ATTR_POLICY_IDX,
+ NL_POLICY_TYPE_ATTR_POLICY_MAXTYPE,
+ NL_POLICY_TYPE_ATTR_BITFIELD32_MASK,
+ NL_POLICY_TYPE_ATTR_PAD,
+
+ /* keep last */
+ __NL_POLICY_TYPE_ATTR_MAX,
+ NL_POLICY_TYPE_ATTR_MAX = __NL_POLICY_TYPE_ATTR_MAX - 1
+};
+
#endif /* _UAPI__LINUX_NETLINK_H */
diff --git a/net/netlink/Makefile b/net/netlink/Makefile
index de42df7f0068..e05202708c90 100644
--- a/net/netlink/Makefile
+++ b/net/netlink/Makefile
@@ -3,7 +3,7 @@
# Makefile for the netlink driver.
#

-obj-y := af_netlink.o genetlink.o
+obj-y := af_netlink.o genetlink.o policy.o

obj-$(CONFIG_NETLINK_DIAG) += netlink_diag.o
netlink_diag-y := diag.o
diff --git a/net/netlink/genetlink.c b/net/netlink/genetlink.c
index 9f357aa22b94..2f049692e012 100644
--- a/net/netlink/genetlink.c
+++ b/net/netlink/genetlink.c
@@ -1043,6 +1043,80 @@ static int genl_ctrl_event(int event, const struct genl_family *family,
return 0;
}

+static int ctrl_dumppolicy(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ const struct genl_family *rt;
+ unsigned int fam_id = cb->args[0];
+ int err;
+
+ if (!fam_id) {
+ struct nlattr *tb[CTRL_ATTR_MAX + 1];
+
+ err = genlmsg_parse(cb->nlh, &genl_ctrl, tb,
+ genl_ctrl.maxattr,
+ genl_ctrl.policy, cb->extack);
+ if (err)
+ return err;
+
+ if (!tb[CTRL_ATTR_FAMILY_ID] && !tb[CTRL_ATTR_FAMILY_NAME])
+ return -EINVAL;
+
+ if (tb[CTRL_ATTR_FAMILY_ID]) {
+ fam_id = nla_get_u16(tb[CTRL_ATTR_FAMILY_ID]);
+ } else {
+ rt = genl_family_find_byname(
+ nla_data(tb[CTRL_ATTR_FAMILY_NAME]));
+ if (!rt)
+ return -ENOENT;
+ fam_id = rt->id;
+ }
+ }
+
+ rt = genl_family_find_byid(fam_id);
+ if (!rt)
+ return -ENOENT;
+
+ if (!rt->policy)
+ return -ENODATA;
+
+ err = netlink_policy_dump_start(rt->policy, rt->maxattr, &cb->args[1]);
+ if (err)
+ return err;
+
+ while (netlink_policy_dump_loop(&cb->args[1])) {
+ void *hdr;
+ struct nlattr *nest;
+
+ hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid,
+ cb->nlh->nlmsg_seq, &genl_ctrl,
+ NLM_F_MULTI, CTRL_CMD_GETPOLICY);
+ if (!hdr)
+ goto nla_put_failure;
+
+ if (nla_put_u16(skb, CTRL_ATTR_FAMILY_ID, rt->id))
+ goto nla_put_failure;
+
+ nest = nla_nest_start(skb, CTRL_ATTR_POLICY);
+ if (!nest)
+ goto nla_put_failure;
+
+ if (netlink_policy_dump_write(skb, cb->args[1]))
+ goto nla_put_failure;
+
+ nla_nest_end(skb, nest);
+
+ genlmsg_end(skb, hdr);
+ continue;
+
+nla_put_failure:
+ genlmsg_cancel(skb, hdr);
+ break;
+ }
+
+ cb->args[0] = fam_id;
+ return skb->len;
+}
+
static const struct genl_ops genl_ctrl_ops[] = {
{
.cmd = CTRL_CMD_GETFAMILY,
@@ -1050,6 +1124,10 @@ static const struct genl_ops genl_ctrl_ops[] = {
.doit = ctrl_getfamily,
.dumpit = ctrl_dumpfamily,
},
+ {
+ .cmd = CTRL_CMD_GETPOLICY,
+ .dumpit = ctrl_dumppolicy,
+ },
};

static const struct genl_multicast_group genl_ctrl_groups[] = {
diff --git a/net/netlink/policy.c b/net/netlink/policy.c
new file mode 100644
index 000000000000..f6491853c797
--- /dev/null
+++ b/net/netlink/policy.c
@@ -0,0 +1,308 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * NETLINK Policy advertisement to userspace
+ *
+ * Authors: Johannes Berg <[email protected]>
+ *
+ * Copyright 2019 Intel Corporation
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <net/netlink.h>
+
+#define INITIAL_POLICIES_ALLOC 10
+
+struct nl_policy_dump {
+ unsigned int policy_idx;
+ unsigned int attr_idx;
+ unsigned int n_alloc;
+ struct {
+ const struct nla_policy *policy;
+ unsigned int maxtype;
+ } policies[];
+};
+
+static int add_policy(struct nl_policy_dump **statep,
+ const struct nla_policy *policy,
+ unsigned int maxtype)
+{
+ struct nl_policy_dump *state = *statep;
+ unsigned int n_alloc, i;
+
+ if (!policy || !maxtype)
+ return 0;
+
+ for (i = 0; i < state->n_alloc; i++) {
+ if (state->policies[i].policy == policy)
+ return 0;
+
+ if (!state->policies[i].policy) {
+ state->policies[i].policy = policy;
+ state->policies[i].maxtype = maxtype;
+ return 0;
+ }
+ }
+
+ n_alloc = state->n_alloc + INITIAL_POLICIES_ALLOC;
+ state = krealloc(state, struct_size(state, policies, n_alloc),
+ GFP_KERNEL);
+ if (!state)
+ return -ENOMEM;
+
+ state->policies[state->n_alloc].policy = policy;
+ state->policies[state->n_alloc].maxtype = maxtype;
+ state->n_alloc = n_alloc;
+ *statep = state;
+
+ return 0;
+}
+
+static unsigned int get_policy_idx(struct nl_policy_dump *state,
+ const struct nla_policy *policy)
+{
+ unsigned int i;
+
+ for (i = 0; i < state->n_alloc; i++) {
+ if (state->policies[i].policy == policy)
+ return i;
+ }
+
+ WARN_ON_ONCE(1);
+ return -1;
+}
+
+int netlink_policy_dump_start(const struct nla_policy *policy,
+ unsigned int maxtype,
+ unsigned long *_state)
+{
+ struct nl_policy_dump *state;
+ unsigned int policy_idx;
+ int err;
+
+ /* also returns 0 if "*_state" is our ERR_PTR() end marker */
+ if (*_state)
+ return 0;
+
+ /*
+ * walk the policies and nested ones first, and build
+ * a linear list of them.
+ */
+
+ state = kzalloc(struct_size(state, policies, INITIAL_POLICIES_ALLOC),
+ GFP_KERNEL);
+ if (!state)
+ return -ENOMEM;
+ state->n_alloc = INITIAL_POLICIES_ALLOC;
+
+ err = add_policy(&state, policy, maxtype);
+ if (err)
+ return err;
+
+ for (policy_idx = 0;
+ policy_idx < state->n_alloc && state->policies[policy_idx].policy;
+ policy_idx++) {
+ const struct nla_policy *policy;
+ unsigned int type;
+
+ policy = state->policies[policy_idx].policy;
+
+ for (type = 0;
+ type <= state->policies[policy_idx].maxtype;
+ type++) {
+ switch (policy[type].type) {
+ case NLA_NESTED:
+ case NLA_NESTED_ARRAY:
+ err = add_policy(&state,
+ policy[type].nested_policy,
+ policy[type].len);
+ if (err)
+ return err;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ *_state = (unsigned long)state;
+
+ return 0;
+}
+
+static bool netlink_policy_dump_finished(struct nl_policy_dump *state)
+{
+ return state->policy_idx >= state->n_alloc ||
+ !state->policies[state->policy_idx].policy;
+}
+
+bool netlink_policy_dump_loop(unsigned long *_state)
+{
+ struct nl_policy_dump *state = (void *)*_state;
+
+ if (IS_ERR(state))
+ return false;
+
+ if (netlink_policy_dump_finished(state)) {
+ kfree(state);
+ /* store end marker instead of freed state */
+ *_state = (unsigned long)ERR_PTR(-ENOENT);
+ return false;
+ }
+
+ return true;
+}
+
+int netlink_policy_dump_write(struct sk_buff *skb, unsigned long _state)
+{
+ struct nl_policy_dump *state = (void *)_state;
+ const struct nla_policy *pt;
+ struct nlattr *policy, *attr;
+ enum netlink_attribute_type type;
+ bool again;
+
+send_attribute:
+ again = false;
+
+ pt = &state->policies[state->policy_idx].policy[state->attr_idx];
+
+ policy = nla_nest_start(skb, state->policy_idx);
+ if (!policy)
+ return -ENOBUFS;
+
+ attr = nla_nest_start(skb, state->attr_idx);
+ if (!attr)
+ goto nla_put_failure;
+
+ switch (pt->type) {
+ default:
+ case NLA_UNSPEC:
+ case NLA_REJECT:
+ /* skip - use NLA_MIN_LEN to advertise such */
+ nla_nest_cancel(skb, policy);
+ again = true;
+ goto next;
+ case NLA_NESTED:
+ type = NL_ATTR_TYPE_NESTED;
+ /* fall through */
+ case NLA_NESTED_ARRAY:
+ if (pt->type == NLA_NESTED_ARRAY)
+ type = NL_ATTR_TYPE_NESTED_ARRAY;
+ if (pt->nested_policy && pt->len &&
+ (nla_put_u32(skb, NL_POLICY_TYPE_ATTR_POLICY_IDX,
+ get_policy_idx(state, pt->nested_policy)) ||
+ nla_put_u32(skb, NL_POLICY_TYPE_ATTR_POLICY_MAXTYPE,
+ pt->len)))
+ goto nla_put_failure;
+ break;
+ case NLA_U8:
+ case NLA_U16:
+ case NLA_U32:
+ case NLA_U64:
+ case NLA_MSECS: {
+ struct netlink_range_validation range;
+
+ if (pt->type == NLA_U8)
+ type = NL_ATTR_TYPE_U8;
+ else if (pt->type == NLA_U16)
+ type = NL_ATTR_TYPE_U16;
+ else if (pt->type == NLA_U32)
+ type = NL_ATTR_TYPE_U32;
+ else
+ type = NL_ATTR_TYPE_U64;
+
+ nla_get_range_unsigned(pt, &range);
+
+ if (nla_put_u64_64bit(skb, NL_POLICY_TYPE_ATTR_MIN_VALUE_U,
+ range.min, NL_POLICY_TYPE_ATTR_PAD) ||
+ nla_put_u64_64bit(skb, NL_POLICY_TYPE_ATTR_MAX_VALUE_U,
+ range.max, NL_POLICY_TYPE_ATTR_PAD))
+ goto nla_put_failure;
+ break;
+ }
+ case NLA_S8:
+ case NLA_S16:
+ case NLA_S32:
+ case NLA_S64: {
+ struct netlink_range_validation_signed range;
+
+ if (pt->type == NLA_S8)
+ type = NL_ATTR_TYPE_S8;
+ else if (pt->type == NLA_S16)
+ type = NL_ATTR_TYPE_S16;
+ else if (pt->type == NLA_S32)
+ type = NL_ATTR_TYPE_S32;
+ else
+ type = NL_ATTR_TYPE_S64;
+
+ nla_get_range_signed(pt, &range);
+
+ if (nla_put_s64(skb, NL_POLICY_TYPE_ATTR_MIN_VALUE_S,
+ range.min, NL_POLICY_TYPE_ATTR_PAD) ||
+ nla_put_s64(skb, NL_POLICY_TYPE_ATTR_MAX_VALUE_S,
+ range.max, NL_POLICY_TYPE_ATTR_PAD))
+ goto nla_put_failure;
+ break;
+ }
+ case NLA_BITFIELD32:
+ type = NL_ATTR_TYPE_BITFIELD32;
+ if (nla_put_u32(skb, NL_POLICY_TYPE_ATTR_BITFIELD32_MASK,
+ pt->bitfield32_valid))
+ goto nla_put_failure;
+ break;
+ case NLA_EXACT_LEN:
+ type = NL_ATTR_TYPE_BINARY;
+ if (nla_put_u32(skb, NL_POLICY_TYPE_ATTR_MIN_LENGTH, pt->len) ||
+ nla_put_u32(skb, NL_POLICY_TYPE_ATTR_MAX_LENGTH, pt->len))
+ goto nla_put_failure;
+ break;
+ case NLA_STRING:
+ case NLA_NUL_STRING:
+ case NLA_BINARY:
+ if (pt->type == NLA_STRING)
+ type = NL_ATTR_TYPE_STRING;
+ else if (pt->type == NLA_NUL_STRING)
+ type = NL_ATTR_TYPE_NUL_STRING;
+ else
+ type = NL_ATTR_TYPE_BINARY;
+ if (pt->len && nla_put_u32(skb, NL_POLICY_TYPE_ATTR_MAX_LENGTH,
+ pt->len))
+ goto nla_put_failure;
+ break;
+ case NLA_MIN_LEN:
+ type = NL_ATTR_TYPE_BINARY;
+ if (nla_put_u32(skb, NL_POLICY_TYPE_ATTR_MIN_LENGTH, pt->len))
+ goto nla_put_failure;
+ break;
+ case NLA_FLAG:
+ type = NL_ATTR_TYPE_FLAG;
+ break;
+ }
+
+ if (nla_put_u32(skb, NL_POLICY_TYPE_ATTR_TYPE, type))
+ goto nla_put_failure;
+
+ /* finish and move state to next attribute */
+ nla_nest_end(skb, attr);
+ nla_nest_end(skb, policy);
+
+next:
+ state->attr_idx += 1;
+ if (state->attr_idx > state->policies[state->policy_idx].maxtype) {
+ state->attr_idx = 0;
+ state->policy_idx++;
+ }
+
+ if (again) {
+ if (netlink_policy_dump_finished(state))
+ return -ENODATA;
+ goto send_attribute;
+ }
+
+ return 0;
+
+nla_put_failure:
+ nla_nest_cancel(skb, policy);
+ return -ENOBUFS;
+}
--
2.25.1

2020-04-29 13:52:38

by Johannes Berg

[permalink] [raw]
Subject: [PATCH 4/7] netlink: extend policy range validation

From: Johannes Berg <[email protected]>

Using a pointer to a struct indicating the min/max values,
extend the ability to do range validation for arbitrary
values. Small values in the s16 range can be kept in the
policy directly.

Signed-off-by: Johannes Berg <[email protected]>
---
include/net/netlink.h | 45 +++++++++++++++++
lib/nlattr.c | 112 ++++++++++++++++++++++++++++++++++--------
2 files changed, 136 insertions(+), 21 deletions(-)

diff --git a/include/net/netlink.h b/include/net/netlink.h
index 671b29d170a8..94a7df4ab122 100644
--- a/include/net/netlink.h
+++ b/include/net/netlink.h
@@ -189,11 +189,20 @@ enum {

#define NLA_TYPE_MAX (__NLA_TYPE_MAX - 1)

+struct netlink_range_validation {
+ u64 min, max;
+};
+
+struct netlink_range_validation_signed {
+ s64 min, max;
+};
+
enum nla_policy_validation {
NLA_VALIDATE_NONE,
NLA_VALIDATE_RANGE,
NLA_VALIDATE_MIN,
NLA_VALIDATE_MAX,
+ NLA_VALIDATE_RANGE_PTR,
NLA_VALIDATE_FUNCTION,
};

@@ -271,6 +280,22 @@ enum nla_policy_validation {
* of s16 - do that as usual in the code instead.
* Use the NLA_POLICY_MIN(), NLA_POLICY_MAX() and
* NLA_POLICY_RANGE() macros.
+ * NLA_U8,
+ * NLA_U16,
+ * NLA_U32,
+ * NLA_U64 If the validation_type field instead is set to
+ * NLA_VALIDATE_RANGE_PTR, `range' must be a pointer
+ * to a struct netlink_range_validation that indicates
+ * the min/max values.
+ * Use NLA_POLICY_FULL_RANGE().
+ * NLA_S8,
+ * NLA_S16,
+ * NLA_S32,
+ * NLA_S64 If the validation_type field instead is set to
+ * NLA_VALIDATE_RANGE_PTR, `range_signed' must be a
+ * pointer to a struct netlink_range_validation_signed
+ * that indicates the min/max values.
+ * Use NLA_POLICY_FULL_RANGE_SIGNED().
* All other Unused - but note that it's a union
*
* Meaning of `validate' field, use via NLA_POLICY_VALIDATE_FN:
@@ -296,6 +321,8 @@ struct nla_policy {
const u32 bitfield32_valid;
const char *reject_message;
const struct nla_policy *nested_policy;
+ struct netlink_range_validation *range;
+ struct netlink_range_validation_signed *range_signed;
struct {
s16 min, max;
};
@@ -342,6 +369,12 @@ struct nla_policy {
{ .type = NLA_BITFIELD32, .bitfield32_valid = valid }

#define __NLA_ENSURE(condition) BUILD_BUG_ON_ZERO(!(condition))
+#define NLA_ENSURE_UINT_TYPE(tp) \
+ (__NLA_ENSURE(tp == NLA_U8 || tp == NLA_U16 || \
+ tp == NLA_U32 || tp == NLA_U64) + tp)
+#define NLA_ENSURE_SINT_TYPE(tp) \
+ (__NLA_ENSURE(tp == NLA_S8 || tp == NLA_S16 || \
+ tp == NLA_S32 || tp == NLA_S64) + tp)
#define NLA_ENSURE_INT_TYPE(tp) \
(__NLA_ENSURE(tp == NLA_S8 || tp == NLA_U8 || \
tp == NLA_S16 || tp == NLA_U16 || \
@@ -360,6 +393,18 @@ struct nla_policy {
.max = _max \
}

+#define NLA_POLICY_FULL_RANGE(tp, _range) { \
+ .type = NLA_ENSURE_UINT_TYPE(tp), \
+ .validation_type = NLA_VALIDATE_RANGE_PTR, \
+ .range = _range, \
+}
+
+#define NLA_POLICY_FULL_RANGE_SIGNED(tp, _range) { \
+ .type = NLA_ENSURE_SINT_TYPE(tp), \
+ .validation_type = NLA_VALIDATE_RANGE_PTR, \
+ .range_signed = _range, \
+}
+
#define NLA_POLICY_MIN(tp, _min) { \
.type = NLA_ENSURE_INT_TYPE(tp), \
.validation_type = NLA_VALIDATE_MIN, \
diff --git a/lib/nlattr.c b/lib/nlattr.c
index 7f7ebd89caa4..bb66d06cc6f9 100644
--- a/lib/nlattr.c
+++ b/lib/nlattr.c
@@ -111,17 +111,33 @@ static int nla_validate_array(const struct nlattr *head, int len, int maxtype,
return 0;
}

-static int nla_validate_int_range(const struct nla_policy *pt,
- const struct nlattr *nla,
- struct netlink_ext_ack *extack)
+static int nla_validate_int_range_unsigned(const struct nla_policy *pt,
+ const struct nlattr *nla,
+ struct netlink_ext_ack *extack)
{
- bool validate_min, validate_max;
- s64 value;
+ struct netlink_range_validation _range = {
+ .min = 0,
+ .max = U64_MAX,
+ }, *range = &_range;
+ u64 value;

- validate_min = pt->validation_type == NLA_VALIDATE_RANGE ||
- pt->validation_type == NLA_VALIDATE_MIN;
- validate_max = pt->validation_type == NLA_VALIDATE_RANGE ||
- pt->validation_type == NLA_VALIDATE_MAX;
+ WARN_ON_ONCE(pt->min < 0 || pt->max < 0);
+
+ switch (pt->validation_type) {
+ case NLA_VALIDATE_RANGE:
+ range->min = pt->min;
+ range->max = pt->max;
+ break;
+ case NLA_VALIDATE_RANGE_PTR:
+ range = pt->range;
+ break;
+ case NLA_VALIDATE_MIN:
+ range->min = pt->min;
+ break;
+ case NLA_VALIDATE_MAX:
+ range->max = pt->max;
+ break;
+ }

switch (pt->type) {
case NLA_U8:
@@ -133,6 +149,49 @@ static int nla_validate_int_range(const struct nla_policy *pt,
case NLA_U32:
value = nla_get_u32(nla);
break;
+ case NLA_U64:
+ value = nla_get_u64(nla);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (value < range->min || value > range->max) {
+ NL_SET_ERR_MSG_ATTR(extack, nla,
+ "integer out of range");
+ return -ERANGE;
+ }
+
+ return 0;
+}
+
+static int nla_validate_int_range_signed(const struct nla_policy *pt,
+ const struct nlattr *nla,
+ struct netlink_ext_ack *extack)
+{
+ struct netlink_range_validation_signed _range = {
+ .min = S64_MIN,
+ .max = S64_MAX,
+ }, *range = &_range;
+ s64 value;
+
+ switch (pt->validation_type) {
+ case NLA_VALIDATE_RANGE:
+ range->min = pt->min;
+ range->max = pt->max;
+ break;
+ case NLA_VALIDATE_RANGE_PTR:
+ range = pt->range_signed;
+ break;
+ case NLA_VALIDATE_MIN:
+ range->min = pt->min;
+ break;
+ case NLA_VALIDATE_MAX:
+ range->max = pt->max;
+ break;
+ }
+
+ switch (pt->type) {
case NLA_S8:
value = nla_get_s8(nla);
break;
@@ -145,22 +204,11 @@ static int nla_validate_int_range(const struct nla_policy *pt,
case NLA_S64:
value = nla_get_s64(nla);
break;
- case NLA_U64:
- /* treat this one specially, since it may not fit into s64 */
- if ((validate_min && nla_get_u64(nla) < pt->min) ||
- (validate_max && nla_get_u64(nla) > pt->max)) {
- NL_SET_ERR_MSG_ATTR(extack, nla,
- "integer out of range");
- return -ERANGE;
- }
- return 0;
default:
- WARN_ON(1);
return -EINVAL;
}

- if ((validate_min && value < pt->min) ||
- (validate_max && value > pt->max)) {
+ if (value < range->min || value > range->max) {
NL_SET_ERR_MSG_ATTR(extack, nla,
"integer out of range");
return -ERANGE;
@@ -169,6 +217,27 @@ static int nla_validate_int_range(const struct nla_policy *pt,
return 0;
}

+static int nla_validate_int_range(const struct nla_policy *pt,
+ const struct nlattr *nla,
+ struct netlink_ext_ack *extack)
+{
+ switch (pt->type) {
+ case NLA_U8:
+ case NLA_U16:
+ case NLA_U32:
+ case NLA_U64:
+ return nla_validate_int_range_unsigned(pt, nla, extack);
+ case NLA_S8:
+ case NLA_S16:
+ case NLA_S32:
+ case NLA_S64:
+ return nla_validate_int_range_signed(pt, nla, extack);
+ default:
+ WARN_ON(1);
+ return -EINVAL;
+ }
+}
+
static int validate_nla(const struct nlattr *nla, int maxtype,
const struct nla_policy *policy, unsigned int validate,
struct netlink_ext_ack *extack, unsigned int depth)
@@ -348,6 +417,7 @@ static int validate_nla(const struct nlattr *nla, int maxtype,
case NLA_VALIDATE_NONE:
/* nothing to do */
break;
+ case NLA_VALIDATE_RANGE_PTR:
case NLA_VALIDATE_RANGE:
case NLA_VALIDATE_MIN:
case NLA_VALIDATE_MAX:
--
2.25.1

2020-04-29 13:52:40

by Johannes Berg

[permalink] [raw]
Subject: [PATCH 5/7] netlink: allow NLA_MSECS to have range validation

From: Johannes Berg <[email protected]>

Since NLA_MSECS is really equivalent to NLA_U64, allow
it to have range validation as well.

Signed-off-by: Johannes Berg <[email protected]>
---
include/net/netlink.h | 6 ++++--
lib/nlattr.c | 2 ++
2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/include/net/netlink.h b/include/net/netlink.h
index 94a7df4ab122..4acd7165e900 100644
--- a/include/net/netlink.h
+++ b/include/net/netlink.h
@@ -371,7 +371,8 @@ struct nla_policy {
#define __NLA_ENSURE(condition) BUILD_BUG_ON_ZERO(!(condition))
#define NLA_ENSURE_UINT_TYPE(tp) \
(__NLA_ENSURE(tp == NLA_U8 || tp == NLA_U16 || \
- tp == NLA_U32 || tp == NLA_U64) + tp)
+ tp == NLA_U32 || tp == NLA_U64 || \
+ tp == NLA_MSECS) + tp)
#define NLA_ENSURE_SINT_TYPE(tp) \
(__NLA_ENSURE(tp == NLA_S8 || tp == NLA_S16 || \
tp == NLA_S32 || tp == NLA_S64) + tp)
@@ -379,7 +380,8 @@ struct nla_policy {
(__NLA_ENSURE(tp == NLA_S8 || tp == NLA_U8 || \
tp == NLA_S16 || tp == NLA_U16 || \
tp == NLA_S32 || tp == NLA_U32 || \
- tp == NLA_S64 || tp == NLA_U64) + tp)
+ tp == NLA_S64 || tp == NLA_U64 || \
+ tp == NLA_MSECS) + tp)
#define NLA_ENSURE_NO_VALIDATION_PTR(tp) \
(__NLA_ENSURE(tp != NLA_BITFIELD32 && \
tp != NLA_REJECT && \
diff --git a/lib/nlattr.c b/lib/nlattr.c
index bb66d06cc6f9..ab7fa7726c12 100644
--- a/lib/nlattr.c
+++ b/lib/nlattr.c
@@ -150,6 +150,7 @@ static int nla_validate_int_range_unsigned(const struct nla_policy *pt,
value = nla_get_u32(nla);
break;
case NLA_U64:
+ case NLA_MSECS:
value = nla_get_u64(nla);
break;
default:
@@ -226,6 +227,7 @@ static int nla_validate_int_range(const struct nla_policy *pt,
case NLA_U16:
case NLA_U32:
case NLA_U64:
+ case NLA_MSECS:
return nla_validate_int_range_unsigned(pt, nla, extack);
case NLA_S8:
case NLA_S16:
--
2.25.1

2020-04-29 18:12:01

by Jakub Kicinski

[permalink] [raw]
Subject: Re: [PATCH 4/7] netlink: extend policy range validation

On Wed, 29 Apr 2020 15:48:40 +0200 Johannes Berg wrote:
> diff --git a/lib/nlattr.c b/lib/nlattr.c
> index 7f7ebd89caa4..bb66d06cc6f9 100644
> --- a/lib/nlattr.c
> +++ b/lib/nlattr.c
> @@ -111,17 +111,33 @@ static int nla_validate_array(const struct nlattr *head, int len, int maxtype,
> return 0;
> }
>
> -static int nla_validate_int_range(const struct nla_policy *pt,
> - const struct nlattr *nla,
> - struct netlink_ext_ack *extack)
> +static int nla_validate_int_range_unsigned(const struct nla_policy *pt,
> + const struct nlattr *nla,
> + struct netlink_ext_ack *extack)
> {
> - bool validate_min, validate_max;
> - s64 value;
> + struct netlink_range_validation _range = {
> + .min = 0,
> + .max = U64_MAX,
> + }, *range = &_range;
> + u64 value;
>
> - validate_min = pt->validation_type == NLA_VALIDATE_RANGE ||
> - pt->validation_type == NLA_VALIDATE_MIN;
> - validate_max = pt->validation_type == NLA_VALIDATE_RANGE ||
> - pt->validation_type == NLA_VALIDATE_MAX;
> + WARN_ON_ONCE(pt->min < 0 || pt->max < 0);

I'm probably missing something, but in case of NLA_VALIDATE_RANGE_PTR
aren't min and max invalid (union has the range pointer set, so this
will read 2 bytes of the pointer).

> + switch (pt->validation_type) {
> + case NLA_VALIDATE_RANGE:
> + range->min = pt->min;
> + range->max = pt->max;
> + break;
> + case NLA_VALIDATE_RANGE_PTR:
> + range = pt->range;
> + break;
> + case NLA_VALIDATE_MIN:
> + range->min = pt->min;
> + break;
> + case NLA_VALIDATE_MAX:
> + range->max = pt->max;
> + break;
> + }

2020-04-29 19:11:54

by David Miller

[permalink] [raw]
Subject: Re: [PATCH 0/7] netlink validation improvements/refactoring

From: Johannes Berg <[email protected]>
Date: Wed, 29 Apr 2020 15:48:36 +0200

> Sorry - again, I got distracted/interrupted before I could send this.
> I made this a little more than a year ago, and then forgot it. Antonio
> asked me something a couple of weeks ago, and that reminded me of this
> so I'm finally sending it out now (rebased & adjusted).
>
> Basically this just does some refactoring & improvements for range
> validation, leading up to a patch to expose the policy to userspace,
> which I'll send separately as RFC for now.

Please fix the pt->min/pt->max WARN_ON() vs. range pointer issue that
Jakub pointed out.

Otherwise this series looks good.

2020-04-29 19:24:20

by Johannes Berg

[permalink] [raw]
Subject: Re: [PATCH 4/7] netlink: extend policy range validation

On Wed, 2020-04-29 at 11:10 -0700, Jakub Kicinski wrote:

> > +static int nla_validate_int_range_unsigned(const struct nla_policy *pt,
> > + const struct nlattr *nla,
> > + struct netlink_ext_ack *extack)
> > {
> > - bool validate_min, validate_max;
> > - s64 value;
> > + struct netlink_range_validation _range = {
> > + .min = 0,
> > + .max = U64_MAX,
> > + }, *range = &_range;
> > + u64 value;
> >
> > - validate_min = pt->validation_type == NLA_VALIDATE_RANGE ||
> > - pt->validation_type == NLA_VALIDATE_MIN;
> > - validate_max = pt->validation_type == NLA_VALIDATE_RANGE ||
> > - pt->validation_type == NLA_VALIDATE_MAX;
> > + WARN_ON_ONCE(pt->min < 0 || pt->max < 0);
>
> I'm probably missing something, but in case of NLA_VALIDATE_RANGE_PTR
> aren't min and max invalid (union has the range pointer set, so this
> will read 2 bytes of the pointer).

No, you're right of course. It's reading 4 bytes, actually, they're both
s16. Which I did because that's the maximum range that doesn't increase
the size on 32-bit.

I could move it into the switch, but, hm.. the unused ones (min/max if
only one is used) should be 0, so I guess just

WARN_ON_ONCE(pt->validation_type != NLA_VALIDATE_RANGE_PTR &&
(pt->min < 0 || pt->max < 0));

will be fine.

Thanks!

johannes