// SPDX-License-Identifier: GPL-2.0 /* * Neil Brown <neilb@cse.unsw.edu.au> * J. Bruce Fields <bfields@umich.edu> * Andy Adamson <andros@umich.edu> * Dug Song <dugsong@monkey.org> * * RPCSEC_GSS server authentication. * This implements RPCSEC_GSS as defined in rfc2203 (rpcsec_gss) and rfc2078 * (gssapi) * * The RPCSEC_GSS involves three stages: * 1/ context creation * 2/ data exchange * 3/ context destruction * * Context creation is handled largely by upcalls to user-space. * In particular, GSS_Accept_sec_context is handled by an upcall * Data exchange is handled entirely within the kernel * In particular, GSS_GetMIC, GSS_VerifyMIC, GSS_Seal, GSS_Unseal are in-kernel. * Context destruction is handled in-kernel * GSS_Delete_sec_context is in-kernel * * Context creation is initiated by a RPCSEC_GSS_INIT request arriving. * The context handle and gss_token are used as a key into the rpcsec_init cache. * The content of this cache includes some of the outputs of GSS_Accept_sec_context, * being major_status, minor_status, context_handle, reply_token. * These are sent back to the client. * Sequence window management is handled by the kernel. The window size if currently * a compile time constant. * * When user-space is happy that a context is established, it places an entry * in the rpcsec_context cache. The key for this cache is the context_handle. * The content includes: * uid/gidlist - for determining access rights * mechanism type * mechanism specific information, such as a key *
*/
/* * Unfortunately there isn't a maximum checksum size exported via the * GSS API. Manufacture one based on GSS mechanisms supported by this * implementation.
*/ #define GSS_MAX_CKSUMSIZE (GSS_KRB5_TOK_HDR_LEN + GSS_KRB5_MAX_CKSUM_LEN)
/* * This value may be increased in the future to accommodate other * usage of the scratch buffer.
*/ #define GSS_SCRATCH_SIZE GSS_MAX_CKSUMSIZE
/* for temporary results */
__be32 gsd_seq_num;
u8 gsd_scratch[GSS_SCRATCH_SIZE];
};
/* The rpcsec_init cache is used for mapping RPCSEC_GSS_{,CONT_}INIT requests * into replies. * * Key is context handle (\x if empty) and gss_token. * Content is major_status minor_status (integers) context_handle, reply_token. *
*/
/* * The rpcsec_context cache is used to store a context that is * used in data exchange. * The key is a context handle. The content is: * uid, gidlist, mechanism, service-set, mech-specific-data
*/
struct gss_svc_seq_data { /* highest seq number seen so far: */
u32 sd_max; /* for i such that sd_max-GSS_SEQ_WIN < i <= sd_max, the i-th bit of
* sd_win is nonzero iff sequence number i has been seen already: */ unsignedlong sd_win[GSS_SEQ_WIN/BITS_PER_LONG];
spinlock_t sd_lock;
};
staticint rsc_parse(struct cache_detail *cd, char *mesg, int mlen)
{ /* contexthandle expiry [ uid gid N <n gids> mechname ...mechdata... ] */ char *buf = mesg; int id; int len, rv; struct rsc rsci, *rscp = NULL;
time64_t expiry; int status = -EINVAL; struct gss_api_mech *gm = NULL;
memset(&rsci, 0, sizeof(rsci)); /* context handle */
len = qword_get(&mesg, buf, mlen); if (len < 0) goto out;
status = -ENOMEM; if (dup_to_netobj(&rsci.handle, buf, len)) goto out;
rsci.h.flags = 0; /* expiry */
status = get_expiry(&mesg, &expiry); if (status) goto out;
status = -EINVAL;
rscp = rsc_lookup(cd, &rsci); if (!rscp) goto out;
/* uid, or NEGATIVE */
rv = get_int(&mesg, &id); if (rv == -EINVAL) goto out; if (rv == -ENOENT)
set_bit(CACHE_NEGATIVE, &rsci.h.flags); else { int N, i;
/* * NOTE: we skip uid_valid()/gid_valid() checks here: * instead, * -1 id's are later mapped to the * (export-specific) anonymous id by nfsd_setuser. * * (But supplementary gid's get no such special * treatment so are checked for validity here.)
*/ /* uid */
rsci.cred.cr_uid = make_kuid(current_user_ns(), id);
/* number of additional gid's */ if (get_int(&mesg, &N)) goto out; if (N < 0 || N > NGROUPS_MAX) goto out;
status = -ENOMEM;
rsci.cred.cr_group_info = groups_alloc(N); if (rsci.cred.cr_group_info == NULL) goto out;
/* gid's */
status = -EINVAL; for (i=0; i<N; i++) {
kgid_t kgid; if (get_int(&mesg, &id)) goto out;
kgid = make_kgid(current_user_ns(), id); if (!gid_valid(kgid)) goto out;
rsci.cred.cr_group_info->gid[i] = kgid;
}
groups_sort(rsci.cred.cr_group_info);
/* mech name */
len = qword_get(&mesg, buf, mlen); if (len < 0) goto out;
gm = rsci.cred.cr_gss_mech = gss_mech_get_by_name(buf);
status = -EOPNOTSUPP; if (!gm) goto out;
status = -EINVAL; /* mech-specific data: */
len = qword_get(&mesg, buf, mlen); if (len < 0) goto out;
status = gss_import_sec_context(buf, len, gm, &rsci.mechctx,
NULL, GFP_KERNEL); if (status) goto out;
/* get client name */
len = qword_get(&mesg, buf, mlen); if (len > 0) {
rsci.cred.cr_principal = kstrdup(buf, GFP_KERNEL); if (!rsci.cred.cr_principal) {
status = -ENOMEM; goto out;
}
}
}
rsci.h.expiry_time = expiry;
rscp = rsc_update(cd, &rsci, rscp);
status = 0;
out:
rsc_free(&rsci); if (rscp)
cache_put(&rscp->h, cd); else
status = -ENOMEM; return status;
}
/* * Decode and verify a Call's verifier field. For RPC_AUTH_GSS Calls, * the body of this field contains a variable length checksum. * * GSS-specific auth_stat values are mandated by RFC 2203 Section * 5.3.3.3.
*/ staticint
svcauth_gss_verify_header(struct svc_rqst *rqstp, struct rsc *rsci,
__be32 *rpcstart, struct rpc_gss_wire_cred *gc)
{ struct xdr_stream *xdr = &rqstp->rq_arg_stream; struct gss_ctx *ctx_id = rsci->mechctx;
u32 flavor, maj_stat; struct xdr_buf rpchdr; struct xdr_netobj checksum; struct kvec iov;
/* * Compute the checksum of the incoming Call from the * XID field to credential field:
*/
iov.iov_base = rpcstart;
iov.iov_len = (u8 *)xdr->p - (u8 *)rpcstart;
xdr_buf_from_iov(&iov, &rpchdr);
/* Did we already verify the signature on the original pass through? */ if (rqstp->rq_deferred) return 0;
if (xdr_stream_decode_u32(xdr, &len) < 0) goto unwrap_failed; if (len & 3) goto unwrap_failed;
offset = xdr_stream_pos(xdr); if (xdr_buf_subsegment(buf, &databody_integ, offset, len)) goto unwrap_failed;
/* * The xdr_stream now points to the @seq_num field. The next * XDR data item is the @arg field, which contains the clear * text RPC program payload. The checksum, which follows the * @arg field, is located and decoded without updating the * xdr_stream.
*/
offset += len; if (xdr_decode_word(buf, offset, &checksum.len)) goto unwrap_failed; if (checksum.len > sizeof(gsd->gsd_scratch)) goto unwrap_failed;
checksum.data = gsd->gsd_scratch; if (read_bytes_from_xdr_buf(buf, offset + XDR_UNIT, checksum.data,
checksum.len)) goto unwrap_failed;
/* The received seqno is protected by the checksum. */ if (xdr_stream_decode_u32(xdr, &seq_num) < 0) goto unwrap_failed; if (seq_num != seq) goto bad_seqno;
if (xdr_stream_decode_u32(xdr, &len) < 0) goto unwrap_failed; if (rqstp->rq_deferred) { /* Already decrypted last time through! The sequence number
* check at out_seq is unnecessary but harmless: */ goto out_seq;
} if (len > xdr_stream_remaining(xdr)) goto unwrap_failed;
offset = xdr_stream_pos(xdr);
/* * A gss export can be specified either by: * export *(sec=krb5,rw) * or by * export gss/krb5(rw) * The latter is deprecated; but for backwards compatibility reasons * the nfsd code will still fall back on trying it if the former * doesn't work; so we try to make both available to nfsd, below.
*/
rqstp->rq_gssclient = find_gss_auth_domain(rsci->mechctx, gc->gc_svc); if (rqstp->rq_gssclient == NULL) return SVC_DENIED;
stat = svcauth_unix_set_client(rqstp); if (stat == SVC_DROP || stat == SVC_CLOSE) return stat;
/* * Having read the cred already and found we're in the context * initiation case, read the verifier and initiate (or check the results * of) upcalls to userspace for help with context initiation. If * the upcall results are available, write the verifier and result. * Otherwise, drop the request pending an answer to the upcall.
*/ staticint
svcauth_gss_legacy_init(struct svc_rqst *rqstp, struct rpc_gss_wire_cred *gc)
{ struct xdr_stream *xdr = &rqstp->rq_arg_stream; struct rsi *rsip, rsikey;
__be32 *p;
u32 len; int ret; struct sunrpc_net *sn = net_generic(SVC_NET(rqstp), sunrpc_net_id);
memset(&rsikey, 0, sizeof(rsikey)); if (dup_netobj(&rsikey.in_handle, &gc->gc_ctx)) return SVC_CLOSE;
memset(&rsci, 0, sizeof(rsci)); /* context handle */
status = -ENOMEM; /* the handle needs to be just a unique id,
* use a static counter */
ctxh = atomic64_inc_return(&ctxhctr);
/* make a copy for the caller */
*handle = ctxh;
/* make a copy for the rsc cache */ if (dup_to_netobj(&rsci.handle, (char *)handle, sizeof(uint64_t))) goto out;
rscp = rsc_lookup(cd, &rsci); if (!rscp) goto out;
/* creds */ if (!ud->found_creds) { /* userspace seem buggy, we should always get at least a
* mapping to nobody */ goto out;
} else { struct timespec64 boot;
switch (ud.major_status) { case GSS_S_CONTINUE_NEEDED:
cli_handle = ud.out_handle; break; case GSS_S_COMPLETE:
status = gss_proxy_save_rsc(sn->rsc_cache, &ud, &handle); if (status) goto out;
cli_handle.data = (u8 *)&handle;
cli_handle.len = sizeof(handle); break; default: goto out;
}
if (!svcauth_gss_proc_init_verf(sn->rsc_cache, rqstp, &cli_handle,
&ud.major_status, GSS_SEQ_WIN)) goto out; if (!svcxdr_set_accept_stat(rqstp)) goto out; if (!svcxdr_encode_gss_init_res(&rqstp->rq_res_stream, &cli_handle,
&ud.out_token, ud.major_status,
ud.minor_status, GSS_SEQ_WIN)) goto out;
ret = SVC_COMPLETE;
out:
gss_free_in_token_pages(&ud.in_token);
gssp_free_upcall_data(&ud); return ret;
}
/* * Try to set the sn->use_gss_proxy variable to a new value. We only allow * it to be changed if it's currently undefined (-1). If it's any other value * then return -EBUSY unless the type wouldn't have changed anyway.
*/ staticint set_gss_proxy(struct net *net, int type)
{ struct sunrpc_net *sn = net_generic(net, sunrpc_net_id); int ret;
WARN_ON_ONCE(type != 0 && type != 1);
ret = cmpxchg(&sn->use_gss_proxy, -1, type); if (ret != -1 && ret != type) return -EBUSY; return 0;
}
if (*ppos || count > sizeof(tbuf)-1) return -EINVAL; if (copy_from_user(tbuf, buf, count)) return -EFAULT;
tbuf[count] = 0;
res = kstrtoul(tbuf, 0, &i); if (res) return res; if (i != 1) return -EINVAL;
res = set_gssp_clnt(net); if (res) return res;
res = set_gss_proxy(net, 1); if (res) return res; return count;
}
if (sn->gss_krb5_enctypes)
remove_proc_entry("gss_krb5_enctypes", sn->proc_net_rpc);
}
#else/* CONFIG_PROC_FS */
staticint create_use_gss_proxy_proc_entry(struct net *net)
{ return 0;
}
staticvoid destroy_use_gss_proxy_proc_entry(struct net *net) {}
staticint create_krb5_enctypes_proc_entry(struct net *net)
{ return 0;
}
staticvoid destroy_krb5_enctypes_proc_entry(struct net *net) {}
#endif/* CONFIG_PROC_FS */
/* * The Call's credential body should contain a struct rpc_gss_cred_t. * * RFC 2203 Section 5 * * struct rpc_gss_cred_t { * union switch (unsigned int version) { * case RPCSEC_GSS_VERS_1: * struct { * rpc_gss_proc_t gss_proc; * unsigned int seq_num; * rpc_gss_service_t service; * opaque handle<>; * } rpc_gss_cred_vers_1_t; * } * };
*/ staticbool
svcauth_gss_decode_credbody(struct xdr_stream *xdr, struct rpc_gss_wire_cred *gc,
__be32 **rpcstart)
{
ssize_t handle_len;
u32 body_len;
__be32 *p;
p = xdr_inline_decode(xdr, XDR_UNIT); if (!p) returnfalse; /* * start of rpc packet is 7 u32's back from here: * xid direction rpcversion prog vers proc flavour
*/
*rpcstart = p - 7;
body_len = be32_to_cpup(p); if (body_len > RPC_MAX_AUTH_SIZE) returnfalse;
/* struct rpc_gss_cred_t */ if (xdr_stream_decode_u32(xdr, &gc->gc_v) < 0) returnfalse; if (xdr_stream_decode_u32(xdr, &gc->gc_proc) < 0) returnfalse; if (xdr_stream_decode_u32(xdr, &gc->gc_seq) < 0) returnfalse; if (xdr_stream_decode_u32(xdr, &gc->gc_svc) < 0) returnfalse;
handle_len = xdr_stream_decode_opaque_inline(xdr,
(void **)&gc->gc_ctx.data,
body_len); if (handle_len < 0) returnfalse; if (body_len != XDR_UNIT * 5 + xdr_align_size(handle_len)) returnfalse;
gc->gc_ctx.len = handle_len; returntrue;
}
/** * svcauth_gss_accept - Decode and validate incoming RPC_AUTH_GSS credential * @rqstp: RPC transaction * * Return values: * %SVC_OK: Success * %SVC_COMPLETE: GSS context lifetime event * %SVC_DENIED: Credential or verifier is not valid * %SVC_GARBAGE: Failed to decode credential or verifier * %SVC_CLOSE: Temporary failure * * The rqstp->rq_auth_stat field is also set (see RFCs 2203 and 5531).
*/ staticenum svc_auth_status
svcauth_gss_accept(struct svc_rqst *rqstp)
{ struct gss_svc_data *svcdata = rqstp->rq_auth_data;
__be32 *rpcstart; struct rpc_gss_wire_cred *gc; struct rsc *rsci = NULL; int ret; struct sunrpc_net *sn = net_generic(SVC_NET(rqstp), sunrpc_net_id);
/* Release can be called twice, but we only wrap once. */
offset = gsd->gsd_databody_offset;
gsd->gsd_databody_offset = 0;
/* AUTH_ERROR replies are not wrapped. */ if (rqstp->rq_auth_stat != rpc_auth_ok) return 0;
/* Also don't wrap if the accept_stat is nonzero: */ if (*rqstp->rq_accept_statp != rpc_success) return 0;
return offset;
}
/* * RFC 2203, Section 5.3.2.2 * * struct rpc_gss_integ_data { * opaque databody_integ<>; * opaque checksum<>; * }; * * struct rpc_gss_data_t { * unsigned int seq_num; * proc_req_arg_t arg; * }; * * The RPC Reply message has already been XDR-encoded. rq_res_stream * is now positioned so that the checksum can be written just past * the RPC Reply message.
*/ staticint svcauth_gss_wrap_integ(struct svc_rqst *rqstp)
{ struct gss_svc_data *gsd = rqstp->rq_auth_data; struct xdr_stream *xdr = &rqstp->rq_res_stream; struct rpc_gss_wire_cred *gc = &gsd->clcred; struct xdr_buf *buf = xdr->buf; struct xdr_buf databody_integ; struct xdr_netobj checksum;
u32 offset, maj_stat;
offset = svcauth_gss_prepare_to_wrap(rqstp, gsd); if (!offset) goto out;
if (xdr_buf_subsegment(buf, &databody_integ, offset + XDR_UNIT,
buf->len - offset - XDR_UNIT)) goto wrap_failed; /* Buffer space for these has already been reserved in
* svcauth_gss_accept(). */ if (xdr_encode_word(buf, offset, databody_integ.len)) goto wrap_failed; if (xdr_encode_word(buf, offset + XDR_UNIT, gc->gc_seq)) goto wrap_failed;
/* * RFC 2203, Section 5.3.2.3 * * struct rpc_gss_priv_data { * opaque databody_priv<> * }; * * struct rpc_gss_data_t { * unsigned int seq_num; * proc_req_arg_t arg; * }; * * gss_wrap() expands the size of the RPC message payload in the * response buffer. The main purpose of svcauth_gss_wrap_priv() * is to ensure there is adequate space in the response buffer to * avoid overflow during the wrap.
*/ staticint svcauth_gss_wrap_priv(struct svc_rqst *rqstp)
{ struct gss_svc_data *gsd = rqstp->rq_auth_data; struct rpc_gss_wire_cred *gc = &gsd->clcred; struct xdr_buf *buf = &rqstp->rq_res; struct kvec *head = buf->head; struct kvec *tail = buf->tail;
u32 offset, pad, maj_stat;
__be32 *p;
offset = svcauth_gss_prepare_to_wrap(rqstp, gsd); if (!offset) return 0;
/* * Buffer space for this field has already been reserved * in svcauth_gss_accept(). Note that the GSS sequence * number is encrypted along with the RPC reply payload.
*/ if (xdr_encode_word(buf, offset + XDR_UNIT, gc->gc_seq)) goto wrap_failed;
/* * If there is currently tail data, make sure there is * room for the head, tail, and 2 * RPC_MAX_AUTH_SIZE in * the page, and move the current tail data such that * there is RPC_MAX_AUTH_SIZE slack space available in * both the head and tail.
*/ if (tail->iov_base) { if (tail->iov_base >= head->iov_base + PAGE_SIZE) goto wrap_failed; if (tail->iov_base < head->iov_base) goto wrap_failed; if (tail->iov_len + head->iov_len
+ 2 * RPC_MAX_AUTH_SIZE > PAGE_SIZE) goto wrap_failed;
memmove(tail->iov_base + RPC_MAX_AUTH_SIZE, tail->iov_base,
tail->iov_len);
tail->iov_base += RPC_MAX_AUTH_SIZE;
} /* * If there is no current tail data, make sure there is * room for the head data, and 2 * RPC_MAX_AUTH_SIZE in the * allotted page, and set up tail information such that there * is RPC_MAX_AUTH_SIZE slack space available in both the * head and tail.
*/ if (!tail->iov_base) { if (head->iov_len + 2 * RPC_MAX_AUTH_SIZE > PAGE_SIZE) goto wrap_failed;
tail->iov_base = head->iov_base
+ head->iov_len + RPC_MAX_AUTH_SIZE;
tail->iov_len = 0;
}
/** * svcauth_gss_release - Wrap payload and release resources * @rqstp: RPC transaction context * * Return values: * %0: the Reply is ready to be sent * %-ENOMEM: failed to allocate memory * %-EINVAL: encoding error
*/ staticint
svcauth_gss_release(struct svc_rqst *rqstp)
{ struct sunrpc_net *sn = net_generic(SVC_NET(rqstp), sunrpc_net_id); struct gss_svc_data *gsd = rqstp->rq_auth_data; struct rpc_gss_wire_cred *gc; int stat;
if (!gsd) goto out;
gc = &gsd->clcred; if (gc->gc_proc != RPC_GSS_PROC_DATA) goto out;
switch (gc->gc_svc) { case RPC_GSS_SVC_NONE: break; case RPC_GSS_SVC_INTEGRITY:
stat = svcauth_gss_wrap_integ(rqstp); if (stat) goto out_err; break; case RPC_GSS_SVC_PRIVACY:
stat = svcauth_gss_wrap_priv(rqstp); if (stat) goto out_err; break; /* * For any other gc_svc value, svcauth_gss_accept() already set * the auth_error appropriately; just fall through:
*/
}
out:
stat = 0;
out_err: if (rqstp->rq_client)
auth_domain_put(rqstp->rq_client);
rqstp->rq_client = NULL; if (rqstp->rq_gssclient)
auth_domain_put(rqstp->rq_gssclient);
rqstp->rq_gssclient = NULL; if (rqstp->rq_cred.cr_group_info)
put_group_info(rqstp->rq_cred.cr_group_info);
rqstp->rq_cred.cr_group_info = NULL; if (gsd && gsd->rsci) {
cache_put(&gsd->rsci->h, sn->rsc_cache);
gsd->rsci = NULL;
} return stat;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.