git: 0644746d5091 - stable/13 - nfscl: Add a new NFSv4.1/4.2 mount option for Kerberized mounts

From: Rick Macklem <rmacklem_at_FreeBSD.org>
Date: Thu, 25 May 2023 21:22:24 UTC
The branch stable/13 has been updated by rmacklem:

URL: https://cgit.FreeBSD.org/src/commit/?id=0644746d50916cd1561ccec06afaa883554b98dc

commit 0644746d50916cd1561ccec06afaa883554b98dc
Author:     Rick Macklem <rmacklem@FreeBSD.org>
AuthorDate: 2023-03-16 22:55:36 +0000
Commit:     Rick Macklem <rmacklem@FreeBSD.org>
CommitDate: 2023-05-25 21:16:29 +0000

    nfscl: Add a new NFSv4.1/4.2 mount option for Kerberized mounts
    
    Without this patch, a Kerberized NFSv4.1/4.2 mount must provide
    a Kerberos credential for the client at mount time.  This credential
    is typically referred to as a "machine credential".  It can be
    created one of two ways:
    - The user (usually root) has a valid TGT at the time the mount
      is done and this becomes the machine credential.
      There are two problems with this.
      1 - The user doing the mount must have a valid TGT for a user
          principal at mount time.  As such, the mount cannot be put
          in fstab(5) or similar.
      2 - When the TGT expires, the mount breaks.
    - The client machine has a service principal in its default keytab
      file and this service principal (typically called a host-based
      initiator credential) is used as the machine credential.
      There are problems with this approach as well:
      1 - There is a certain amount of administrative overhead creating
          the service principal for the NFS client, creating a keytab
          entry for this principal and then copying the keytab entry
          into the client's default keytab file via some secure means.
      2 - The NFS client must have a fixed, well known, DNS name, since
          that FQDN is in the service principal name as the instance.
    
    This patch uses a feature of NFSv4.1/4.2 called SP4_NONE, which
    allows the state maintenance operations to be performed by any
    authentication mechanism, to do these operations via AUTH_SYS
    instead of RPCSEC_GSS (Kerberos).  As such, neither of the above
    mechanisms is needed.
    
    It is hoped that this option will encourage adoption of Kerberized
    NFS mounts using TLS, to provide a more secure NFS mount.
    
    This new NFSv4.1/4.2 mount option, called "syskrb5" must be used
    with "sec=krb5[ip]" to avoid the need for either of the above
    Kerberos setups to be done by the client.
    
    Note that all file access/modification operations still require
    users on the NFS client to have a valid TGT recognized by the
    NFSv4.1/4.2 server.  As such, this option allows, at most, a
    malicious client to do some sort of DOS attack.
    
    Although not required, use of "tls" with this new option is
    encouraged, since it provides on-the-wire encryption plus,
    optionally, client identity verification via a X.509
    certificate provided to the server during TLS handshake.
    Alternately, "sec=krb5p" does provide on-the-wire
    encryption of file data.
    
    A mount_nfs(8) man page update will be done in a separate commit.
    
    (cherry picked from commit 896516e54a8c39c1c8be3ad918f38fbf196b57ed)
---
 sys/fs/nfs/nfs.h                 |   7 ++
 sys/fs/nfs/nfs_commonkrpc.c      |  86 ++++++++++++++++++++++++-
 sys/fs/nfs/nfs_commonsubs.c      |  17 +++--
 sys/fs/nfs/nfs_var.h             |   5 +-
 sys/fs/nfs/nfsport.h             |   1 +
 sys/fs/nfsclient/nfs_clnode.c    |   4 +-
 sys/fs/nfsclient/nfs_clport.c    |  26 +++++++-
 sys/fs/nfsclient/nfs_clrpcops.c  | 122 +++++++++++++++++++++++++++++------
 sys/fs/nfsclient/nfs_clvfsops.c  | 134 +++++++++++++++++++++++++++++++++++----
 sys/fs/nfsclient/nfs_clvnops.c   |   6 +-
 sys/fs/nfsclient/nfsmount.h      |   3 +
 sys/fs/nfsserver/nfs_nfsdport.c  |   3 +-
 sys/fs/nfsserver/nfs_nfsdserv.c  |  18 +++---
 sys/fs/nfsserver/nfs_nfsdstate.c |   6 +-
 14 files changed, 378 insertions(+), 60 deletions(-)

diff --git a/sys/fs/nfs/nfs.h b/sys/fs/nfs/nfs.h
index ffd612331c1f..eac318512a35 100644
--- a/sys/fs/nfs/nfs.h
+++ b/sys/fs/nfs/nfs.h
@@ -522,6 +522,13 @@ typedef struct {
 	(b)->bits[2] = NFSGETATTRBIT_STATFS2;				\
 } while (0)
 
+#define	NFSROOTFS_GETATTRBIT(b)	do { 					\
+	(b)->bits[0] = NFSGETATTRBIT_STATFS0 | NFSATTRBIT_GETATTR0 |	\
+	    NFSATTRBM_LEASETIME;					\
+	(b)->bits[1] = NFSGETATTRBIT_STATFS1 | NFSATTRBIT_GETATTR1;	\
+	(b)->bits[2] = NFSGETATTRBIT_STATFS2 | NFSATTRBIT_GETATTR2;	\
+} while (0)
+
 #define	NFSISSETSTATFS_ATTRBIT(b) 					\
 		(((b)->bits[0] & NFSATTRBIT_STATFS0) || 		\
 		 ((b)->bits[1] & NFSATTRBIT_STATFS1) ||			\
diff --git a/sys/fs/nfs/nfs_commonkrpc.c b/sys/fs/nfs/nfs_commonkrpc.c
index 9badd8be47d4..f98d9f8df99a 100644
--- a/sys/fs/nfs/nfs_commonkrpc.c
+++ b/sys/fs/nfs/nfs_commonkrpc.c
@@ -162,6 +162,87 @@ static int nfsv2_procid[NFS_V3NPROCS] = {
 	NFSV2PROC_NOOP,
 };
 
+/*
+ * This static array indicates that a NFSv4 RPC should use
+ * RPCSEC_GSS, if the mount indicates that via sec=krb5[ip].
+ * System RPCs that do not use file handles will be false
+ * in this array so that they will use AUTH_SYS when the
+ * "syskrb5" mount option is specified, along with
+ * "sec=krb5[ip]".
+ */
+static bool nfscl_use_gss[NFSV42_NPROCS] = {
+	true,
+	true,
+	true,
+	true,
+	true,
+	true,
+	true,
+	true,
+	true,
+	true,
+	true,
+	true,
+	true,
+	true,
+	true,
+	true,
+	true,
+	true,
+	true,
+	true,
+	true,
+	true,
+	true,
+	false,		/* SetClientID */
+	false,		/* SetClientIDConfirm */
+	true,
+	true,
+	true,
+	true,
+	true,
+	true,
+	true,
+	false,		/* Renew */
+	true,
+	false,		/* ReleaseLockOwn */
+	true,
+	true,
+	true,
+	true,
+	true,
+	true,
+	false,		/* ExchangeID */
+	false,		/* CreateSession */
+	false,		/* DestroySession */
+	false,		/* DestroyClientID */
+	false,		/* FreeStateID */
+	true,
+	true,
+	true,
+	true,
+	false,		/* ReclaimComplete */
+	true,
+	true,
+	true,
+	true,
+	true,
+	true,
+	true,
+	true,
+	true,
+	true,
+	true,
+	true,
+	true,
+	true,
+	false,		/* BindConnectionToSession */
+	true,
+	true,
+	true,
+	true,
+};
+
 /*
  * Initialize sockets and congestion for a new NFS connection.
  * We do not free the sockaddr if error.
@@ -679,7 +760,8 @@ newnfs_request(struct nfsrv_descript *nd, struct nfsmount *nmp,
 		}
 		NFSUNLOCKSTATE();
 	} else if (nmp != NULL && NFSHASKERB(nmp) &&
-	     nd->nd_procnum != NFSPROC_NULL) {
+	     nd->nd_procnum != NFSPROC_NULL && (!NFSHASSYSKRB5(nmp) ||
+	     nfscl_use_gss[nd->nd_procnum])) {
 		if (NFSHASALLGSSNAME(nmp) && nmp->nm_krbnamelen > 0)
 			nd->nd_flag |= ND_USEGSSNAME;
 		if ((nd->nd_flag & ND_USEGSSNAME) != 0) {
@@ -720,7 +802,7 @@ newnfs_request(struct nfsrv_descript *nd, struct nfsmount *nmp,
 		else
 			secflavour = RPCSEC_GSS_KRB5;
 		srv_principal = NFSMNT_SRVKRBNAME(nmp);
-	} else if (nmp != NULL && !NFSHASKERB(nmp) &&
+	} else if (nmp != NULL && (!NFSHASKERB(nmp) || NFSHASSYSKRB5(nmp)) &&
 	    nd->nd_procnum != NFSPROC_NULL &&
 	    (nd->nd_flag & ND_USEGSSNAME) != 0) {
 		/*
diff --git a/sys/fs/nfs/nfs_commonsubs.c b/sys/fs/nfs/nfs_commonsubs.c
index 00cc8c68e9bf..2565b4edaf23 100644
--- a/sys/fs/nfs/nfs_commonsubs.c
+++ b/sys/fs/nfs/nfs_commonsubs.c
@@ -45,6 +45,7 @@ __FBSDID("$FreeBSD$");
 #include "opt_inet6.h"
 
 #include <fs/nfs/nfsport.h>
+#include <fs/nfsclient/nfsmount.h>
 
 #include <sys/extattr.h>
 
@@ -436,7 +437,7 @@ nfscl_reqstart(struct nfsrv_descript *nd, int procnum, struct nfsmount *nmp,
 		if (nfsv4_opflag[nfsv4_opmap[procnum].op].needscfh > 0) {
 			NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
 			*tl = txdr_unsigned(NFSV4OP_PUTFH);
-			(void) nfsm_fhtom(nd, nfhp, fhlen, 0);
+			nfsm_fhtom(nmp, nd, nfhp, fhlen, 0);
 			if (nfsv4_opflag[nfsv4_opmap[procnum].op].needscfh
 			    == 2 && procnum != NFSPROC_WRITEDS &&
 			    procnum != NFSPROC_COMMITDS) {
@@ -466,7 +467,7 @@ nfscl_reqstart(struct nfsrv_descript *nd, int procnum, struct nfsmount *nmp,
 			*tl = txdr_unsigned(nfsv4_opmap[procnum].op);
 		}
 	} else {
-		(void) nfsm_fhtom(nd, nfhp, fhlen, 0);
+		nfsm_fhtom(NULL, nd, nfhp, fhlen, 0);
 	}
 	if (procnum < NFSV42_NPROCS)
 		NFSINCRGLOBAL(nfsstatsv1.rpccnt[procnum]);
@@ -954,12 +955,15 @@ newnfs_init(void)
  * Return the number of bytes output, including XDR overhead.
  */
 int
-nfsm_fhtom(struct nfsrv_descript *nd, u_int8_t *fhp, int size, int set_true)
+nfsm_fhtom(struct nfsmount *nmp, struct nfsrv_descript *nd, u_int8_t *fhp,
+    int size, int set_true)
 {
 	u_int32_t *tl;
 	u_int8_t *cp;
 	int fullsiz, rem, bytesize = 0;
 
+	KASSERT(nmp == NULL || nmp->nm_fhsize > 0,
+	    ("nfsm_fhtom: 0 length fh"));
 	if (size == 0)
 		size = NFSX_MYFH;
 	switch (nd->nd_flag & (ND_NFSV2 | ND_NFSV3 | ND_NFSV4)) {
@@ -974,6 +978,11 @@ nfsm_fhtom(struct nfsrv_descript *nd, u_int8_t *fhp, int size, int set_true)
 		break;
 	case ND_NFSV3:
 	case ND_NFSV4:
+		if (size == NFSX_FHMAX + 1 && nmp != NULL &&
+		    (nmp->nm_privflag & NFSMNTP_FAKEROOTFH) != 0) {
+			fhp = nmp->nm_fh;
+			size = nmp->nm_fhsize;
+		}
 		fullsiz = NFSM_RNDUP(size);
 		rem = fullsiz - size;
 		if (set_true) {
@@ -2741,7 +2750,7 @@ nfsv4_fillattr(struct nfsrv_descript *nd, struct mount *mp, vnode_t vp,
 			retnum += NFSX_UNSIGNED;
 			break;
 		case NFSATTRBIT_FILEHANDLE:
-			retnum += nfsm_fhtom(nd, (u_int8_t *)fhp, 0, 0);
+			retnum += nfsm_fhtom(NULL, nd, (u_int8_t *)fhp, 0, 0);
 			break;
 		case NFSATTRBIT_FILEID:
 			NFSM_BUILD(tl, u_int32_t *, NFSX_HYPER);
diff --git a/sys/fs/nfs/nfs_var.h b/sys/fs/nfs/nfs_var.h
index ef7d0050e1a3..16d82053a961 100644
--- a/sys/fs/nfs/nfs_var.h
+++ b/sys/fs/nfs/nfs_var.h
@@ -325,7 +325,8 @@ int nfsaddr_match(int, union nethostaddr *, NFSSOCKADDR_T);
 int nfsaddr2_match(NFSSOCKADDR_T, NFSSOCKADDR_T);
 int nfsm_strtom(struct nfsrv_descript *, const char *, int);
 int nfsm_mbufuio(struct nfsrv_descript *, struct uio *, int);
-int nfsm_fhtom(struct nfsrv_descript *, u_int8_t *, int, int);
+int nfsm_fhtom(struct nfsmount *, struct nfsrv_descript *, u_int8_t *, int,
+    int);
 int nfsm_advance(struct nfsrv_descript *, int, int);
 void *nfsm_dissct(struct nfsrv_descript *, int, int);
 void newnfs_copycred(struct nfscred *, struct ucred *);
@@ -510,7 +511,7 @@ int nfsrpc_lockt(struct nfsrv_descript *, vnode_t,
 int nfsrpc_lock(struct nfsrv_descript *, struct nfsmount *, vnode_t,
     u_int8_t *, int, struct nfscllockowner *, int, int, u_int64_t,
     u_int64_t, short, struct ucred *, NFSPROC_T *, int);
-int nfsrpc_statfs(vnode_t, struct nfsstatfs *, struct nfsfsinfo *,
+int nfsrpc_statfs(vnode_t, struct nfsstatfs *, struct nfsfsinfo *, uint32_t *,
     struct ucred *, NFSPROC_T *, struct nfsvattr *, int *, void *);
 int nfsrpc_fsinfo(vnode_t, struct nfsfsinfo *, struct ucred *,
     NFSPROC_T *, struct nfsvattr *, int *, void *);
diff --git a/sys/fs/nfs/nfsport.h b/sys/fs/nfs/nfsport.h
index bcfbae857755..50071be4cc06 100644
--- a/sys/fs/nfs/nfsport.h
+++ b/sys/fs/nfs/nfsport.h
@@ -1086,6 +1086,7 @@ void ncl_copy_vattr(struct vattr *dst, struct vattr *src);
 #define	NFSHASONEOPENOWN(n)	(((n)->nm_flag & NFSMNT_ONEOPENOWN) != 0 &&	\
 				    (n)->nm_minorvers > 0)
 #define	NFSHASTLS(n)		(((n)->nm_newflag & NFSMNT_TLS) != 0)
+#define	NFSHASSYSKRB5(n)	(((n)->nm_newflag & NFSMNT_SYSKRB5) != 0)
 
 /*
  * Set boottime.
diff --git a/sys/fs/nfsclient/nfs_clnode.c b/sys/fs/nfsclient/nfs_clnode.c
index 9718c2c36a3c..8807cb2df712 100644
--- a/sys/fs/nfsclient/nfs_clnode.c
+++ b/sys/fs/nfsclient/nfs_clnode.c
@@ -156,8 +156,8 @@ ncl_nget(struct mount *mntp, u_int8_t *fhp, int fhsize, struct nfsnode **npp,
 	 * Are we getting the root? If so, make sure the vnode flags
 	 * are correct 
 	 */
-	if ((fhsize == nmp->nm_fhsize) &&
-	    !bcmp(fhp, nmp->nm_fh, fhsize)) {
+	if (fhsize == NFSX_FHMAX + 1 || (fhsize == nmp->nm_fhsize &&
+	    !bcmp(fhp, nmp->nm_fh, fhsize))) {
 		if (vp->v_type == VNON)
 			vp->v_type = VDIR;
 		vp->v_vflag |= VV_ROOT;
diff --git a/sys/fs/nfsclient/nfs_clport.c b/sys/fs/nfsclient/nfs_clport.c
index 1d2617b2bb9b..6501827e04a8 100644
--- a/sys/fs/nfsclient/nfs_clport.c
+++ b/sys/fs/nfsclient/nfs_clport.c
@@ -140,6 +140,19 @@ nfscl_nget(struct mount *mntp, struct vnode *dvp, struct nfsfh *nfhp,
 	dnp = VTONFS(dvp);
 	*npp = NULL;
 
+	/*
+	 * If this is the mount point fh and NFSMNTP_FAKEROOT is set, replace
+	 * it with the fake fh.
+	 */
+	if ((nmp->nm_privflag & NFSMNTP_FAKEROOTFH) != 0 &&
+	    nmp->nm_fhsize > 0 && nmp->nm_fhsize == nfhp->nfh_len &&
+	    !NFSBCMP(nmp->nm_fh, nfhp->nfh_fh, nmp->nm_fhsize)) {
+		free(nfhp, M_NFSFH);
+		nfhp = malloc(sizeof(struct nfsfh) + NFSX_FHMAX + 1,
+		    M_NFSFH, M_WAITOK | M_ZERO);
+		nfhp->nfh_len = NFSX_FHMAX + 1;
+	}
+
 	hash = fnv_32_buf(nfhp->nfh_fh, nfhp->nfh_len, FNV1_32_INIT);
 
 	error = vfs_hash_get(mntp, hash, lkflags,
@@ -241,8 +254,9 @@ nfscl_nget(struct mount *mntp, struct vnode *dvp, struct nfsfh *nfhp,
 	 * Are we getting the root? If so, make sure the vnode flags
 	 * are correct 
 	 */
-	if ((nfhp->nfh_len == nmp->nm_fhsize) &&
-	    !bcmp(nfhp->nfh_fh, nmp->nm_fh, nfhp->nfh_len)) {
+	if (nfhp->nfh_len == NFSX_FHMAX + 1 ||
+	    (nfhp->nfh_len == nmp->nm_fhsize &&
+	     !bcmp(nfhp->nfh_fh, nmp->nm_fh, nfhp->nfh_len))) {
 		if (vp->v_type == VNON)
 			vp->v_type = VDIR;
 		vp->v_vflag |= VV_ROOT;
@@ -492,9 +506,15 @@ nfscl_loadattrcache(struct vnode **vpp, struct nfsvattr *nap, void *nvaper,
 		 * this reliably with Clang and .c files during parallel build.
 		 * A pcap revealed packet fragmentation and GETATTR RPC
 		 * responses with wholly wrong fileids.
+		 * For the case where the file handle is a fake one
+		 * generated via the "syskrb5" mount option and
+		 * the old fileid is 2, ignore the test, since this might
+		 * be replacing the fake attributes with correct ones.
 		 */
 		if ((np->n_vattr.na_fileid != 0 &&
-		     np->n_vattr.na_fileid != nap->na_fileid) ||
+		     np->n_vattr.na_fileid != nap->na_fileid &&
+		     (np->n_vattr.na_fileid != 2 || !NFSHASSYSKRB5(nmp) ||
+		      np->n_fhp->nfh_len != NFSX_FHMAX + 1)) ||
 		    force_fid_err) {
 			nfscl_warn_fileid(nmp, &np->n_vattr, nap);
 			error = EIDRM;
diff --git a/sys/fs/nfsclient/nfs_clrpcops.c b/sys/fs/nfsclient/nfs_clrpcops.c
index 51c7bb25d357..00c54ce71e28 100644
--- a/sys/fs/nfsclient/nfs_clrpcops.c
+++ b/sys/fs/nfsclient/nfs_clrpcops.c
@@ -227,6 +227,7 @@ static int nfsrpc_copyrpc(vnode_t, off_t, vnode_t, off_t, size_t *,
 static int nfsrpc_seekrpc(vnode_t, off_t *, nfsv4stateid_t *, bool *,
     int, struct nfsvattr *, int *, struct ucred *);
 static struct mbuf *nfsm_split(struct mbuf *, uint64_t);
+static void nfscl_statfs(struct vnode *, struct ucred *, NFSPROC_T *);
 
 int nfs_pnfsio(task_fn_t *, void *);
 
@@ -305,9 +306,22 @@ nfsrpc_accessrpc(vnode_t vp, u_int32_t mode, struct ucred *cred,
 	int error;
 	struct nfsrv_descript nfsd, *nd = &nfsd;
 	nfsattrbit_t attrbits;
+	struct nfsmount *nmp;
+	struct nfsnode *np;
 
 	*attrflagp = 0;
 	supported = mode;
+	nmp = VFSTONFS(vp->v_mount);
+	np = VTONFS(vp);
+	if ((nmp->nm_privflag & NFSMNTP_FAKEROOTFH) != 0 &&
+	    nmp->nm_fhsize == 0) {
+		/* Attempt to get the actual root file handle. */
+		error = nfsrpc_getdirpath(nmp, NFSMNT_DIRPATH(nmp), cred, p);
+		if (error != 0)
+			return (EACCES);
+		if (np->n_fhp->nfh_len == NFSX_FHMAX + 1)
+			nfscl_statfs(vp, cred, p);
+	}
 	NFSCL_REQSTART(nd, NFSPROC_ACCESS, vp, cred);
 	NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
 	*tl = txdr_unsigned(mode);
@@ -1211,7 +1225,20 @@ nfsrpc_getattr(vnode_t vp, struct ucred *cred, NFSPROC_T *p,
 	struct nfsrv_descript nfsd, *nd = &nfsd;
 	int error;
 	nfsattrbit_t attrbits;
+	struct nfsnode *np;
+	struct nfsmount *nmp;
 
+	nmp = VFSTONFS(vp->v_mount);
+	np = VTONFS(vp);
+	if ((nmp->nm_privflag & NFSMNTP_FAKEROOTFH) != 0 &&
+	    nmp->nm_fhsize == 0) {
+		/* Attempt to get the actual root file handle. */
+		error = nfsrpc_getdirpath(nmp, NFSMNT_DIRPATH(nmp), cred, p);
+		if (error != 0)
+			return (EACCES);
+		if (np->n_fhp->nfh_len == NFSX_FHMAX + 1)
+			nfscl_statfs(vp, cred, p);
+	}
 	NFSCL_REQSTART(nd, NFSPROC_GETATTR, vp, cred);
 	if (nd->nd_flag & ND_NFSV4) {
 		NFSGETATTR_ATTRBIT(&attrbits);
@@ -2316,7 +2343,7 @@ nfsrpc_createv4(vnode_t dvp, char *name, int namelen, struct vattr *vap,
 	/* Get the directory's post-op attributes. */
 	NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
 	*tl = txdr_unsigned(NFSV4OP_PUTFH);
-	(void) nfsm_fhtom(nd, np->n_fhp->nfh_fh, np->n_fhp->nfh_len, 0);
+	nfsm_fhtom(nmp, nd, np->n_fhp->nfh_fh, np->n_fhp->nfh_len, 0);
 	NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
 	*tl = txdr_unsigned(NFSV4OP_GETATTR);
 	(void) nfsrv_putattrbit(nd, &attrbits);
@@ -2517,7 +2544,7 @@ tryagain:
 			*tl++ = dstateid.other[2];
 			*tl = txdr_unsigned(NFSV4OP_PUTFH);
 			np = VTONFS(dvp);
-			(void) nfsm_fhtom(nd, np->n_fhp->nfh_fh,
+			nfsm_fhtom(nmp, nd, np->n_fhp->nfh_fh,
 			    np->n_fhp->nfh_len, 0);
 			NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
 			*tl = txdr_unsigned(NFSV4OP_REMOVE);
@@ -2605,7 +2632,7 @@ tryagain:
 				NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
 				*tl = txdr_unsigned(NFSV4OP_PUTFH);
 				np = VTONFS(tvp);
-				(void) nfsm_fhtom(nd, np->n_fhp->nfh_fh,
+				nfsm_fhtom(nmp, nd, np->n_fhp->nfh_fh,
 				    np->n_fhp->nfh_len, 0);
 				NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
 				*tl = txdr_unsigned(NFSV4OP_DELEGRETURN);
@@ -2625,7 +2652,7 @@ tryagain:
 			NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
 			*tl = txdr_unsigned(NFSV4OP_PUTFH);
 			np = VTONFS(fdvp);
-			(void) nfsm_fhtom(nd, np->n_fhp->nfh_fh,
+			nfsm_fhtom(nmp, nd, np->n_fhp->nfh_fh,
 			    np->n_fhp->nfh_len, 0);
 			NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
 			*tl = txdr_unsigned(NFSV4OP_SAVEFH);
@@ -2642,7 +2669,7 @@ tryagain:
 		(void) nfsrv_putattrbit(nd, &attrbits);
 		NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
 		*tl = txdr_unsigned(NFSV4OP_PUTFH);
-		(void) nfsm_fhtom(nd, VTONFS(tdvp)->n_fhp->nfh_fh,
+		nfsm_fhtom(nmp, nd, VTONFS(tdvp)->n_fhp->nfh_fh,
 		    VTONFS(tdvp)->n_fhp->nfh_len, 0);
 		NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
 		*tl = txdr_unsigned(NFSV4OP_GETATTR);
@@ -2653,7 +2680,7 @@ tryagain:
 	}
 	(void) nfsm_strtom(nd, fnameptr, fnamelen);
 	if (!(nd->nd_flag & ND_NFSV4))
-		(void) nfsm_fhtom(nd, VTONFS(tdvp)->n_fhp->nfh_fh,
+		nfsm_fhtom(nmp, nd, VTONFS(tdvp)->n_fhp->nfh_fh,
 			VTONFS(tdvp)->n_fhp->nfh_len, 0);
 	(void) nfsm_strtom(nd, tnameptr, tnamelen);
 	error = nfscl_request(nd, fdvp, p, cred, fstuff);
@@ -2738,7 +2765,7 @@ nfsrpc_link(vnode_t dvp, vnode_t vp, char *name, int namelen,
 		NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
 		*tl = txdr_unsigned(NFSV4OP_PUTFH);
 	}
-	(void) nfsm_fhtom(nd, VTONFS(dvp)->n_fhp->nfh_fh,
+	nfsm_fhtom(VFSTONFS(dvp->v_mount), nd, VTONFS(dvp)->n_fhp->nfh_fh,
 		VTONFS(dvp)->n_fhp->nfh_len, 0);
 	if (nd->nd_flag & ND_NFSV4) {
 		NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
@@ -2878,7 +2905,7 @@ nfsrpc_mkdir(vnode_t dvp, char *name, int namelen, struct vattr *vap,
 		(void) nfsrv_putattrbit(nd, &attrbits);
 		NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
 		*tl = txdr_unsigned(NFSV4OP_PUTFH);
-		(void) nfsm_fhtom(nd, fhp->nfh_fh, fhp->nfh_len, 0);
+		nfsm_fhtom(nmp, nd, fhp->nfh_fh, fhp->nfh_len, 0);
 		NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
 		*tl = txdr_unsigned(NFSV4OP_GETATTR);
 		(void) nfsrv_putattrbit(nd, &attrbits);
@@ -4398,8 +4425,8 @@ nfsmout:
  */
 int
 nfsrpc_statfs(vnode_t vp, struct nfsstatfs *sbp, struct nfsfsinfo *fsp,
-    struct ucred *cred, NFSPROC_T *p, struct nfsvattr *nap, int *attrflagp,
-    void *stuff)
+    uint32_t *leasep, struct ucred *cred, NFSPROC_T *p, struct nfsvattr *nap,
+    int *attrflagp, void *stuff)
 {
 	u_int32_t *tl = NULL;
 	struct nfsrv_descript nfsd, *nd = &nfsd;
@@ -4414,7 +4441,10 @@ nfsrpc_statfs(vnode_t vp, struct nfsstatfs *sbp, struct nfsfsinfo *fsp,
 		 * For V4, you actually do a getattr.
 		 */
 		NFSCL_REQSTART(nd, NFSPROC_GETATTR, vp, cred);
-		NFSSTATFS_GETATTRBIT(&attrbits);
+		if (leasep != NULL)
+			NFSROOTFS_GETATTRBIT(&attrbits);
+		else
+			NFSSTATFS_GETATTRBIT(&attrbits);
 		(void) nfsrv_putattrbit(nd, &attrbits);
 		nd->nd_flag |= ND_USEGSSNAME;
 		error = nfscl_request(nd, vp, p, cred, stuff);
@@ -4422,8 +4452,8 @@ nfsrpc_statfs(vnode_t vp, struct nfsstatfs *sbp, struct nfsfsinfo *fsp,
 			return (error);
 		if (nd->nd_repstat == 0) {
 			error = nfsv4_loadattr(nd, NULL, nap, NULL, NULL, 0,
-			    NULL, NULL, sbp, fsp, NULL, 0, NULL, NULL, NULL, p,
-			    cred);
+			    NULL, NULL, sbp, fsp, NULL, 0, NULL, leasep, NULL,
+			    p, cred);
 			if (!error) {
 				nmp->nm_fsid[0] = nap->na_filesid[0];
 				nmp->nm_fsid[1] = nap->na_filesid[1];
@@ -4485,10 +4515,22 @@ nfsrpc_pathconf(vnode_t vp, struct nfsv3_pathconf *pc,
 	u_int32_t *tl;
 	nfsattrbit_t attrbits;
 	int error;
+	struct nfsnode *np;
 
 	*attrflagp = 0;
 	nmp = VFSTONFS(vp->v_mount);
 	if (NFSHASNFSV4(nmp)) {
+		np = VTONFS(vp);
+		if ((nmp->nm_privflag & NFSMNTP_FAKEROOTFH) != 0 &&
+		    nmp->nm_fhsize == 0) {
+			/* Attempt to get the actual root file handle. */
+			error = nfsrpc_getdirpath(nmp, NFSMNT_DIRPATH(nmp),
+			    cred, p);
+			if (error != 0)
+				return (EACCES);
+			if (np->n_fhp->nfh_len == NFSX_FHMAX + 1)
+				nfscl_statfs(vp, cred, p);
+		}
 		/*
 		 * For V4, you actually do a getattr.
 		 */
@@ -4675,7 +4717,7 @@ nfsrpc_getdirpath(struct nfsmount *nmp, u_char *dirpath, struct ucred *cred,
 	u_int32_t *tl;
 	struct nfsrv_descript nfsd;
 	struct nfsrv_descript *nd = &nfsd;
-	u_char *cp, *cp2;
+	u_char *cp, *cp2, *fhp;
 	int error, cnt, len, setnil;
 	u_int32_t *opcntp;
 
@@ -4723,9 +4765,17 @@ nfsrpc_getdirpath(struct nfsmount *nmp, u_char *dirpath, struct ucred *cred,
 			len > NFSX_FHMAX) {
 			nd->nd_repstat = NFSERR_BADXDR;
 		} else {
-			nd->nd_repstat = nfsrv_mtostr(nd, nmp->nm_fh, len);
-			if (nd->nd_repstat == 0)
-				nmp->nm_fhsize = len;
+			fhp = malloc(len + 1, M_TEMP, M_WAITOK);
+			nd->nd_repstat = nfsrv_mtostr(nd, fhp, len);
+			if (nd->nd_repstat == 0) {
+				NFSLOCKMNT(nmp);
+				if (nmp->nm_fhsize == 0) {
+					NFSBCOPY(fhp, nmp->nm_fh, len);
+					nmp->nm_fhsize = len;
+				}
+				NFSUNLOCKMNT(nmp);
+			}
+			free(fhp, M_TEMP);
 		}
 	}
 	error = nd->nd_repstat;
@@ -7949,7 +7999,7 @@ nfsrpc_createlayout(vnode_t dvp, char *name, int namelen, struct vattr *vap,
 	/* Get the directory's post-op attributes. */
 	NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
 	*tl = txdr_unsigned(NFSV4OP_PUTFH);
-	nfsm_fhtom(nd, np->n_fhp->nfh_fh, np->n_fhp->nfh_len, 0);
+	nfsm_fhtom(nmp, nd, np->n_fhp->nfh_fh, np->n_fhp->nfh_len, 0);
 	NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
 	*tl = txdr_unsigned(NFSV4OP_GETATTR);
 	nfsrv_putattrbit(nd, &attrbits);
@@ -8354,7 +8404,7 @@ nfsrpc_copyrpc(vnode_t invp, off_t inoff, vnode_t outvp, off_t outoff,
 	nfsrv_putattrbit(nd, &attrbits);
 	NFSM_BUILD(tl, uint32_t *, NFSX_UNSIGNED);
 	*tl = txdr_unsigned(NFSV4OP_PUTFH);
-	nfsm_fhtom(nd, VTONFS(outvp)->n_fhp->nfh_fh,
+	nfsm_fhtom(nmp, nd, VTONFS(outvp)->n_fhp->nfh_fh,
 	    VTONFS(outvp)->n_fhp->nfh_len, 0);
 	NFSM_BUILD(tl, uint32_t *, NFSX_UNSIGNED);
 	*tl = txdr_unsigned(NFSV4OP_COPY);
@@ -8943,3 +8993,37 @@ nfsmout:
 		printf("nfsrpc_bindconnsess: reply bad xdr\n");
 	m_freem(nd->nd_mrep);
 }
+
+/*
+ * Do roughly what nfs_statfs() does for NFSv4, but when called with a shared
+ * locked vnode.
+ */
+static void
+nfscl_statfs(struct vnode *vp, struct ucred *cred, NFSPROC_T *td)
+{
+	struct nfsvattr nfsva;
+	struct nfsfsinfo fs;
+	struct nfsstatfs sb;
+	struct mount *mp;
+	struct nfsmount *nmp;
+	uint32_t lease;
+	int attrflag, error;
+
+	mp = vp->v_mount;
+	nmp = VFSTONFS(mp);
+	error = nfsrpc_statfs(vp, &sb, &fs, &lease, cred, td, &nfsva,
+	    &attrflag, NULL);
+	if (attrflag != 0)
+		nfscl_loadattrcache(&vp, &nfsva, NULL, NULL, 0, 1);
+	if (error == 0) {
+		NFSLOCKCLSTATE();
+		if (nmp->nm_clp != NULL)
+			nmp->nm_clp->nfsc_renew = NFSCL_RENEW(lease);
+		NFSUNLOCKCLSTATE();
+		mtx_lock(&nmp->nm_mtx);
+		nfscl_loadfsinfo(nmp, &fs);
+		nfscl_loadsbinfo(nmp, &sb, &mp->mnt_stat);
+		mp->mnt_stat.f_iosize = newnfs_iosize(nmp);
+		mtx_unlock(&nmp->nm_mtx);
+	}
+}
diff --git a/sys/fs/nfsclient/nfs_clvfsops.c b/sys/fs/nfsclient/nfs_clvfsops.c
index be863cc9d70a..d60b8f66f8c9 100644
--- a/sys/fs/nfsclient/nfs_clvfsops.c
+++ b/sys/fs/nfsclient/nfs_clvfsops.c
@@ -291,13 +291,37 @@ nfs_statfs(struct mount *mp, struct statfs *sbp)
 	struct nfsstatfs sb;
 	int error = 0, attrflag, gotfsinfo = 0, ret;
 	struct nfsnode *np;
+	char *fakefh;
 
 	td = curthread;
 
 	error = vfs_busy(mp, MBF_NOWAIT);
 	if (error)
 		return (error);
-	error = ncl_nget(mp, nmp->nm_fh, nmp->nm_fhsize, &np, LK_EXCLUSIVE);
+	if ((nmp->nm_privflag & NFSMNTP_FAKEROOTFH) != 0) {
+		if (nmp->nm_fhsize == 0) {
+			error = nfsrpc_getdirpath(nmp, NFSMNT_DIRPATH(nmp),
+			    td->td_ucred, td);
+			if (error != 0) {
+				/*
+				 * We cannot do anything yet.  Hopefully what
+				 * is in mnt_stat is sufficient.
+				 */
+				if (sbp != &mp->mnt_stat)
+					*sbp = mp->mnt_stat;
+				strncpy(&sbp->f_fstypename[0],
+				    mp->mnt_vfc->vfc_name, MFSNAMELEN);
+				vfs_unbusy(mp);
+				return (0);
+			}
+		}
+		fakefh = malloc(NFSX_FHMAX + 1, M_TEMP, M_WAITOK | M_ZERO);
+		error = ncl_nget(mp, fakefh, NFSX_FHMAX + 1, &np, LK_EXCLUSIVE);
+		free(fakefh, M_TEMP);
+	} else {
+		error = ncl_nget(mp, nmp->nm_fh, nmp->nm_fhsize, &np,
+		    LK_EXCLUSIVE);
+	}
 	if (error) {
 		vfs_unbusy(mp);
 		return (error);
@@ -313,8 +337,19 @@ nfs_statfs(struct mount *mp, struct statfs *sbp)
 	} else
 		mtx_unlock(&nmp->nm_mtx);
 	if (!error)
-		error = nfsrpc_statfs(vp, &sb, &fs, td->td_ucred, td, &nfsva,
-		    &attrflag, NULL);
+		error = nfsrpc_statfs(vp, &sb, &fs, NULL, td->td_ucred, td,
+		    &nfsva, &attrflag, NULL);
+	if ((nmp->nm_privflag & NFSMNTP_FAKEROOTFH) != 0 &&
+	    error == NFSERR_WRONGSEC) {
+		/* Cannot get new stats, so return what is in mnt_stat. */
+		if (sbp != &mp->mnt_stat)
+			*sbp = mp->mnt_stat;
+		strncpy(&sbp->f_fstypename[0], mp->mnt_vfc->vfc_name,
+		    MFSNAMELEN);
+		vput(vp);
+		vfs_unbusy(mp);
+		return (0);
+	}
 	if (error != 0)
 		NFSCL_DEBUG(2, "statfs=%d\n", error);
 	if (attrflag == 0) {
@@ -749,7 +784,7 @@ static const char *nfs_opts[] = { "from", "nfs_args",
     "nfsv3", "sec", "principal", "nfsv4", "gssname", "allgssname", "dirpath",
     "minorversion", "nametimeo", "negnametimeo", "nocto", "noncontigwr",
     "pnfs", "wcommitsize", "oneopenown", "tls", "tlscertname", "nconnect",
-    NULL };
+    "syskrb5", NULL };
 
 /*
  * Parse the "from" mountarg, passed by the generic mount(8) program
@@ -1205,6 +1240,8 @@ nfs_mount(struct mount *mp)
 		 */
 		aconn--;
 	}
+	if (vfs_getopt(mp->mnt_optnew, "syskrb5", NULL, NULL) == 0)
+		newflag |= NFSMNT_SYSKRB5;
 	if (vfs_getopt(mp->mnt_optnew, "sec",
 		(void **) &secname, NULL) == 0)
 		nfs_sec_name(secname, &args.flags);
@@ -1387,6 +1424,39 @@ nfs_mount(struct mount *mp)
 		goto out;
 	}
 
+	if ((newflag & NFSMNT_SYSKRB5) != 0 &&
+	    ((args.flags & NFSMNT_NFSV4) == 0 || minvers == 0)) {
+		/*
+		 * This option requires the use of SP4_NONE, which
+		 * is only in NFSv4.1/4.2.
+		 */
+		vfs_mount_error(mp, "syskrb5 should only be used "
+		    "for NFSv4.1/4.2 mounts");
+		error = EINVAL;
+		goto out;
+	}
+
+	if ((newflag & NFSMNT_SYSKRB5) != 0 &&
+	    (args.flags & NFSMNT_KERB) == 0) {
+		/*
+		 * This option modifies the behaviour of sec=krb5[ip].
+		 */
+		vfs_mount_error(mp, "syskrb5 should only be used "
+		    "for sec=krb5[ip] mounts");
+		error = EINVAL;
+		goto out;
+	}
+
+	if ((newflag & NFSMNT_SYSKRB5) != 0 && krbname[0] != '\0') {
+		/*
+		 * This option is used as an alternative to "gssname".
+		 */
+		vfs_mount_error(mp, "syskrb5 should not be used "
+		    "with the gssname option");
+		error = EINVAL;
+		goto out;
+	}
+
 	args.fh = nfh;
 	error = mountnfs(&args, mp, nam, hst, krbname, krbnamelen, dirpath,
 	    dirlen, srvkrbname, srvkrbnamelen, &vp, td->td_ucred, td,
@@ -1448,6 +1518,7 @@ mountnfs(struct nfs_args *argp, struct mount *mp, struct sockaddr *nam,
 	struct nfsclds *dsp, *tdsp;
 	uint32_t lease;
 	bool tryminvers;
+	char *fakefh;
 	static u_int64_t clval = 0;
 #ifdef KERN_TLS
 	u_int maxlen;
@@ -1621,6 +1692,12 @@ mountnfs(struct nfs_args *argp, struct mount *mp, struct sockaddr *nam,
 			error = EINVAL;
 			goto bad;
 		}
+		if (NFSHASSYSKRB5(nmp) && nmp->nm_minorvers == 0) {
+			vfs_mount_error(mp, "syskrb5 should only be used "
+			    "for NFSv4.1/4.2 mounts");
+			error = EINVAL;
+			goto bad;
+		}
 	}
 
 	if (nmp->nm_fhsize == 0 && (nmp->nm_flag & NFSMNT_NFSV4) &&
@@ -1635,10 +1712,13 @@ mountnfs(struct nfs_args *argp, struct mount *mp, struct sockaddr *nam,
 			error = nfsrpc_getdirpath(nmp, NFSMNT_DIRPATH(nmp),
 			    cred, td);
 			NFSCL_DEBUG(3, "aft dirp=%d\n", error);
-			if (error)
+			if (error != 0 && (!NFSHASSYSKRB5(nmp) ||
+			    error != NFSERR_WRONGSEC))
 				(void) nfs_catnap(PZERO, error, "nfsgetdirp");
-		} while (error && --trycnt > 0);
-		if (error)
+		} while (error != 0 && --trycnt > 0 &&
+		    (!NFSHASSYSKRB5(nmp) || error != NFSERR_WRONGSEC));
+		if (error != 0 && (!NFSHASSYSKRB5(nmp) ||
+		    error != NFSERR_WRONGSEC))
 			goto bad;
 	}
 
@@ -1649,16 +1729,27 @@ mountnfs(struct nfs_args *argp, struct mount *mp, struct sockaddr *nam,
 	 * the nfsnode gets flushed out of the cache. Ufs does not have
 	 * this problem, because one can identify root inodes by their
 	 * number == UFS_ROOTINO (2).
+	 * For the "syskrb5" mount, the file handle might not have
+	 * been acquired.  As such, use a "fake" file handle which
+	 * can never be returned by a server for the root vnode.
 	 */
-	if (nmp->nm_fhsize > 0) {
+	if (nmp->nm_fhsize > 0 || NFSHASSYSKRB5(nmp)) {
 		/*
 		 * Set f_iosize to NFS_DIRBLKSIZ so that bo_bsize gets set
 		 * non-zero for the root vnode. f_iosize will be set correctly
 		 * by nfs_statfs() before any I/O occurs.
 		 */
 		mp->mnt_stat.f_iosize = NFS_DIRBLKSIZ;
-		error = ncl_nget(mp, nmp->nm_fh, nmp->nm_fhsize, &np,
-		    LK_EXCLUSIVE);
+		if (nmp->nm_fhsize == 0) {
+			fakefh = malloc(NFSX_FHMAX + 1, M_TEMP, M_WAITOK |
+			    M_ZERO);
+			error = ncl_nget(mp, fakefh, NFSX_FHMAX + 1, &np,
+			    LK_EXCLUSIVE);
+			free(fakefh, M_TEMP);
+			nmp->nm_privflag |= NFSMNTP_FAKEROOTFH;
+		} else
+			error = ncl_nget(mp, nmp->nm_fh, nmp->nm_fhsize, &np,
+			    LK_EXCLUSIVE);
 		if (error)
 			goto bad;
 		*vpp = NFSTOV(np);
@@ -1668,8 +1759,10 @@ mountnfs(struct nfs_args *argp, struct mount *mp, struct sockaddr *nam,
 		 * mountpoint.  This has the side effect of filling in
 		 * (*vpp)->v_type with the correct value.
 		 */
-		ret = nfsrpc_getattrnovp(nmp, nmp->nm_fh, nmp->nm_fhsize, 1,
-		    cred, td, &nfsva, NULL, &lease);
+		ret = ENXIO;
+		if (nmp->nm_fhsize > 0)
+			ret = nfsrpc_getattrnovp(nmp, nmp->nm_fh,
+			    nmp->nm_fhsize, 1, cred, td, &nfsva, NULL, &lease);
 		if (ret) {
 			/*
 			 * Just set default values to get things going.
@@ -1684,7 +1777,7 @@ mountnfs(struct nfs_args *argp, struct mount *mp, struct sockaddr *nam,
 			nfsva.na_vattr.va_gen = 1;
 			nfsva.na_vattr.va_blocksize = NFS_FABLKSIZE;
 			nfsva.na_vattr.va_size = 512 * 1024;
-			lease = 60;
+			lease = 20;
 		}
 		(void) nfscl_loadattrcache(vpp, &nfsva, NULL, NULL, 0, 1);
 		if ((argp->flags & NFSMNT_NFSV4) != 0) {
@@ -1870,9 +1963,20 @@ nfs_root(struct mount *mp, int flags, struct vnode **vpp)
 	struct nfsmount *nmp;
 	struct nfsnode *np;
 	int error;
+	char *fakefh;
 
 	nmp = VFSTONFS(mp);
-	error = ncl_nget(mp, nmp->nm_fh, nmp->nm_fhsize, &np, flags);
+	if ((nmp->nm_privflag & NFSMNTP_FAKEROOTFH) != 0) {
+		/* Attempt to get the actual root file handle. */
+		if (nmp->nm_fhsize == 0)
+			error = nfsrpc_getdirpath(nmp, NFSMNT_DIRPATH(nmp),
+			    curthread->td_ucred, curthread);
+		fakefh = malloc(NFSX_FHMAX + 1, M_TEMP, M_WAITOK | M_ZERO);
+		error = ncl_nget(mp, fakefh, NFSX_FHMAX + 1, &np, flags);
+		free(fakefh, M_TEMP);
+	} else {
+		error = ncl_nget(mp, nmp->nm_fh, nmp->nm_fhsize, &np, flags);
+	}
 	if (error)
 		return error;
 	vp = NFSTOV(np);
@@ -2113,6 +2217,8 @@ void nfscl_retopts(struct nfsmount *nmp, char *buffer, size_t buflen)
 	    &buf, &blen);
 	nfscl_printopt(nmp, (nmp->nm_newflag & NFSMNT_TLS) != 0, ",tls", &buf,
 	    &blen);
+	nfscl_printopt(nmp, (nmp->nm_newflag & NFSMNT_SYSKRB5) != 0,
+	    ",syskrb5", &buf, &blen);
 	nfscl_printopt(nmp, (nmp->nm_flag & NFSMNT_NOCONN) != 0, ",noconn",
 	    &buf, &blen);
 	nfscl_printoptval(nmp, nmp->nm_aconnect + 1, ",nconnect", &buf, &blen);
diff --git a/sys/fs/nfsclient/nfs_clvnops.c b/sys/fs/nfsclient/nfs_clvnops.c
index d1d84b05bbd7..425d75c10586 100644
--- a/sys/fs/nfsclient/nfs_clvnops.c
+++ b/sys/fs/nfsclient/nfs_clvnops.c
@@ -996,7 +996,9 @@ nfs_getattr(struct vop_getattr_args *ap)
 	struct nfsvattr nfsva;
 	struct vattr *vap = ap->a_vap;
 	struct vattr vattr;
+	struct nfsmount *nmp;
 
+	nmp = VFSTONFS(vp->v_mount);
 	/*
 	 * Update local times for special files.
 	 */
@@ -1006,8 +1008,10 @@ nfs_getattr(struct vop_getattr_args *ap)
 	NFSUNLOCKNODE(np);
 	/*
 	 * First look in the cache.
+	 * For "syskrb5" mounts, nm_fhsize might still be zero and
+	 * cached attributes should be ignored.
 	 */
-	if (ncl_getattrcache(vp, &vattr) == 0) {
+	if (nmp->nm_fhsize > 0 && ncl_getattrcache(vp, &vattr) == 0) {
 		ncl_copy_vattr(vap, &vattr);
 
 		/*
diff --git a/sys/fs/nfsclient/nfsmount.h b/sys/fs/nfsclient/nfsmount.h
index a5997e474be9..c9183ebe919f 100644
--- a/sys/fs/nfsclient/nfsmount.h
+++ b/sys/fs/nfsclient/nfsmount.h
@@ -124,9 +124,12 @@ struct	nfsmount {
 #define	NFSMNTP_NOADVISE	0x00000100
 #define	NFSMNTP_NOALLOCATE	0x00000200
 #define	NFSMNTP_DELEGISSUED	0x00000400
+#define	NFSMNTP_NODEALLOCATE	0x00000800
+#define	NFSMNTP_FAKEROOTFH	0x00001000
 
 /* New mount flags only used by the kernel via nmount(2). */
 #define	NFSMNT_TLS		0x00000001
+#define	NFSMNT_SYSKRB5		0x00000002
 
 #define	NFSMNT_DIRPATH(m)	(&((m)->nm_name[(m)->nm_krbnamelen + 1]))
 #define	NFSMNT_SRVKRBNAME(m)						\
diff --git a/sys/fs/nfsserver/nfs_nfsdport.c b/sys/fs/nfsserver/nfs_nfsdport.c
index 06e6a4147f30..b83e51c231b0 100644
--- a/sys/fs/nfsserver/nfs_nfsdport.c
+++ b/sys/fs/nfsserver/nfs_nfsdport.c
@@ -2769,7 +2769,8 @@ again:
 				NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
 				txdr_hyper(*cookiep, tl);
 				nfsrv_postopattr(nd, 0, nvap);
-				dirlen += nfsm_fhtom(nd,(u_int8_t *)&nfh,0,1);
+				dirlen += nfsm_fhtom(NULL, nd, (u_int8_t *)&nfh,
+				    0, 1);
 				dirlen += (5*NFSX_UNSIGNED+NFSX_V3POSTOPATTR);
 				if (nvp != NULL)
 					vput(nvp);
diff --git a/sys/fs/nfsserver/nfs_nfsdserv.c b/sys/fs/nfsserver/nfs_nfsdserv.c
index 2d1048d26486..6238160eda13 100644
--- a/sys/fs/nfsserver/nfs_nfsdserv.c
+++ b/sys/fs/nfsserver/nfs_nfsdserv.c
@@ -674,10 +674,10 @@ nfsrvd_lookup(struct nfsrv_descript *nd, __unused int isdgram,
 		goto out;
 	}
 	if (nd->nd_flag & ND_NFSV2) {
-		(void) nfsm_fhtom(nd, (u_int8_t *)fhp, 0, 0);
+		nfsm_fhtom(NULL, nd, (u_int8_t *)fhp, 0, 0);
 		nfsrv_fillattr(nd, &nva);
 	} else if (nd->nd_flag & ND_NFSV3) {
-		(void) nfsm_fhtom(nd, (u_int8_t *)fhp, 0, 0);
+		nfsm_fhtom(NULL, nd, (u_int8_t *)fhp, 0, 0);
 		nfsrv_postopattr(nd, 0, &nva);
 		nfsrv_postopattr(nd, dattr_ret, &dattr);
 	}
@@ -1284,7 +1284,7 @@ nfsrvd_create(struct nfsrv_descript *nd, __unused int isdgram,
 	}
 	if (nd->nd_flag & ND_NFSV2) {
 		if (!nd->nd_repstat) {
-			(void) nfsm_fhtom(nd, (u_int8_t *)&fh, 0, 0);
+			nfsm_fhtom(NULL, nd, (u_int8_t *)&fh, 0, 0);
 			nfsrv_fillattr(nd, &nva);
 		}
 	} else {
@@ -1294,7 +1294,7 @@ nfsrvd_create(struct nfsrv_descript *nd, __unused int isdgram,
 		diraft_ret = nfsvno_getattr(dirp, &diraft, nd, p, 0, NULL);
 		vrele(dirp);
 		if (!nd->nd_repstat) {
-			(void) nfsm_fhtom(nd, (u_int8_t *)&fh, 0, 1);
+			nfsm_fhtom(NULL, nd, (u_int8_t *)&fh, 0, 1);
 			nfsrv_postopattr(nd, 0, &nva);
 		}
 		nfsrv_wcc(nd, dirfor_ret, &dirfor, diraft_ret, &diraft);
*** 73 LINES SKIPPED ***