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


Reply via email to