diff --git a/net/netlink/af_netlink.c b/net/netlink/af_netlink.c
index 94c1390e8dc5..6f47a797df1b 100644
--- a/net/netlink/af_netlink.c
+++ b/net/netlink/af_netlink.c
@@ -780,6 +780,8 @@ static int netlink_release(struct socket *sock)
old = nl_deref_protected(nl_table[sk->sk_protocol].listeners);
RCU_INIT_POINTER(nl_table[sk->sk_protocol].listeners, NULL);
kfree_rcu(old, rcu);
+ kfree(nl_table[sk->sk_protocol].mc_gen);
+ nl_table[sk->sk_protocol].mc_gen = NULL;
nl_table[sk->sk_protocol].module = NULL;
nl_table[sk->sk_protocol].bind = NULL;
nl_table[sk->sk_protocol].unbind = NULL;
@@ -990,6 +992,9 @@ static int netlink_bind(struct socket *sock, struct sockaddr_unsized *addr,
struct net *net = sock_net(sk);
struct netlink_sock *nlk = nlk_sk(sk);
struct sockaddr_nl *nladdr = (struct sockaddr_nl *)addr;
+ struct netlink_table *tbl = &nl_table[sk->sk_protocol];
+ unsigned int bound_gen[BITS_PER_TYPE(u32)];
+ unsigned int gen[BITS_PER_TYPE(u32)];
int err = 0;
unsigned long groups;
bool bound;
@@ -1023,9 +1028,18 @@ static int netlink_bind(struct socket *sock, struct sockaddr_unsized *addr,
return -EINVAL;
}
+retry:
if (nlk->netlink_bind && groups) {
int group;
+ netlink_lock_table();
+ if (!tbl->mc_gen) {
+ netlink_unlock_table();
+ return -ENOENT;
+ }
+ memcpy(gen, tbl->mc_gen, sizeof(gen));
+ netlink_unlock_table();
+
/* nl_groups is a u32, so cap the maximum groups we can bind */
for (group = 0; group < BITS_PER_TYPE(u32); group++) {
if (!test_bit(group, &groups))@@ -1042,6 +1056,15 @@ static int netlink_bind(struct socket *sock, struct sockaddr_unsized *addr,
netlink_undo_bind(group, groups, sk);
return err;
}
+
+ netlink_lock_table();
+ if (!tbl->mc_gen) {
+ netlink_unlock_table();
+ netlink_undo_bind(BITS_PER_TYPE(u32), groups, sk);
+ return -ENOENT;
+ }
+ memcpy(bound_gen, tbl->mc_gen, sizeof(bound_gen));
+ netlink_unlock_table();
}
/* No need for barriers here as we return to user-space without@@ -1063,6 +1086,31 @@ static int netlink_bind(struct socket *sock, struct sockaddr_unsized *addr,
netlink_unlock_table();
netlink_table_grab();
+ if (nlk->netlink_bind && groups) {
+ unsigned long gone = 0;
+ bool released = false;
+ int group;
+
+ if (!tbl->mc_gen) {
+ netlink_table_ungrab();
+ netlink_undo_bind(BITS_PER_TYPE(u32), groups, sk);
+ return -ENOENT;
+ }
+ for_each_set_bit(group, &groups, BITS_PER_TYPE(u32)) {
+ if (gen[group] != tbl->mc_gen[group])
+ released = true;
+ if (bound_gen[group] != tbl->mc_gen[group])
+ __set_bit(group, &gone);
+ }
+ /* see netlink_setsockopt(), undo all but gone and retry all */
+ if (released) {
+ netlink_table_ungrab();
+ netlink_undo_bind(BITS_PER_TYPE(u32), groups & ~gone,
+ sk);
+ bound = true;
+ goto retry;
+ }
+ }
netlink_update_subscriptions(sk, nlk->subscriptions +
hweight32(groups) -
hweight32(nlk->groups[0]));@@ -1692,6 +1740,8 @@ static int netlink_setsockopt(struct socket *sock, int level, int optname,
break;
case NETLINK_ADD_MEMBERSHIP:
case NETLINK_DROP_MEMBERSHIP: {
+ struct netlink_table *tbl = &nl_table[sk->sk_protocol];
+ unsigned int gen = 0, bound_gen = 0;
int err;
if (!netlink_allowed(sock, NL_CFG_F_NONROOT_RECV))@@ -1701,12 +1751,51 @@ static int netlink_setsockopt(struct socket *sock, int level, int optname,
return err;
if (!val || val - 1 >= nlk->ngroups)
return -EINVAL;
+retry:
if (optname == NETLINK_ADD_MEMBERSHIP && nlk->netlink_bind) {
+ netlink_lock_table();
+ if (!tbl->mc_gen) {
+ netlink_unlock_table();
+ return -ENOENT;
+ }
+ gen = tbl->mc_gen[val - 1];
+ netlink_unlock_table();
err = nlk->netlink_bind(sock_net(sk), val);
if (err)
return err;
+ netlink_lock_table();
+ if (!tbl->mc_gen) {
+ netlink_unlock_table();
+ if (nlk->netlink_unbind)
+ nlk->netlink_unbind(sock_net(sk), val);
+ return -ENOENT;
+ }
+ bound_gen = tbl->mc_gen[val - 1];
+ netlink_unlock_table();
}
netlink_table_grab();
+ if (optname == NETLINK_ADD_MEMBERSHIP && nlk->netlink_bind &&
+ !tbl->mc_gen) {
+ netlink_table_ungrab();
+ if (nlk->netlink_unbind)
+ nlk->netlink_unbind(sock_net(sk), val);
+ return -ENOENT;
+ }
+ /* The group was released around ->netlink_bind(), and may have
+ * a new owner now, so check it again. If it was released only
+ * after ->netlink_bind() returned, the owner that was bound is
+ * gone: no ->netlink_unbind() for it. Otherwise assume that the
+ * current owner was bound.
+ */
+ if (optname == NETLINK_ADD_MEMBERSHIP && nlk->netlink_bind &&
+ gen != tbl->mc_gen[val - 1]) {
+ bool unbind = bound_gen == tbl->mc_gen[val - 1];
+
+ netlink_table_ungrab();
+ if (unbind && nlk->netlink_unbind)
+ nlk->netlink_unbind(sock_net(sk), val);
+ goto retry;
+ }
netlink_update_socket_mc(nlk, val,
optname == NETLINK_ADD_MEMBERSHIP);
netlink_table_ungrab();@@ -2037,6 +2126,7 @@ __netlink_kernel_create(struct net *net, int unit, struct module *module,
struct sock *sk;
struct netlink_sock *nlk;
struct listeners *listeners = NULL;
+ unsigned int *mc_gen = NULL;
unsigned int groups;
BUG_ON(!nl_table);
@@ -2058,7 +2148,8 @@ __netlink_kernel_create(struct net *net, int unit, struct module *module,
groups = cfg->groups;
listeners = kzalloc(sizeof(*listeners) + NLGRPSZ(groups), GFP_KERNEL);
- if (!listeners)
+ mc_gen = kcalloc(groups, sizeof(*mc_gen), GFP_KERNEL);
+ if (!listeners || !mc_gen)
goto out_sock_release;
sk->sk_data_ready = netlink_data_ready;
@@ -2075,6 +2166,7 @@ __netlink_kernel_create(struct net *net, int unit, struct module *module,
if (!nl_table[unit].registered) {
nl_table[unit].groups = groups;
rcu_assign_pointer(nl_table[unit].listeners, listeners);
+ nl_table[unit].mc_gen = mc_gen;
nl_table[unit].module = module;
if (cfg) {
nl_table[unit].bind = cfg->bind;@@ -2085,6 +2177,7 @@ __netlink_kernel_create(struct net *net, int unit, struct module *module,
nl_table[unit].registered = 1;
} else {
kfree(listeners);
+ kfree(mc_gen);
nl_table[unit].registered++;
}
netlink_table_ungrab();@@ -2092,6 +2185,7 @@ __netlink_kernel_create(struct net *net, int unit, struct module *module,
out_sock_release:
kfree(listeners);
+ kfree(mc_gen);
netlink_kernel_release(sk);
return NULL;
@@ -2119,6 +2213,17 @@ int __netlink_change_ngroups(struct sock *sk, unsigned int groups)
if (groups < 32)
groups = 32;
+ if (tbl->groups < groups) {
+ unsigned int *mc_gen;
+
+ mc_gen = kcalloc(groups, sizeof(*mc_gen), GFP_ATOMIC);
+ if (!mc_gen)
+ return -ENOMEM;
+ memcpy(mc_gen, tbl->mc_gen, tbl->groups * sizeof(*mc_gen));
+ kfree(tbl->mc_gen);
+ tbl->mc_gen = mc_gen;
+ }
+
if (NLGRPSZ(tbl->groups) < NLGRPSZ(groups)) {
new = kzalloc(sizeof(*new) + NLGRPSZ(groups), GFP_ATOMIC);
if (!new)@@ -2163,6 +2268,9 @@ void __netlink_clear_multicast_users(struct sock *ksk, unsigned int group)
struct netlink_table *tbl = &nl_table[ksk->sk_protocol];
struct hlist_node *tmp;
+ /* the group can be given to another user, see netlink_setsockopt() */
+ tbl->mc_gen[group - 1]++;
+
sk_for_each_bound_safe(sk, tmp, &tbl->mc_list) {
/* bitmap predates the group, can't be a member */
if (group - 1 >= nlk_sk(sk)->ngroups)diff --git a/net/netlink/af_netlink.h b/net/netlink/af_netlink.h
index 778a3809361f..d5e2e404fd11 100644
--- a/net/netlink/af_netlink.h
+++ b/net/netlink/af_netlink.h
@@ -64,6 +64,7 @@ struct netlink_table {
struct listeners __rcu *listeners;
unsigned int flags;
unsigned int groups;
+ unsigned int *mc_gen;
struct mutex *cb_mutex;
struct module *module;
int (*bind)(struct net *net, int group);--
2.25.1