// 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);
#ifdef CONFIG_LOCKDEP
bool lockdep_genl_is_held(void)
{
return lockdep_is_held(&genl_mutex);
}
EXPORT_SYMBOL(lockdep_genl_is_held);
#endif
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;
static int genl_ctrl_event(int event, const struct genl_family *family,
const struct genl_multicast_group *grp,
int grp_id);
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 const struct genl_ops *genl_get_cmd(u8 cmd,
const struct genl_family *family)
{
int i;
for (i = 0; i < family->n_ops; i++)
if (family->ops[i].cmd == cmd)
return &family->ops[i];
return NULL;
}
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 +
BITS_TO_LONGS(n_groups);
size_t nlen = new_longs * sizeof(unsigned long);
if (mc_groups == &mc_group_start) {
new_groups = kzalloc(nlen, GFP_KERNEL);
if (!new_groups)
return -ENOMEM;
mc_groups = new_groups;
*mc_groups = mc_group_start;
} else {
new_groups = krealloc(mc_groups, nlen,
GFP_KERNEL);
if (!new_groups)