diff options
Diffstat (limited to 'net/netlink')
| -rw-r--r-- | net/netlink/af_netlink.c | 19 | 
1 files changed, 15 insertions, 4 deletions
diff --git a/net/netlink/af_netlink.c b/net/netlink/af_netlink.c index 01e944a017a..4da797fa5ec 100644 --- a/net/netlink/af_netlink.c +++ b/net/netlink/af_netlink.c @@ -138,6 +138,8 @@ static int netlink_dump(struct sock *sk);  static DEFINE_RWLOCK(nl_table_lock);  static atomic_t nl_table_users = ATOMIC_INIT(0); +#define nl_deref_protected(X) rcu_dereference_protected(X, lockdep_is_held(&nl_table_lock)); +  static ATOMIC_NOTIFIER_HEAD(netlink_chain);  static inline u32 netlink_group_mask(u32 group) @@ -345,6 +347,11 @@ netlink_update_listeners(struct sock *sk)  	struct hlist_node *node;  	unsigned long mask;  	unsigned int i; +	struct listeners *listeners; + +	listeners = nl_deref_protected(tbl->listeners); +	if (!listeners) +		return;  	for (i = 0; i < NLGRPLONGS(tbl->groups); i++) {  		mask = 0; @@ -352,7 +359,7 @@ netlink_update_listeners(struct sock *sk)  			if (i < NLGRPLONGS(nlk_sk(sk)->ngroups))  				mask |= nlk_sk(sk)->groups[i];  		} -		tbl->listeners->masks[i] = mask; +		listeners->masks[i] = mask;  	}  	/* this function is only called with the netlink table "grabbed", which  	 * makes sure updates are visible before bind or setsockopt return. */ @@ -536,7 +543,11 @@ static int netlink_release(struct socket *sock)  	if (netlink_is_kernel(sk)) {  		BUG_ON(nl_table[sk->sk_protocol].registered == 0);  		if (--nl_table[sk->sk_protocol].registered == 0) { -			kfree(nl_table[sk->sk_protocol].listeners); +			struct listeners *old; + +			old = nl_deref_protected(nl_table[sk->sk_protocol].listeners); +			RCU_INIT_POINTER(nl_table[sk->sk_protocol].listeners, NULL); +			kfree_rcu(old, rcu);  			nl_table[sk->sk_protocol].module = NULL;  			nl_table[sk->sk_protocol].bind = NULL;  			nl_table[sk->sk_protocol].flags = 0; @@ -982,7 +993,7 @@ int netlink_has_listeners(struct sock *sk, unsigned int group)  	rcu_read_lock();  	listeners = rcu_dereference(nl_table[sk->sk_protocol].listeners); -	if (group - 1 < nl_table[sk->sk_protocol].groups) +	if (listeners && group - 1 < nl_table[sk->sk_protocol].groups)  		res = test_bit(group - 1, listeners->masks);  	rcu_read_unlock(); @@ -1625,7 +1636,7 @@ int __netlink_change_ngroups(struct sock *sk, unsigned int groups)  		new = kzalloc(sizeof(*new) + NLGRPSZ(groups), GFP_ATOMIC);  		if (!new)  			return -ENOMEM; -		old = rcu_dereference_protected(tbl->listeners, 1); +		old = nl_deref_protected(tbl->listeners);  		memcpy(new->masks, old->masks, NLGRPSZ(tbl->groups));  		rcu_assign_pointer(tbl->listeners, new);  |