nfscl: Fix handling of gssd upcalls for the NFS client

Without this patch, all upcalls to the gssd daemon are
done in vnet0 (outside of any vnet jail).  This does
not work well, because a user principal's credential
cache can be within the jail (/tmp/krb5cc_NNN in the
jail's namespace).

This patch modifies the client so that RPCs done
from within vnet jails does an upcall to a gssd
daemon running within the vnet jail.  It required
that the cache of uid->credential shorthands in
the rpcsec_gss be vnet'd.

The situation is still less than ideal and sec=krb5[ip]
mounts that are visible within vnet jails is still
not something I would recommend, but it can work ok
with this patch.

Vnet'ng the NFS client so that mounts can be done
within vnet jails is probably more useful, but that
will require additional work.

Discussed with:	glebius
MFC after:	1 month
This commit is contained in:
Rick Macklem
2026-05-02 12:36:00 -07:00
parent 72b1aae09b
commit a6e527f893
8 changed files with 107 additions and 60 deletions
+21 -2
View File
@@ -561,7 +561,9 @@ newnfs_disconnect(struct nfsmount *nmp, struct nfssockreq *nrp)
}
}
mtx_unlock(&nrp->nr_mtx);
CURVNET_SET_QUIET(CRED_TO_VNET(nrp->nr_cred));
rpc_gss_secpurge_call(client);
CURVNET_RESTORE();
CLNT_CLOSE(client);
CLNT_RELEASE(client);
if (nmp != NULL && nmp->nm_aconnect > 0) {
@@ -685,7 +687,7 @@ newnfs_request(struct nfsrv_descript *nd, struct nfsmount *nmp,
struct nfsreq *rep = NULL;
char *srv_principal = NULL, *clnt_principal = NULL;
sigset_t oldset;
struct ucred *authcred;
struct ucred *authcred, *savcred;
struct nfsclsession *sep;
uint8_t sessionid[NFSX_V4SESSIONID];
bool nextconn_set;
@@ -832,6 +834,11 @@ newnfs_request(struct nfsrv_descript *nd, struct nfsmount *nmp,
((nmp->nm_tprintf_delay)-(nmp->nm_tprintf_initial_delay));
}
/*
* For Kerberos, the upcall needs to be done to the gssd daemon
* running in the correct vnet.
*/
CURVNET_SET_QUIET(CRED_TO_VNET(authcred));
if (nd->nd_procnum == NFSPROC_NULL)
auth = authnone_create();
else if (usegssname) {
@@ -849,8 +856,9 @@ newnfs_request(struct nfsrv_descript *nd, struct nfsmount *nmp,
} else
auth = nfs_getauth(nrp, secflavour, NULL,
srv_principal, NULL, authcred);
crfree(authcred);
CURVNET_RESTORE();
if (auth == NULL) {
crfree(authcred);
m_freem(nd->nd_mreq);
if (set_sigset)
newnfs_restore_sigmask(td, &oldset);
@@ -967,6 +975,13 @@ newnfs_request(struct nfsrv_descript *nd, struct nfsmount *nmp,
}
}
/*
* In case CLNT_CALL_MBUF()/clnt_bck_call() does an AUTH_REFRESH(),
* the thread's credentials need to be set to authcred, so that the
* correct vnet will be set.
*/
savcred = curthread->td_ucred;
curthread->td_ucred = authcred;
nd->nd_mrep = NULL;
if (clp != NULL && sep != NULL)
stat = clnt_bck_call(nrp->nr_client, &ext, procnum,
@@ -988,6 +1003,7 @@ newnfs_request(struct nfsrv_descript *nd, struct nfsmount *nmp,
stat = CLNT_CALL_MBUF(nrp->nr_client, &ext, procnum,
nd->nd_mreq, &nd->nd_mrep, timo);
NFSCL_DEBUG(2, "clnt call=%d\n", stat);
curthread->td_ucred = savcred;
if (rep != NULL) {
/*
@@ -1069,6 +1085,7 @@ newnfs_request(struct nfsrv_descript *nd, struct nfsmount *nmp,
error = EACCES;
}
if (error) {
crfree(authcred);
m_freem(nd->nd_mreq);
if (usegssname == 0)
AUTH_DESTROY(auth);
@@ -1429,6 +1446,7 @@ newnfs_request(struct nfsrv_descript *nd, struct nfsmount *nmp,
}
}
out:
crfree(authcred);
#ifdef KDTRACE_HOOKS
if (nmp != NULL && dtrace_nfscl_nfs234_done_probe != NULL) {
@@ -1460,6 +1478,7 @@ newnfs_request(struct nfsrv_descript *nd, struct nfsmount *nmp,
newnfs_restore_sigmask(td, &oldset);
return (0);
nfsmout:
crfree(authcred);
m_freem(nd->nd_mrep);
m_freem(nd->nd_mreq);
if (usegssname == 0)
+2 -1
View File
@@ -4167,7 +4167,7 @@ nfsrv_nfsuserdport(struct nfsuserd_args *nargs, NFSPROC_T *p)
rp->nr_sotype = SOCK_DGRAM;
rp->nr_soproto = IPPROTO_UDP;
rp->nr_lock = (NFSR_RESERVEDPORT | NFSR_LOCALHOST);
rp->nr_cred = NULL;
rp->nr_cred = crhold(curthread->td_ucred);
rp->nr_prog = RPCPROG_NFSUSERD;
error = 0;
switch (nargs->nuserd_family) {
@@ -4235,6 +4235,7 @@ nfsrv_nfsuserddelport(void)
NFSUNLOCKNAMEID();
newnfs_disconnect(NULL, &NFSD_VNET(nfsrv_nfsuserdsock));
free(NFSD_VNET(nfsrv_nfsuserdsock).nr_nam, M_SONAME);
crfree(VNET(nfsrv_nfsuserdsock).nr_cred);
NFSLOCKNAMEID();
NFSD_VNET(nfsrv_nfsuserd) = NOTRUNNING;
NFSUNLOCKNAMEID();
+6 -2
View File
@@ -4269,7 +4269,7 @@ nfsrvd_setclientid(struct nfsrv_descript *nd, __unused int isdgram,
/* Allocated large enough for an AF_INET or AF_INET6 socket. */
clp->lc_req.nr_nam = malloc(sizeof(struct sockaddr_in6), M_SONAME,
M_WAITOK | M_ZERO);
clp->lc_req.nr_cred = NULL;
clp->lc_req.nr_cred = crhold(nd->nd_cred);
NFSBCOPY(verf, clp->lc_verf, NFSX_VERF);
clp->lc_idlen = idlen;
error = nfsrv_mtostr(nd, clp->lc_id, idlen);
@@ -4359,6 +4359,7 @@ nfsrvd_setclientid(struct nfsrv_descript *nd, __unused int isdgram,
if (clp) {
free(clp->lc_req.nr_nam, M_SONAME);
NFSFREEMUTEX(&clp->lc_req.nr_mtx);
crfree(clp->lc_req.nr_cred);
free(clp->lc_stateid, M_NFSDCLIENT);
free(clp, M_NFSDCLIENT);
}
@@ -4377,6 +4378,7 @@ nfsrvd_setclientid(struct nfsrv_descript *nd, __unused int isdgram,
if (clp) {
free(clp->lc_req.nr_nam, M_SONAME);
NFSFREEMUTEX(&clp->lc_req.nr_mtx);
crfree(clp->lc_req.nr_cred);
free(clp->lc_stateid, M_NFSDCLIENT);
free(clp, M_NFSDCLIENT);
}
@@ -4634,7 +4636,7 @@ nfsrvd_exchangeid(struct nfsrv_descript *nd, __unused int isdgram,
break;
#endif
}
clp->lc_req.nr_cred = NULL;
clp->lc_req.nr_cred = crhold(nd->nd_cred);
NFSBCOPY(verf, clp->lc_verf, NFSX_VERF);
clp->lc_idlen = idlen;
error = nfsrv_mtostr(nd, clp->lc_id, idlen);
@@ -4707,6 +4709,7 @@ nfsrvd_exchangeid(struct nfsrv_descript *nd, __unused int isdgram,
if (clp != NULL) {
free(clp->lc_req.nr_nam, M_SONAME);
NFSFREEMUTEX(&clp->lc_req.nr_mtx);
crfree(clp->lc_req.nr_cred);
free(clp->lc_stateid, M_NFSDCLIENT);
free(clp, M_NFSDCLIENT);
}
@@ -4750,6 +4753,7 @@ nfsrvd_exchangeid(struct nfsrv_descript *nd, __unused int isdgram,
if (clp != NULL) {
free(clp->lc_req.nr_nam, M_SONAME);
NFSFREEMUTEX(&clp->lc_req.nr_mtx);
crfree(clp->lc_req.nr_cred);
free(clp->lc_stateid, M_NFSDCLIENT);
free(clp, M_NFSDCLIENT);
}
+1
View File
@@ -1504,6 +1504,7 @@ nfsrv_zapclient(struct nfsclient *clp, NFSPROC_T *p)
newnfs_disconnect(NULL, &clp->lc_req);
free(clp->lc_req.nr_nam, M_SONAME);
NFSFREEMUTEX(&clp->lc_req.nr_mtx);
crfree(clp->lc_req.nr_cred);
free(clp->lc_stateid, M_NFSDCLIENT);
free(clp, M_NFSDCLIENT);
NFSLOCKSTATE();
+6 -1
View File
@@ -58,6 +58,7 @@
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/jail.h>
#include <sys/ktls.h>
#include <sys/lock.h>
#include <sys/malloc.h>
@@ -440,15 +441,19 @@ clnt_bck_call(
* If unsuccessful AND error is an authentication error
* then refresh credentials and try again, else break
*/
else if (stat == RPC_AUTHERROR)
else if (stat == RPC_AUTHERROR) {
/* maybe our credentials need to be refreshed ... */
CURVNET_SET_QUIET(TD_TO_VNET(curthread));
if (nrefreshes > 0 && AUTH_REFRESH(auth, &reply_msg)) {
CURVNET_RESTORE();
nrefreshes--;
XDR_DESTROY(&xdrs);
mtx_lock(&ct->ct_lock);
goto call_again;
}
CURVNET_RESTORE();
/* end of unsuccessful completion */
}
/* end of valid reply message */
} else
errp->re_status = stat = RPC_CANTDECODERES;
+6 -1
View File
@@ -39,6 +39,7 @@
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/jail.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
@@ -738,15 +739,19 @@ clnt_dg_call(
* If unsuccessful AND error is an authentication error
* then refresh credentials and try again, else break
*/
else if (stat == RPC_AUTHERROR)
else if (stat == RPC_AUTHERROR) {
/* maybe our credentials need to be refreshed ... */
CURVNET_SET_QUIET(TD_TO_VNET(curthread));
if (nrefreshes > 0 &&
AUTH_REFRESH(auth, &reply_msg)) {
CURVNET_RESTORE();
nrefreshes--;
XDR_DESTROY(&xdrs);
mtx_lock(&cs->cs_lock);
goto call_again;
}
CURVNET_RESTORE();
}
/* end of unsuccessful completion */
} /* end of valid reply message */
else {
+6 -1
View File
@@ -54,6 +54,7 @@
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/jail.h>
#include <sys/kernel.h>
#include <sys/kthread.h>
#include <sys/ktls.h>
@@ -559,15 +560,19 @@ clnt_vc_call(
* If unsuccessful AND error is an authentication error
* then refresh credentials and try again, else break
*/
else if (stat == RPC_AUTHERROR)
else if (stat == RPC_AUTHERROR) {
/* maybe our credentials need to be refreshed ... */
CURVNET_SET_QUIET(TD_TO_VNET(curthread));
if (nrefreshes > 0 &&
AUTH_REFRESH(auth, &reply_msg)) {
CURVNET_RESTORE();
nrefreshes--;
XDR_DESTROY(&xdrs);
mtx_lock(&ct->ct_lock);
goto call_again;
}
CURVNET_RESTORE();
}
/* end of unsuccessful completion */
} /* end of valid reply message */
else {
+59 -52
View File
@@ -150,26 +150,42 @@ static struct timeval AUTH_TIMEOUT = { 25, 0 };
#define RPC_GSS_HASH_SIZE 11
#define RPC_GSS_MAX 256
static struct rpc_gss_data_list rpc_gss_cache[RPC_GSS_HASH_SIZE];
static struct rpc_gss_data_list rpc_gss_all;
static struct sx rpc_gss_lock;
static int rpc_gss_count;
VNET_DEFINE_STATIC(struct rpc_gss_data_list *, rpc_gss_cache);
VNET_DEFINE_STATIC(struct rpc_gss_data_list, rpc_gss_all);
VNET_DEFINE_STATIC(struct sx, rpc_gss_lock);
VNET_DEFINE_STATIC(int, rpc_gss_count);
static AUTH *rpc_gss_seccreate_int(CLIENT *, struct ucred *, const char *,
const char *, gss_OID, rpc_gss_service_t, u_int, rpc_gss_options_req_t *,
rpc_gss_options_ret_t *);
static void
rpc_gss_hashinit(void *dummy)
rpc_gss_hashinit(void *dummy __unused)
{
int i;
VNET(rpc_gss_cache) = mem_alloc(sizeof(struct rpc_gss_data_list) *
RPC_GSS_HASH_SIZE);
for (i = 0; i < RPC_GSS_HASH_SIZE; i++)
TAILQ_INIT(&rpc_gss_cache[i]);
TAILQ_INIT(&rpc_gss_all);
sx_init(&rpc_gss_lock, "rpc_gss_lock");
TAILQ_INIT(&VNET(rpc_gss_cache)[i]);
TAILQ_INIT(&VNET(rpc_gss_all));
sx_init(&VNET(rpc_gss_lock), "rpc_gss_lock");
}
SYSINIT(rpc_gss_hashinit, SI_SUB_KMEM, SI_ORDER_ANY, rpc_gss_hashinit, NULL);
VNET_SYSINIT(rpc_gss_hashinit, SI_SUB_VNET_DONE, SI_ORDER_ANY,
rpc_gss_hashinit, NULL);
static void
rpc_gss_hashinit_cleanup(void *dummy __unused)
{
rpc_gss_secpurge(NULL);
mem_free(VNET(rpc_gss_cache), sizeof(struct rpc_gss_data_list) *
RPC_GSS_HASH_SIZE);
sx_destroy(&VNET(rpc_gss_lock));
}
VNET_SYSUNINIT(rpc_gss_hashinit_cleanup, SI_SUB_VNET_DONE, SI_ORDER_ANY,
rpc_gss_hashinit_cleanup, NULL);
static uint32_t
rpc_gss_hash(const char *principal, gss_OID mech,
@@ -198,15 +214,16 @@ rpc_gss_secfind(CLIENT *clnt, struct ucred *cred, const char *principal,
struct rpc_gss_data *gd, *tgd;
rpc_gss_options_ret_t options;
if (rpc_gss_count > RPC_GSS_MAX) {
while (rpc_gss_count > RPC_GSS_MAX) {
sx_xlock(&rpc_gss_lock);
tgd = TAILQ_FIRST(&rpc_gss_all);
CURVNET_ASSERT_SET();
if (VNET(rpc_gss_count) > RPC_GSS_MAX) {
while (VNET(rpc_gss_count) > RPC_GSS_MAX) {
sx_xlock(&VNET(rpc_gss_lock));
tgd = TAILQ_FIRST(&VNET(rpc_gss_all));
th = tgd->gd_hash;
TAILQ_REMOVE(&rpc_gss_cache[th], tgd, gd_link);
TAILQ_REMOVE(&rpc_gss_all, tgd, gd_alllink);
rpc_gss_count--;
sx_xunlock(&rpc_gss_lock);
TAILQ_REMOVE(&VNET(rpc_gss_cache)[th], tgd, gd_link);
TAILQ_REMOVE(&VNET(rpc_gss_all), tgd, gd_alllink);
VNET(rpc_gss_count)--;
sx_xunlock(&VNET(rpc_gss_lock));
AUTH_DESTROY(tgd->gd_auth);
}
}
@@ -217,23 +234,24 @@ rpc_gss_secfind(CLIENT *clnt, struct ucred *cred, const char *principal,
h = rpc_gss_hash(principal, mech_oid, cred, service);
again:
sx_slock(&rpc_gss_lock);
TAILQ_FOREACH(gd, &rpc_gss_cache[h], gd_link) {
sx_slock(&VNET(rpc_gss_lock));
TAILQ_FOREACH(gd, &VNET(rpc_gss_cache)[h], gd_link) {
if (gd->gd_ucred->cr_uid == cred->cr_uid
&& !strcmp(gd->gd_principal, principal)
&& gd->gd_mech == mech_oid
&& gd->gd_cred.gc_svc == service) {
refcount_acquire(&gd->gd_refs);
if (sx_try_upgrade(&rpc_gss_lock)) {
if (sx_try_upgrade(&VNET(rpc_gss_lock))) {
/*
* Keep rpc_gss_all LRU sorted.
*/
TAILQ_REMOVE(&rpc_gss_all, gd, gd_alllink);
TAILQ_INSERT_TAIL(&rpc_gss_all, gd,
TAILQ_REMOVE(&VNET(rpc_gss_all), gd,
gd_alllink);
sx_xunlock(&rpc_gss_lock);
TAILQ_INSERT_TAIL(&VNET(rpc_gss_all), gd,
gd_alllink);
sx_xunlock(&VNET(rpc_gss_lock));
} else {
sx_sunlock(&rpc_gss_lock);
sx_sunlock(&VNET(rpc_gss_lock));
}
/*
@@ -249,7 +267,7 @@ rpc_gss_secfind(CLIENT *clnt, struct ucred *cred, const char *principal,
return (gd->gd_auth);
}
}
sx_sunlock(&rpc_gss_lock);
sx_sunlock(&VNET(rpc_gss_lock));
/*
* We missed in the cache - create a new association.
@@ -262,8 +280,8 @@ rpc_gss_secfind(CLIENT *clnt, struct ucred *cred, const char *principal,
gd = AUTH_PRIVATE(auth);
gd->gd_hash = h;
sx_xlock(&rpc_gss_lock);
TAILQ_FOREACH(tgd, &rpc_gss_cache[h], gd_link) {
sx_xlock(&VNET(rpc_gss_lock));
TAILQ_FOREACH(tgd, &VNET(rpc_gss_cache)[h], gd_link) {
if (tgd->gd_ucred->cr_uid == cred->cr_uid
&& !strcmp(tgd->gd_principal, principal)
&& tgd->gd_mech == mech_oid
@@ -272,17 +290,17 @@ rpc_gss_secfind(CLIENT *clnt, struct ucred *cred, const char *principal,
* We lost a race to create the AUTH that
* matches this cred.
*/
sx_xunlock(&rpc_gss_lock);
sx_xunlock(&VNET(rpc_gss_lock));
AUTH_DESTROY(auth);
goto again;
}
}
rpc_gss_count++;
TAILQ_INSERT_TAIL(&rpc_gss_cache[h], gd, gd_link);
TAILQ_INSERT_TAIL(&rpc_gss_all, gd, gd_alllink);
VNET(rpc_gss_count)++;
TAILQ_INSERT_TAIL(&VNET(rpc_gss_cache)[h], gd, gd_link);
TAILQ_INSERT_TAIL(&VNET(rpc_gss_all), gd, gd_alllink);
refcount_acquire(&gd->gd_refs); /* one for the cache, one for user */
sx_xunlock(&rpc_gss_lock);
sx_xunlock(&VNET(rpc_gss_lock));
return (auth);
}
@@ -293,14 +311,15 @@ rpc_gss_secpurge(CLIENT *clnt)
uint32_t h;
struct rpc_gss_data *gd, *tgd;
TAILQ_FOREACH_SAFE(gd, &rpc_gss_all, gd_alllink, tgd) {
if (gd->gd_clnt == clnt) {
sx_xlock(&rpc_gss_lock);
CURVNET_ASSERT_SET();
TAILQ_FOREACH_SAFE(gd, &VNET(rpc_gss_all), gd_alllink, tgd) {
if (clnt == NULL || gd->gd_clnt == clnt) {
sx_xlock(&VNET(rpc_gss_lock));
h = gd->gd_hash;
TAILQ_REMOVE(&rpc_gss_cache[h], gd, gd_link);
TAILQ_REMOVE(&rpc_gss_all, gd, gd_alllink);
rpc_gss_count--;
sx_xunlock(&rpc_gss_lock);
TAILQ_REMOVE(&VNET(rpc_gss_cache)[h], gd, gd_link);
TAILQ_REMOVE(&VNET(rpc_gss_all), gd, gd_alllink);
VNET(rpc_gss_count)--;
sx_xunlock(&VNET(rpc_gss_lock));
AUTH_DESTROY(gd->gd_auth);
}
}
@@ -748,6 +767,7 @@ rpc_gss_init(AUTH *auth, rpc_gss_options_ret_t *options_ret)
gss_OID_set mechlist;
static enum krb_imp my_krb_imp = KRBIMP_UNKNOWN;
CURVNET_ASSERT_SET();
rpc_gss_log_debug("in rpc_gss_refresh()");
gd = AUTH_PRIVATE(auth);
@@ -773,17 +793,6 @@ rpc_gss_init(AUTH *auth, rpc_gss_options_ret_t *options_ret)
gd->gd_cred.gc_proc = RPCSEC_GSS_INIT;
gd->gd_cred.gc_seq = 0;
/*
* XXX Threads from inside jails can get here via calls
* to clnt_vc_call()->AUTH_REFRESH()->rpc_gss_refresh()
* but the NFS mount is always done outside of the
* jails in vnet0. Since the thread credentials won't
* necessarily have cr_prison == vnet0 and this function
* has no access to the socket, using vnet0 seems the
* only option. This is broken if NFS mounts are enabled
* within vnet prisons.
*/
CURVNET_SET_QUIET(vnet0);
/*
* For KerberosV, if there is a client principal name, that implies
* that this is a host based initiator credential in the default
@@ -1030,14 +1039,12 @@ rpc_gss_init(AUTH *auth, rpc_gss_options_ret_t *options_ret)
gss_delete_sec_context(&min_stat, &gd->gd_ctx,
GSS_C_NO_BUFFER);
}
CURVNET_RESTORE();
mtx_lock(&gd->gd_lock);
gd->gd_state = RPCSEC_GSS_START;
wakeup(gd);
mtx_unlock(&gd->gd_lock);
return (FALSE);
}
CURVNET_RESTORE();
mtx_lock(&gd->gd_lock);
gd->gd_state = RPCSEC_GSS_ESTABLISHED;