nfsd_nl_listener_set_doit() calls svc_xprt_create_from_sa() once for each
requested listener and passes flags of 0, so every listener registers with
rpcbind on its own. A rpcbind that accepts the connection and never replies
therefore costs one timeout for each entry. With the cap of 1024 entries
the request can hold nfsd_mutex for about 34 minutes, which is roughly 17
times the hung-task threshold.
One failure is enough to know that the next call will not fare better.
Read svc_rpcb_failure_count() before the create loop, and pass
SVC_SOCK_ANONYMOUS for the rest of the request once the count moves.
Fixes: 16a471177496 ("NFSD: add listener-{set,get} netlink command")
Assisted-by: LLM
Link: https://syzkaller.appspot.com/bug?extid=c7eae0eb80858a2dba0f
Suggested-by: Olga Kornievskaia <[email protected]>
Signed-off-by: Jeff Layton <[email protected]>
---
fs/nfsd/nfsctl.c | 42 ++++++++++++++++++++++++++++++++++++++----
1 file changed, 38 insertions(+), 4 deletions(-)
diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
index 6cbdcee4b733..c6f6bc3b1281 100644
--- a/fs/nfsd/nfsctl.c
+++ b/fs/nfsd/nfsctl.c
@@ -2092,7 +2092,10 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb,
struct genl_info *info)
const struct nlattr *bad_attr = NULL;
struct svc_xprt *xprt, *tmp;
const char *bad_xprt = NULL;
+ unsigned int rpcb_failures;
const struct nlattr *attr;
+ bool skipped_rpcb = false;
+ bool bad_rpcb = false;
struct svc_serv *serv;
LIST_HEAD(permsocks);
struct nfsd_net *nn;
@@ -2182,13 +2185,16 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb,
struct genl_info *info)
if (delete)
svc_xprt_destroy_all(serv, net, false);
+ rpcb_failures = svc_rpcb_failure_count(serv);
+
/* walk list of addrs again, open any that still don't exist */
nlmsg_for_each_attr_type(attr, NFSD_A_SERVER_SOCK_ADDR, info->nlhdr,
GENL_HDRLEN, rem) {
struct nlattr *tb[NFSD_A_SOCK_MAX + 1];
const char *xcl_name;
struct sockaddr *sa;
- int ret;
+ bool hit_rpcb;
+ int flags, ret;
/* validated up front in nfsd_nl_validate_listeners() */
if (nla_parse_nested(tb, NFSD_A_SOCK_MAX, attr,
@@ -2207,12 +2213,27 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb,
struct genl_info *info)
continue;
}
- ret = svc_xprt_create_from_sa(serv, xcl_name, net, sa, 0,
+ flags = 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 &&
+ svc_rpcb_failure_count(serv) != rpcb_failures) {
+ skipped_rpcb = true;
+ hit_rpcb = true;
+ if (ret < 0)
+ ret = svc_xprt_create_from_sa(serv, xcl_name,
+ net, sa,
+
SVC_SOCK_ANONYMOUS,
+ current_cred());
+ }
+
/* always save the latest error */
if (ret < 0) {
bad_attr = attr;
bad_xprt = xcl_name;
+ bad_rpcb = hit_rpcb;
err = ret;
}
}
@@ -2224,8 +2245,21 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb,
struct genl_info *info)
*/
if (err) {
NL_SET_BAD_ATTR(info->extack, bad_attr);
- NL_SET_ERR_MSG_FMT(info->extack, "cannot create %s listener",
- bad_xprt);
+ if (bad_rpcb)
+ NL_SET_ERR_MSG_FMT(info->extack,
+ "cannot create %s listener; rpcbind
did not answer",
+ bad_xprt);
+ else if (skipped_rpcb)
+ NL_SET_ERR_MSG_FMT(info->extack,
+ "cannot create %s listener; rpcbind
did not answer earlier, so some listeners are not registered",
+ bad_xprt);
+ else
+ NL_SET_ERR_MSG_FMT(info->extack,
+ "cannot create %s listener",
+ bad_xprt);
+ } else if (skipped_rpcb) {
+ NL_SET_ERR_MSG(info->extack,
+ "rpcbind did not answer, some listeners are not
registered");
}
if (!serv->sv_nrthreads && list_empty(&nn->nfsd_serv->sv_permsocks))
--
2.55.0