/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* for authdes_create() */ #include #include #define MAXCLIENTS 16 /* * Currently there is no maximum length defined withing the gss * specification. Because of security issues the maximum gss * authentication length is checked to be under the MAXAUTHLEN * defined below. The value was chosen because it will be a safe * maximum value for some time. Currently lengths are generally * under the 16 byte length */ #define MINAUTHLEN 1 /* minimum gss authentication length */ #define MAXAUTHLEN 65535 /* maximum gss authentication length */ static int clnt_authdes_cachesz = 64; static uint_t authdes_win = 5*60; /* 5 minutes -- should be mount option */ struct kmem_cache *authkern_cache; struct kmem_cache *authnone_cache; struct kmem_cache *authloopback_cache; static struct desauthent { struct sec_data *da_data; uid_t da_uid; zoneid_t da_zoneid; short da_inuse; AUTH *da_auth; } *desauthtab; static int nextdesvictim; static kmutex_t desauthtab_lock; /* Lock to protect DES auth cache */ /* RPC stuff */ kmutex_t authdes_ops_lock; /* auth_ops initialization in authdes_ops() */ static void purge_authtab(struct sec_data *); /* Zone stuff */ zone_key_t auth_zone_key; /* * Load RPCSEC_GSS specific data from user space to kernel space. */ /*ARGSUSED*/ static int gss_clnt_loadinfo(caddr_t usrdata, caddr_t *kdata, model_t model) { struct gss_clnt_data *data; caddr_t elements; int error = 0; /* map opaque data to gss specific structure */ data = kmem_alloc(sizeof (*data), KM_SLEEP); #ifdef _SYSCALL32_IMPL if (model != DATAMODEL_NATIVE) { struct gss_clnt_data32 gd32; if (copyin(usrdata, &gd32, sizeof (gd32)) == -1) { error = EFAULT; } else { data->mechanism.length = gd32.mechanism.length; data->mechanism.elements = (caddr_t)(uintptr_t)gd32.mechanism.elements; data->service = gd32.service; bcopy(gd32.uname, data->uname, sizeof (gd32.uname)); bcopy(gd32.inst, data->inst, sizeof (gd32.inst)); bcopy(gd32.realm, data->realm, sizeof (gd32.realm)); data->qop = gd32.qop; } } else #endif /* _SYSCALL32_IMPL */ if (copyin(usrdata, data, sizeof (*data))) error = EFAULT; if (error == 0) { if (data->mechanism.length >= MINAUTHLEN && data->mechanism.length <= MAXAUTHLEN) { elements = kmem_alloc(data->mechanism.length, KM_SLEEP); if (!(copyin(data->mechanism.elements, elements, data->mechanism.length))) { data->mechanism.elements = elements; *kdata = (caddr_t)data; return (0); } kmem_free(elements, data->mechanism.length); } } *kdata = NULL; kmem_free(data, sizeof (*data)); return (EFAULT); } /* * Load AUTH_DES specific data from user space to kernel space. */ /*ARGSUSED2*/ int dh_k4_clnt_loadinfo(caddr_t usrdata, caddr_t *kdata, model_t model) { size_t nlen; int error = 0; char *userbufptr; dh_k4_clntdata_t *data; char netname[MAXNETNAMELEN+1]; struct netbuf *syncaddr; struct knetconfig *knconf; /* map opaque data to des specific strucutre */ data = kmem_alloc(sizeof (*data), KM_SLEEP); #ifdef _SYSCALL32_IMPL if (model != DATAMODEL_NATIVE) { struct des_clnt_data32 data32; if (copyin(usrdata, &data32, sizeof (data32)) == -1) { error = EFAULT; } else { data->syncaddr.maxlen = data32.syncaddr.maxlen; data->syncaddr.len = data32.syncaddr.len; data->syncaddr.buf = (caddr_t)(uintptr_t)data32.syncaddr.buf; data->knconf = (struct knetconfig *)(uintptr_t)data32.knconf; data->netname = (caddr_t)(uintptr_t)data32.netname; data->netnamelen = data32.netnamelen; } } else #endif /* _SYSCALL32_IMPL */ if (copyin(usrdata, data, sizeof (*data))) error = EFAULT; if (error == 0) { syncaddr = &data->syncaddr; if (syncaddr->len < MINAUTHLEN || syncaddr->len > MAXAUTHLEN) error = EINVAL; else { userbufptr = syncaddr->buf; syncaddr->buf = kmem_alloc(syncaddr->len, KM_SLEEP); syncaddr->maxlen = syncaddr->len; if (copyin(userbufptr, syncaddr->buf, syncaddr->len)) { kmem_free(syncaddr->buf, syncaddr->len); syncaddr->buf = NULL; error = EFAULT; } else { (void) copyinstr(data->netname, netname, sizeof (netname), &nlen); if (nlen != 0) { data->netname = kmem_alloc(nlen, KM_SLEEP); bcopy(netname, data->netname, nlen); data->netnamelen = (int)nlen; } } } } if (!error) { /* * Allocate space for a knetconfig structure and * its strings and copy in from user-land. */ knconf = kmem_alloc(sizeof (*knconf), KM_SLEEP); #ifdef _SYSCALL32_IMPL if (model != DATAMODEL_NATIVE) { struct knetconfig32 knconf32; if (copyin(data->knconf, &knconf32, sizeof (knconf32)) == -1) { kmem_free(knconf, sizeof (*knconf)); kmem_free(syncaddr->buf, syncaddr->len); syncaddr->buf = NULL; kmem_free(data->netname, nlen); error = EFAULT; } else { knconf->knc_semantics = knconf32.knc_semantics; knconf->knc_protofmly = (caddr_t)(uintptr_t)knconf32.knc_protofmly; knconf->knc_proto = (caddr_t)(uintptr_t)knconf32.knc_proto; knconf->knc_rdev = expldev(knconf32.knc_rdev); } } else #endif /* _SYSCALL32_IMPL */ if (copyin(data->knconf, knconf, sizeof (*knconf))) { kmem_free(knconf, sizeof (*knconf)); kmem_free(syncaddr->buf, syncaddr->len); syncaddr->buf = NULL; kmem_free(data->netname, nlen); error = EFAULT; } } if (!error) { size_t nmoved_tmp; char *p, *pf; pf = kmem_alloc(KNC_STRSIZE, KM_SLEEP); p = kmem_alloc(KNC_STRSIZE, KM_SLEEP); error = copyinstr(knconf->knc_protofmly, pf, KNC_STRSIZE, &nmoved_tmp); if (error) { kmem_free(pf, KNC_STRSIZE); kmem_free(p, KNC_STRSIZE); kmem_free(knconf, sizeof (*knconf)); kmem_free(syncaddr->buf, syncaddr->len); kmem_free(data->netname, nlen); } if (!error) { error = copyinstr(knconf->knc_proto, p, KNC_STRSIZE, &nmoved_tmp); if (error) { kmem_free(pf, KNC_STRSIZE); kmem_free(p, KNC_STRSIZE); kmem_free(knconf, sizeof (*knconf)); kmem_free(syncaddr->buf, syncaddr->len); kmem_free(data->netname, nlen); } } if (!error) { knconf->knc_protofmly = pf; knconf->knc_proto = p; } } if (error) { *kdata = NULL; kmem_free(data, sizeof (*data)); return (error); } data->knconf = knconf; *kdata = (caddr_t)data; return (0); } /* * Free up AUTH_DES specific data. */ void dh_k4_clnt_freeinfo(caddr_t cdata) { dh_k4_clntdata_t *data; data = (dh_k4_clntdata_t *)cdata; if (data->netnamelen > 0) { kmem_free(data->netname, data->netnamelen); } if (data->syncaddr.buf != NULL) { kmem_free(data->syncaddr.buf, data->syncaddr.len); } if (data->knconf != NULL) { kmem_free(data->knconf->knc_protofmly, KNC_STRSIZE); kmem_free(data->knconf->knc_proto, KNC_STRSIZE); kmem_free(data->knconf, sizeof (*data->knconf)); } kmem_free(data, sizeof (*data)); } /* * Load application auth related data from user land to kernel. * Map opaque data field to dh_k4_clntdata_t for AUTH_DES * */ int sec_clnt_loadinfo(struct sec_data *in, struct sec_data **out, model_t model) { struct sec_data *secdata; int error = 0; secdata = kmem_alloc(sizeof (*secdata), KM_SLEEP); #ifdef _SYSCALL32_IMPL if (model != DATAMODEL_NATIVE) { struct sec_data32 sd32; if (copyin(in, &sd32, sizeof (sd32)) == -1) { error = EFAULT; } else { secdata->secmod = sd32.secmod; secdata->rpcflavor = sd32.rpcflavor; secdata->uid = sd32.uid; secdata->flags = sd32.flags; secdata->data = (caddr_t)(uintptr_t)sd32.data; } } else #endif /* _SYSCALL32_IMPL */ if (copyin(in, secdata, sizeof (*secdata)) == -1) { error = EFAULT; } /* * Copy in opaque data field per flavor. */ if (!error) { switch (secdata->rpcflavor) { case AUTH_NONE: case AUTH_UNIX: case AUTH_LOOPBACK: break; case AUTH_DES: error = dh_k4_clnt_loadinfo(secdata->data, &secdata->data, model); break; case RPCSEC_GSS: error = gss_clnt_loadinfo(secdata->data, &secdata->data, model); break; default: error = EINVAL; break; } } if (!error) { *out = secdata; } else { kmem_free(secdata, sizeof (*secdata)); *out = (struct sec_data *)NULL; } return (error); } /* * Null the sec_data index in the cache table, and * free the memory allocated by sec_clnt_loadinfo. */ void sec_clnt_freeinfo(struct sec_data *secdata) { switch (secdata->rpcflavor) { case AUTH_DES: purge_authtab(secdata); if (secdata->data) dh_k4_clnt_freeinfo(secdata->data); break; case RPCSEC_GSS: rpc_gss_secpurge((void *)secdata); if (secdata->data) { gss_clntdata_t *gss_data; gss_data = (gss_clntdata_t *)secdata->data; if (gss_data->mechanism.elements) { kmem_free(gss_data->mechanism.elements, gss_data->mechanism.length); } kmem_free(secdata->data, sizeof (gss_clntdata_t)); } break; case AUTH_NONE: case AUTH_UNIX: case AUTH_LOOPBACK: default: break; } kmem_free(secdata, sizeof (*secdata)); } /* * Get an AUTH handle for a RPC client based on the given sec_data. * If an AUTH handle exists for the same sec_data, use that AUTH handle, * otherwise create a new one. */ int sec_clnt_geth(CLIENT *client, struct sec_data *secdata, cred_t *cr, AUTH **ap) { int i; struct desauthent *da; int authflavor; cred_t *savecred; int stat; /* return (errno) status */ char gss_svc_name[MAX_GSS_NAME]; dh_k4_clntdata_t *desdata; AUTH *auth; gss_clntdata_t *gssdata; zoneid_t zoneid = getzoneid(); if ((client == NULL) || (secdata == NULL) || (ap == NULL)) return (EINVAL); *ap = (AUTH *)NULL; authflavor = secdata->rpcflavor; for (;;) { int nlen; char *netname; switch (authflavor) { case AUTH_NONE: *ap = (AUTH *) authnone_create(); return ((*ap != NULL) ? 0 : EINTR); case AUTH_UNIX: *ap = (AUTH *) authkern_create(); return ((*ap != NULL) ? 0 : EINTR); case AUTH_LOOPBACK: *ap = (AUTH *) authloopback_create(); return ((*ap != NULL) ? 0 : EINTR); case AUTH_DES: mutex_enter(&desauthtab_lock); if (desauthtab == NULL) { desauthtab = kmem_zalloc(clnt_authdes_cachesz * sizeof (struct desauthent), KM_SLEEP); } for (da = desauthtab; da < &desauthtab[clnt_authdes_cachesz]; da++) { if (da->da_data == secdata && da->da_uid == crgetuid(cr) && da->da_zoneid == zoneid && !da->da_inuse && da->da_auth != NULL) { da->da_inuse = 1; mutex_exit(&desauthtab_lock); *ap = da->da_auth; return (0); } } mutex_exit(&desauthtab_lock); /* * A better way would be to have a cred paramater to * authdes_create. */ savecred = curthread->t_cred; curthread->t_cred = cr; /* * Note that authdes_create() expects a * NUL-terminated string for netname, but * dh_k4_clntdata_t gives us netname & netnamelen. * * We must create a string for authdes_create(); * the latter takes a copy of it, so we may * immediately free it. */ desdata = (dh_k4_clntdata_t *)secdata->data; nlen = desdata->netnamelen; /* must be NUL-terminated */ netname = kmem_zalloc(nlen + 1, KM_SLEEP); bcopy(desdata->netname, netname, nlen); stat = authdes_create(netname, authdes_win, &desdata->syncaddr, desdata->knconf, (des_block *)NULL, (secdata->flags & AUTH_F_RPCTIMESYNC) ? 1 : 0, &auth); kmem_free(netname, nlen + 1); curthread->t_cred = savecred; *ap = auth; if (stat != 0) { /* * If AUTH_F_TRYNONE is on, try again * with AUTH_NONE. See bug 1180236. */ if (secdata->flags & AUTH_F_TRYNONE) { authflavor = AUTH_NONE; continue; } else return (stat); } i = clnt_authdes_cachesz; mutex_enter(&desauthtab_lock); do { da = &desauthtab[nextdesvictim++]; nextdesvictim %= clnt_authdes_cachesz; } while (da->da_inuse && --i > 0); if (da->da_inuse) { mutex_exit(&desauthtab_lock); /* overflow of des auths */ return (stat); } da->da_inuse = 1; mutex_exit(&desauthtab_lock); if (da->da_auth != NULL) auth_destroy(da->da_auth); da->da_auth = auth; da->da_uid = crgetuid(cr); da->da_zoneid = zoneid; da->da_data = secdata; return (stat); case RPCSEC_GSS: /* * For RPCSEC_GSS, cache is done in rpc_gss_secget(). * For every rpc_gss_secget(), it should have * a corresponding rpc_gss_secfree() call. */ gssdata = (gss_clntdata_t *)secdata->data; (void) sprintf(gss_svc_name, "%s@%s", gssdata->uname, gssdata->inst); stat = rpc_gss_secget(client, gss_svc_name, &gssdata->mechanism, gssdata->service, gssdata->qop, NULL, NULL, (caddr_t)secdata, cr, &auth); *ap = auth; /* success */ if (stat == 0) return (stat); /* * let the caller retry if connection timedout * or reset. */ if (stat == ETIMEDOUT || stat == ECONNRESET) return (stat); /* * If AUTH_F_TRYNONE is on, try again * with AUTH_NONE. See bug 1180236. */ if (secdata->flags & AUTH_F_TRYNONE) { authflavor = AUTH_NONE; continue; } RPCLOG(1, "sec_clnt_geth: rpc_gss_secget" " failed with %d", stat); return (stat); default: /* * auth create must have failed, try AUTH_NONE * (this relies on AUTH_NONE never failing) */ cmn_err(CE_NOTE, "sec_clnt_geth: unknown " "authflavor %d, trying AUTH_NONE", authflavor); authflavor = AUTH_NONE; } } } void sec_clnt_freeh(AUTH *auth) { struct desauthent *da; switch (auth->ah_cred.oa_flavor) { case AUTH_NONE: /* XXX: do real AUTH_NONE */ case AUTH_UNIX: case AUTH_LOOPBACK: auth_destroy(auth); /* was overflow */ break; case AUTH_DES: mutex_enter(&desauthtab_lock); if (desauthtab != NULL) { for (da = desauthtab; da < &desauthtab[clnt_authdes_cachesz]; da++) { if (da->da_auth == auth) { da->da_inuse = 0; mutex_exit(&desauthtab_lock); return; } } } mutex_exit(&desauthtab_lock); auth_destroy(auth); /* was overflow */ break; case RPCSEC_GSS: (void) rpc_gss_secfree(auth); break; default: cmn_err(CE_NOTE, "sec_clnt_freeh: unknown authflavor %d", auth->ah_cred.oa_flavor); break; } } /* * Revoke the authentication key in the given AUTH handle by setting * it to NULL. If newkey is true, then generate a new key instead of * nulling out the old one. This is necessary for AUTH_DES because * the new key will be used next time the user does a keylogin. If * the zero'd key is used as actual key, then it cannot be revoked * again! */ void revoke_key(AUTH *auth, int newkey) { if (auth == NULL) return; if (newkey) { if (key_gendes(&auth->ah_key) != RPC_SUCCESS) { /* failed to get new key, munge the old one */ auth->ah_key.key.high ^= auth->ah_key.key.low; auth->ah_key.key.low += auth->ah_key.key.high; } } else { /* null out old key */ auth->ah_key.key.high = 0; auth->ah_key.key.low = 0; } } /* * Revoke all rpc credentials (of the selected auth type) for the given uid * from the auth cache. Must be root to do this if the requested uid is not * the effective uid of the requestor. * * Called from nfssys() for backward compatibility, and also * called from krpc_sys(). * * AUTH_DES does not refer to the "mechanism" information. * RPCSEC_GSS requires the "mechanism" input. * The input argument, mechanism, is a user-space address and needs * to be copied into the kernel address space. * * Returns error number. */ /*ARGSUSED*/ int sec_clnt_revoke(int rpcflavor, uid_t uid, cred_t *cr, void *mechanism, model_t model) { struct desauthent *da; int error = 0; zoneid_t zoneid = getzoneid(); if (uid != crgetuid(cr) && secpolicy_nfs(cr) != 0) return (EPERM); switch (rpcflavor) { case AUTH_DES: mutex_enter(&desauthtab_lock); if (desauthtab != NULL) { for (da = desauthtab; da < &desauthtab[clnt_authdes_cachesz]; da++) { if (uid == da->da_uid && zoneid == da->da_zoneid) revoke_key(da->da_auth, 1); } } mutex_exit(&desauthtab_lock); return (0); case RPCSEC_GSS: { rpc_gss_OID mech; caddr_t elements; if (!mechanism) return (EINVAL); /* copyin the gss mechanism type */ mech = kmem_alloc(sizeof (rpc_gss_OID_desc), KM_SLEEP); #ifdef _SYSCALL32_IMPL if (model != DATAMODEL_NATIVE) { gss_OID_desc32 mech32; if (copyin(mechanism, &mech32, sizeof (gss_OID_desc32))) { kmem_free(mech, sizeof (rpc_gss_OID_desc)); return (EFAULT); } mech->length = mech32.length; mech->elements = (caddr_t)(uintptr_t)mech32.elements; } else #endif /* _SYSCALL32_IMPL */ if (copyin(mechanism, mech, sizeof (rpc_gss_OID_desc))) { kmem_free(mech, sizeof (rpc_gss_OID_desc)); return (EFAULT); } if (mech->length < MINAUTHLEN || mech->length > MAXAUTHLEN) { kmem_free(mech, sizeof (rpc_gss_OID_desc)); return (EINVAL); } elements = kmem_alloc(mech->length, KM_SLEEP); if (copyin(mech->elements, elements, mech->length)) { kmem_free(elements, mech->length); kmem_free(mech, sizeof (rpc_gss_OID_desc)); return (EFAULT); } mech->elements = elements; error = rpc_gss_revauth(uid, mech); kmem_free(elements, mech->length); kmem_free(mech, sizeof (rpc_gss_OID_desc)); return (error); } default: /* not an auth type with cached creds */ return (EINVAL); } } /* * Since sec_data is the index for the client auth handles * cache table, whenever the sec_data is freed, the index needs * to be nulled. */ void purge_authtab(struct sec_data *secdata) { struct desauthent *da; switch (secdata->rpcflavor) { case AUTH_DES: mutex_enter(&desauthtab_lock); if (desauthtab != NULL) { for (da = desauthtab; da < &desauthtab[clnt_authdes_cachesz]; da++) { if (da->da_data == secdata) { da->da_data = NULL; da->da_inuse = 0; } } } mutex_exit(&desauthtab_lock); return; case RPCSEC_GSS: rpc_gss_secpurge((void *)secdata); return; default: return; } } void sec_subrinit(void) { authkern_cache = kmem_cache_create("authkern_cache", sizeof (AUTH), 0, authkern_init, NULL, NULL, NULL, NULL, 0); authnone_cache = kmem_cache_create("authnone_cache", sizeof (AUTH), 0, authnone_init, NULL, NULL, NULL, NULL, 0); authloopback_cache = kmem_cache_create("authloopback_cache", sizeof (AUTH), 0, authloopback_init, NULL, NULL, NULL, NULL, 0); mutex_init(&desauthtab_lock, NULL, MUTEX_DEFAULT, NULL); /* RPC stuff */ mutex_init(&authdes_ops_lock, NULL, MUTEX_DEFAULT, NULL); zone_key_create(&auth_zone_key, auth_zone_init, NULL, auth_zone_fini); } /* * Destroys the caches and mutexes previously allocated and initialized * in sec_subrinit(). * This routine is called by _init() if mod_install() failed. */ void sec_subrfini(void) { mutex_destroy(&desauthtab_lock); kmem_cache_destroy(authkern_cache); kmem_cache_destroy(authnone_cache); kmem_cache_destroy(authloopback_cache); /* RPC stuff */ mutex_destroy(&authdes_ops_lock); (void) zone_key_delete(auth_zone_key); }