mirror of
https://github.com/torvalds/linux.git
synced 2024-11-26 14:12:06 +00:00
8aa26c575f
Add range validation for NLA_BINARY, allowing validation of any combination of combination minimum or maximum lengths, using the existing NLA_POLICY_RANGE()/NLA_POLICY_FULL_RANGE() macros, just like for integers where the value is checked. Also make NLA_POLICY_EXACT_LEN(), NLA_POLICY_EXACT_LEN_WARN() and NLA_POLICY_MIN_LEN() special cases of this, removing the old types NLA_EXACT_LEN and NLA_MIN_LEN. This allows us to save some code where both minimum and maximum lengths are requires, currently the policy only allows maximum (NLA_BINARY), minimum (NLA_MIN_LEN) or exact (NLA_EXACT_LEN), so a range of lengths cannot be accepted and must be checked by the code that consumes the attributes later. Also, this allows advertising the correct ranges in the policy export to userspace. Here, NLA_MIN_LEN and NLA_EXACT_LEN already were special cases of NLA_BINARY with min and min/max length respectively. Signed-off-by: Johannes Berg <johannes.berg@intel.com> Signed-off-by: David S. Miller <davem@davemloft.net>
315 lines
7.0 KiB
C
315 lines
7.0 KiB
C
// 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 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_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_NONE) {
|
|
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;
|
|
|
|
/* 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;
|
|
}
|