[PATCH v3 6/7] netlink: Add multicast group level permissions

From: Anjali Kulkarni
Date: Wed Mar 29 2023 - 14:26:27 EST


A new field perm_groups is added in netlink_sock to store the protocol's
multicast group access permissions. This is to allow for a more fine
grained access control than just at the protocol level. These
permissions can be supplied by the protocol via the netlink_kernel_cfg.
A new function netlink_multicast_allowed() is added, which checks if
the protocol's multicast group has non-root access before allowing bind.

Signed-off-by: Anjali Kulkarni <anjali.k.kulkarni@xxxxxxxxxx>
---
include/linux/netlink.h | 1 +
net/netlink/af_netlink.c | 25 +++++++++++++++++++++++--
net/netlink/af_netlink.h | 2 ++
3 files changed, 26 insertions(+), 2 deletions(-)

diff --git a/include/linux/netlink.h b/include/linux/netlink.h
index 05a316aa93b4..253cbcd7a290 100644
--- a/include/linux/netlink.h
+++ b/include/linux/netlink.h
@@ -46,6 +46,7 @@ void netlink_table_ungrab(void);
struct netlink_kernel_cfg {
unsigned int groups;
unsigned int flags;
+ long unsigned perm_groups;
void (*input)(struct sk_buff *skb);
struct mutex *cb_mutex;
int (*bind)(struct net *net, int group);
diff --git a/net/netlink/af_netlink.c b/net/netlink/af_netlink.c
index dc7880055705..f31173d28dd0 100644
--- a/net/netlink/af_netlink.c
+++ b/net/netlink/af_netlink.c
@@ -679,6 +679,7 @@ static int netlink_create(struct net *net, struct socket *sock, int protocol,
void (*unbind)(struct net *net, int group);
void (*release)(struct sock *sock, unsigned long *groups);
int err = 0;
+ unsigned long perm_groups;

sock->state = SS_UNCONNECTED;

@@ -706,6 +707,7 @@ static int netlink_create(struct net *net, struct socket *sock, int protocol,
bind = nl_table[protocol].bind;
unbind = nl_table[protocol].unbind;
release = nl_table[protocol].release;
+ perm_groups = nl_table[protocol].perm_groups;
netlink_unlock_table();

if (err < 0)
@@ -722,6 +724,7 @@ static int netlink_create(struct net *net, struct socket *sock, int protocol,
nlk->netlink_bind = bind;
nlk->netlink_unbind = unbind;
nlk->netlink_release = release;
+ nlk->perm_groups = perm_groups;
out:
return err;

@@ -938,6 +941,20 @@ bool netlink_net_capable(const struct sk_buff *skb, int cap)
}
EXPORT_SYMBOL(netlink_net_capable);

+static inline bool netlink_multicast_allowed(unsigned long perm_groups,
+ unsigned long groups)
+{
+ int group;
+
+ for (group = 0; group < BITS_PER_TYPE(u32); group++) {
+ if (test_bit(group, &groups)) {
+ if (!test_bit(group, &perm_groups))
+ return false;
+ }
+ }
+ return true;
+}
+
static inline int netlink_allowed(const struct socket *sock, unsigned int flag)
{
return (nl_table[sock->sk->sk_protocol].flags & flag) ||
@@ -1023,8 +1040,11 @@ static int netlink_bind(struct socket *sock, struct sockaddr *addr,

/* Only superuser is allowed to listen multicasts */
if (groups) {
- if (!netlink_allowed(sock, NL_CFG_F_NONROOT_RECV))
- return -EPERM;
+ if (!netlink_allowed(sock, NL_CFG_F_NONROOT_RECV)) {
+ if (!netlink_multicast_allowed(nlk->perm_groups,
+ groups))
+ return -EPERM;
+ }
err = netlink_realloc_groups(sk);
if (err)
return err;
@@ -2124,6 +2144,7 @@ __netlink_kernel_create(struct net *net, int unit, struct module *module,
nl_table[unit].unbind = cfg->unbind;
nl_table[unit].release = cfg->release;
nl_table[unit].flags = cfg->flags;
+ nl_table[unit].perm_groups = cfg->perm_groups;
if (cfg->compare)
nl_table[unit].compare = cfg->compare;
}
diff --git a/net/netlink/af_netlink.h b/net/netlink/af_netlink.h
index 054335a34804..b7880254c716 100644
--- a/net/netlink/af_netlink.h
+++ b/net/netlink/af_netlink.h
@@ -29,6 +29,7 @@ struct netlink_sock {
u32 flags;
u32 subscriptions;
u32 ngroups;
+ unsigned long perm_groups;
unsigned long *groups;
unsigned long state;
size_t max_recvmsg_len;
@@ -62,6 +63,7 @@ struct netlink_table {
struct listeners __rcu *listeners;
unsigned int flags;
unsigned int groups;
+ unsigned long perm_groups;
struct mutex *cb_mutex;
struct module *module;
int (*bind)(struct net *net, int group);
--
2.40.0