A listener_set request that carries userspace-rpcbind now sets sv_no_rpcbind on the serv. The kernel then makes no rpcbind call at all, avoiding synchronous rpcbind RPCs under nfsd_mutex. Ownership cannot change under a live serv. An empty listener list is exempt: it destroys the serv, and nfsd_destroy_serv() drops whatever svc_bind() took either way, so teardown is not an ownership change. Without the exemption a caller that never learned about the flag could not shut nfsd down, and the extack blamed an ownership change it had not asked for. nfsd_nl_validate_listeners() returns the entry count so the gate can tell the two cases apart. The legacy portlist add-fd interface refuses to run against such a serv. nfsd_create_serv() returns early when the serv exists, so it cannot flip ownership back, svc_addsock() does not pass SVC_SOCK_ANONYMOUS and so expects svc_register() to register the listener, and there is no way to tell the rpcbind owner about it. -EBUSY, matching listener_set. add-xprt already passes SVC_SOCK_ANONYMOUS and never registered anything. lockd is unaffected. It owns a separate svc_serv and still registers NLM. Assisted-by: LLM Signed-off-by: Jeff Layton --- fs/nfsd/nfsctl.c | 49 ++++++++++++++++++++++++++++++++++++++----------- fs/nfsd/nfsd.h | 2 +- fs/nfsd/nfssvc.c | 14 ++++++++++++-- 3 files changed, 51 insertions(+), 14 deletions(-) diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index 1be8f98a293d..6e57e4d20e75 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -748,7 +748,14 @@ static ssize_t __write_ports_addfd(char *buf, struct net *net, const struct cred return -EINVAL; trace_nfsd_ctl_ports_addfd(net, fd); - err = nfsd_create_serv(net); + /* + * svc_register() is a no-op once userland owns rpcbind, and this + * interface has no way to hand the new listener to that owner. + */ + if (nn->nfsd_serv && nn->nfsd_serv->sv_no_rpcbind) + return -EBUSY; + + err = nfsd_create_serv(net, false); if (err != 0) return err; @@ -780,7 +787,7 @@ static ssize_t __write_ports_addxprt(char *buf, struct net *net, const struct cr return -EINVAL; trace_nfsd_ctl_ports_addxprt(net, transport, port); - err = nfsd_create_serv(net); + err = nfsd_create_serv(net, false); if (err != 0) return err; @@ -2022,7 +2029,8 @@ static bool nfsd_nl_transport_supported(const char *name) * Walk every NFSD_A_SERVER_SOCK_ADDR attribute and confirm that the list is * not oversized and that each entry is well-formed. * - * Return: 0 if every entry is valid, or a negative errno otherwise. + * Return: the number of entries if every entry is valid, or a negative + * errno otherwise. */ static int nfsd_nl_validate_listeners(struct genl_info *info) { @@ -2076,7 +2084,7 @@ static int nfsd_nl_validate_listeners(struct genl_info *info) } } - return 0; + return count; } /** @@ -2095,11 +2103,13 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct genl_info *info) unsigned int rpcb_failures; const struct nlattr *attr; bool skipped_rpcb = false; + bool userspace_rpcbind; bool bad_rpcb = false; struct svc_serv *serv; LIST_HEAD(permsocks); struct nfsd_net *nn; bool delete = false; + int nlisteners; int err, rem; /* @@ -2107,19 +2117,36 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct genl_info *info) * malformed request fails cleanly without creating a serv or touching * the existing listeners. */ - err = nfsd_nl_validate_listeners(info); - if (err) - return err; + nlisteners = nfsd_nl_validate_listeners(info); + if (nlisteners < 0) + return nlisteners; + + userspace_rpcbind = nla_get_flag(info->attrs[NFSD_A_SERVER_SOCK_USERSPACE_RPCBIND]); mutex_lock(&nfsd_mutex); - err = nfsd_create_serv(net); + nn = net_generic(net, nfsd_net_id); + + /* + * An empty list destroys the serv, and nfsd_destroy_serv() drops + * whatever svc_bind() took either way, so teardown is not an + * ownership change. Only a request that leaves a listener standing + * has to agree with the serv it found. + */ + if (nlisteners && nn->nfsd_serv && + nn->nfsd_serv->sv_no_rpcbind != userspace_rpcbind) { + NL_SET_ERR_MSG(info->extack, + "cannot change rpcbind ownership while a server exists"); + mutex_unlock(&nfsd_mutex); + return -EBUSY; + } + + err = nfsd_create_serv(net, userspace_rpcbind); if (err) { mutex_unlock(&nfsd_mutex); return err; } - nn = net_generic(net, nfsd_net_id); serv = nn->nfsd_serv; spin_lock_bh(&serv->sv_lock); @@ -2213,12 +2240,12 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct genl_info *info) continue; } - flags = skipped_rpcb ? SVC_SOCK_ANONYMOUS : 0; + flags = (userspace_rpcbind || skipped_rpcb) ? SVC_SOCK_ANONYMOUS : 0; ret = svc_xprt_create_from_sa(serv, xcl_name, net, sa, flags, current_cred()); hit_rpcb = false; - if (!skipped_rpcb && + if (!userspace_rpcbind && !skipped_rpcb && svc_rpcb_failure_count(serv) != rpcb_failures) { skipped_rpcb = true; hit_rpcb = true; diff --git a/fs/nfsd/nfsd.h b/fs/nfsd/nfsd.h index a145294c59c8..dcce45d58322 100644 --- a/fs/nfsd/nfsd.h +++ b/fs/nfsd/nfsd.h @@ -119,7 +119,7 @@ enum vers_op {NFSD_SET, NFSD_CLEAR, NFSD_TEST, NFSD_AVAIL }; int nfsd_vers(struct nfsd_net *nn, int vers, enum vers_op change); int nfsd_minorversion(struct nfsd_net *nn, u32 minorversion, enum vers_op change); void nfsd_reset_versions(struct nfsd_net *nn); -int nfsd_create_serv(struct net *net); +int nfsd_create_serv(struct net *net, bool no_rpcbind); void nfsd_destroy_serv(struct net *net); #ifdef CONFIG_DEBUG_FS diff --git a/fs/nfsd/nfssvc.c b/fs/nfsd/nfssvc.c index c04ef9d180ce..ef520d0562d6 100644 --- a/fs/nfsd/nfssvc.c +++ b/fs/nfsd/nfssvc.c @@ -607,7 +607,14 @@ struct svc_rqst *nfsd_current_rqst(void) return NULL; } -int nfsd_create_serv(struct net *net) +/** + * nfsd_create_serv - create the svc_serv for a namespace if it has none + * @net: network namespace to operate within + * @no_rpcbind: true if the caller registers the listeners with rpcbind + * + * Return: 0 on success or a negative errno. + */ +int nfsd_create_serv(struct net *net, bool no_rpcbind) { int error; struct nfsd_net *nn = net_generic(net, nfsd_net_id); @@ -635,6 +642,9 @@ int nfsd_create_serv(struct net *net) return -ENOMEM; } + /* svc_bind() reads this, so set it first. */ + serv->sv_no_rpcbind = no_rpcbind; + error = svc_bind(serv, net); if (error < 0) { svc_destroy(&serv); @@ -775,7 +785,7 @@ nfsd_svc(int n, int *nthreads, struct net *net, const struct cred *cred, const c strscpy(nn->nfsd_name, scope ? scope : utsname()->nodename, sizeof(nn->nfsd_name)); - error = nfsd_create_serv(net); + error = nfsd_create_serv(net, false); if (error) goto out; serv = nn->nfsd_serv; -- 2.55.0