nfsd: Add support for the SP4_MACH_CRED case in ExchangeID

Commit f4179ad46f 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
This commit is contained in:
Rick Macklem 2023-04-07 12:49:23 -07:00
parent 66fbc19fbd
commit ff2f1f691c
6 changed files with 215 additions and 21 deletions

View File

@ -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 *);

View File

@ -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# */

View File

@ -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);

View File

@ -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);

View File

@ -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 @@ nfsrvd_compound(struct nfsrv_descript *nd, int isdgram, u_char *tag,
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);
}

View File

@ -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 @@ nfsrv_getclient(nfsquad_t clientid, int opflags, struct nfsclient **clpp,
* 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);
}