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.

The entry that moves the count has already paid the timeout, and with v3
enabled svc_register() turns that into -ETIMEDOUT and no listener. Nothing
marks it out from the rest of the request, and a retry of the request
would fail it again, so retry it with SVC_SOCK_ANONYMOUS rather than
leave the set permanently short of whichever entry went first.

A silent rpcbind therefore no longer fails an entry. Report it as its own
condition in the ack, instead of appending it to whatever unrelated error
the last failing entry had.

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 | 33 +++++++++++++++++++++++++++++----
 1 file changed, 29 insertions(+), 4 deletions(-)

diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
index 6cbdcee4b733..2256c53277b8 100644
--- a/fs/nfsd/nfsctl.c
+++ b/fs/nfsd/nfsctl.c
@@ -2092,7 +2092,9 @@ 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;
        struct svc_serv *serv;
        LIST_HEAD(permsocks);
        struct nfsd_net *nn;
@@ -2182,13 +2184,15 @@ 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;
+               int flags, ret;
 
                /* validated up front in nfsd_nl_validate_listeners() */
                if (nla_parse_nested(tb, NFSD_A_SOCK_MAX, attr,
@@ -2207,8 +2211,20 @@ 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());
+
+               if (!skipped_rpcb &&
+                   svc_rpcb_failure_count(serv) != rpcb_failures) {
+                       skipped_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;
@@ -2224,8 +2240,17 @@ 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 (skipped_rpcb)
+                       NL_SET_ERR_MSG_FMT(info->extack,
+                                          "cannot create %s listener; rpcbind 
did not answer",
+                                          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