// SPDX-License-Identifier: GPL-2.0
/*
* NETLINK Generic Netlink Family
*
* Authors: Jamal Hadi Salim
* Thomas Graf <tgraf@suug.ch>
* Johannes Berg <johannes@sipsolutions.net>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/string.h>
#include <linux/skbuff.h>
#include <linux/mutex.h>
#include <linux/bitmap.h>
#include <linux/rwsem.h>
#include <linux/idr.h>
#include <net/sock.h>
#include <net/genetlink.h>
static DEFINE_MUTEX(genl_mutex); /* serialization of message processing */
static DECLARE_RWSEM(cb_lock);
atomic_t genl_sk_destructing_cnt = ATOMIC_INIT(0);
DECLARE_WAIT_QUEUE_HEAD(genl_sk_destructing_waitq);
void genl_lock(void)
{
mutex_lock(&genl_mutex);
}
EXPORT_SYMBOL(genl_lock);
void genl_unlock(void)
{
mutex_unlock(&genl_mutex);
}
EXPORT_SYMBOL(genl_unlock);
static void genl_lock_all(void)
{
down_write(&cb_lock);
genl_lock();
}
static void genl_unlock_all(void)
{
genl_unlock();
up_write(&cb_lock);
}
static DEFINE_IDR(genl_fam_idr);
/*
* Bitmap of multicast groups that are currently in use.
*
* To avoid an allocation at boot of just one unsigned long,
* declare it global instead.
* Bit 0 is marked as already used since group 0 is invalid.
* Bit 1 is marked as already used since the drop-monitor code
* abuses the API and thinks it can statically use group 1.
* That group will typically conflict with other groups that
* any proper users use.
* Bit 16 is marked as used since it's used for generic netlink
* and the code no longer marks pre-reserved IDs as used.
* Bit 17 is marked as already used since the VFS quota code
* also abused this API and relied on family == group ID, we
* cater to that by giving it a static family and group ID.
* Bit 18 is marked as already used since the PMCRAID driver
* did the same thing as the VFS quota code (maybe copied?)
*/
static unsigned long mc_group_start = 0x3 | BIT(GENL_ID_CTRL) |
BIT(GENL_ID_VFS_DQUOT) |
BIT(GENL_ID_PMCRAID);
static unsigned long *mc_groups = &mc_group_start;
static unsigned long mc_groups_longs = 1;
/* We need the last attribute with non-zero ID therefore a 2-entry array */
static struct nla_policy genl_policy_reject_all[] = {
{ .type = NLA_REJECT },
{ .type = NLA_REJECT },
};
static int genl_ctrl_event(int event, const struct genl_family *family,
const struct genl_multicast_group *grp,
int grp_id);
static void
genl_op_fill_in_reject_policy(const struct genl_family *family,
struct genl_ops *op)
{
BUILD_BUG_ON(ARRAY_SIZE(genl_policy_reject_all) - 1 != 1);
if (op->policy || op->cmd < family->resv_start_op)
return;
op->policy = genl_policy_reject_all;
op->maxattr = 1;
}
static const struct genl_family *genl_family_find_byid(unsigned int id)
{
return idr_find(&genl_fam_idr, id);
}
static const struct genl_family *genl_family_find_byname(char *name)
{
const struct genl_family *family;
unsigned int id;
idr_for_each_entry(&genl_fam_idr, family, id)
if (strcmp(family->name, name) == 0)
return family;
return NULL;
}
static int genl_get_cmd_cnt(const struct genl_family *family)
{
return family->n_ops + family->n_small_ops;
}
static void genl_op_from_full(const struct genl_family *family,
unsigned int i, struct genl_ops *op)
{
*op = family->ops[i];
if (!op->maxattr)
op->maxattr = family->maxattr;
if (!op->policy)
op->policy = family->policy;
genl_op_fill_in_reject_policy(family, op);
}
static int genl_get_cmd_full(u32 cmd, const struct genl_family *family,
struct genl_ops *op)
{
int i;
for (i = 0; i < family->n_ops; i++)
if (family->ops[i].cmd == cmd) {
genl_op_from_full(family, i, op);
return 0;
}
return -ENOENT;
}
static void genl_op_from_small(const struct genl_family *family,
unsigned int i, struct genl_ops *op)
{
memset(op, 0, sizeof(*op));
op->doit = family->small_ops[i].doit;
op->dumpit = family->small_ops[i].dumpit;
op->cmd = family->small_ops[i].cmd;
op->internal_flags = family->small_ops[i].internal_flags;
op->flags = family->small_ops[i].flags;
op->validate = family->small_ops[i].validate;
op->maxattr = family->maxattr;
op->policy = family->policy;
genl_op_fill_in_reject_policy(family, op);
}
static int genl_get_cmd_small(u32 cmd, const struct genl_family *family,
struct genl_ops *op)
{
int i;
for (i = 0; i < family->n_small_ops; i++)
if (family->small_ops[i].cmd == cmd) {
genl_op_from_small(family, i, op);
return 0;
}
return -ENOENT;
}
static int genl_get_cmd(u32 cmd, const struct genl_family *family,
struct genl_ops *op)
{
if (!genl_get_cmd_full(cmd, family, op))
return 0;
return genl_get_cmd_small(cmd, family, op);
}
static void genl_get_cmd_by_index(unsigned int i,
const struct genl_family *family,
struct genl_ops *op)
{
if (i < family->n_ops)
genl_op_from_full(family, i, op);
else if (i < family->n_ops + family->n_small_ops)
genl_op_from_small(family, i - family->n_ops, op);
else
WARN_ON_ONCE(1);
}
static int genl_allocate_reserve_groups(int n_groups, int *first_id)
{
unsigned long *new_groups;
int start = 0;
int i;
int id;
bool fits;
do {
if (start == 0)
id = find_first_zero_bit(mc_groups,
mc_groups_longs *
BITS_PER_LONG);
else
id = find_next_zero_bit(mc_groups,
mc_groups_longs * BITS_PER_LONG,
start);
fits = true;
for (i = id;
i < min_t(int, id + n_groups,
mc_groups_longs * BITS_PER_LONG);
i++) {
if (test_bit(i, mc_groups)) {
start = i;
fits = false;
break;
}
}
if (id + n_groups > mc_groups_longs * BITS_PER_LONG) {
unsigned long new_longs = mc_groups_longs +
|