diff --git a/include/net/netlink.h b/include/net/netlink.h index 00258590f2cb3..5a5ff97cc596d 100644 --- a/include/net/netlink.h +++ b/include/net/netlink.h @@ -1937,9 +1937,12 @@ void nla_get_range_signed(const struct nla_policy *pt, struct netlink_policy_dump_state; -int netlink_policy_dump_start(const struct nla_policy *policy, - unsigned int maxtype, - struct netlink_policy_dump_state **state); +int netlink_policy_dump_add_policy(struct netlink_policy_dump_state **pstate, + const struct nla_policy *policy, + unsigned int maxtype); +int netlink_policy_dump_get_policy_idx(struct netlink_policy_dump_state *state, + const struct nla_policy *policy, + unsigned int maxtype); bool netlink_policy_dump_loop(struct netlink_policy_dump_state *state); int netlink_policy_dump_write(struct sk_buff *skb, struct netlink_policy_dump_state *state); diff --git a/include/uapi/linux/genetlink.h b/include/uapi/linux/genetlink.h index 9c0636ec2286f..d83f214b4134a 100644 --- a/include/uapi/linux/genetlink.h +++ b/include/uapi/linux/genetlink.h @@ -64,6 +64,8 @@ enum { CTRL_ATTR_OPS, CTRL_ATTR_MCAST_GROUPS, CTRL_ATTR_POLICY, + CTRL_ATTR_OP_POLICY, + CTRL_ATTR_OP, __CTRL_ATTR_MAX, }; @@ -85,6 +87,15 @@ enum { __CTRL_ATTR_MCAST_GRP_MAX, }; +enum { + CTRL_ATTR_POLICY_UNSPEC, + CTRL_ATTR_POLICY_DO, + CTRL_ATTR_POLICY_DUMP, + + __CTRL_ATTR_POLICY_DUMP_MAX, + CTRL_ATTR_POLICY_DUMP_MAX = __CTRL_ATTR_POLICY_DUMP_MAX - 1 +}; + #define CTRL_ATTR_MCAST_GRP_MAX (__CTRL_ATTR_MCAST_GRP_MAX - 1) diff --git a/net/netlink/genetlink.c b/net/netlink/genetlink.c index 7ddc574931f4d..c992424e4d632 100644 --- a/net/netlink/genetlink.c +++ b/net/netlink/genetlink.c @@ -123,7 +123,7 @@ static void genl_op_from_full(const struct genl_family *family, op->policy = family->policy; } -static int genl_get_cmd_full(u8 cmd, const struct genl_family *family, +static int genl_get_cmd_full(u32 cmd, const struct genl_family *family, struct genl_ops *op) { int i; @@ -152,7 +152,7 @@ static void genl_op_from_small(const struct genl_family *family, op->policy = family->policy; } -static int genl_get_cmd_small(u8 cmd, const struct genl_family *family, +static int genl_get_cmd_small(u32 cmd, const struct genl_family *family, struct genl_ops *op) { int i; @@ -166,7 +166,7 @@ static int genl_get_cmd_small(u8 cmd, const struct genl_family *family, return -ENOENT; } -static int genl_get_cmd(u8 cmd, const struct genl_family *family, +static int genl_get_cmd(u32 cmd, const struct genl_family *family, struct genl_ops *op) { if (!genl_get_cmd_full(cmd, family, op)) @@ -1112,13 +1112,19 @@ static int genl_ctrl_event(int event, const struct genl_family *family, struct ctrl_dump_policy_ctx { struct netlink_policy_dump_state *state; + const struct genl_family *rt; + unsigned int opidx; + u32 op; u16 fam_id; + u8 policies:1, + single_op:1; }; static const struct nla_policy ctrl_policy_policy[] = { [CTRL_ATTR_FAMILY_ID] = { .type = NLA_U16 }, [CTRL_ATTR_FAMILY_NAME] = { .type = NLA_NUL_STRING, .len = GENL_NAMSIZ - 1 }, + [CTRL_ATTR_OP] = { .type = NLA_U32 }, }; static int ctrl_dumppolicy_start(struct netlink_callback *cb) @@ -1127,6 +1133,8 @@ static int ctrl_dumppolicy_start(struct netlink_callback *cb) struct ctrl_dump_policy_ctx *ctx = (void *)cb->ctx; struct nlattr **tb = info->attrs; const struct genl_family *rt; + struct genl_ops op; + int err, i; BUILD_BUG_ON(sizeof(*ctx) > sizeof(cb->ctx)); @@ -1147,29 +1155,149 @@ static int ctrl_dumppolicy_start(struct netlink_callback *cb) if (!rt) return -ENOENT; - if (!rt->policy) + ctx->rt = rt; + + if (tb[CTRL_ATTR_OP]) { + ctx->single_op = true; + ctx->op = nla_get_u32(tb[CTRL_ATTR_OP]); + + err = genl_get_cmd(ctx->op, rt, &op); + if (err) { + NL_SET_BAD_ATTR(cb->extack, tb[CTRL_ATTR_OP]); + return err; + } + + if (!op.policy) + return -ENODATA; + + return netlink_policy_dump_add_policy(&ctx->state, op.policy, + op.maxattr); + } + + for (i = 0; i < genl_get_cmd_cnt(rt); i++) { + genl_get_cmd_by_index(i, rt, &op); + + if (op.policy) { + err = netlink_policy_dump_add_policy(&ctx->state, + op.policy, + op.maxattr); + if (err) + return err; + } + } + + if (!ctx->state) return -ENODATA; + return 0; +} - return netlink_policy_dump_start(rt->policy, rt->maxattr, &ctx->state); +static void *ctrl_dumppolicy_prep(struct sk_buff *skb, + struct netlink_callback *cb) +{ + struct ctrl_dump_policy_ctx *ctx = (void *)cb->ctx; + void *hdr; + + hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, &genl_ctrl, + NLM_F_MULTI, CTRL_CMD_GETPOLICY); + if (!hdr) + return NULL; + + if (nla_put_u16(skb, CTRL_ATTR_FAMILY_ID, ctx->fam_id)) + return NULL; + + return hdr; +} + +static int ctrl_dumppolicy_put_op(struct sk_buff *skb, + struct netlink_callback *cb, + struct genl_ops *op) +{ + struct ctrl_dump_policy_ctx *ctx = (void *)cb->ctx; + struct nlattr *nest_pol, *nest_op; + void *hdr; + int idx; + + /* skip if we have nothing to show */ + if (!op->policy) + return 0; + if (!op->doit && + (!op->dumpit || op->validate & GENL_DONT_VALIDATE_DUMP)) + return 0; + + hdr = ctrl_dumppolicy_prep(skb, cb); + if (!hdr) + return -ENOBUFS; + + nest_pol = nla_nest_start(skb, CTRL_ATTR_OP_POLICY); + if (!nest_pol) + goto err; + + nest_op = nla_nest_start(skb, op->cmd); + if (!nest_op) + goto err; + + /* for now both do/dump are always the same */ + idx = netlink_policy_dump_get_policy_idx(ctx->state, + op->policy, + op->maxattr); + + if (op->doit && nla_put_u32(skb, CTRL_ATTR_POLICY_DO, idx)) + goto err; + + if (op->dumpit && !(op->validate & GENL_DONT_VALIDATE_DUMP) && + nla_put_u32(skb, CTRL_ATTR_POLICY_DUMP, idx)) + goto err; + + nla_nest_end(skb, nest_op); + nla_nest_end(skb, nest_pol); + genlmsg_end(skb, hdr); + + return 0; +err: + genlmsg_cancel(skb, hdr); + return -ENOBUFS; } static int ctrl_dumppolicy(struct sk_buff *skb, struct netlink_callback *cb) { struct ctrl_dump_policy_ctx *ctx = (void *)cb->ctx; + void *hdr; + + if (!ctx->policies) { + while (ctx->opidx < genl_get_cmd_cnt(ctx->rt)) { + struct genl_ops op; + + if (ctx->single_op) { + int err; + + err = genl_get_cmd(ctx->op, ctx->rt, &op); + if (WARN_ON(err)) + return skb->len; + + /* break out of the loop after this one */ + ctx->opidx = genl_get_cmd_cnt(ctx->rt); + } else { + genl_get_cmd_by_index(ctx->opidx, ctx->rt, &op); + } + + if (ctrl_dumppolicy_put_op(skb, cb, &op)) + return skb->len; + + ctx->opidx++; + } + + /* completed with the per-op policy index list */ + ctx->policies = true; + } while (netlink_policy_dump_loop(ctx->state)) { - 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); + hdr = ctrl_dumppolicy_prep(skb, cb); if (!hdr) goto nla_put_failure; - if (nla_put_u16(skb, CTRL_ATTR_FAMILY_ID, ctx->fam_id)) - goto nla_put_failure; - nest = nla_nest_start(skb, CTRL_ATTR_POLICY); if (!nest) goto nla_put_failure; @@ -1180,14 +1308,13 @@ static int ctrl_dumppolicy(struct sk_buff *skb, struct netlink_callback *cb) nla_nest_end(skb, nest); genlmsg_end(skb, hdr); - continue; - -nla_put_failure: - genlmsg_cancel(skb, hdr); - break; } return skb->len; + +nla_put_failure: + genlmsg_cancel(skb, hdr); + return skb->len; } static int ctrl_dumppolicy_done(struct netlink_callback *cb) diff --git a/net/netlink/policy.c b/net/netlink/policy.c index ebc64b20b6ee3..cf23c01517219 100644 --- a/net/netlink/policy.c +++ b/net/netlink/policy.c @@ -35,7 +35,8 @@ static int add_policy(struct netlink_policy_dump_state **statep, return 0; for (i = 0; i < state->n_alloc; i++) { - if (state->policies[i].policy == policy) + if (state->policies[i].policy == policy && + state->policies[i].maxtype == maxtype) return 0; if (!state->policies[i].policy) { @@ -62,42 +63,85 @@ static int add_policy(struct netlink_policy_dump_state **statep, return 0; } -static unsigned int get_policy_idx(struct netlink_policy_dump_state *state, - const struct nla_policy *policy) +/** + * 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) + if (state->policies[i].policy == policy && + state->policies[i].maxtype == maxtype) return i; } - WARN_ON_ONCE(1); - return -1; + WARN_ON(1); + return 0; } -int netlink_policy_dump_start(const struct nla_policy *policy, - unsigned int maxtype, - struct netlink_policy_dump_state **statep) +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 (*statep) - return 0; + 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. */ - 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; @@ -128,8 +172,7 @@ int netlink_policy_dump_start(const struct nla_policy *policy, } } - *statep = state; - + *pstate = state; return 0; } @@ -140,11 +183,26 @@ netlink_policy_dump_finished(struct netlink_policy_dump_state *state) !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); } +/** + * 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) { @@ -182,7 +240,9 @@ int netlink_policy_dump_write(struct sk_buff *skb, 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)) || + 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; @@ -305,6 +365,12 @@ int netlink_policy_dump_write(struct sk_buff *skb, 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);