Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

NFSv4: Fix races in the legacy idmapper upcall

nfs_idmap_instantiate() will cause the process that is waiting in
request_key_with_auxdata() to wake up and exit. If there is a second
process waiting for the idmap->idmap_mutex, then it may wake up and
start a new call to request_key_with_auxdata(). If the call to
idmap_pipe_downcall() from the first process has not yet finished
calling nfs_idmap_complete_pipe_upcall_locked(), then we may end up
triggering the WARN_ON_ONCE() in nfs_idmap_prepare_pipe_upcall().

The fix is to ensure that we clear idmap->idmap_upcall_data before
calling nfs_idmap_instantiate().

Fixes: e9ab41b620e4 ("NFSv4: Clean up the legacy idmapper upcall")
Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>

+24 -22
+24 -22
fs/nfs/nfs4idmap.c
··· 561 561 return true; 562 562 } 563 563 564 - static void 565 - nfs_idmap_complete_pipe_upcall_locked(struct idmap *idmap, int ret) 564 + static void nfs_idmap_complete_pipe_upcall(struct idmap_legacy_upcalldata *data, 565 + int ret) 566 566 { 567 - struct key *authkey = idmap->idmap_upcall_data->authkey; 568 - 569 - kfree(idmap->idmap_upcall_data); 570 - idmap->idmap_upcall_data = NULL; 571 - complete_request_key(authkey, ret); 572 - key_put(authkey); 567 + complete_request_key(data->authkey, ret); 568 + key_put(data->authkey); 569 + kfree(data); 573 570 } 574 571 575 - static void 576 - nfs_idmap_abort_pipe_upcall(struct idmap *idmap, int ret) 572 + static void nfs_idmap_abort_pipe_upcall(struct idmap *idmap, 573 + struct idmap_legacy_upcalldata *data, 574 + int ret) 577 575 { 578 - if (idmap->idmap_upcall_data != NULL) 579 - nfs_idmap_complete_pipe_upcall_locked(idmap, ret); 576 + if (cmpxchg(&idmap->idmap_upcall_data, data, NULL) == data) 577 + nfs_idmap_complete_pipe_upcall(data, ret); 580 578 } 581 579 582 580 static int nfs_idmap_legacy_upcall(struct key *authkey, void *aux) ··· 611 613 612 614 ret = rpc_queue_upcall(idmap->idmap_pipe, msg); 613 615 if (ret < 0) 614 - nfs_idmap_abort_pipe_upcall(idmap, ret); 616 + nfs_idmap_abort_pipe_upcall(idmap, data, ret); 615 617 616 618 return ret; 617 619 out2: ··· 667 669 struct request_key_auth *rka; 668 670 struct rpc_inode *rpci = RPC_I(file_inode(filp)); 669 671 struct idmap *idmap = (struct idmap *)rpci->private; 672 + struct idmap_legacy_upcalldata *data; 670 673 struct key *authkey; 671 674 struct idmap_msg im; 672 675 size_t namelen_in; ··· 677 678 * will have been woken up and someone else may now have used 678 679 * idmap_key_cons - so after this point we may no longer touch it. 679 680 */ 680 - if (idmap->idmap_upcall_data == NULL) 681 + data = xchg(&idmap->idmap_upcall_data, NULL); 682 + if (data == NULL) 681 683 goto out_noupcall; 682 684 683 - authkey = idmap->idmap_upcall_data->authkey; 685 + authkey = data->authkey; 684 686 rka = get_request_key_auth(authkey); 685 687 686 688 if (mlen != sizeof(im)) { ··· 703 703 if (namelen_in == 0 || namelen_in == IDMAP_NAMESZ) { 704 704 ret = -EINVAL; 705 705 goto out; 706 - } 706 + } 707 707 708 - ret = nfs_idmap_read_and_verify_message(&im, 709 - &idmap->idmap_upcall_data->idmap_msg, 710 - rka->target_key, authkey); 708 + ret = nfs_idmap_read_and_verify_message(&im, &data->idmap_msg, 709 + rka->target_key, authkey); 711 710 if (ret >= 0) { 712 711 key_set_timeout(rka->target_key, nfs_idmap_cache_timeout); 713 712 ret = mlen; 714 713 } 715 714 716 715 out: 717 - nfs_idmap_complete_pipe_upcall_locked(idmap, ret); 716 + nfs_idmap_complete_pipe_upcall(data, ret); 718 717 out_noupcall: 719 718 return ret; 720 719 } ··· 727 728 struct idmap *idmap = data->idmap; 728 729 729 730 if (msg->errno) 730 - nfs_idmap_abort_pipe_upcall(idmap, msg->errno); 731 + nfs_idmap_abort_pipe_upcall(idmap, data, msg->errno); 731 732 } 732 733 733 734 static void ··· 735 736 { 736 737 struct rpc_inode *rpci = RPC_I(inode); 737 738 struct idmap *idmap = (struct idmap *)rpci->private; 739 + struct idmap_legacy_upcalldata *data; 738 740 739 - nfs_idmap_abort_pipe_upcall(idmap, -EPIPE); 741 + data = xchg(&idmap->idmap_upcall_data, NULL); 742 + if (data) 743 + nfs_idmap_complete_pipe_upcall(data, -EPIPE); 740 744 } 741 745 742 746 int nfs_map_name_to_uid(const struct nfs_server *server, const char *name, size_t namelen, kuid_t *uid)