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

nfsd4: fix 4.1 connection registration race

If a connection is closed just after a sequence or create_session
is sent over it, we could end up trying to register a callback that will
never get called since the xprt is already marked dead.

Signed-off-by: J. Bruce Fields <bfields@redhat.com>

+29 -11
+12 -4
fs/nfsd/nfs4state.c
··· 673 673 spin_unlock(&clp->cl_lock); 674 674 } 675 675 676 - static void nfsd4_register_conn(struct nfsd4_conn *conn) 676 + static int nfsd4_register_conn(struct nfsd4_conn *conn) 677 677 { 678 678 conn->cn_xpt_user.callback = nfsd4_conn_lost; 679 - register_xpt_user(conn->cn_xprt, &conn->cn_xpt_user); 679 + return register_xpt_user(conn->cn_xprt, &conn->cn_xpt_user); 680 680 } 681 681 682 682 static __be32 nfsd4_new_conn(struct svc_rqst *rqstp, struct nfsd4_session *ses) 683 683 { 684 684 struct nfsd4_conn *conn; 685 685 u32 flags = NFS4_CDFC4_FORE; 686 + int ret; 686 687 687 688 if (ses->se_flags & SESSION4_BACK_CHAN) 688 689 flags |= NFS4_CDFC4_BACK; ··· 691 690 if (!conn) 692 691 return nfserr_jukebox; 693 692 nfsd4_hash_conn(conn, ses); 694 - nfsd4_register_conn(conn); 693 + ret = nfsd4_register_conn(conn); 694 + if (ret) 695 + /* oops; xprt is already down: */ 696 + nfsd4_conn_lost(&conn->cn_xpt_user); 695 697 return nfs_ok; 696 698 } 697 699 ··· 1648 1644 { 1649 1645 struct nfs4_client *clp = ses->se_client; 1650 1646 struct nfsd4_conn *c; 1647 + int ret; 1651 1648 1652 1649 spin_lock(&clp->cl_lock); 1653 1650 c = __nfsd4_find_conn(new->cn_xprt, ses); ··· 1659 1654 } 1660 1655 __nfsd4_hash_conn(new, ses); 1661 1656 spin_unlock(&clp->cl_lock); 1662 - nfsd4_register_conn(new); 1657 + ret = nfsd4_register_conn(new); 1658 + if (ret) 1659 + /* oops; xprt is already down: */ 1660 + nfsd4_conn_lost(&new->cn_xpt_user); 1663 1661 return; 1664 1662 } 1665 1663
+17 -7
include/linux/sunrpc/svc_xprt.h
··· 82 82 struct net *xpt_net; 83 83 }; 84 84 85 - static inline void register_xpt_user(struct svc_xprt *xpt, struct svc_xpt_user *u) 86 - { 87 - spin_lock(&xpt->xpt_lock); 88 - list_add(&u->list, &xpt->xpt_users); 89 - spin_unlock(&xpt->xpt_lock); 90 - } 91 - 92 85 static inline void unregister_xpt_user(struct svc_xprt *xpt, struct svc_xpt_user *u) 93 86 { 94 87 spin_lock(&xpt->xpt_lock); 95 88 list_del_init(&u->list); 96 89 spin_unlock(&xpt->xpt_lock); 90 + } 91 + 92 + static inline int register_xpt_user(struct svc_xprt *xpt, struct svc_xpt_user *u) 93 + { 94 + spin_lock(&xpt->xpt_lock); 95 + if (test_bit(XPT_CLOSE, &xpt->xpt_flags)) { 96 + /* 97 + * The connection is about to be deleted soon (or, 98 + * worse, may already be deleted--in which case we've 99 + * already notified the xpt_users). 100 + */ 101 + spin_unlock(&xpt->xpt_lock); 102 + return -ENOTCONN; 103 + } 104 + list_add(&u->list, &xpt->xpt_users); 105 + spin_unlock(&xpt->xpt_lock); 106 + return 0; 97 107 } 98 108 99 109 int svc_reg_xprt_class(struct svc_xprt_class *);