Skip to content

Commit

Permalink
NFSD: fix corruption in notifier registration
Browse files Browse the repository at this point in the history
By design notifier can be registered once only, however nfsd registers
the same inetaddr notifiers per net-namespace.  When this happen it
corrupts list of notifiers, as result some notifiers can be not called
on proper event, traverse on list can be cycled forever, and second
unregister can access already freed memory.

Cc: [email protected]
fixes: 36684996 ("nfsd: Register callbacks on the inetaddr_chain and inet6addr_chain")
Signed-off-by: Vasily Averin <[email protected]>
Reviewed-by: Jeff Layton <[email protected]>
Cc: [email protected]
Signed-off-by: J. Bruce Fields <[email protected]>
  • Loading branch information
vaverin authored and J. Bruce Fields committed Sep 26, 2016
1 parent 25d5529 commit 1eca45f
Showing 1 changed file with 14 additions and 4 deletions.
18 changes: 14 additions & 4 deletions fs/nfsd/nfssvc.c
Original file line number Diff line number Diff line change
Expand Up @@ -366,14 +366,21 @@ static struct notifier_block nfsd_inet6addr_notifier = {
};
#endif

/* Only used under nfsd_mutex, so this atomic may be overkill: */
static atomic_t nfsd_notifier_refcount = ATOMIC_INIT(0);

static void nfsd_last_thread(struct svc_serv *serv, struct net *net)
{
struct nfsd_net *nn = net_generic(net, nfsd_net_id);

unregister_inetaddr_notifier(&nfsd_inetaddr_notifier);
/* check if the notifier still has clients */
if (atomic_dec_return(&nfsd_notifier_refcount) == 0) {
unregister_inetaddr_notifier(&nfsd_inetaddr_notifier);
#if IS_ENABLED(CONFIG_IPV6)
unregister_inet6addr_notifier(&nfsd_inet6addr_notifier);
unregister_inet6addr_notifier(&nfsd_inet6addr_notifier);
#endif
}

/*
* write_ports can create the server without actually starting
* any threads--if we get shut down before any threads are
Expand Down Expand Up @@ -488,10 +495,13 @@ int nfsd_create_serv(struct net *net)
}

set_max_drc();
register_inetaddr_notifier(&nfsd_inetaddr_notifier);
/* check if the notifier is already set */
if (atomic_inc_return(&nfsd_notifier_refcount) == 1) {
register_inetaddr_notifier(&nfsd_inetaddr_notifier);
#if IS_ENABLED(CONFIG_IPV6)
register_inet6addr_notifier(&nfsd_inet6addr_notifier);
register_inet6addr_notifier(&nfsd_inet6addr_notifier);
#endif
}
do_gettimeofday(&nn->nfssvc_boot); /* record boot time */
return 0;
}
Expand Down

0 comments on commit 1eca45f

Please sign in to comment.