git: ff2f1f691cdb - main - nfsd: Add support for the SP4_MACH_CRED case in ExchangeID

From: Rick Macklem <rmacklem_at_FreeBSD.org>
Date: Fri, 07 Apr 2023 19:51:42 UTC
The branch main has been updated by rmacklem:

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

commit ff2f1f691cdb376be70b9579d2f9c91b06ac839d
Author:     Rick Macklem <rmacklem@FreeBSD.org>
AuthorDate: 2023-04-07 19:49:23 +0000
Commit:     Rick Macklem <rmacklem@FreeBSD.org>
CommitDate: 2023-04-07 19:49:23 +0000

    nfsd: Add support for the SP4_MACH_CRED case in ExchangeID
    
    Commit f4179ad46fa4 added support for operation bitmaps for
    NFSv4.1/4.2.  This commit uses those to implement the SP4_MACH_CRED
    case for the NFSv4.1/4.2 ExchangeID operation since the Linux
    NFSv4.1/4.2 client is now using this for Kerberized mounts.
    The Linux Kerberized NFSv4.1/4.2 mounts currently work without
    support for this because Linux will fall back to SP4_NONE,
    but there is no guarantee this fallback will work forever.
    
    This commit only affects Kerberized NFSv4.1/4.2 mounts from
    Linux at this time.
    
    MFC after:      3 months
---
 sys/fs/nfs/nfs_var.h              |  2 +-
 sys/fs/nfs/nfsrvstate.h           |  2 +
 sys/fs/nfsserver/nfs_nfsdkrpc.c   | 58 ++++++++++++++++++++++++-
 sys/fs/nfsserver/nfs_nfsdserv.c   | 29 +++++++++++--
 sys/fs/nfsserver/nfs_nfsdsocket.c | 55 +++++++++++++++++++++++-
 sys/fs/nfsserver/nfs_nfsdstate.c  | 90 +++++++++++++++++++++++++++++++++------
 6 files changed, 215 insertions(+), 21 deletions(-)

diff --git a/sys/fs/nfs/nfs_var.h b/sys/fs/nfs/nfs_var.h
index ad1eb73b1090..8fabe6f92fc6 100644
--- a/sys/fs/nfs/nfs_var.h
+++ b/sys/fs/nfs/nfs_var.h
@@ -99,7 +99,7 @@ int nfsrv_setclient(struct nfsrv_descript *, struct nfsclient **,
     nfsquad_t *, nfsquad_t *, NFSPROC_T *);
 int nfsrv_getclient(nfsquad_t, int, struct nfsclient **, struct nfsdsession *,
     nfsquad_t, uint32_t, struct nfsrv_descript *, NFSPROC_T *);
-int nfsrv_destroyclient(nfsquad_t, NFSPROC_T *);
+int nfsrv_destroyclient(struct nfsrv_descript *, nfsquad_t, NFSPROC_T *);
 int nfsrv_destroysession(struct nfsrv_descript *, uint8_t *);
 int nfsrv_bindconnsess(struct nfsrv_descript *, uint8_t *, int *);
 int nfsrv_freestateid(struct nfsrv_descript *, nfsv4stateid_t *, NFSPROC_T *);
diff --git a/sys/fs/nfs/nfsrvstate.h b/sys/fs/nfs/nfsrvstate.h
index 4dc9729a952f..5a2c9496804d 100644
--- a/sys/fs/nfs/nfsrvstate.h
+++ b/sys/fs/nfs/nfsrvstate.h
@@ -105,6 +105,8 @@ struct nfsclient {
 	time_t		lc_delegtime;		/* Old deleg expiry (sec) */
 	nfsquad_t	lc_clientid;		/* 64 bit clientid */
 	nfsquad_t	lc_confirm;		/* 64 bit confirm value */
+	nfsopbit_t	lc_mustops;		/* Must ops SP4_MACH_CRED */
+	nfsopbit_t	lc_allowops;		/* Allowed ops SP4_MACH_CRED */
 	u_int32_t	lc_program;		/* RPC Program # */
 	u_int32_t	lc_callback;		/* Callback id */
 	u_int32_t	lc_stateindex;		/* Current state index# */
diff --git a/sys/fs/nfsserver/nfs_nfsdkrpc.c b/sys/fs/nfsserver/nfs_nfsdkrpc.c
index 45459d115346..5999727fccb6 100644
--- a/sys/fs/nfsserver/nfs_nfsdkrpc.c
+++ b/sys/fs/nfsserver/nfs_nfsdkrpc.c
@@ -108,6 +108,7 @@ extern time_t nfsdev_time;
 extern int nfsrv_writerpc[NFS_NPROCS];
 extern volatile int nfsrv_devidcnt;
 extern struct nfsv4_opflag nfsv4_opflag[NFSV42_NOPS];
+extern int nfsd_debuglevel;
 
 NFSD_VNET_DECLARE(struct proc *, nfsd_master_proc);
 
@@ -126,7 +127,9 @@ nfssvc_program(struct svc_req *rqst, SVCXPRT *xprt)
 {
 	struct nfsrv_descript nd;
 	struct nfsrvcache *rp = NULL;
-	int cacherep, credflavor;
+	rpc_gss_rawcred_t *rcredp;
+	int cacherep, credflavor, i, j;
+	u_char *p;
 #ifdef KERN_TLS
 	u_int maxlen;
 #endif
@@ -245,6 +248,58 @@ nfssvc_program(struct svc_req *rqst, SVCXPRT *xprt)
 			goto out;
 		}
 
+		/* Acquire the principal name for the RPCSEC_GSS cases. */
+		if ((nd.nd_flag & (ND_NFSV4 | ND_GSS)) == (ND_NFSV4 | ND_GSS)) {
+			rcredp = NULL;
+			rpc_gss_getcred_call(rqst, &rcredp, NULL, NULL);
+			/*
+			 * The exported principal name consists of:
+			 * - TOK_ID bytes with value of 4 and 1.
+			 * - 2 byte mech length
+			 * - mech
+			 * - 4 byte principal name length
+			 * - principal name
+			 * A call to gss_import_name() would be an
+			 * upcall to the gssd, so parse it here.
+			 * See lib/libgssapi/gss_import_name.c for the
+			 * above format.
+			 */
+			if (rcredp != NULL &&
+			    rcredp->client_principal->len > 4 &&
+			    rcredp->client_principal->name[0] == 4 &&
+			    rcredp->client_principal->name[1] == 1) {
+				/* Skip over the mech. */
+				p = &rcredp->client_principal->name[2];
+				i = (p[0] << 8) | p[1];
+				p += i + 2;
+				i += 4;
+				/*
+				 * Set "j" to a bogus length so that the
+				 * "i + j" check will fail unless the below
+				 * code sets "j" correctly.
+				 */
+				j = rcredp->client_principal->len;
+				if (rcredp->client_principal->len > i + 4) {
+					j = (p[0] << 24) | (p[1] << 16) |
+					    (p[2] << 8) | p[3];
+					i += 4;
+					p += 4;
+				}
+				if (i + j == rcredp->client_principal->len) {
+					nd.nd_principal = malloc(j + 1, M_TEMP,
+					    M_WAITOK);
+					nd.nd_princlen = j;
+					memcpy(nd.nd_principal, p, j);
+					nd.nd_principal[j] = '\0';
+					NFSD_DEBUG(1, "nfssvc_program: "
+					    "principal=%s\n", nd.nd_principal);
+				}
+			}
+			if (nd.nd_princlen == 0)
+				printf("nfssvc_program: cannot get RPCSEC_GSS "
+				    "principal name\n");
+		}
+
 		if ((xprt->xp_tls & RPCTLS_FLAGS_HANDSHAKE) != 0) {
 			nd.nd_flag |= ND_TLS;
 			if ((xprt->xp_tls & RPCTLS_FLAGS_VERIFIED) != 0)
@@ -334,6 +389,7 @@ nfssvc_program(struct svc_req *rqst, SVCXPRT *xprt)
 	svc_freereq(rqst);
 
 out:
+	free(nd.nd_principal, M_TEMP);
 	NFSD_CURVNET_RESTORE();
 	ast_kclear(curthread);
 	NFSEXITCODE(0);
diff --git a/sys/fs/nfsserver/nfs_nfsdserv.c b/sys/fs/nfsserver/nfs_nfsdserv.c
index 01ecf0b09ebd..a4fa3a934090 100644
--- a/sys/fs/nfsserver/nfs_nfsdserv.c
+++ b/sys/fs/nfsserver/nfs_nfsdserv.c
@@ -4282,6 +4282,7 @@ nfsrvd_exchangeid(struct nfsrv_descript *nd, __unused int isdgram,
 	uint8_t *verf;
 	uint32_t sp4type, v41flags;
 	struct timespec verstime;
+	nfsopbit_t mustops, allowops;
 #ifdef INET
 	struct sockaddr_in *sin, *rin;
 #endif
@@ -4376,7 +4377,22 @@ nfsrvd_exchangeid(struct nfsrv_descript *nd, __unused int isdgram,
  	else
  		v41flags = NFSV4EXCH_USEPNFSMDS;
 	sp4type = fxdr_unsigned(uint32_t, *tl);
-	if (sp4type != NFSV4EXCH_SP4NONE) {
+	if (sp4type == NFSV4EXCH_SP4MACHCRED) {
+		if ((nd->nd_flag & (ND_GSSINTEGRITY | ND_GSSPRIVACY)) == 0 ||
+		    nd->nd_princlen == 0)
+			nd->nd_repstat = (NFSERR_AUTHERR | AUTH_TOOWEAK);
+		if (nd->nd_repstat == 0)
+			nd->nd_repstat = nfsrv_getopbits(nd, &mustops, NULL);
+		if (nd->nd_repstat == 0)
+			nd->nd_repstat = nfsrv_getopbits(nd, &allowops, NULL);
+		if (nd->nd_repstat != 0)
+			goto nfsmout;
+		NFSOPBIT_CLRNOTMUST(&mustops);
+		NFSSET_OPBIT(&clp->lc_mustops, &mustops);
+		NFSOPBIT_CLRNOTALLOWED(&allowops);
+		NFSSET_OPBIT(&clp->lc_allowops, &allowops);
+		clp->lc_flags |= LCL_MACHCRED;
+	} else if (sp4type != NFSV4EXCH_SP4NONE) {
 		nd->nd_repstat = NFSERR_NOTSUPP;
 		goto nfsmout;
 	}
@@ -4398,12 +4414,17 @@ nfsrvd_exchangeid(struct nfsrv_descript *nd, __unused int isdgram,
 	if (nd->nd_repstat == 0) {
 		if (confirm.lval[1] != 0)
 			v41flags |= NFSV4EXCH_CONFIRMEDR;
-		NFSM_BUILD(tl, uint32_t *, 2 * NFSX_HYPER + 3 * NFSX_UNSIGNED);
+		NFSM_BUILD(tl, uint32_t *, NFSX_HYPER + 3 * NFSX_UNSIGNED);
 		*tl++ = clientid.lval[0];			/* ClientID */
 		*tl++ = clientid.lval[1];
 		*tl++ = txdr_unsigned(confirm.lval[0]);		/* SequenceID */
 		*tl++ = txdr_unsigned(v41flags);		/* Exch flags */
-		*tl++ = txdr_unsigned(NFSV4EXCH_SP4NONE);	/* No SSV */
+		*tl = txdr_unsigned(sp4type);			/* No SSV */
+		if (sp4type == NFSV4EXCH_SP4MACHCRED) {
+			nfsrv_putopbit(nd, &mustops);
+			nfsrv_putopbit(nd, &allowops);
+		}
+		NFSM_BUILD(tl, uint32_t *, NFSX_HYPER);
 		txdr_hyper(nfsrv_owner_minor, tl);	/* Owner Minor */
 		if (nfsrv_owner_major[0] != 0)
 			s = nfsrv_owner_major;
@@ -4642,7 +4663,7 @@ nfsrvd_destroyclientid(struct nfsrv_descript *nd, __unused int isdgram,
 	NFSM_DISSECT(tl, uint32_t *, 2 * NFSX_UNSIGNED);
 	clientid.lval[0] = *tl++;
 	clientid.lval[1] = *tl;
-	nd->nd_repstat = nfsrv_destroyclient(clientid, p);
+	nd->nd_repstat = nfsrv_destroyclient(nd, clientid, p);
 nfsmout:
 	NFSEXITCODE2(error, nd);
 	return (error);
diff --git a/sys/fs/nfsserver/nfs_nfsdsocket.c b/sys/fs/nfsserver/nfs_nfsdsocket.c
index 6b64b3ff98eb..75e20b6c3f2f 100644
--- a/sys/fs/nfsserver/nfs_nfsdsocket.c
+++ b/sys/fs/nfsserver/nfs_nfsdsocket.c
@@ -42,6 +42,8 @@ __FBSDID("$FreeBSD$");
 
 #include <fs/nfs/nfsport.h>
 
+#include <security/mac/mac_framework.h>
+
 extern struct nfsrvfh nfs_pubfh;
 extern int nfs_pubfhset;
 extern struct nfsv4lock nfsv4rootfs_lock;
@@ -466,6 +468,8 @@ static int nfsv3to4op[NFS_V3NPROCS] = {
 static struct mtx nfsrvd_statmtx;
 MTX_SYSINIT(nfsst, &nfsrvd_statmtx, "NFSstat", MTX_DEF);
 
+static struct ucred *nfsrv_createrootcred(void);
+
 static void
 nfsrvd_statstart(int op, struct bintime *now)
 {
@@ -715,7 +719,7 @@ nfsrvd_compound(struct nfsrv_descript *nd, int isdgram, u_char *tag,
 	vnode_t vp, nvp, savevp;
 	struct nfsrvfh fh;
 	mount_t new_mp, temp_mp = NULL;
-	struct ucred *credanon;
+	struct ucred *credanon, *rootcred, *savecred;
 	struct nfsexstuff nes, vpnes, savevpnes;
 	fsid_t cur_fsid, save_fsid;
 	static u_int64_t compref = 0;
@@ -726,6 +730,7 @@ nfsrvd_compound(struct nfsrv_descript *nd, int isdgram, u_char *tag,
 	int bextpg, bextpgsiz;
 
 	p = curthread;
+	rootcred = savecred = NULL;
 
 	/* Check for and optionally clear the no space flags for DSs. */
 	nfsrv_checknospc();
@@ -967,6 +972,30 @@ nfsrvd_compound(struct nfsrv_descript *nd, int isdgram, u_char *tag,
 			retops++;
 			break;
 		}
+
+		/*
+		 * Check for the case of SP4_MACH_CRED and an operation in
+		 * the allow set.  For these operations, replace nd_cred with
+		 * root credentials so that the operation will not fail due
+		 * to credentials.
+		 * NB: ND_MACHCRED is set by Sequence when the ClientID
+		 * specifies LCL_MACHCRED and the RPC is being performed
+		 * via krb5i or krb5p using the machine principal.
+		 */
+		if ((nd->nd_flag & ND_MACHCRED) != 0) {
+			if (NFSISSET_OPBIT(&nd->nd_allowops, op)) {
+				/* Replace nd_cred with root creds. */
+				if (rootcred == NULL)
+					rootcred = nfsrv_createrootcred();
+				if (savecred == NULL)
+					savecred = nd->nd_cred;
+				nd->nd_cred = rootcred;
+			} else if (savecred != NULL) {
+				nd->nd_cred = savecred;
+				savecred = NULL;
+			}
+		}
+
 		if (nfsv4_opflag[op].savereply)
 			nd->nd_flag |= ND_SAVEREPLY;
 		switch (op) {
@@ -1379,9 +1408,33 @@ nfsmout:
 		vrele(vp);
 	if (savevp)
 		vrele(savevp);
+	if (savecred != NULL)
+		nd->nd_cred = savecred;
+	if (rootcred != NULL)
+		crfree(rootcred);
 	NFSLOCKV4ROOTMUTEX();
 	nfsv4_relref(&nfsv4rootfs_lock);
 	NFSUNLOCKV4ROOTMUTEX();
 
 	NFSEXITCODE2(0, nd);
 }
+
+/* Create a credential for "root". */
+static struct ucred *
+nfsrv_createrootcred(void)
+{
+	struct ucred *cr;
+	gid_t grp;
+
+	cr = crget();
+	cr->cr_uid = cr->cr_ruid = cr->cr_svuid = UID_ROOT;
+	grp = GID_WHEEL;
+	crsetgroups(cr, 1, &grp);
+	cr->cr_rgid = cr->cr_svgid = cr->cr_groups[0];
+	cr->cr_prison = curthread->td_ucred->cr_prison;
+	prison_hold(cr->cr_prison);
+#ifdef MAC
+	mac_cred_associate_nfsd(cr);
+#endif
+	return (cr);
+}
diff --git a/sys/fs/nfsserver/nfs_nfsdstate.c b/sys/fs/nfsserver/nfs_nfsdstate.c
index 4a552298671e..caadfd7c9422 100644
--- a/sys/fs/nfsserver/nfs_nfsdstate.c
+++ b/sys/fs/nfsserver/nfs_nfsdstate.c
@@ -184,7 +184,7 @@ static int nfsrv_delegconflict(struct nfsstate *stp, int *haslockp,
     NFSPROC_T *p, vnode_t vp);
 static int nfsrv_cleandeleg(vnode_t vp, struct nfslockfile *lfp,
     struct nfsclient *clp, int *haslockp, NFSPROC_T *p);
-static int nfsrv_notsamecredname(struct nfsrv_descript *nd,
+static int nfsrv_notsamecredname(int op, struct nfsrv_descript *nd,
     struct nfsclient *clp);
 static time_t nfsrv_leaseexpiry(void);
 static void nfsrv_delaydelegtimeout(struct nfsstate *stp);
@@ -205,7 +205,8 @@ static void nfsrv_locallock_commit(struct nfslockfile *lfp, int flags,
 static void nfsrv_locklf(struct nfslockfile *lfp);
 static void nfsrv_unlocklf(struct nfslockfile *lfp);
 static struct nfsdsession *nfsrv_findsession(uint8_t *sessionid);
-static int nfsrv_freesession(struct nfsdsession *sep, uint8_t *sessionid);
+static int nfsrv_freesession(struct nfsrv_descript *nd, struct nfsdsession *sep,
+    uint8_t *sessionid);
 static int nfsv4_setcbsequence(struct nfsrv_descript *nd, struct nfsclient *clp,
     int dont_replycache, struct nfsdsession **sepp, int *slotposp);
 static int nfsv4_getcbsession(struct nfsclient *clp, struct nfsdsession **sepp);
@@ -239,6 +240,8 @@ static int nfsrv_createdsfile(vnode_t vp, fhandle_t *fhp, struct pnfsdsfile *pf,
     vnode_t dvp, struct nfsdevice *ds, struct ucred *cred, NFSPROC_T *p,
     vnode_t *tvpp);
 static struct nfsdevice *nfsrv_findmirroredds(struct nfsmount *nmp);
+static int nfsrv_checkmachcred(int op, struct nfsrv_descript *nd,
+    struct nfsclient *clp);
 
 /*
  * Scan the client list for a match and either return the current one,
@@ -378,7 +381,7 @@ nfsrv_setclient(struct nfsrv_descript *nd, struct nfsclient **new_clpp,
 	/*
 	 * Now, handle the cases where the id is already issued.
 	 */
-	if (nfsrv_notsamecredname(nd, clp)) {
+	if (nfsrv_notsamecredname(NFSV4OP_EXCHANGEID, nd, clp)) {
 	    /*
 	     * Check to see if there is expired state that should go away.
 	     */
@@ -447,7 +450,7 @@ nfsrv_setclient(struct nfsrv_descript *nd, struct nfsclient **new_clpp,
 
 		/* Get rid of all sessions on this clientid. */
 		LIST_FOREACH_SAFE(sep, &clp->lc_session, sess_list, nsep) {
-			ret = nfsrv_freesession(sep, NULL);
+			ret = nfsrv_freesession(NULL, sep, NULL);
 			if (ret != 0)
 				printf("nfsrv_setclient: verifier changed free"
 				    " session failed=%d\n", ret);
@@ -706,7 +709,8 @@ nfsrv_getclient(nfsquad_t clientid, int opflags, struct nfsclient **clpp,
 		} else if ((nd->nd_flag & ND_NFSV41) == 0 &&
 		     clp->lc_confirm.qval != confirm.qval)
 			error = NFSERR_STALECLIENTID;
-		if (error == 0 && nfsrv_notsamecredname(nd, clp))
+		if (error == 0 && nfsrv_notsamecredname(NFSV4OP_CREATESESSION,
+		    nd, clp))
 			error = NFSERR_CLIDINUSE;
 
 		if (!error) {
@@ -779,7 +783,7 @@ nfsrv_getclient(nfsquad_t clientid, int opflags, struct nfsclient **clpp,
 	 * If called by the Renew Op, we must check the principal.
 	 */
 	if (!error && (opflags & CLOPS_RENEWOP)) {
-	    if (nfsrv_notsamecredname(nd, clp)) {
+	    if (nfsrv_notsamecredname(0, nd, clp)) {
 		doneok = 0;
 		for (i = 0; i < nfsrv_statehashsize && doneok == 0; i++) {
 		    LIST_FOREACH(stp, &clp->lc_stateid[i], ls_hash) {
@@ -819,7 +823,7 @@ out:
  * Perform the NFSv4.1 destroy clientid.
  */
 int
-nfsrv_destroyclient(nfsquad_t clientid, NFSPROC_T *p)
+nfsrv_destroyclient(struct nfsrv_descript *nd, nfsquad_t clientid, NFSPROC_T *p)
 {
 	struct nfsclient *clp;
 	struct nfsclienthashhead *hp;
@@ -852,6 +856,15 @@ nfsrv_destroyclient(nfsquad_t clientid, NFSPROC_T *p)
 		goto out;
 	}
 
+	/* Check for the SP4_MACH_CRED case. */
+	error = nfsrv_checkmachcred(NFSV4OP_DESTROYCLIENTID, nd, clp);
+	if (error != 0) {
+		NFSLOCKV4ROOTMUTEX();
+		nfsv4_unlock(&nfsv4rootfs_lock, 1);
+		NFSUNLOCKV4ROOTMUTEX();
+		goto out;
+	}
+
 	/*
 	 * Free up all layouts on the clientid.  Should the client return the
 	 * layouts?
@@ -1374,7 +1387,7 @@ nfsrv_cleanclient(struct nfsclient *clp, NFSPROC_T *p)
 		nfsrv_freeopenowner(stp, 1, p);
 	if ((clp->lc_flags & LCL_ADMINREVOKED) == 0)
 		LIST_FOREACH_SAFE(sep, &clp->lc_session, sess_list, nsep)
-			(void)nfsrv_freesession(sep, NULL);
+			(void)nfsrv_freesession(NULL, sep, NULL);
 }
 
 /*
@@ -4605,7 +4618,7 @@ nfsrv_docallback(struct nfsclient *clp, int procnum, nfsv4stateid_t *stateidp,
 			if (procnum != NFSV4PROC_CBNULL)
 				nfsv4_freeslot(&sep->sess_cbsess, slotpos,
 				    true);
-			nfsrv_freesession(sep, NULL);
+			nfsrv_freesession(NULL, sep, NULL);
 		} else if (nd->nd_procnum == NFSV4PROC_CBNULL)
 			error = newnfs_connect(NULL, &clp->lc_req, cred,
 			    NULL, 1, dotls, &clp->lc_req.nr_client);
@@ -4654,7 +4667,7 @@ nfsrv_docallback(struct nfsclient *clp, int procnum, nfsv4stateid_t *stateidp,
 				nfsv4_freeslot(&sep->sess_cbsess, slotpos,
 				    true);
 			}
-			nfsrv_freesession(sep, NULL);
+			nfsrv_freesession(NULL, sep, NULL);
 		} else
 			error = newnfs_request(nd, NULL, clp, &clp->lc_req,
 			    NULL, NULL, cred, clp->lc_program,
@@ -5877,12 +5890,18 @@ nfsrv_throwawayopens(NFSPROC_T *p)
 /*
  * This function checks to see if the credentials are the same.
  * The check for same credentials is needed for state management operations
- * for NFSv4.0 where 1 is returned if not same, 0 is returned otherwise.
+ * for NFSv4.0 or NFSv4.1/4.2 when SP4_MACH_CRED is configured via
+ * ExchangeID.
+ * Returns 1 for not same, 0 otherwise.
  */
 static int
-nfsrv_notsamecredname(struct nfsrv_descript *nd, struct nfsclient *clp)
+nfsrv_notsamecredname(int op, struct nfsrv_descript *nd, struct nfsclient *clp)
 {
 
+	/* Check for the SP4_MACH_CRED case. */
+	if (op != 0 && nfsrv_checkmachcred(op, nd, clp) != 0)
+		return (1);
+
 	/* For NFSv4.1/4.2, SP4_NONE always allows this. */
 	if ((nd->nd_flag & ND_NFSV41) != 0)
 		return (0);
@@ -6301,6 +6320,16 @@ nfsrv_checksequence(struct nfsrv_descript *nd, uint32_t sequenceid,
 	nd->nd_clientid.qval = sep->sess_clp->lc_clientid.qval;
 	nd->nd_flag |= ND_IMPLIEDCLID;
 
+	/* Handle the SP4_MECH_CRED case for NFSv4.1/4.2. */
+	if ((sep->sess_clp->lc_flags & LCL_MACHCRED) != 0 &&
+	    (nd->nd_flag & (ND_GSSINTEGRITY | ND_GSSPRIVACY)) != 0 &&
+	    nd->nd_princlen == sep->sess_clp->lc_namelen &&
+	    !NFSBCMP(sep->sess_clp->lc_name, nd->nd_principal,
+	    nd->nd_princlen)) {
+		nd->nd_flag |= ND_MACHCRED;
+		NFSSET_OPBIT(&nd->nd_allowops, &sep->sess_clp->lc_allowops);
+	}
+
 	/* Save maximum request and reply sizes. */
 	nd->nd_maxreq = sep->sess_maxreq;
 	nd->nd_maxresp = sep->sess_maxresp;
@@ -6458,7 +6487,7 @@ nfsrv_destroysession(struct nfsrv_descript *nd, uint8_t *sessionid)
 	} while (igotlock == 0);
 	NFSUNLOCKV4ROOTMUTEX();
 
-	error = nfsrv_freesession(NULL, sessionid);
+	error = nfsrv_freesession(nd, NULL, sessionid);
 	if (error == 0 && samesess != 0)
 		nd->nd_flag &= ~ND_HASSEQUENCE;
 
@@ -6491,6 +6520,9 @@ nfsrv_bindconnsess(struct nfsrv_descript *nd, uint8_t *sessionid, int *foreaftp)
 	sep = nfsrv_findsession(sessionid);
 	if (sep != NULL) {
 		clp = sep->sess_clp;
+		error = nfsrv_checkmachcred(NFSV4OP_BINDCONNTOSESS, nd, clp);
+		if (error != 0)
+			goto out;
 		if (*foreaftp == NFSCDFC4_BACK ||
 		    *foreaftp == NFSCDFC4_BACK_OR_BOTH ||
 		    *foreaftp == NFSCDFC4_FORE_OR_BOTH) {
@@ -6538,6 +6570,7 @@ nfsrv_bindconnsess(struct nfsrv_descript *nd, uint8_t *sessionid, int *foreaftp)
 		}
 	} else
 		error = NFSERR_BADSESSION;
+out:
 	NFSUNLOCKSESSION(shp);
 	NFSUNLOCKSTATE();
 	if (savxprt != NULL)
@@ -6549,7 +6582,8 @@ nfsrv_bindconnsess(struct nfsrv_descript *nd, uint8_t *sessionid, int *foreaftp)
  * Free up a session structure.
  */
 static int
-nfsrv_freesession(struct nfsdsession *sep, uint8_t *sessionid)
+nfsrv_freesession(struct nfsrv_descript *nd, struct nfsdsession *sep,
+    uint8_t *sessionid)
 {
 	struct nfssessionhash *shp;
 	int i;
@@ -6564,6 +6598,14 @@ nfsrv_freesession(struct nfsdsession *sep, uint8_t *sessionid)
 		NFSLOCKSESSION(shp);
 	}
 	if (sep != NULL) {
+		/* Check for the SP4_MACH_CRED case. */
+		if (nd != NULL && nfsrv_checkmachcred(NFSV4OP_DESTROYSESSION,
+		    nd, sep->sess_clp) != 0) {
+			NFSUNLOCKSESSION(shp);
+			NFSUNLOCKSTATE();
+			return (NFSERR_AUTHERR | AUTH_TOOWEAK);
+		}
+
 		sep->sess_refcnt--;
 		if (sep->sess_refcnt > 0) {
 			NFSUNLOCKSESSION(shp);
@@ -8883,3 +8925,23 @@ nfsrv_marknospc(char *devid, bool setit)
 		NFSUNLOCKLAYOUT(lhyp);
 	}
 }
+
+/*
+ * Check to see if SP4_MACH_CRED is in use and, if it is, check that the
+ * correct machine credential is being used.
+ */
+static int
+nfsrv_checkmachcred(int op, struct nfsrv_descript *nd, struct nfsclient *clp)
+{
+
+	if ((clp->lc_flags & LCL_MACHCRED) == 0 ||
+	    !NFSISSET_OPBIT(&clp->lc_mustops, op))
+		return (0);
+	KASSERT((nd->nd_flag & ND_NFSV41) != 0,
+	    ("nfsrv_checkmachcred: MachCred for NFSv4.0"));
+	if ((nd->nd_flag & (ND_GSSINTEGRITY | ND_GSSPRIVACY)) != 0 &&
+	    nd->nd_princlen == clp->lc_namelen &&
+	    !NFSBCMP(nd->nd_principal, clp->lc_name, nd->nd_princlen))
+		return (0);
+	return (NFSERR_AUTHERR | AUTH_TOOWEAK);
+}