aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVasily Averin <vvs@virtuozzo.com>2017-11-10 02:19:35 -0500
committerJ. Bruce Fields <bfields@redhat.com>2017-11-27 16:45:11 -0500
commit2317dc557a3b6d5b73b697034611d658eb2cbde9 (patch)
tree9854e4dff8bb6981a73aca72427187d3afea4d52
parent6b18dd1c03e07262ea0866084856b2a3c5ba8d09 (diff)
race of nfsd inetaddr notifiers vs nn->nfsd_serv change
nfsd_inet[6]addr_event uses nn->nfsd_serv without taking nfsd_mutex, which can be changed during execution of notifiers and crash the host. Moreover if notifiers were enabled in one net namespace they are enabled in all other net namespaces, from creation until destruction. This patch allows notifiers to access nn->nfsd_serv only after the pointer is correctly initialized and delays cleanup until notifiers are no longer in use. Signed-off-by: Vasily Averin <vvs@virtuozzo.com> Tested-by: Scott Mayhew <smayhew@redhat.com> Signed-off-by: J. Bruce Fields <bfields@redhat.com>
-rw-r--r--fs/nfsd/netns.h3
-rw-r--r--fs/nfsd/nfsctl.c3
-rw-r--r--fs/nfsd/nfssvc.c14
3 files changed, 17 insertions, 3 deletions
diff --git a/fs/nfsd/netns.h b/fs/nfsd/netns.h
index 1c91391f4805..36358d435cb0 100644
--- a/fs/nfsd/netns.h
+++ b/fs/nfsd/netns.h
@@ -119,6 +119,9 @@ struct nfsd_net {
119 u32 clverifier_counter; 119 u32 clverifier_counter;
120 120
121 struct svc_serv *nfsd_serv; 121 struct svc_serv *nfsd_serv;
122
123 wait_queue_head_t ntf_wq;
124 atomic_t ntf_refcnt;
122}; 125};
123 126
124/* Simple check to find out if a given net was properly initialized */ 127/* Simple check to find out if a given net was properly initialized */
diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
index 6493df6b1bd5..d107b4426f7e 100644
--- a/fs/nfsd/nfsctl.c
+++ b/fs/nfsd/nfsctl.c
@@ -1241,6 +1241,9 @@ static __net_init int nfsd_init_net(struct net *net)
1241 nn->nfsd4_grace = 90; 1241 nn->nfsd4_grace = 90;
1242 nn->clverifier_counter = prandom_u32(); 1242 nn->clverifier_counter = prandom_u32();
1243 nn->clientid_counter = prandom_u32(); 1243 nn->clientid_counter = prandom_u32();
1244
1245 atomic_set(&nn->ntf_refcnt, 0);
1246 init_waitqueue_head(&nn->ntf_wq);
1244 return 0; 1247 return 0;
1245 1248
1246out_idmap_error: 1249out_idmap_error:
diff --git a/fs/nfsd/nfssvc.c b/fs/nfsd/nfssvc.c
index 33117d4ffce0..89cb484f1cfb 100644
--- a/fs/nfsd/nfssvc.c
+++ b/fs/nfsd/nfssvc.c
@@ -335,7 +335,8 @@ static int nfsd_inetaddr_event(struct notifier_block *this, unsigned long event,
335 struct nfsd_net *nn = net_generic(net, nfsd_net_id); 335 struct nfsd_net *nn = net_generic(net, nfsd_net_id);
336 struct sockaddr_in sin; 336 struct sockaddr_in sin;
337 337
338 if (event != NETDEV_DOWN) 338 if ((event != NETDEV_DOWN) ||
339 !atomic_inc_not_zero(&nn->ntf_refcnt))
339 goto out; 340 goto out;
340 341
341 if (nn->nfsd_serv) { 342 if (nn->nfsd_serv) {
@@ -344,6 +345,8 @@ static int nfsd_inetaddr_event(struct notifier_block *this, unsigned long event,
344 sin.sin_addr.s_addr = ifa->ifa_local; 345 sin.sin_addr.s_addr = ifa->ifa_local;
345 svc_age_temp_xprts_now(nn->nfsd_serv, (struct sockaddr *)&sin); 346 svc_age_temp_xprts_now(nn->nfsd_serv, (struct sockaddr *)&sin);
346 } 347 }
348 atomic_dec(&nn->ntf_refcnt);
349 wake_up(&nn->ntf_wq);
347 350
348out: 351out:
349 return NOTIFY_DONE; 352 return NOTIFY_DONE;
@@ -363,7 +366,8 @@ static int nfsd_inet6addr_event(struct notifier_block *this,
363 struct nfsd_net *nn = net_generic(net, nfsd_net_id); 366 struct nfsd_net *nn = net_generic(net, nfsd_net_id);
364 struct sockaddr_in6 sin6; 367 struct sockaddr_in6 sin6;
365 368
366 if (event != NETDEV_DOWN) 369 if ((event != NETDEV_DOWN) ||
370 !atomic_inc_not_zero(&nn->ntf_refcnt))
367 goto out; 371 goto out;
368 372
369 if (nn->nfsd_serv) { 373 if (nn->nfsd_serv) {
@@ -374,7 +378,8 @@ static int nfsd_inet6addr_event(struct notifier_block *this,
374 sin6.sin6_scope_id = ifa->idev->dev->ifindex; 378 sin6.sin6_scope_id = ifa->idev->dev->ifindex;
375 svc_age_temp_xprts_now(nn->nfsd_serv, (struct sockaddr *)&sin6); 379 svc_age_temp_xprts_now(nn->nfsd_serv, (struct sockaddr *)&sin6);
376 } 380 }
377 381 atomic_dec(&nn->ntf_refcnt);
382 wake_up(&nn->ntf_wq);
378out: 383out:
379 return NOTIFY_DONE; 384 return NOTIFY_DONE;
380} 385}
@@ -391,6 +396,7 @@ static void nfsd_last_thread(struct svc_serv *serv, struct net *net)
391{ 396{
392 struct nfsd_net *nn = net_generic(net, nfsd_net_id); 397 struct nfsd_net *nn = net_generic(net, nfsd_net_id);
393 398
399 atomic_dec(&nn->ntf_refcnt);
394 /* check if the notifier still has clients */ 400 /* check if the notifier still has clients */
395 if (atomic_dec_return(&nfsd_notifier_refcount) == 0) { 401 if (atomic_dec_return(&nfsd_notifier_refcount) == 0) {
396 unregister_inetaddr_notifier(&nfsd_inetaddr_notifier); 402 unregister_inetaddr_notifier(&nfsd_inetaddr_notifier);
@@ -398,6 +404,7 @@ static void nfsd_last_thread(struct svc_serv *serv, struct net *net)
398 unregister_inet6addr_notifier(&nfsd_inet6addr_notifier); 404 unregister_inet6addr_notifier(&nfsd_inet6addr_notifier);
399#endif 405#endif
400 } 406 }
407 wait_event(nn->ntf_wq, atomic_read(&nn->ntf_refcnt) == 0);
401 408
402 /* 409 /*
403 * write_ports can create the server without actually starting 410 * write_ports can create the server without actually starting
@@ -517,6 +524,7 @@ int nfsd_create_serv(struct net *net)
517 register_inet6addr_notifier(&nfsd_inet6addr_notifier); 524 register_inet6addr_notifier(&nfsd_inet6addr_notifier);
518#endif 525#endif
519 } 526 }
527 atomic_inc(&nn->ntf_refcnt);
520 ktime_get_real_ts64(&nn->nfssvc_boot); /* record boot time */ 528 ktime_get_real_ts64(&nn->nfssvc_boot); /* record boot time */
521 return 0; 529 return 0;
522} 530}