diff options
Diffstat (limited to 'net/netlink/policy.c')
-rw-r--r-- | net/netlink/policy.c | 479 |
1 files changed, 479 insertions, 0 deletions
diff --git a/net/netlink/policy.c b/net/netlink/policy.c new file mode 100644 index 000000000..87e3de0fd --- /dev/null +++ b/net/netlink/policy.c @@ -0,0 +1,479 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * NETLINK Policy advertisement to userspace + * + * Authors: Johannes Berg <johannes@sipsolutions.net> + * + * 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 netlink_policy_dump_state { + 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 netlink_policy_dump_state **statep, + const struct nla_policy *policy, + unsigned int maxtype) +{ + struct netlink_policy_dump_state *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 && + state->policies[i].maxtype == maxtype) + 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; + + memset(&state->policies[state->n_alloc], 0, + flex_array_size(state, policies, n_alloc - state->n_alloc)); + + state->policies[state->n_alloc].policy = policy; + state->policies[state->n_alloc].maxtype = maxtype; + state->n_alloc = n_alloc; + *statep = state; + + return 0; +} + +/** + * netlink_policy_dump_get_policy_idx - retrieve policy index + * @state: the policy dump state + * @policy: the policy to find + * @maxtype: the policy's maxattr + * + * Returns: the index of the given policy in the dump state + * + * Call this to find a policy index when you've added multiple and e.g. + * need to tell userspace which command has which policy (by index). + * + * Note: this will WARN and return 0 if the policy isn't found, which + * means it wasn't added in the first place, which would be an + * internal consistency bug. + */ +int netlink_policy_dump_get_policy_idx(struct netlink_policy_dump_state *state, + const struct nla_policy *policy, + unsigned int maxtype) +{ + unsigned int i; + + if (WARN_ON(!policy || !maxtype)) + return 0; + + for (i = 0; i < state->n_alloc; i++) { + if (state->policies[i].policy == policy && + state->policies[i].maxtype == maxtype) + return i; + } + + WARN_ON(1); + return 0; +} + +static struct netlink_policy_dump_state *alloc_state(void) +{ + struct netlink_policy_dump_state *state; + + state = kzalloc(struct_size(state, policies, INITIAL_POLICIES_ALLOC), + GFP_KERNEL); + if (!state) + return ERR_PTR(-ENOMEM); + state->n_alloc = INITIAL_POLICIES_ALLOC; + + return state; +} + +/** + * netlink_policy_dump_add_policy - add a policy to the dump + * @pstate: state to add to, may be reallocated, must be %NULL the first time + * @policy: the new policy to add to the dump + * @maxtype: the new policy's max attr type + * + * Returns: 0 on success, a negative error code otherwise. + * + * Call this to allocate a policy dump state, and to add policies to it. This + * should be called from the dump start() callback. + * + * Note: on failures, any previously allocated state is freed. + */ +int netlink_policy_dump_add_policy(struct netlink_policy_dump_state **pstate, + const struct nla_policy *policy, + unsigned int maxtype) +{ + struct netlink_policy_dump_state *state = *pstate; + unsigned int policy_idx; + int err; + + if (!state) { + state = alloc_state(); + if (IS_ERR(state)) + return PTR_ERR(state); + } + + /* + * walk the policies and nested ones first, and build + * a linear list of them. + */ + + err = add_policy(&state, policy, maxtype); + if (err) + goto err_try_undo; + + 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) + goto err_try_undo; + break; + default: + break; + } + } + } + + *pstate = state; + return 0; + +err_try_undo: + /* Try to preserve reasonable unwind semantics - if we're starting from + * scratch clean up fully, otherwise record what we got and caller will. + */ + if (!*pstate) + netlink_policy_dump_free(state); + else + *pstate = state; + return err; +} + +static bool +netlink_policy_dump_finished(struct netlink_policy_dump_state *state) +{ + return state->policy_idx >= state->n_alloc || + !state->policies[state->policy_idx].policy; +} + +/** + * netlink_policy_dump_loop - dumping loop indicator + * @state: the policy dump state + * + * Returns: %true if the dump continues, %false otherwise + * + * Note: this frees the dump state when finishing + */ +bool netlink_policy_dump_loop(struct netlink_policy_dump_state *state) +{ + return !netlink_policy_dump_finished(state); +} + +int netlink_policy_dump_attr_size_estimate(const struct nla_policy *pt) +{ + /* nested + type */ + int common = 2 * nla_attr_size(sizeof(u32)); + + switch (pt->type) { + case NLA_UNSPEC: + case NLA_REJECT: + /* these actually don't need any space */ + return 0; + case NLA_NESTED: + case NLA_NESTED_ARRAY: + /* common, policy idx, policy maxattr */ + return common + 2 * nla_attr_size(sizeof(u32)); + case NLA_U8: + case NLA_U16: + case NLA_U32: + case NLA_U64: + case NLA_MSECS: + case NLA_S8: + case NLA_S16: + case NLA_S32: + case NLA_S64: + /* maximum is common, u64 min/max with padding */ + return common + + 2 * (nla_attr_size(0) + nla_attr_size(sizeof(u64))); + case NLA_BITFIELD32: + return common + nla_attr_size(sizeof(u32)); + case NLA_STRING: + case NLA_NUL_STRING: + case NLA_BINARY: + /* maximum is common, u32 min-length/max-length */ + return common + 2 * nla_attr_size(sizeof(u32)); + case NLA_FLAG: + return common; + } + + /* this should then cause a warning later */ + return 0; +} + +static int +__netlink_policy_dump_write_attr(struct netlink_policy_dump_state *state, + struct sk_buff *skb, + const struct nla_policy *pt, + int nestattr) +{ + int estimate = netlink_policy_dump_attr_size_estimate(pt); + enum netlink_attribute_type type; + struct nlattr *attr; + + attr = nla_nest_start(skb, nestattr); + if (!attr) + return -ENOBUFS; + + switch (pt->type) { + default: + case NLA_UNSPEC: + case NLA_REJECT: + /* skip - use NLA_MIN_LEN to advertise such */ + nla_nest_cancel(skb, attr); + return -ENODATA; + case NLA_NESTED: + type = NL_ATTR_TYPE_NESTED; + fallthrough; + case NLA_NESTED_ARRAY: + if (pt->type == NLA_NESTED_ARRAY) + type = NL_ATTR_TYPE_NESTED_ARRAY; + if (state && pt->nested_policy && pt->len && + (nla_put_u32(skb, NL_POLICY_TYPE_ATTR_POLICY_IDX, + netlink_policy_dump_get_policy_idx(state, + pt->nested_policy, + pt->len)) || + 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; + + if (pt->validation_type == NLA_VALIDATE_MASK) { + if (nla_put_u64_64bit(skb, NL_POLICY_TYPE_ATTR_MASK, + pt->mask, + NL_POLICY_TYPE_ATTR_PAD)) + goto nla_put_failure; + break; + } + + 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_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->validation_type == NLA_VALIDATE_RANGE || + pt->validation_type == NLA_VALIDATE_RANGE_WARN_TOO_LONG) { + struct netlink_range_validation range; + + nla_get_range_unsigned(pt, &range); + + if (range.min && + nla_put_u32(skb, NL_POLICY_TYPE_ATTR_MIN_LENGTH, + range.min)) + goto nla_put_failure; + + if (range.max < U16_MAX && + nla_put_u32(skb, NL_POLICY_TYPE_ATTR_MAX_LENGTH, + range.max)) + goto nla_put_failure; + } else if (pt->len && + nla_put_u32(skb, NL_POLICY_TYPE_ATTR_MAX_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; + + nla_nest_end(skb, attr); + WARN_ON(attr->nla_len > estimate); + + return 0; +nla_put_failure: + nla_nest_cancel(skb, attr); + return -ENOBUFS; +} + +/** + * netlink_policy_dump_write_attr - write a given attribute policy + * @skb: the message skb to write to + * @pt: the attribute's policy + * @nestattr: the nested attribute ID to use + * + * Returns: 0 on success, an error code otherwise; -%ENODATA is + * special, indicating that there's no policy data and + * the attribute is generally rejected. + */ +int netlink_policy_dump_write_attr(struct sk_buff *skb, + const struct nla_policy *pt, + int nestattr) +{ + return __netlink_policy_dump_write_attr(NULL, skb, pt, nestattr); +} + +/** + * netlink_policy_dump_write - write current policy dump attributes + * @skb: the message skb to write to + * @state: the policy dump state + * + * Returns: 0 on success, an error code otherwise + */ +int netlink_policy_dump_write(struct sk_buff *skb, + struct netlink_policy_dump_state *state) +{ + const struct nla_policy *pt; + struct nlattr *policy; + bool again; + int err; + +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; + + err = __netlink_policy_dump_write_attr(state, skb, pt, state->attr_idx); + if (err == -ENODATA) { + nla_nest_cancel(skb, policy); + again = true; + goto next; + } else if (err) { + goto nla_put_failure; + } + + /* finish and move state to next attribute */ + 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; +} + +/** + * netlink_policy_dump_free - free policy dump state + * @state: the policy dump state to free + * + * Call this from the done() method to ensure dump state is freed. + */ +void netlink_policy_dump_free(struct netlink_policy_dump_state *state) +{ + kfree(state); +} |