249801360d
If construction of the array of policies fails when recording
non-first policy we need to unwind.
netlink_policy_dump_add_policy() itself also needs fixing as
it currently gives up on error without recording the allocated
pointer in the pstate pointer.
Reported-by: syzbot+dc54d9ba8153b216cae0@syzkaller.appspotmail.com
Fixes: 50a896cf2d
("genetlink: properly support per-op policy dumping")
Link: https://lore.kernel.org/r/20220816161939.577583-1-kuba@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
480 lines
12 KiB
C
480 lines
12 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 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);
|
|
}
|