[PATCH 07/18] netlink: Pass groups pointer to .bind()

From: Dmitry Safonov
Date: Wed Jul 25 2018 - 22:33:01 EST


Netlink messages sent by xfrm differ in size between 64-bit native and
32-bit compatible applications. To know which UABI to use to send the
message from kernel, I'll use the type of bind() syscall.
Xfrm will have hidden from userspace kernel-only groups for compatible
applications.
So, add pointer to groups to netlink_bind().
With later patches xfrm will set a proper compat group for netlink
socket during bind().

Cc: "David S. Miller" <davem@xxxxxxxxxxxxx>
Cc: Eric Paris <eparis@xxxxxxxxxx>
Cc: Florian Westphal <fw@xxxxxxxxx>
Cc: Herbert Xu <herbert@xxxxxxxxxxxxxxxxxxx>
Cc: Jozsef Kadlecsik <kadlec@xxxxxxxxxxxxxxxxx>
Cc: Pablo Neira Ayuso <pablo@xxxxxxxxxxxxx>
Cc: Paul Moore <paul@xxxxxxxxxxxxxx>
Cc: Steffen Klassert <steffen.klassert@xxxxxxxxxxx>
Cc: coreteam@xxxxxxxxxxxxx
Cc: linux-audit@xxxxxxxxxx
Cc: netdev@xxxxxxxxxxxxxxx
Cc: netfilter-devel@xxxxxxxxxxxxxxx
Signed-off-by: Dmitry Safonov <dima@xxxxxxxxxx>
---
include/linux/netlink.h | 2 +-
kernel/audit.c | 2 +-
net/core/rtnetlink.c | 14 ++++++--------
net/core/sock_diag.c | 25 ++++++++++++-------------
net/netfilter/nfnetlink.c | 24 ++++++++++++++----------
net/netlink/af_netlink.c | 27 ++++++++++-----------------
net/netlink/af_netlink.h | 4 ++--
net/netlink/genetlink.c | 26 ++++++++++++++++++--------
8 files changed, 64 insertions(+), 60 deletions(-)

diff --git a/include/linux/netlink.h b/include/linux/netlink.h
index f3075d6c7e82..19202648e04a 100644
--- a/include/linux/netlink.h
+++ b/include/linux/netlink.h
@@ -46,7 +46,7 @@ struct netlink_kernel_cfg {
unsigned int flags;
void (*input)(struct sk_buff *skb);
struct mutex *cb_mutex;
- int (*bind)(struct net *net, int group);
+ int (*bind)(struct net *net, unsigned long *groups);
void (*unbind)(struct net *net, int group);
bool (*compare)(struct net *net, struct sock *sk);
};
diff --git a/kernel/audit.c b/kernel/audit.c
index e7478cb58079..87ca0214bcf2 100644
--- a/kernel/audit.c
+++ b/kernel/audit.c
@@ -1523,7 +1523,7 @@ static void audit_receive(struct sk_buff *skb)
}

/* Run custom bind function on netlink socket group connect or bind requests. */
-static int audit_bind(struct net *net, int group)
+static int audit_bind(struct net *net, unsigned long *groups)
{
if (!capable(CAP_AUDIT_READ))
return -EPERM;
diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
index e3f743c141b3..0465e692ae32 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -4683,15 +4683,13 @@ static void rtnetlink_rcv(struct sk_buff *skb)
netlink_rcv_skb(skb, &rtnetlink_rcv_msg);
}

-static int rtnetlink_bind(struct net *net, int group)
+static int rtnetlink_bind(struct net *net, unsigned long *groups)
{
- switch (group) {
- case RTNLGRP_IPV4_MROUTE_R:
- case RTNLGRP_IPV6_MROUTE_R:
- if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
- return -EPERM;
- break;
- }
+ unsigned long mroute_r;
+
+ mroute_r = 1UL << RTNLGRP_IPV4_MROUTE_R | 1UL << RTNLGRP_IPV6_MROUTE_R;
+ if ((*groups & mroute_r) && !ns_capable(net->user_ns, CAP_NET_ADMIN))
+ return -EPERM;
return 0;
}

diff --git a/net/core/sock_diag.c b/net/core/sock_diag.c
index c37b5be7c5e4..befa6759f2ad 100644
--- a/net/core/sock_diag.c
+++ b/net/core/sock_diag.c
@@ -273,20 +273,19 @@ static void sock_diag_rcv(struct sk_buff *skb)
mutex_unlock(&sock_diag_mutex);
}

-static int sock_diag_bind(struct net *net, int group)
+static int sock_diag_bind(struct net *net, unsigned long *groups)
{
- switch (group) {
- case SKNLGRP_INET_TCP_DESTROY:
- case SKNLGRP_INET_UDP_DESTROY:
- if (!sock_diag_handlers[AF_INET])
- sock_load_diag_module(AF_INET, 0);
- break;
- case SKNLGRP_INET6_TCP_DESTROY:
- case SKNLGRP_INET6_UDP_DESTROY:
- if (!sock_diag_handlers[AF_INET6])
- sock_load_diag_module(AF_INET6, 0);
- break;
- }
+ unsigned long inet_mask, inet6_mask;
+
+ inet_mask = 1UL << SKNLGRP_INET_TCP_DESTROY;
+ inet_mask |= 1UL << SKNLGRP_INET_UDP_DESTROY;
+ inet6_mask = 1UL << SKNLGRP_INET6_TCP_DESTROY;
+ inet6_mask |= 1UL << SKNLGRP_INET6_UDP_DESTROY;
+
+ if ((*groups & inet_mask) && !sock_diag_handlers[AF_INET])
+ sock_load_diag_module(AF_INET, 0);
+ if ((*groups & inet6_mask) && !sock_diag_handlers[AF_INET6])
+ sock_load_diag_module(AF_INET6, 0);
return 0;
}

diff --git a/net/netfilter/nfnetlink.c b/net/netfilter/nfnetlink.c
index e1b6be29848d..6a8893df5285 100644
--- a/net/netfilter/nfnetlink.c
+++ b/net/netfilter/nfnetlink.c
@@ -556,21 +556,25 @@ static void nfnetlink_rcv(struct sk_buff *skb)
}

#ifdef CONFIG_MODULES
-static int nfnetlink_bind(struct net *net, int group)
+static int nfnetlink_bind(struct net *net, unsigned long *groups)
{
const struct nfnetlink_subsystem *ss;
- int type;
+ unsigned long _groups = *groups;
+ int type, group_bit, group = -1;

- if (group <= NFNLGRP_NONE || group > NFNLGRP_MAX)
- return 0;
+ while ((group_bit = __builtin_ffsl(_groups))) {
+ group += group_bit;

- type = nfnl_group2type[group];
+ type = nfnl_group2type[group];
+ rcu_read_lock();
+ ss = nfnetlink_get_subsys(type << 8);
+ rcu_read_unlock();
+ if (!ss)
+ request_module("nfnetlink-subsys-%d", type);
+
+ _groups >>= group_bit;
+ }

- rcu_read_lock();
- ss = nfnetlink_get_subsys(type << 8);
- rcu_read_unlock();
- if (!ss)
- request_module("nfnetlink-subsys-%d", type);
return 0;
}
#endif
diff --git a/net/netlink/af_netlink.c b/net/netlink/af_netlink.c
index ac805caed2e2..1e11e706c683 100644
--- a/net/netlink/af_netlink.c
+++ b/net/netlink/af_netlink.c
@@ -668,7 +668,7 @@ static int netlink_create(struct net *net, struct socket *sock, int protocol,
struct module *module = NULL;
struct mutex *cb_mutex;
struct netlink_sock *nlk;
- int (*bind)(struct net *net, int group);
+ int (*bind)(struct net *net, unsigned long *groups);
void (*unbind)(struct net *net, int group);
int err = 0;

@@ -969,8 +969,7 @@ static int netlink_realloc_groups(struct sock *sk)
return err;
}

-static void netlink_undo_bind(int group, long unsigned int groups,
- struct sock *sk)
+static void netlink_undo_bind(unsigned long groups, struct sock *sk)
{
struct netlink_sock *nlk = nlk_sk(sk);
int undo;
@@ -978,7 +977,7 @@ static void netlink_undo_bind(int group, long unsigned int groups,
if (!nlk->netlink_unbind)
return;

- for (undo = 0; undo < group; undo++)
+ for (undo = 0; undo < nlk->ngroups; undo++)
if (test_bit(undo, &groups))
nlk->netlink_unbind(sock_net(sk), undo + 1);
}
@@ -991,7 +990,7 @@ static int netlink_bind(struct socket *sock, struct sockaddr *addr,
struct netlink_sock *nlk = nlk_sk(sk);
struct sockaddr_nl *nladdr = (struct sockaddr_nl *)addr;
int err = 0;
- long unsigned int groups = nladdr->nl_groups;
+ unsigned long groups = nladdr->nl_groups;
bool bound;

if (addr_len < sizeof(struct sockaddr_nl))
@@ -1021,17 +1020,9 @@ static int netlink_bind(struct socket *sock, struct sockaddr *addr,

netlink_lock_table();
if (nlk->netlink_bind && groups) {
- int group;
-
- for (group = 0; group < nlk->ngroups; group++) {
- if (!test_bit(group, &groups))
- continue;
- err = nlk->netlink_bind(net, group + 1);
- if (!err)
- continue;
- netlink_undo_bind(group, groups, sk);
+ err = nlk->netlink_bind(net, &groups);
+ if (err)
goto unlock;
- }
}

/* No need for barriers here as we return to user-space without
@@ -1042,7 +1033,7 @@ static int netlink_bind(struct socket *sock, struct sockaddr *addr,
netlink_insert(sk, nladdr->nl_pid) :
netlink_autobind(sock);
if (err) {
- netlink_undo_bind(nlk->ngroups, groups, sk);
+ netlink_undo_bind(groups, sk);
goto unlock;
}
}
@@ -1652,7 +1643,9 @@ static int netlink_setsockopt(struct socket *sock, int level, int optname,
if (!val || val - 1 >= nlk->ngroups)
return -EINVAL;
if (optname == NETLINK_ADD_MEMBERSHIP && nlk->netlink_bind) {
- err = nlk->netlink_bind(sock_net(sk), val);
+ unsigned long groups = 1UL << val;
+
+ err = nlk->netlink_bind(sock_net(sk), &groups);
if (err)
return err;
}
diff --git a/net/netlink/af_netlink.h b/net/netlink/af_netlink.h
index 962de7b3c023..e765172abbb7 100644
--- a/net/netlink/af_netlink.h
+++ b/net/netlink/af_netlink.h
@@ -39,7 +39,7 @@ struct netlink_sock {
struct mutex *cb_mutex;
struct mutex cb_def_mutex;
void (*netlink_rcv)(struct sk_buff *skb);
- int (*netlink_bind)(struct net *net, int group);
+ int (*netlink_bind)(struct net *net, unsigned long *groups);
void (*netlink_unbind)(struct net *net, int group);
struct module *module;

@@ -61,7 +61,7 @@ struct netlink_table {
unsigned int groups;
struct mutex *cb_mutex;
struct module *module;
- int (*bind)(struct net *net, int group);
+ int (*bind)(struct net *net, unsigned long *groups);
void (*unbind)(struct net *net, int group);
bool (*compare)(struct net *net, struct sock *sock);
int registered;
diff --git a/net/netlink/genetlink.c b/net/netlink/genetlink.c
index 25eeb6d2a75a..a86b105730cf 100644
--- a/net/netlink/genetlink.c
+++ b/net/netlink/genetlink.c
@@ -960,28 +960,38 @@ static struct genl_family genl_ctrl __ro_after_init = {
.netnsok = true,
};

-static int genl_bind(struct net *net, int group)
+static int genl_bind(struct net *net, unsigned long *groups)
{
+ unsigned long mcgrps;
struct genl_family *f;
- int err = -ENOENT;
+ int err = 0;
unsigned int id;

down_read(&cb_lock);

idr_for_each_entry(&genl_fam_idr, f, id) {
- if (group >= f->mcgrp_offset &&
- group < f->mcgrp_offset + f->n_mcgrps) {
- int fam_grp = group - f->mcgrp_offset;
+ int fam_grp_bit, fam_grp = -1;
+
+ mcgrps = (1UL << f->n_mcgrps) - 1;
+ mcgrps <<= f->mcgrp_offset;
+ mcgrps &= *groups;
+
+ if (!mcgrps)
+ continue;
+
+ while ((fam_grp_bit = __builtin_ffsl(mcgrps))) {
+ fam_grp += fam_grp_bit;

if (!f->netnsok && net != &init_net)
err = -ENOENT;
else if (f->mcast_bind)
err = f->mcast_bind(net, fam_grp);
- else
- err = 0;
- break;
+
+ if (err)
+ goto out;
}
}
+out:
up_read(&cb_lock);

return err;
--
2.13.6