diff --git a/SOURCES/nfs-utils-2.3.3-gssd-debug-cleanup.patch b/SOURCES/nfs-utils-2.3.3-gssd-debug-cleanup.patch
new file mode 100644
index 0000000..cac0c28
--- /dev/null
+++ b/SOURCES/nfs-utils-2.3.3-gssd-debug-cleanup.patch
@@ -0,0 +1,290 @@
+diff -up nfs-utils-2.3.3/utils/gssd/err_util.c.orig nfs-utils-2.3.3/utils/gssd/err_util.c
+--- nfs-utils-2.3.3/utils/gssd/err_util.c.orig	2018-09-06 14:09:08.000000000 -0400
++++ nfs-utils-2.3.3/utils/gssd/err_util.c	2021-07-19 12:29:21.366829573 -0400
+@@ -70,3 +70,17 @@ int get_verbosity(void)
+ {
+ 	return verbosity;
+ }
++
++char * 
++sec2time(int value)
++{
++    static char buf[BUFSIZ];
++    int hr, min, sec;
++
++    hr = (value / 3600);
++    min = (value  - (3600*hr))/60;
++    sec = (value  - (3600*hr) - (min*60));
++    sprintf(buf, "%dh:%dm:%ds", hr, min, sec);
++    return(buf);
++}
++
+diff -up nfs-utils-2.3.3/utils/gssd/err_util.h.orig nfs-utils-2.3.3/utils/gssd/err_util.h
+--- nfs-utils-2.3.3/utils/gssd/err_util.h.orig	2018-09-06 14:09:08.000000000 -0400
++++ nfs-utils-2.3.3/utils/gssd/err_util.h	2021-07-19 12:29:21.367829599 -0400
+@@ -34,5 +34,6 @@
+ void initerr(char *progname, int verbosity, int fg);
+ void printerr(int priority, char *format, ...);
+ int get_verbosity(void);
++char * sec2time(int);
+ 
+ #endif /* _ERR_UTIL_H_ */
+diff -up nfs-utils-2.3.3/utils/gssd/gssd.c.orig nfs-utils-2.3.3/utils/gssd/gssd.c
+--- nfs-utils-2.3.3/utils/gssd/gssd.c.orig	2021-07-19 12:24:13.963644016 -0400
++++ nfs-utils-2.3.3/utils/gssd/gssd.c	2021-07-19 12:29:21.368829626 -0400
+@@ -396,7 +396,7 @@ gssd_free_client(struct clnt_info *clp)
+ 	if (refcnt > 0)
+ 		return;
+ 
+-	printerr(3, "freeing client %s\n", clp->relpath);
++	printerr(4, "freeing client %s\n", clp->relpath);
+ 
+ 	if (clp->krb5_fd >= 0)
+ 		close(clp->krb5_fd);
+@@ -417,7 +417,7 @@ gssd_free_client(struct clnt_info *clp)
+ static void
+ gssd_destroy_client(struct clnt_info *clp)
+ {
+-	printerr(3, "destroying client %s\n", clp->relpath);
++	printerr(4, "destroying client %s\n", clp->relpath);
+ 
+ 	if (clp->krb5_ev) {
+ 		event_del(clp->krb5_ev);
+@@ -494,7 +494,7 @@ scan_active_thread_list(void)
+ 			 * upcall_thread_info from the list and free it.
+ 			 */
+ 			if (tret == PTHREAD_CANCELED)
+-				printerr(3, "watchdog: thread id 0x%lx cancelled successfully\n",
++				printerr(2, "watchdog: thread id 0x%lx cancelled successfully\n",
+ 						info->tid);
+ 			saveprev = info->list.tqe_prev;
+ 			TAILQ_REMOVE(&active_thread_list, info, list);
+@@ -783,7 +783,7 @@ gssd_scan(void)
+ {
+ 	struct dirent *d;
+ 
+-	printerr(3, "doing a full rescan\n");
++	printerr(4, "doing a full rescan\n");
+ 	rewinddir(pipefs_dir);
+ 
+ 	while ((d = readdir(pipefs_dir))) {
+diff -up nfs-utils-2.3.3/utils/gssd/gssd_proc.c.orig nfs-utils-2.3.3/utils/gssd/gssd_proc.c
+--- nfs-utils-2.3.3/utils/gssd/gssd_proc.c.orig	2021-07-19 12:24:13.964644043 -0400
++++ nfs-utils-2.3.3/utils/gssd/gssd_proc.c	2021-07-19 12:29:21.368829626 -0400
+@@ -166,8 +166,9 @@ do_downcall(int k5_fd, uid_t uid, struct
+ 	unsigned int buf_size = 0;
+ 	pthread_t tid = pthread_self();
+ 
+-	printerr(2, "do_downcall(0x%x): lifetime_rec=%u acceptor=%.*s\n",
+-		tid, lifetime_rec, acceptor->length, acceptor->value);
++	if (get_verbosity() > 1)
++		printerr(2, "do_downcall(0x%lx): lifetime_rec=%s acceptor=%.*s\n",
++			tid, sec2time(lifetime_rec), acceptor->length, acceptor->value);
+ 	buf_size = sizeof(uid) + sizeof(timeout) + sizeof(pd->pd_seq_win) +
+ 		sizeof(pd->pd_ctx_hndl.length) + pd->pd_ctx_hndl.length +
+ 		sizeof(context_token->length) + context_token->length +
+@@ -193,7 +194,7 @@ do_downcall(int k5_fd, uid_t uid, struct
+ 	return;
+ out_err:
+ 	free(buf);
+-	printerr(1, "do_downcall(0x%x): Failed to write downcall!\n", tid);
++	printerr(1, "do_downcall(0x%lx): Failed to write downcall!\n", tid);
+ 	return;
+ }
+ 
+@@ -204,8 +205,9 @@ do_error_downcall(int k5_fd, uid_t uid,
+ 	char	*p = buf, *end = buf + 1024;
+ 	unsigned int timeout = 0;
+ 	int	zero = 0;
++	pthread_t tid = pthread_self();
+ 
+-	printerr(2, "doing error downcall\n");
++	printerr(2, "do_error_downcall(0x%lx): uid %d err %d\n", tid, uid, err);
+ 
+ 	if (WRITE_BYTES(&p, end, uid)) goto out_err;
+ 	if (WRITE_BYTES(&p, end, timeout)) goto out_err;
+@@ -328,6 +330,7 @@ create_auth_rpc_client(struct clnt_info
+ 	struct timeval	timeout;
+ 	struct sockaddr		*addr = (struct sockaddr *) &clp->addr;
+ 	socklen_t		salen;
++	pthread_t tid = pthread_self();
+ 
+ 	sec.qop = GSS_C_QOP_DEFAULT;
+ 	sec.svc = RPCSEC_GSS_SVC_NONE;
+@@ -361,8 +364,8 @@ create_auth_rpc_client(struct clnt_info
+ 
+ 	/* create an rpc connection to the nfs server */
+ 
+-	printerr(2, "creating %s client for server %s\n", clp->protocol,
+-			clp->servername);
++	printerr(3, "create_auth_rpc_client(0x%lx): creating %s client for server %s\n", 
++		tid, clp->protocol, clp->servername);
+ 
+ 	protocol = IPPROTO_TCP;
+ 	if ((strcmp(clp->protocol, "udp")) == 0)
+@@ -405,7 +408,8 @@ create_auth_rpc_client(struct clnt_info
+ 	if (!tgtname)
+ 		tgtname = clp->servicename;
+ 
+-	printerr(2, "creating context with server %s\n", tgtname);
++	printerr(3, "create_auth_rpc_client(0x%lx): creating context with server %s\n", 
++		tid, tgtname);
+ 	auth = authgss_create_default(rpc_clnt, tgtname, &sec);
+ 	if (!auth) {
+ 		/* Our caller should print appropriate message */
+@@ -507,9 +511,10 @@ krb5_not_machine_creds(struct clnt_info
+ 	gss_cred_id_t	gss_cred;
+ 	char		**dname;
+ 	int		err, resp = -1;
++	pthread_t tid = pthread_self();
+ 
+-	printerr(2, "krb5_not_machine_creds: uid %d tgtname %s\n", 
+-		uid, tgtname);
++	printerr(2, "krb5_not_machine_creds(0x%lx): uid %d tgtname %s\n", 
++		tid, uid, tgtname);
+ 
+ 	*chg_err = change_identity(uid);
+ 	if (*chg_err) {
+@@ -555,9 +560,10 @@ krb5_use_machine_creds(struct clnt_info
+ 	char	**ccname;
+ 	int	nocache = 0;
+ 	int	success = 0;
++	pthread_t tid = pthread_self();
+ 
+-	printerr(2, "krb5_use_machine_creds: uid %d tgtname %s\n", 
+-		uid, tgtname);
++	printerr(2, "krb5_use_machine_creds(0x%lx): uid %d tgtname %s\n", 
++		tid, uid, tgtname);
+ 
+ 	do {
+ 		gssd_refresh_krb5_machine_credential(clp->servername,
+@@ -874,6 +880,7 @@ start_upcall_thread(void (*func)(struct
+ 	pthread_t th;
+ 	struct upcall_thread_info *tinfo;
+ 	int ret;
++	pthread_t tid = pthread_self();
+ 
+ 	tinfo = alloc_upcall_thread_info();
+ 	if (!tinfo)
+@@ -896,6 +903,9 @@ start_upcall_thread(void (*func)(struct
+ 		free(tinfo);
+ 		return ret;
+ 	}
++	printerr(2, "start_upcall_thread(0x%lx): created thread id 0x%lx\n", 
++		tid, th);
++
+ 	tinfo->tid = th;
+ 	pthread_mutex_lock(&active_thread_list_lock);
+ 	clock_gettime(CLOCK_MONOTONIC, &tinfo->timeout);
+@@ -958,7 +968,7 @@ handle_gssd_upcall(struct clnt_info *clp
+ 	}
+ 	lbuf[lbuflen-1] = 0;
+ 
+-	printerr(2, "\n%s(0x%x): '%s' (%s)\n", __func__, tid,
++	printerr(2, "\n%s(0x%lx): '%s' (%s)\n", __func__, tid,
+ 		 lbuf, clp->relpath);
+ 
+ 	for (p = strtok(lbuf, " "); p; p = strtok(NULL, " ")) {
+diff -up nfs-utils-2.3.3/utils/gssd/krb5_util.c.orig nfs-utils-2.3.3/utils/gssd/krb5_util.c
+--- nfs-utils-2.3.3/utils/gssd/krb5_util.c.orig	2021-07-19 12:24:13.951643697 -0400
++++ nfs-utils-2.3.3/utils/gssd/krb5_util.c	2021-07-19 12:36:27.746223992 -0400
+@@ -375,6 +375,7 @@ gssd_get_single_krb5_cred(krb5_context c
+ 	char *cache_type;
+ 	char *pname = NULL;
+ 	char *k5err = NULL;
++	pthread_t tid = pthread_self();
+ 
+ 	memset(&my_creds, 0, sizeof(my_creds));
+ 
+@@ -385,8 +386,8 @@ gssd_get_single_krb5_cred(krb5_context c
+ 	now += 300;
+ 	pthread_mutex_lock(&ple_lock);
+ 	if (ple->ccname && ple->endtime > now && !nocache) {
+-		printerr(3, "INFO: Credentials in CC '%s' are good until %d\n",
+-			 ple->ccname, ple->endtime);
++		printerr(3, "%s(0x%lx): Credentials in CC '%s' are good until %s",
++			 __func__, tid, ple->ccname, ctime((time_t *)&ple->endtime));
+ 		code = 0;
+ 		pthread_mutex_unlock(&ple_lock);
+ 		goto out;
+@@ -486,7 +487,8 @@ gssd_get_single_krb5_cred(krb5_context c
+ 	}
+ 
+ 	code = 0;
+-	printerr(2, "%s: principal '%s' ccache:'%s'\n", __func__, pname, cc_name);
++	printerr(2, "%s(0x%lx): principal '%s' ccache:'%s'\n", 
++		__func__, tid, pname, cc_name);
+   out:
+ #ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS
+ 	if (init_opts)
+@@ -615,6 +617,7 @@ get_full_hostname(const char *inhost, ch
+ 	struct addrinfo hints;
+ 	int retval;
+ 	char *c;
++	pthread_t tid = pthread_self();
+ 
+ 	memset(&hints, 0, sizeof(hints));
+ 	hints.ai_socktype = SOCK_STREAM;
+@@ -624,8 +627,8 @@ get_full_hostname(const char *inhost, ch
+ 	/* Get full target hostname */
+ 	retval = getaddrinfo(inhost, NULL, &hints, &addrs);
+ 	if (retval) {
+-		printerr(1, "%s while getting full hostname for '%s'\n",
+-			 gai_strerror(retval), inhost);
++		printerr(1, "%s(0x%lx): getaddrinfo(%s) failed: %s\n",
++			 __func__, tid, inhost, gai_strerror(retval));
+ 		goto out;
+ 	}
+ 	strncpy(outhost, addrs->ai_canonname, outhostlen);
+@@ -633,7 +636,10 @@ get_full_hostname(const char *inhost, ch
+ 	for (c = outhost; *c != '\0'; c++)
+ 	    *c = tolower(*c);
+ 
+-	printerr(3, "Full hostname for '%s' is '%s'\n", inhost, outhost);
++	if (get_verbosity() && strcmp(inhost, outhost))
++		printerr(1, "%s(0x%0lx): inhost '%s' different than outhost'%s'\n", 
++			inhost, outhost);
++
+ 	retval = 0;
+ out:
+ 	return retval;
+@@ -819,6 +825,7 @@ find_keytab_entry(krb5_context context,
+ 	krb5_principal princ;
+ 	const char *notsetstr = "not set";
+ 	char *adhostoverride = NULL;
++	pthread_t tid = pthread_self();
+ 
+ 
+ 	/* Get full target hostname */
+@@ -972,7 +979,7 @@ find_keytab_entry(krb5_context context,
+ 					tried_upper = 1;
+ 				}
+ 			} else {
+-				printerr(2, "Success getting keytab entry for '%s'\n",spn);
++				printerr(2, "find_keytab_entry(0x%lx): Success getting keytab entry for '%s'\n",tid, spn);
+ 				retval = 0;
+ 				goto out;
+ 			}
+@@ -1113,9 +1120,6 @@ gssd_refresh_krb5_machine_credential_int
+ 	char *k5err = NULL;
+ 	const char *svcnames[] = { "$", "root", "nfs", "host", NULL };
+ 
+-	printerr(2, "%s: hostname=%s ple=%p service=%s srchost=%s\n",
+-		__func__, hostname, ple, service, srchost);
+-
+ 	/*
+ 	 * If a specific service name was specified, use it.
+ 	 * Otherwise, use the default list.
+@@ -1124,9 +1128,10 @@ gssd_refresh_krb5_machine_credential_int
+ 		svcnames[0] = service;
+ 		svcnames[1] = NULL;
+ 	}
+-	if (hostname == NULL && ple == NULL)
++	if (hostname == NULL && ple == NULL) {
++		printerr(0, "ERROR: %s: Invalid args\n", __func__);
+ 		return EINVAL;
+-
++	}
+ 	code = krb5_init_context(&context);
+ 	if (code) {
+ 		k5err = gssd_k5_err_msg(NULL, code);
diff --git a/SOURCES/nfs-utils-2.3.3-gssd-failed-thread.patch b/SOURCES/nfs-utils-2.3.3-gssd-failed-thread.patch
new file mode 100644
index 0000000..53c68e5
--- /dev/null
+++ b/SOURCES/nfs-utils-2.3.3-gssd-failed-thread.patch
@@ -0,0 +1,402 @@
+diff -up nfs-utils-2.3.3/utils/gssd/gssd.c.orig nfs-utils-2.3.3/utils/gssd/gssd.c
+--- nfs-utils-2.3.3/utils/gssd/gssd.c.orig	2021-07-19 09:39:04.273895536 -0400
++++ nfs-utils-2.3.3/utils/gssd/gssd.c	2021-07-19 09:40:13.942751214 -0400
+@@ -364,7 +364,7 @@ out:
+ /* Actually frees clp and fields that might be used from other
+  * threads if was last reference.
+  */
+-static void
++void
+ gssd_free_client(struct clnt_info *clp)
+ {
+ 	int refcnt;
+@@ -416,55 +416,6 @@ gssd_destroy_client(struct clnt_info *cl
+ 
+ static void gssd_scan(void);
+ 
+-static int
+-start_upcall_thread(void (*func)(struct clnt_upcall_info *), void *info)
+-{
+-	pthread_attr_t attr;
+-	pthread_t th;
+-	int ret;
+-
+-	ret = pthread_attr_init(&attr);
+-	if (ret != 0) {
+-		printerr(0, "ERROR: failed to init pthread attr: ret %d: %s\n",
+-			 ret, strerror(errno));
+-		return ret;
+-	}
+-	ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+-	if (ret != 0) {
+-		printerr(0, "ERROR: failed to create pthread attr: ret %d: "
+-			 "%s\n", ret, strerror(errno));
+-		return ret;
+-	}
+-
+-	ret = pthread_create(&th, &attr, (void *)func, (void *)info);
+-	if (ret != 0)
+-		printerr(0, "ERROR: pthread_create failed: ret %d: %s\n",
+-			 ret, strerror(errno));
+-	return ret;
+-}
+-
+-static struct clnt_upcall_info *alloc_upcall_info(struct clnt_info *clp)
+-{
+-	struct clnt_upcall_info *info;
+-
+-	info = malloc(sizeof(struct clnt_upcall_info));
+-	if (info == NULL)
+-		return NULL;
+-
+-	pthread_mutex_lock(&clp_lock);
+-	clp->refcount++;
+-	pthread_mutex_unlock(&clp_lock);
+-	info->clp = clp;
+-
+-	return info;
+-}
+-
+-void free_upcall_info(struct clnt_upcall_info *info)
+-{
+-	gssd_free_client(info->clp);
+-	free(info);
+-}
+-
+ /* For each upcall read the upcall info into the buffer, then create a
+  * thread in a detached state so that resources are released back into
+  * the system without the need for a join.
+@@ -473,44 +424,16 @@ static void
+ gssd_clnt_gssd_cb(int UNUSED(fd), short UNUSED(which), void *data)
+ {
+ 	struct clnt_info *clp = data;
+-	struct clnt_upcall_info *info;
+-
+-	info = alloc_upcall_info(clp);
+-	if (info == NULL)
+-		return;
+-
+-	info->lbuflen = read(clp->gssd_fd, info->lbuf, sizeof(info->lbuf));
+-	if (info->lbuflen <= 0 || info->lbuf[info->lbuflen-1] != '\n') {
+-		printerr(0, "WARNING: %s: failed reading request\n", __func__);
+-		free_upcall_info(info);
+-		return;
+-	}
+-	info->lbuf[info->lbuflen-1] = 0;
+ 
+-	if (start_upcall_thread(handle_gssd_upcall, info))
+-		free_upcall_info(info);
++	handle_gssd_upcall(clp);
+ }
+ 
+ static void
+ gssd_clnt_krb5_cb(int UNUSED(fd), short UNUSED(which), void *data)
+ {
+ 	struct clnt_info *clp = data;
+-	struct clnt_upcall_info *info;
+-
+-	info = alloc_upcall_info(clp);
+-	if (info == NULL)
+-		return;
+-
+-	if (read(clp->krb5_fd, &info->uid,
+-			sizeof(info->uid)) < (ssize_t)sizeof(info->uid)) {
+-		printerr(0, "WARNING: %s: failed reading uid from krb5 "
+-			 "upcall pipe: %s\n", __func__, strerror(errno));
+-		free_upcall_info(info);
+-		return;
+-	}
+ 
+-	if (start_upcall_thread(handle_krb5_upcall, info))
+-		free_upcall_info(info);
++	handle_krb5_upcall(clp);
+ }
+ 
+ static struct clnt_info *
+diff -up nfs-utils-2.3.3/utils/gssd/gssd.h.orig nfs-utils-2.3.3/utils/gssd/gssd.h
+--- nfs-utils-2.3.3/utils/gssd/gssd.h.orig	2021-07-19 09:39:04.269895430 -0400
++++ nfs-utils-2.3.3/utils/gssd/gssd.h	2021-07-19 09:40:13.943751240 -0400
+@@ -84,14 +84,17 @@ struct clnt_info {
+ 
+ struct clnt_upcall_info {
+ 	struct clnt_info 	*clp;
+-	char			lbuf[RPC_CHAN_BUF_SIZE];
+-	int			lbuflen;
+ 	uid_t			uid;
++	int			fd;
++	char			*srchost;
++	char			*target;
++	char			*service;
+ };
+ 
+-void handle_krb5_upcall(struct clnt_upcall_info *clp);
+-void handle_gssd_upcall(struct clnt_upcall_info *clp);
++void handle_krb5_upcall(struct clnt_info *clp);
++void handle_gssd_upcall(struct clnt_info *clp);
+ void free_upcall_info(struct clnt_upcall_info *info);
++void gssd_free_client(struct clnt_info *clp);
+ 
+ 
+ #endif /* _RPC_GSSD_H_ */
+diff -up nfs-utils-2.3.3/utils/gssd/gssd_proc.c.orig nfs-utils-2.3.3/utils/gssd/gssd_proc.c
+--- nfs-utils-2.3.3/utils/gssd/gssd_proc.c.orig	2021-07-19 09:39:04.269895430 -0400
++++ nfs-utils-2.3.3/utils/gssd/gssd_proc.c	2021-07-19 09:40:13.944751267 -0400
+@@ -80,6 +80,8 @@
+ #include "nfslib.h"
+ #include "gss_names.h"
+ 
++extern pthread_mutex_t clp_lock;
++
+ /* Encryption types supported by the kernel rpcsec_gss code */
+ int num_krb5_enctypes = 0;
+ krb5_enctype *krb5_enctypes = NULL;
+@@ -719,22 +721,133 @@ out_return_error:
+ 	goto out;
+ }
+ 
+-void
+-handle_krb5_upcall(struct clnt_upcall_info *info)
+-{
+-	struct clnt_info *clp = info->clp;
++static struct clnt_upcall_info *
++alloc_upcall_info(struct clnt_info *clp, uid_t uid, int fd, char *srchost,
++		  char *target, char *service)
++{
++	struct clnt_upcall_info *info;
++
++	info = malloc(sizeof(struct clnt_upcall_info));
++	if (info == NULL)
++		return NULL;
++
++	memset(info, 0, sizeof(*info));
++	pthread_mutex_lock(&clp_lock);
++	clp->refcount++;
++	pthread_mutex_unlock(&clp_lock);
++	info->clp = clp;
++	info->uid = uid;
++	info->fd = fd;
++	if (srchost) {
++		info->srchost = strdup(srchost);
++		if (info->srchost == NULL)
++			goto out_info;
++	}
++	if (target) {
++		info->target = strdup(target);
++		if (info->target == NULL)
++			goto out_srchost;
++	}
++	if (service) {
++		info->service = strdup(service);
++		if (info->service == NULL)
++			goto out_target;
++	}
++
++out:
++	return info;
+ 
+-	printerr(2, "\n%s: uid %d (%s)\n", __func__, info->uid, clp->relpath);
++out_target:
++	if (info->target)
++		free(info->target);
++out_srchost:
++	if (info->srchost)
++		free(info->srchost);
++out_info:
++	free(info);
++	info = NULL;
++	goto out;
++}
++
++void free_upcall_info(struct clnt_upcall_info *info)
++{
++	gssd_free_client(info->clp);
++	if (info->service)
++		free(info->service);
++	if (info->target)
++		free(info->target);
++	if (info->srchost)
++		free(info->srchost);
++	free(info);
++}
+ 
+-	process_krb5_upcall(clp, info->uid, clp->krb5_fd, NULL, NULL, NULL);
++static void
++gssd_work_thread_fn(struct clnt_upcall_info *info)
++{
++	process_krb5_upcall(info->clp, info->uid, info->fd, info->srchost, info->target, info->service);
+ 	free_upcall_info(info);
+ }
+ 
++static int
++start_upcall_thread(void (*func)(struct clnt_upcall_info *), void *info)
++{
++	pthread_attr_t attr;
++	pthread_t th;
++	int ret;
++
++	ret = pthread_attr_init(&attr);
++	if (ret != 0) {
++		printerr(0, "ERROR: failed to init pthread attr: ret %d: %s\n",
++			 ret, strerror(errno));
++		return ret;
++	}
++	ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
++	if (ret != 0) {
++		printerr(0, "ERROR: failed to create pthread attr: ret %d: "
++			 "%s\n", ret, strerror(errno));
++		return ret;
++	}
++
++	ret = pthread_create(&th, &attr, (void *)func, (void *)info);
++	if (ret != 0)
++		printerr(0, "ERROR: pthread_create failed: ret %d: %s\n",
++			 ret, strerror(errno));
++	return ret;
++}
++
+ void
+-handle_gssd_upcall(struct clnt_upcall_info *info)
++handle_krb5_upcall(struct clnt_info *clp)
+ {
+-	struct clnt_info	*clp = info->clp;
+ 	uid_t			uid;
++	struct clnt_upcall_info	*info;
++	int			err;
++
++	if (read(clp->krb5_fd, &uid, sizeof(uid)) < (ssize_t)sizeof(uid)) {
++		printerr(0, "WARNING: failed reading uid from krb5 "
++			    "upcall pipe: %s\n", strerror(errno));
++		return;
++	}
++	printerr(2, "\n%s: uid %d (%s)\n", __func__, uid, clp->relpath);
++
++	info = alloc_upcall_info(clp, uid, clp->krb5_fd, NULL, NULL, NULL);
++	if (info == NULL) {
++		printerr(0, "%s: failed to allocate clnt_upcall_info\n", __func__);
++		do_error_downcall(clp->krb5_fd, uid, -EACCES);
++		return;
++	}
++	err = start_upcall_thread(gssd_work_thread_fn, info);
++	if (err != 0) {
++		do_error_downcall(clp->krb5_fd, uid, -EACCES);
++		free_upcall_info(info);
++	}
++}
++
++void
++handle_gssd_upcall(struct clnt_info *clp)
++{
++	uid_t			uid;
++	char			lbuf[RPC_CHAN_BUF_SIZE];
++	int			lbuflen = 0;
+ 	char			*p;
+ 	char			*mech = NULL;
+ 	char			*uidstr = NULL;
+@@ -742,20 +855,22 @@ handle_gssd_upcall(struct clnt_upcall_in
+ 	char			*service = NULL;
+ 	char			*srchost = NULL;
+ 	char			*enctypes = NULL;
+-	char			*upcall_str;
+-	char			*pbuf = info->lbuf;
+ 	pthread_t tid = pthread_self();
++	struct clnt_upcall_info	*info;
++	int			err;
+ 
+-	printerr(2, "\n%s(0x%x): '%s' (%s)\n", __func__, tid, 
+-		info->lbuf, clp->relpath);
+-
+-	upcall_str = strdup(info->lbuf);
+-	if (upcall_str == NULL) {
+-		printerr(0, "ERROR: malloc failure\n");
+-		goto out_nomem;
++	lbuflen = read(clp->gssd_fd, lbuf, sizeof(lbuf));
++	if (lbuflen <= 0 || lbuf[lbuflen-1] != '\n') {
++		printerr(0, "WARNING: handle_gssd_upcall: "
++			    "failed reading request\n");
++		return;
+ 	}
++	lbuf[lbuflen-1] = 0;
++
++	printerr(2, "\n%s(0x%x): '%s' (%s)\n", __func__, tid,
++		 lbuf, clp->relpath);
+ 
+-	while ((p = strsep(&pbuf, " "))) {
++	for (p = strtok(lbuf, " "); p; p = strtok(NULL, " ")) {
+ 		if (!strncmp(p, "mech=", strlen("mech=")))
+ 			mech = p + strlen("mech=");
+ 		else if (!strncmp(p, "uid=", strlen("uid=")))
+@@ -773,8 +888,8 @@ handle_gssd_upcall(struct clnt_upcall_in
+ 	if (!mech || strlen(mech) < 1) {
+ 		printerr(0, "WARNING: handle_gssd_upcall: "
+ 			    "failed to find gss mechanism name "
+-			    "in upcall string '%s'\n", upcall_str);
+-		goto out;
++			    "in upcall string '%s'\n", lbuf);
++		return;
+ 	}
+ 
+ 	if (uidstr) {
+@@ -786,21 +901,21 @@ handle_gssd_upcall(struct clnt_upcall_in
+ 	if (!uidstr) {
+ 		printerr(0, "WARNING: handle_gssd_upcall: "
+ 			    "failed to find uid "
+-			    "in upcall string '%s'\n", upcall_str);
+-		goto out;
++			    "in upcall string '%s'\n", lbuf);
++		return;
+ 	}
+ 
+ 	if (enctypes && parse_enctypes(enctypes) != 0) {
+ 		printerr(0, "WARNING: handle_gssd_upcall: "
+ 			 "parsing encryption types failed: errno %d\n", errno);
+-		goto out;
++		return;
+ 	}
+ 
+ 	if (target && strlen(target) < 1) {
+ 		printerr(0, "WARNING: handle_gssd_upcall: "
+ 			 "failed to parse target name "
+-			 "in upcall string '%s'\n", upcall_str);
+-		goto out;
++			 "in upcall string '%s'\n", lbuf);
++		return;
+ 	}
+ 
+ 	/*
+@@ -814,21 +929,26 @@ handle_gssd_upcall(struct clnt_upcall_in
+ 	if (service && strlen(service) < 1) {
+ 		printerr(0, "WARNING: handle_gssd_upcall: "
+ 			 "failed to parse service type "
+-			 "in upcall string '%s'\n", upcall_str);
+-		goto out;
++			 "in upcall string '%s'\n", lbuf);
++		return;
+ 	}
+ 
+-	if (strcmp(mech, "krb5") == 0 && clp->servername)
+-		process_krb5_upcall(clp, uid, clp->gssd_fd, srchost, target, service);
+-	else {
++	if (strcmp(mech, "krb5") == 0 && clp->servername) {
++		info = alloc_upcall_info(clp, uid, clp->gssd_fd, srchost, target, service);
++		if (info == NULL) {
++			printerr(0, "%s: failed to allocate clnt_upcall_info\n", __func__);
++			do_error_downcall(clp->gssd_fd, uid, -EACCES);
++			return;
++		}
++		err = start_upcall_thread(gssd_work_thread_fn, info);
++		if (err != 0) {
++			do_error_downcall(clp->gssd_fd, uid, -EACCES);
++			free_upcall_info(info);
++		}
++	} else {
+ 		if (clp->servername)
+ 			printerr(0, "WARNING: handle_gssd_upcall: "
+ 				 "received unknown gss mech '%s'\n", mech);
+ 		do_error_downcall(clp->gssd_fd, uid, -EACCES);
+ 	}
+-out:
+-	free(upcall_str);
+-out_nomem:
+-	free_upcall_info(info);
+-	return;
+ }
diff --git a/SOURCES/nfs-utils-2.3.3-gssd-mutex-refcnt.patch b/SOURCES/nfs-utils-2.3.3-gssd-mutex-refcnt.patch
new file mode 100644
index 0000000..7764ff4
--- /dev/null
+++ b/SOURCES/nfs-utils-2.3.3-gssd-mutex-refcnt.patch
@@ -0,0 +1,43 @@
+diff -up nfs-utils-2.3.3/utils/gssd/krb5_util.c.orig nfs-utils-2.3.3/utils/gssd/krb5_util.c
+--- nfs-utils-2.3.3/utils/gssd/krb5_util.c.orig	2021-07-22 15:27:27.728680553 -0400
++++ nfs-utils-2.3.3/utils/gssd/krb5_util.c	2021-07-22 15:30:08.916979585 -0400
+@@ -165,18 +165,28 @@ static int gssd_get_single_krb5_cred(krb
+ static int query_krb5_ccache(const char* cred_cache, char **ret_princname,
+ 		char **ret_realm);
+ 
+-static void release_ple(krb5_context context, struct gssd_k5_kt_princ *ple)
++static void release_ple_locked(krb5_context context,
++			       struct gssd_k5_kt_princ *ple)
+ {
+ 	if (--ple->refcount)
+ 		return;
+ 
+-	printerr(3, "freeing cached principal (ccname=%s, realm=%s)\n", ple->ccname, ple->realm);
++	printerr(3, "freeing cached principal (ccname=%s, realm=%s)\n",
++		 ple->ccname, ple->realm);
+ 	krb5_free_principal(context, ple->princ);
+ 	free(ple->ccname);
+ 	free(ple->realm);
+ 	free(ple);
+ }
+ 
++static void release_ple(krb5_context context, struct gssd_k5_kt_princ *ple)
++{
++	pthread_mutex_lock(&ple_lock);
++	release_ple_locked(context, ple);
++	pthread_mutex_unlock(&ple_lock);
++}
++
++
+ /*
+  * Called from the scandir function to weed out potential krb5
+  * credentials cache files
+@@ -1396,7 +1406,7 @@ gssd_destroy_krb5_principals(int destroy
+ 			}
+ 		}
+ 
+-		release_ple(context, ple);
++		release_ple_locked(context, ple);
+ 	}
+ 	pthread_mutex_unlock(&ple_lock);
+ 	krb5_free_context(context);
diff --git a/SOURCES/nfs-utils-2.3.3-gssd-timeout-thread.patch b/SOURCES/nfs-utils-2.3.3-gssd-timeout-thread.patch
new file mode 100644
index 0000000..6b57377
--- /dev/null
+++ b/SOURCES/nfs-utils-2.3.3-gssd-timeout-thread.patch
@@ -0,0 +1,625 @@
+diff -up nfs-utils-2.3.3/nfs.conf.orig nfs-utils-2.3.3/nfs.conf
+--- nfs-utils-2.3.3/nfs.conf.orig	2021-07-19 09:45:40.441448059 -0400
++++ nfs-utils-2.3.3/nfs.conf	2021-07-19 12:08:55.314182838 -0400
+@@ -22,6 +22,8 @@ use-gss-proxy=1
+ # cred-cache-directory=
+ # preferred-realm=
+ # set-home=1
++# upcall-timeout=30
++# cancel-timed-out-upcalls=0
+ #
+ [lockd]
+ # port=0
+diff -up nfs-utils-2.3.3/utils/gssd/gssd.c.orig nfs-utils-2.3.3/utils/gssd/gssd.c
+--- nfs-utils-2.3.3/utils/gssd/gssd.c.orig	2021-07-19 09:45:40.448448246 -0400
++++ nfs-utils-2.3.3/utils/gssd/gssd.c	2021-07-19 12:08:55.315182865 -0400
+@@ -96,8 +96,29 @@ pthread_mutex_t clp_lock = PTHREAD_MUTEX
+ static bool signal_received = false;
+ static struct event_base *evbase = NULL;
+ 
++int upcall_timeout = DEF_UPCALL_TIMEOUT;
++static bool cancel_timed_out_upcalls = false;
++
+ TAILQ_HEAD(topdir_list_head, topdir) topdir_list;
+ 
++/*
++ * active_thread_list:
++ *
++ * 	used to track upcalls for timeout purposes.
++ *
++ * 	protected by the active_thread_list_lock mutex.
++ *
++ * 	upcall_thread_info structures are added to the tail of the list
++ * 	by start_upcall_thread(), so entries closer to the head of the list
++ * 	will be closer to hitting the upcall timeout.
++ *
++ * 	upcall_thread_info structures are removed from the list upon a
++ * 	sucessful join of the upcall thread by the watchdog thread (via
++ * 	scan_active_thread_list().
++ */
++TAILQ_HEAD(active_thread_list_head, upcall_thread_info) active_thread_list;
++pthread_mutex_t active_thread_list_lock = PTHREAD_MUTEX_INITIALIZER;
++
+ struct topdir {
+ 	TAILQ_ENTRY(topdir) list;
+ 	TAILQ_HEAD(clnt_list_head, clnt_info) clnt_list;
+@@ -436,6 +457,138 @@ gssd_clnt_krb5_cb(int UNUSED(fd), short
+ 	handle_krb5_upcall(clp);
+ }
+ 
++/*
++ * scan_active_thread_list:
++ *
++ * Walks the active_thread_list, trying to join as many upcall threads as
++ * possible.  For threads that have terminated, the corresponding
++ * upcall_thread_info will be removed from the list and freed.  Threads that
++ * are still busy and have exceeded the upcall_timeout will cause an error to
++ * be logged and may be canceled (depending on the value of
++ * cancel_timed_out_upcalls).
++ *
++ * Returns the number of seconds that the watchdog thread should wait before
++ * calling scan_active_thread_list() again.
++ */
++static int
++scan_active_thread_list(void)
++{
++	struct upcall_thread_info *info;
++	struct timespec now;
++	unsigned int sleeptime;
++	bool sleeptime_set = false;
++	int err;
++	void *tret, *saveprev;
++
++	sleeptime = upcall_timeout;
++	pthread_mutex_lock(&active_thread_list_lock);
++	clock_gettime(CLOCK_MONOTONIC, &now);
++	TAILQ_FOREACH(info, &active_thread_list, list) {
++		err = pthread_tryjoin_np(info->tid, &tret);
++		switch (err) {
++		case 0:
++			/*
++			 * The upcall thread has either completed successfully, or
++			 * has been canceled _and_ has acted on the cancellation request
++			 * (i.e. has hit a cancellation point).  We can now remove the
++			 * upcall_thread_info from the list and free it.
++			 */
++			if (tret == PTHREAD_CANCELED)
++				printerr(3, "watchdog: thread id 0x%lx cancelled successfully\n",
++						info->tid);
++			saveprev = info->list.tqe_prev;
++			TAILQ_REMOVE(&active_thread_list, info, list);
++			free(info);
++			info = saveprev;
++			break;
++		case EBUSY:
++			/*
++			 * The upcall thread is still running.  If the timeout has expired
++			 * then we either cancel the thread, log an error, and do an error
++			 * downcall to the kernel (cancel_timed_out_upcalls=true) or simply
++			 * log an error (cancel_timed_out_upcalls=false).  In either case,
++			 * the error is logged only once.
++			 */
++			if (now.tv_sec >= info->timeout.tv_sec) {
++				if (cancel_timed_out_upcalls && !(info->flags & UPCALL_THREAD_CANCELED)) {
++					printerr(0, "watchdog: thread id 0x%lx timed out\n",
++							info->tid);
++					pthread_cancel(info->tid);
++					info->flags |= (UPCALL_THREAD_CANCELED|UPCALL_THREAD_WARNED);
++					do_error_downcall(info->fd, info->uid, -ETIMEDOUT);
++				} else {
++					if (!(info->flags & UPCALL_THREAD_WARNED)) {
++						printerr(0, "watchdog: thread id 0x%lx running for %ld seconds\n",
++								info->tid,
++								now.tv_sec - info->timeout.tv_sec + upcall_timeout);
++						info->flags |= UPCALL_THREAD_WARNED;
++					}
++				}
++			} else if (!sleeptime_set) {
++			/*
++			 * The upcall thread is still running, but the timeout has not yet
++			 * expired.  Calculate the time remaining until the timeout will
++			 * expire.  This is the amount of time the watchdog thread will
++			 * wait before running again.  We only need to do this for the busy
++			 * thread closest to the head of the list - entries appearing later
++			 * in the list will time out later.
++			 */
++				sleeptime = info->timeout.tv_sec - now.tv_sec;
++				sleeptime_set = true;
++			}
++			break;
++		default:
++			/* EDEADLK, EINVAL, and ESRCH... none of which should happen! */
++			printerr(0, "watchdog: attempt to join thread id 0x%lx returned %d (%s)!\n",
++					info->tid, err, strerror(err));
++			break;
++		}
++	}
++	pthread_mutex_unlock(&active_thread_list_lock);
++
++	return sleeptime;
++}
++
++static void *
++watchdog_thread_fn(void *UNUSED(arg))
++{
++	unsigned int sleeptime;
++
++	for (;;) {
++		sleeptime = scan_active_thread_list();
++		printerr(4, "watchdog: sleeping %u secs\n", sleeptime);
++		sleep(sleeptime);
++	}
++	return (void *)0;
++}
++
++static int
++start_watchdog_thread(void)
++{
++	pthread_attr_t attr;
++	pthread_t th;
++	int ret;
++
++	ret = pthread_attr_init(&attr);
++	if (ret != 0) {
++		printerr(0, "ERROR: failed to init pthread attr: ret %d: %s\n",
++			 ret, strerror(errno));
++		return ret;
++	}
++	ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
++	if (ret != 0) {
++		printerr(0, "ERROR: failed to create pthread attr: ret %d: %s\n",
++			 ret, strerror(errno));
++		return ret;
++	}
++	ret = pthread_create(&th, &attr, watchdog_thread_fn, NULL);
++	if (ret != 0) {
++		printerr(0, "ERROR: pthread_create failed: ret %d: %s\n",
++			 ret, strerror(errno));
++	}
++	return ret;
++}
++
+ static struct clnt_info *
+ gssd_get_clnt(struct topdir *tdi, const char *name)
+ {
+@@ -810,7 +963,7 @@ sig_die(int signal)
+ static void
+ usage(char *progname)
+ {
+-	fprintf(stderr, "usage: %s [-f] [-l] [-M] [-n] [-v] [-r] [-p pipefsdir] [-k keytab] [-d ccachedir] [-t timeout] [-R preferred realm] [-D] [-H]\n",
++	fprintf(stderr, "usage: %s [-f] [-l] [-M] [-n] [-v] [-r] [-p pipefsdir] [-k keytab] [-d ccachedir] [-t timeout] [-R preferred realm] [-D] [-H] [-U upcall timeout] [-C]\n",
+ 		progname);
+ 	exit(1);
+ }
+@@ -831,6 +984,9 @@ read_gss_conf(void)
+ #endif
+ 	context_timeout = conf_get_num("gssd", "context-timeout", context_timeout);
+ 	rpc_timeout = conf_get_num("gssd", "rpc-timeout", rpc_timeout);
++	upcall_timeout = conf_get_num("gssd", "upcall-timeout", upcall_timeout);
++	cancel_timed_out_upcalls = conf_get_bool("gssd", "cancel-timed-out-upcalls",
++						cancel_timed_out_upcalls);
+ 	s = conf_get_str("gssd", "pipefs-directory");
+ 	if (!s)
+ 		s = conf_get_str("general", "pipefs-directory");
+@@ -872,7 +1028,7 @@ main(int argc, char *argv[])
+ 	verbosity = conf_get_num("gssd", "verbosity", verbosity);
+ 	rpc_verbosity = conf_get_num("gssd", "rpc-verbosity", rpc_verbosity);
+ 
+-	while ((opt = getopt(argc, argv, "HDfvrlmnMp:k:d:t:T:R:")) != -1) {
++	while ((opt = getopt(argc, argv, "HDfvrlmnMp:k:d:t:T:R:U:C")) != -1) {
+ 		switch (opt) {
+ 			case 'f':
+ 				fg = 1;
+@@ -923,6 +1079,12 @@ main(int argc, char *argv[])
+ 			case 'H':
+ 				set_home = false;
+ 				break;
++			case 'U':
++				upcall_timeout = atoi(optarg);
++				break;
++			case 'C':
++				cancel_timed_out_upcalls = true;
++				break;
+ 			default:
+ 				usage(argv[0]);
+ 				break;
+@@ -995,6 +1157,11 @@ main(int argc, char *argv[])
+ 	else
+ 		progname = argv[0];
+ 
++	if (upcall_timeout > MAX_UPCALL_TIMEOUT)
++		upcall_timeout = MAX_UPCALL_TIMEOUT;
++	else if (upcall_timeout < MIN_UPCALL_TIMEOUT)
++		upcall_timeout = MIN_UPCALL_TIMEOUT;
++
+ 	initerr(progname, verbosity, fg);
+ #ifdef HAVE_LIBTIRPC_SET_DEBUG
+ 	/*
+@@ -1045,6 +1212,14 @@ main(int argc, char *argv[])
+ 			       gssd_inotify_cb, NULL);
+ 	event_add(inotify_ev, NULL);
+ 
++	TAILQ_INIT(&active_thread_list);
++
++	rc = start_watchdog_thread();
++	if (rc != 0) {
++		printerr(0, "ERROR: failed to start watchdog thread: %d\n", rc);
++		exit(EXIT_FAILURE);
++	}
++
+ 	TAILQ_INIT(&topdir_list);
+ 	gssd_scan();
+ 	daemon_ready();
+diff -up nfs-utils-2.3.3/utils/gssd/gssd.h.orig nfs-utils-2.3.3/utils/gssd/gssd.h
+--- nfs-utils-2.3.3/utils/gssd/gssd.h.orig	2021-07-19 09:45:40.449448272 -0400
++++ nfs-utils-2.3.3/utils/gssd/gssd.h	2021-07-19 12:08:55.315182865 -0400
+@@ -50,6 +50,12 @@
+ #define GSSD_DEFAULT_KEYTAB_FILE		"/etc/krb5.keytab"
+ #define GSSD_SERVICE_NAME			"nfs"
+ #define RPC_CHAN_BUF_SIZE			32768
++
++/* timeouts are in seconds */
++#define MIN_UPCALL_TIMEOUT			5
++#define DEF_UPCALL_TIMEOUT			30
++#define MAX_UPCALL_TIMEOUT			600
++
+ /*
+  * The gss mechanisms that we can handle
+  */
+@@ -91,10 +97,22 @@ struct clnt_upcall_info {
+ 	char			*service;
+ };
+ 
++struct upcall_thread_info {
++	TAILQ_ENTRY(upcall_thread_info) list;
++	pthread_t		tid;
++	struct timespec		timeout;
++	uid_t			uid;
++	int			fd;
++	unsigned short		flags;
++#define UPCALL_THREAD_CANCELED	0x0001
++#define UPCALL_THREAD_WARNED	0x0002
++};
++
+ void handle_krb5_upcall(struct clnt_info *clp);
+ void handle_gssd_upcall(struct clnt_info *clp);
+ void free_upcall_info(struct clnt_upcall_info *info);
+ void gssd_free_client(struct clnt_info *clp);
++int do_error_downcall(int k5_fd, uid_t uid, int err);
+ 
+ 
+ #endif /* _RPC_GSSD_H_ */
+diff -up nfs-utils-2.3.3/utils/gssd/gssd.man.orig nfs-utils-2.3.3/utils/gssd/gssd.man
+--- nfs-utils-2.3.3/utils/gssd/gssd.man.orig	2021-07-19 09:45:40.443448112 -0400
++++ nfs-utils-2.3.3/utils/gssd/gssd.man	2021-07-19 12:08:55.315182865 -0400
+@@ -8,7 +8,7 @@
+ rpc.gssd \- RPCSEC_GSS daemon
+ .SH SYNOPSIS
+ .B rpc.gssd
+-.RB [ \-DfMnlvrH ]
++.RB [ \-DfMnlvrHC ]
+ .RB [ \-k
+ .IR keytab ]
+ .RB [ \-p
+@@ -17,6 +17,10 @@ rpc.gssd \- RPCSEC_GSS daemon
+ .IR ccachedir ]
+ .RB [ \-t
+ .IR timeout ]
++.RB [ \-T
++.IR timeout ]
++.RB [ \-U
++.IR timeout ]
+ .RB [ \-R
+ .IR realm ]
+ .SH INTRODUCTION
+@@ -290,7 +294,7 @@ seconds, which allows changing Kerberos
+ The default is no explicit timeout, which means the kernel context will live
+ the lifetime of the Kerberos service ticket used in its creation.
+ .TP
+-.B -T timeout
++.BI "-T " timeout
+ Timeout, in seconds, to create an RPC connection with a server while
+ establishing an authenticated gss context for a user.
+ The default timeout is set to 5 seconds.
+@@ -298,6 +302,18 @@ If you get messages like "WARNING: can't
+ %servername% for user with uid %uid%: RPC: Remote system error -
+ Connection timed out", you should consider an increase of this timeout.
+ .TP
++.BI "-U " timeout
++Timeout, in seconds, for upcall threads.  Threads executing longer than
++.I timeout
++seconds will cause an error message to be logged.  The default
++.I timeout
++is 30 seconds.  The minimum is 5 seconds.  The maximum is 600 seconds.
++.TP
++.B -C
++In addition to logging an error message for threads that have timed out,
++the thread will be canceled and an error of -ETIMEDOUT will be reported
++to the kernel.
++.TP
+ .B -H
+ Avoids setting $HOME to "/". This allows rpc.gssd to read per user k5identity
+ files versus trying to read /.k5identity for each user.
+@@ -365,6 +381,17 @@ Equivalent to
+ Equivalent to
+ .BR -R .
+ .TP
++.B upcall-timeout
++Equivalent to
++.BR -U .
++.TP
++.B cancel-timed-out-upcalls
++Setting to
++.B true
++is equivalent to providing the
++.B -C
++flag.
++.TP
+ .B set-home
+ Setting to
+ .B false
+diff -up nfs-utils-2.3.3/utils/gssd/gssd_proc.c.orig nfs-utils-2.3.3/utils/gssd/gssd_proc.c
+--- nfs-utils-2.3.3/utils/gssd/gssd_proc.c.orig	2021-07-19 09:45:40.449448272 -0400
++++ nfs-utils-2.3.3/utils/gssd/gssd_proc.c	2021-07-19 12:08:55.316182891 -0400
+@@ -81,11 +81,24 @@
+ #include "gss_names.h"
+ 
+ extern pthread_mutex_t clp_lock;
++extern pthread_mutex_t active_thread_list_lock;
++extern int upcall_timeout;
++extern TAILQ_HEAD(active_thread_list_head, upcall_thread_info) active_thread_list;
+ 
+ /* Encryption types supported by the kernel rpcsec_gss code */
+ int num_krb5_enctypes = 0;
+ krb5_enctype *krb5_enctypes = NULL;
+ 
++/* Args for the cleanup_handler() */
++struct cleanup_args  {
++	OM_uint32 	*min_stat;
++	gss_buffer_t	acceptor;
++	gss_buffer_t	token;
++	struct authgss_private_data *pd;
++	AUTH		**auth;
++	CLIENT		**rpc_clnt;
++};
++
+ /*
+  * Parse the supported encryption type information
+  */
+@@ -184,7 +197,7 @@ out_err:
+ 	return;
+ }
+ 
+-static int
++int
+ do_error_downcall(int k5_fd, uid_t uid, int err)
+ {
+ 	char	buf[1024];
+@@ -604,27 +617,66 @@ out:
+ }
+ 
+ /*
++ * cleanup_handler:
++ *
++ * Free any resources allocated by process_krb5_upcall().
++ *
++ * Runs upon normal termination of process_krb5_upcall as well as if the
++ * thread is canceled.
++ */
++static void
++cleanup_handler(void *arg)
++{
++	struct cleanup_args *args = (struct cleanup_args *)arg;
++
++	gss_release_buffer(args->min_stat, args->acceptor);
++	if (args->token->value)
++		free(args->token->value);
++#ifdef HAVE_AUTHGSS_FREE_PRIVATE_DATA
++	if (args->pd->pd_ctx_hndl.length != 0 || args->pd->pd_ctx != 0)
++		authgss_free_private_data(args->pd);
++#endif
++	if (*args->auth)
++		AUTH_DESTROY(*args->auth);
++	if (*args->rpc_clnt)
++		clnt_destroy(*args->rpc_clnt);
++}
++
++/*
++ * process_krb5_upcall:
++ *
+  * this code uses the userland rpcsec gss library to create a krb5
+  * context on behalf of the kernel
++ *
++ * This is the meat of the upcall thread.  Note that cancelability is disabled
++ * and enabled at various points to ensure that any resources reserved by the
++ * lower level libraries are released safely.
+  */
+ static void
+-process_krb5_upcall(struct clnt_info *clp, uid_t uid, int fd, char *srchost,
+-		    char *tgtname, char *service)
++process_krb5_upcall(struct clnt_upcall_info *info)
+ {
++	struct clnt_info	*clp = info->clp;
++	uid_t			uid = info->uid;
++	int			fd = info->fd;
++	char			*srchost = info->srchost;
++	char			*tgtname = info->target;
++	char			*service = info->service;
+ 	CLIENT			*rpc_clnt = NULL;
+ 	AUTH			*auth = NULL;
+ 	struct authgss_private_data pd;
+ 	gss_buffer_desc		token;
+-	int			err, downcall_err = -EACCES;
++	int			err, downcall_err;
+ 	OM_uint32		maj_stat, min_stat, lifetime_rec;
+ 	gss_name_t		gacceptor = GSS_C_NO_NAME;
+ 	gss_OID			mech;
+ 	gss_buffer_desc		acceptor  = {0};
++	struct cleanup_args cleanup_args = {&min_stat, &acceptor, &token, &pd, &auth, &rpc_clnt};
+ 
+ 	token.length = 0;
+ 	token.value = NULL;
+ 	memset(&pd, 0, sizeof(struct authgss_private_data));
+ 
++	pthread_cleanup_push(cleanup_handler, &cleanup_args);
+ 	/*
+ 	 * If "service" is specified, then the kernel is indicating that
+ 	 * we must use machine credentials for this request.  (Regardless
+@@ -646,6 +698,8 @@ process_krb5_upcall(struct clnt_info *cl
+ 	 * used for this case is not important.
+ 	 *
+ 	 */
++	downcall_err = -EACCES;
++	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+ 	if (uid != 0 || (uid == 0 && root_uses_machine_creds == 0 &&
+ 				service == NULL)) {
+ 
+@@ -666,15 +720,21 @@ process_krb5_upcall(struct clnt_info *cl
+ 			goto out_return_error;
+ 		}
+ 	}
++	pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
++	pthread_testcancel();
+ 
++	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+ 	if (!authgss_get_private_data(auth, &pd)) {
+ 		printerr(1, "WARNING: Failed to obtain authentication "
+ 			    "data for user with uid %d for server %s\n",
+ 			 uid, clp->servername);
+ 		goto out_return_error;
+ 	}
++	pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
++	pthread_testcancel();
+ 
+ 	/* Grab the context lifetime and acceptor name out of the ctx. */
++	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+ 	maj_stat = gss_inquire_context(&min_stat, pd.pd_ctx, NULL, &gacceptor,
+ 				       &lifetime_rec, &mech, NULL, NULL, NULL);
+ 
+@@ -686,37 +746,35 @@ process_krb5_upcall(struct clnt_info *cl
+ 		get_hostbased_client_buffer(gacceptor, mech, &acceptor);
+ 		gss_release_name(&min_stat, &gacceptor);
+ 	}
++	pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
++	pthread_testcancel();
+ 
+ 	/*
+ 	 * The serialization can mean turning pd.pd_ctx into a lucid context. If
+ 	 * that happens then the pd.pd_ctx will be unusable, so we must never
+ 	 * try to use it after this point.
+ 	 */
++	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+ 	if (serialize_context_for_kernel(&pd.pd_ctx, &token, &krb5oid, NULL)) {
+ 		printerr(1, "WARNING: Failed to serialize krb5 context for "
+ 			    "user with uid %d for server %s\n",
+ 			 uid, clp->servername);
+ 		goto out_return_error;
+ 	}
++	pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
++	pthread_testcancel();
+ 
+ 	do_downcall(fd, uid, &pd, &token, lifetime_rec, &acceptor);
+ 
+ out:
+-	gss_release_buffer(&min_stat, &acceptor);
+-	if (token.value)
+-		free(token.value);
+-#ifdef HAVE_AUTHGSS_FREE_PRIVATE_DATA
+-	if (pd.pd_ctx_hndl.length != 0 || pd.pd_ctx != 0)
+-		authgss_free_private_data(&pd);
+-#endif
+-	if (auth)
+-		AUTH_DESTROY(auth);
+-	if (rpc_clnt)
+-		clnt_destroy(rpc_clnt);
++	pthread_cleanup_pop(1);
+ 
+ 	return;
+ 
+ out_return_error:
++	pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
++	pthread_testcancel();
++
+ 	do_error_downcall(fd, uid, downcall_err);
+ 	goto out;
+ }
+@@ -782,36 +840,69 @@ void free_upcall_info(struct clnt_upcall
+ }
+ 
+ static void
+-gssd_work_thread_fn(struct clnt_upcall_info *info)
++cleanup_clnt_upcall_info(void *arg)
+ {
+-	process_krb5_upcall(info->clp, info->uid, info->fd, info->srchost, info->target, info->service);
++	struct clnt_upcall_info *info = (struct clnt_upcall_info *)arg;
++
+ 	free_upcall_info(info);
+ }
+ 
++static void
++gssd_work_thread_fn(struct clnt_upcall_info *info)
++{
++	pthread_cleanup_push(cleanup_clnt_upcall_info, info);
++	process_krb5_upcall(info);
++	pthread_cleanup_pop(1);
++}
++
++static struct upcall_thread_info *
++alloc_upcall_thread_info(void)
++{
++	struct upcall_thread_info *info;
++
++	info = malloc(sizeof(struct upcall_thread_info));
++	if (info == NULL)
++		return NULL;
++	memset(info, 0, sizeof(*info));
++	return info;
++}
++
+ static int
+-start_upcall_thread(void (*func)(struct clnt_upcall_info *), void *info)
++start_upcall_thread(void (*func)(struct clnt_upcall_info *), struct clnt_upcall_info *info)
+ {
+ 	pthread_attr_t attr;
+ 	pthread_t th;
++	struct upcall_thread_info *tinfo;
+ 	int ret;
+ 
++	tinfo = alloc_upcall_thread_info();
++	if (!tinfo)
++		return -ENOMEM;
++	tinfo->fd = info->fd;
++	tinfo->uid = info->uid;
++
+ 	ret = pthread_attr_init(&attr);
+ 	if (ret != 0) {
+ 		printerr(0, "ERROR: failed to init pthread attr: ret %d: %s\n",
+ 			 ret, strerror(errno));
+-		return ret;
+-	}
+-	ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+-	if (ret != 0) {
+-		printerr(0, "ERROR: failed to create pthread attr: ret %d: "
+-			 "%s\n", ret, strerror(errno));
++		free(tinfo);
+ 		return ret;
+ 	}
+ 
+ 	ret = pthread_create(&th, &attr, (void *)func, (void *)info);
+-	if (ret != 0)
++	if (ret != 0) {
+ 		printerr(0, "ERROR: pthread_create failed: ret %d: %s\n",
+ 			 ret, strerror(errno));
++		free(tinfo);
++		return ret;
++	}
++	tinfo->tid = th;
++	pthread_mutex_lock(&active_thread_list_lock);
++	clock_gettime(CLOCK_MONOTONIC, &tinfo->timeout);
++	tinfo->timeout.tv_sec += upcall_timeout;
++	TAILQ_INSERT_TAIL(&active_thread_list, tinfo, list);
++	pthread_mutex_unlock(&active_thread_list_lock);
++
+ 	return ret;
+ }
+ 
diff --git a/SOURCES/nfs-utils-2.3.3-mount-sloppy.patch b/SOURCES/nfs-utils-2.3.3-mount-sloppy.patch
new file mode 100644
index 0000000..e734c74
--- /dev/null
+++ b/SOURCES/nfs-utils-2.3.3-mount-sloppy.patch
@@ -0,0 +1,116 @@
+diff -up nfs-utils-2.3.3/utils/mount/nfs.man.save nfs-utils-2.3.3/utils/mount/nfs.man
+--- nfs-utils-2.3.3/utils/mount/nfs.man.save	2021-07-28 14:42:20.977740892 -0400
++++ nfs-utils-2.3.3/utils/mount/nfs.man	2021-07-28 14:42:01.133212815 -0400
+@@ -525,6 +525,13 @@ using the FS-Cache facility. See cachefi
+ and <kernel_soruce>/Documentation/filesystems/caching
+ for detail on how to configure the FS-Cache facility.
+ Default value is nofsc.
++.TP 1.5i
++.B sloppy
++The
++.B sloppy
++option is an alternative to specifying
++.BR mount.nfs " -s " option.
++
+ .SS "Options for NFS versions 2 and 3 only"
+ Use these options, along with the options in the above subsection,
+ for NFS versions 2 and 3 only.
+diff -up nfs-utils-2.3.3/utils/mount/parse_opt.c.save nfs-utils-2.3.3/utils/mount/parse_opt.c
+--- nfs-utils-2.3.3/utils/mount/parse_opt.c.save	2021-07-28 14:40:15.467400995 -0400
++++ nfs-utils-2.3.3/utils/mount/parse_opt.c	2021-07-28 14:39:57.666927309 -0400
+@@ -178,6 +178,22 @@ static void options_tail_insert(struct m
+ 	options->count++;
+ }
+ 
++static void options_head_insert(struct mount_options *options,
++				struct mount_option *option)
++{
++	struct mount_option *ohead = options->head;
++
++	option->prev = NULL;
++	option->next = ohead;
++	if (ohead)
++		ohead->prev = option;
++	else
++		options->tail = option;
++	options->head = option;
++
++	options->count++;
++}
++
+ static void options_delete(struct mount_options *options,
+ 			   struct mount_option *option)
+ {
+@@ -374,6 +390,23 @@ po_return_t po_join(struct mount_options
+ }
+ 
+ /**
++ * po_insert - insert an option into a group of options
++ * @options: pointer to mount options
++ * @option: pointer to a C string containing the option to add
++ *
++ */
++po_return_t po_insert(struct mount_options *options, char *str)
++{
++	struct mount_option *option = option_create(str);
++
++	if (option) {
++		options_head_insert(options, option);
++		return PO_SUCCEEDED;
++	}
++	return PO_FAILED;
++}
++
++/**
+  * po_append - concatenate an option onto a group of options
+  * @options: pointer to mount options
+  * @option: pointer to a C string containing the option to add
+diff -up nfs-utils-2.3.3/utils/mount/parse_opt.h.save nfs-utils-2.3.3/utils/mount/parse_opt.h
+--- nfs-utils-2.3.3/utils/mount/parse_opt.h.save	2021-07-28 14:40:54.292434148 -0400
++++ nfs-utils-2.3.3/utils/mount/parse_opt.h	2021-07-28 14:39:57.666927309 -0400
+@@ -43,6 +43,7 @@ void			po_replace(struct mount_options *
+ 				   struct mount_options *);
+ po_return_t		po_join(struct mount_options *, char **);
+ 
++po_return_t		po_insert(struct mount_options *, char *);
+ po_return_t		po_append(struct mount_options *, char *);
+ po_found_t		po_contains(struct mount_options *, char *);
+ po_found_t		po_contains_prefix(struct mount_options *options,
+diff -up nfs-utils-2.3.3/utils/mount/stropts.c.save nfs-utils-2.3.3/utils/mount/stropts.c
+--- nfs-utils-2.3.3/utils/mount/stropts.c.save	2021-07-28 14:41:14.842981010 -0400
++++ nfs-utils-2.3.3/utils/mount/stropts.c	2021-07-28 14:42:01.134212842 -0400
+@@ -336,13 +336,21 @@ static int nfs_verify_lock_option(struct
+ 	return 1;
+ }
+ 
+-static int nfs_append_sloppy_option(struct mount_options *options)
++static int nfs_insert_sloppy_option(struct mount_options *options)
+ {
+-	if (!sloppy || linux_version_code() < MAKE_VERSION(2, 6, 27))
++	if (linux_version_code() < MAKE_VERSION(2, 6, 27))
+ 		return 1;
+ 
+-	if (po_append(options, "sloppy") == PO_FAILED)
+-		return 0;
++	if (po_contains(options, "sloppy")) {
++		po_remove_all(options, "sloppy");
++		sloppy++;
++	}
++
++	if (sloppy) {
++		if (po_insert(options, "sloppy") == PO_FAILED)
++			return 0;
++	}
++
+ 	return 1;
+ }
+ 
+@@ -424,7 +432,7 @@ static int nfs_validate_options(struct n
+ 	if (!nfs_set_version(mi))
+ 		return 0;
+ 
+-	if (!nfs_append_sloppy_option(mi->options))
++	if (!nfs_insert_sloppy_option(mi->options))
+ 		return 0;
+ 
+ 	return 1;
diff --git a/SPECS/nfs-utils.spec b/SPECS/nfs-utils.spec
index 8648b80..345fa9f 100644
--- a/SPECS/nfs-utils.spec
+++ b/SPECS/nfs-utils.spec
@@ -2,7 +2,7 @@ Summary: NFS utilities and supporting clients and daemons for the kernel NFS ser
 Name: nfs-utils
 URL: http://linux-nfs.org/
 Version: 2.3.3
-Release: 42%{?dist}
+Release: 46%{?dist}
 Epoch: 1
 
 # group all 32bit related archs
@@ -79,6 +79,11 @@ Patch037: nfs-utils-2.3.3-mountd-pseudofs.patch
 Patch038: nfs-utils-2.3.3-gssd-k5identity.patch
 Patch039: nfs-utils-2.3.3-gssd-man-tflag.patch
 Patch040: nfs-utils-2.3.3-exportfs-root.patch
+Patch041: nfs-utils-2.3.3-mount-sloppy.patch
+Patch042: nfs-utils-2.3.3-gssd-failed-thread.patch
+Patch043: nfs-utils-2.3.3-gssd-timeout-thread.patch
+Patch044: nfs-utils-2.3.3-gssd-debug-cleanup.patch
+Patch045: nfs-utils-2.3.3-gssd-mutex-refcnt.patch
 
 
 Patch100: nfs-utils-1.2.1-statdpath-man.patch
@@ -148,16 +153,8 @@ developing programs which use the libnfsidmap library.
 
 
 %description
-The nfs-utils package provides a daemon for the kernel NFS server and
-related tools, which provides a much higher level of performance than the
-traditional Linux NFS server used by most users.
-
-This package also contains the showmount program.  Showmount queries the
-mount daemon on a remote host for information about the NFS (Network File
-System) server on the remote host.  For example, showmount can display the
-clients which are mounted on that host.
-
-This package also contains the mount.nfs and umount.nfs program.
+The nfs-utils package provides various utilities for use with NFS
+clients and servers.
 
 %prep
 %autosetup -p1
@@ -363,6 +360,21 @@ fi
 %{_libdir}/libnfsidmap.so
 
 %changelog
+* Wed Jul 28 2021 Steve Dickson <steved@redhat.com> 2.3.3-46
+- mount.nfs: Fix the sloppy option processing (bz 1967883)
+
+* Thu Jul 22 2021 Steve Dickson <steved@redhat.com> 2.3.3-45
+- gssd: use mutex to protect decrement of refcount (bz 1511706)
+
+* Mon Jul 19 2021 Steve Dickson <steved@redhat.com> 2.3.3-44
+- gssd: Deal with failed thread creation (bz 1981400)
+- gssd: Add timeout for upcall threads (bz 1981403)
+- gssd: Cleaned up debug messages (bz 1961056)
+- spec: Updated description of the nfs-utils rpm (bz 1981419)
+
+* Sat Jul 10 2021 Steve Dickson <steved@redhat.com> 2.3.3-43
+- mount.nfs: insert 'sloppy' at beginning of the options (bz 1967883)
+
 * Mon May 10 2021 Steve Dickson <steved@redhat.com> 2.3.3-42
 - gssd: Add options to allow for the use of ~/.k5identity file (bz 1868087)
 - man: Correct gssd(8) description of rpc-timeout and context-timeout (bz 1908232)