mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02:00 
			
		
		
		
	genetlink: remove genl_bind
A potential deadlock can occur during registering or unregistering a
new generic netlink family between the main nl_table_lock and the
cb_lock where each thread wants the lock held by the other, as
demonstrated below.
1) Thread 1 is performing a netlink_bind() operation on a socket. As part
   of this call, it will call netlink_lock_table(), incrementing the
   nl_table_users count to 1.
2) Thread 2 is registering (or unregistering) a genl_family via the
   genl_(un)register_family() API. The cb_lock semaphore will be taken for
   writing.
3) Thread 1 will call genl_bind() as part of the bind operation to handle
   subscribing to GENL multicast groups at the request of the user. It will
   attempt to take the cb_lock semaphore for reading, but it will fail and
   be scheduled away, waiting for Thread 2 to finish the write.
4) Thread 2 will call netlink_table_grab() during the (un)registration
   call. However, as Thread 1 has incremented nl_table_users, it will not
   be able to proceed, and both threads will be stuck waiting for the
   other.
genl_bind() is a noop, unless a genl_family implements the mcast_bind()
function to handle setting up family-specific multicast operations. Since
no one in-tree uses this functionality as Cong pointed out, simply removing
the genl_bind() function will remove the possibility for deadlock, as there
is no attempt by Thread 1 above to take the cb_lock semaphore.
Fixes: c380d9a7af ("genetlink: pass multicast bind/unbind to families")
Suggested-by: Cong Wang <xiyou.wangcong@gmail.com>
Acked-by: Johannes Berg <johannes.berg@intel.com>
Reported-by: kernel test robot <lkp@intel.com>
Signed-off-by: Sean Tranchetti <stranche@codeaurora.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
			
			
This commit is contained in:
		
							parent
							
								
									d3c54f7f18
								
							
						
					
					
						commit
						1e82a62fec
					
				
					 2 changed files with 0 additions and 57 deletions
				
			
		| 
						 | 
				
			
			@ -35,12 +35,6 @@ struct genl_info;
 | 
			
		|||
 *	do additional, common, filtering and return an error
 | 
			
		||||
 * @post_doit: called after an operation's doit callback, it may
 | 
			
		||||
 *	undo operations done by pre_doit, for example release locks
 | 
			
		||||
 * @mcast_bind: a socket bound to the given multicast group (which
 | 
			
		||||
 *	is given as the offset into the groups array)
 | 
			
		||||
 * @mcast_unbind: a socket was unbound from the given multicast group.
 | 
			
		||||
 *	Note that unbind() will not be called symmetrically if the
 | 
			
		||||
 *	generic netlink family is removed while there are still open
 | 
			
		||||
 *	sockets.
 | 
			
		||||
 * @mcgrps: multicast groups used by this family
 | 
			
		||||
 * @n_mcgrps: number of multicast groups
 | 
			
		||||
 * @mcgrp_offset: starting number of multicast group IDs in this family
 | 
			
		||||
| 
						 | 
				
			
			@ -63,8 +57,6 @@ struct genl_family {
 | 
			
		|||
	void			(*post_doit)(const struct genl_ops *ops,
 | 
			
		||||
					     struct sk_buff *skb,
 | 
			
		||||
					     struct genl_info *info);
 | 
			
		||||
	int			(*mcast_bind)(struct net *net, int group);
 | 
			
		||||
	void			(*mcast_unbind)(struct net *net, int group);
 | 
			
		||||
	const struct genl_ops *	ops;
 | 
			
		||||
	const struct genl_multicast_group *mcgrps;
 | 
			
		||||
	unsigned int		n_ops;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1144,60 +1144,11 @@ static struct genl_family genl_ctrl __ro_after_init = {
 | 
			
		|||
	.netnsok = true,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static int genl_bind(struct net *net, int group)
 | 
			
		||||
{
 | 
			
		||||
	struct genl_family *f;
 | 
			
		||||
	int err = -ENOENT;
 | 
			
		||||
	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;
 | 
			
		||||
 | 
			
		||||
			if (!f->netnsok && net != &init_net)
 | 
			
		||||
				err = -ENOENT;
 | 
			
		||||
			else if (f->mcast_bind)
 | 
			
		||||
				err = f->mcast_bind(net, fam_grp);
 | 
			
		||||
			else
 | 
			
		||||
				err = 0;
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	up_read(&cb_lock);
 | 
			
		||||
 | 
			
		||||
	return err;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void genl_unbind(struct net *net, int group)
 | 
			
		||||
{
 | 
			
		||||
	struct genl_family *f;
 | 
			
		||||
	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;
 | 
			
		||||
 | 
			
		||||
			if (f->mcast_unbind)
 | 
			
		||||
				f->mcast_unbind(net, fam_grp);
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	up_read(&cb_lock);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int __net_init genl_pernet_init(struct net *net)
 | 
			
		||||
{
 | 
			
		||||
	struct netlink_kernel_cfg cfg = {
 | 
			
		||||
		.input		= genl_rcv,
 | 
			
		||||
		.flags		= NL_CFG_F_NONROOT_RECV,
 | 
			
		||||
		.bind		= genl_bind,
 | 
			
		||||
		.unbind		= genl_unbind,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	/* we'll bump the group number right afterwards */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue