summaryrefslogtreecommitdiff
path: root/net/sunrpc/auth_gss/svcauth_gss.c
diff options
context:
space:
mode:
Diffstat (limited to 'net/sunrpc/auth_gss/svcauth_gss.c')
-rw-r--r--net/sunrpc/auth_gss/svcauth_gss.c1081
1 files changed, 596 insertions, 485 deletions
diff --git a/net/sunrpc/auth_gss/svcauth_gss.c b/net/sunrpc/auth_gss/svcauth_gss.c
index acb822b23af1..9c843974bb48 100644
--- a/net/sunrpc/auth_gss/svcauth_gss.c
+++ b/net/sunrpc/auth_gss/svcauth_gss.c
@@ -71,12 +71,11 @@
struct gss_svc_data {
/* decoded gss client cred: */
struct rpc_gss_wire_cred clcred;
- /* save a pointer to the beginning of the encoded verifier,
- * for use in encryption/checksumming in svcauth_gss_release: */
- __be32 *verf_start;
+ u32 gsd_databody_offset;
struct rsc *rsci;
/* for temporary results */
+ __be32 gsd_seq_num;
u8 gsd_scratch[GSS_SCRATCH_SIZE];
};
@@ -692,78 +691,49 @@ alreadyseen:
goto out;
}
-static inline u32 round_up_to_quad(u32 i)
-{
- return (i + 3 ) & ~3;
-}
-
-static inline int
-svc_safe_getnetobj(struct kvec *argv, struct xdr_netobj *o)
-{
- int l;
-
- if (argv->iov_len < 4)
- return -1;
- o->len = svc_getnl(argv);
- l = round_up_to_quad(o->len);
- if (argv->iov_len < l)
- return -1;
- o->data = argv->iov_base;
- argv->iov_base += l;
- argv->iov_len -= l;
- return 0;
-}
-
-static inline int
-svc_safe_putnetobj(struct kvec *resv, struct xdr_netobj *o)
-{
- u8 *p;
-
- if (resv->iov_len + 4 > PAGE_SIZE)
- return -1;
- svc_putnl(resv, o->len);
- p = resv->iov_base + resv->iov_len;
- resv->iov_len += round_up_to_quad(o->len);
- if (resv->iov_len > PAGE_SIZE)
- return -1;
- memcpy(p, o->data, o->len);
- memset(p + o->len, 0, round_up_to_quad(o->len) - o->len);
- return 0;
-}
-
/*
- * Verify the checksum on the header and return SVC_OK on success.
- * Otherwise, return SVC_DROP (in the case of a bad sequence number)
- * or return SVC_DENIED and indicate error in rqstp->rq_auth_stat.
+ * 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.
*/
static int
-gss_verify_header(struct svc_rqst *rqstp, struct rsc *rsci,
- __be32 *rpcstart, struct rpc_gss_wire_cred *gc)
+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;
- u32 flavor = 0;
- struct kvec *argv = &rqstp->rq_arg.head[0];
struct kvec iov;
- /* data to compute the checksum over: */
+ /*
+ * Compute the checksum of the incoming Call from the
+ * XID field to credential field:
+ */
iov.iov_base = rpcstart;
- iov.iov_len = (u8 *)argv->iov_base - (u8 *)rpcstart;
+ iov.iov_len = (u8 *)xdr->p - (u8 *)rpcstart;
xdr_buf_from_iov(&iov, &rpchdr);
- rqstp->rq_auth_stat = rpc_autherr_badverf;
- if (argv->iov_len < 4)
+ /* Call's verf field: */
+ if (xdr_stream_decode_opaque_auth(xdr, &flavor,
+ (void **)&checksum.data,
+ &checksum.len) < 0) {
+ rqstp->rq_auth_stat = rpc_autherr_badverf;
return SVC_DENIED;
- flavor = svc_getnl(argv);
- if (flavor != RPC_AUTH_GSS)
- return SVC_DENIED;
- if (svc_safe_getnetobj(argv, &checksum))
+ }
+ if (flavor != RPC_AUTH_GSS) {
+ rqstp->rq_auth_stat = rpc_autherr_badverf;
return SVC_DENIED;
+ }
- if (rqstp->rq_deferred) /* skip verification of revisited request */
+ if (rqstp->rq_deferred)
return SVC_OK;
- if (gss_verify_mic(ctx_id, &rpchdr, &checksum) != GSS_S_COMPLETE) {
+ maj_stat = gss_verify_mic(ctx_id, &rpchdr, &checksum);
+ if (maj_stat != GSS_S_COMPLETE) {
+ trace_rpcgss_svc_mic(rqstp, maj_stat);
rqstp->rq_auth_stat = rpcsec_gsserr_credproblem;
return SVC_DENIED;
}
@@ -778,54 +748,36 @@ gss_verify_header(struct svc_rqst *rqstp, struct rsc *rsci,
return SVC_OK;
}
-static int
-gss_write_null_verf(struct svc_rqst *rqstp)
-{
- __be32 *p;
-
- svc_putnl(rqstp->rq_res.head, RPC_AUTH_NULL);
- p = rqstp->rq_res.head->iov_base + rqstp->rq_res.head->iov_len;
- /* don't really need to check if head->iov_len > PAGE_SIZE ... */
- *p++ = 0;
- if (!xdr_ressize_check(rqstp, p))
- return -1;
- return 0;
-}
-
-static int
-gss_write_verf(struct svc_rqst *rqstp, struct gss_ctx *ctx_id, u32 seq)
+/*
+ * Construct and encode a Reply's verifier field. The verifier's body
+ * field contains a variable-length checksum of the GSS sequence
+ * number.
+ */
+static bool
+svcauth_gss_encode_verf(struct svc_rqst *rqstp, struct gss_ctx *ctx_id, u32 seq)
{
- __be32 *xdr_seq;
+ struct gss_svc_data *gsd = rqstp->rq_auth_data;
u32 maj_stat;
struct xdr_buf verf_data;
- struct xdr_netobj mic;
- __be32 *p;
+ struct xdr_netobj checksum;
struct kvec iov;
- int err = -1;
-
- svc_putnl(rqstp->rq_res.head, RPC_AUTH_GSS);
- xdr_seq = kmalloc(4, GFP_KERNEL);
- if (!xdr_seq)
- return -ENOMEM;
- *xdr_seq = htonl(seq);
- iov.iov_base = xdr_seq;
- iov.iov_len = 4;
+ gsd->gsd_seq_num = cpu_to_be32(seq);
+ iov.iov_base = &gsd->gsd_seq_num;
+ iov.iov_len = XDR_UNIT;
xdr_buf_from_iov(&iov, &verf_data);
- p = rqstp->rq_res.head->iov_base + rqstp->rq_res.head->iov_len;
- mic.data = (u8 *)(p + 1);
- maj_stat = gss_get_mic(ctx_id, &verf_data, &mic);
+
+ checksum.data = gsd->gsd_scratch;
+ maj_stat = gss_get_mic(ctx_id, &verf_data, &checksum);
if (maj_stat != GSS_S_COMPLETE)
- goto out;
- *p++ = htonl(mic.len);
- memset((u8 *)p + mic.len, 0, round_up_to_quad(mic.len) - mic.len);
- p += XDR_QUADLEN(mic.len);
- if (!xdr_ressize_check(rqstp, p))
- goto out;
- err = 0;
-out:
- kfree(xdr_seq);
- return err;
+ goto bad_mic;
+
+ return xdr_stream_encode_opaque_auth(&rqstp->rq_res_stream, RPC_AUTH_GSS,
+ checksum.data, checksum.len) > 0;
+
+bad_mic:
+ trace_rpcgss_svc_get_mic(rqstp, maj_stat);
+ return false;
}
struct gss_domain {
@@ -891,31 +843,28 @@ out:
}
EXPORT_SYMBOL_GPL(svcauth_gss_register_pseudoflavor);
-static inline int
-read_u32_from_xdr_buf(struct xdr_buf *buf, int base, u32 *obj)
-{
- __be32 raw;
- int status;
-
- status = read_bytes_from_xdr_buf(buf, base, &raw, sizeof(*obj));
- if (status)
- return status;
- *obj = ntohl(raw);
- return 0;
-}
-
-/* It would be nice if this bit of code could be shared with the client.
- * Obstacles:
- * The client shouldn't malloc(), would have to pass in own memory.
- * The server uses base of head iovec as read pointer, while the
- * client uses separate pointer. */
-static int
-unwrap_integ_data(struct svc_rqst *rqstp, struct xdr_buf *buf, u32 seq, struct gss_ctx *ctx)
+/*
+ * 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;
+ * };
+ */
+static noinline_for_stack int
+svcauth_gss_unwrap_integ(struct svc_rqst *rqstp, u32 seq, struct gss_ctx *ctx)
{
struct gss_svc_data *gsd = rqstp->rq_auth_data;
- u32 integ_len, rseqno, maj_stat;
- struct xdr_netobj mic;
- struct xdr_buf integ_buf;
+ struct xdr_stream *xdr = &rqstp->rq_arg_stream;
+ u32 len, offset, seq_num, maj_stat;
+ struct xdr_buf *buf = xdr->buf;
+ struct xdr_buf databody_integ;
+ struct xdr_netobj checksum;
/* NFS READ normally uses splice to send data in-place. However
* the data in cache can change after the reply's MIC is computed
@@ -929,104 +878,100 @@ unwrap_integ_data(struct svc_rqst *rqstp, struct xdr_buf *buf, u32 seq, struct g
if (rqstp->rq_deferred)
return 0;
- integ_len = svc_getnl(&buf->head[0]);
- if (integ_len & 3)
+ if (xdr_stream_decode_u32(xdr, &len) < 0)
goto unwrap_failed;
- if (integ_len > buf->len)
+ if (len & 3)
goto unwrap_failed;
- if (xdr_buf_subsegment(buf, &integ_buf, 0, integ_len))
+ offset = xdr_stream_pos(xdr);
+ if (xdr_buf_subsegment(buf, &databody_integ, offset, len))
goto unwrap_failed;
- /* copy out mic... */
- if (read_u32_from_xdr_buf(buf, integ_len, &mic.len))
+ /*
+ * 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 (mic.len > sizeof(gsd->gsd_scratch))
+ if (checksum.len > sizeof(gsd->gsd_scratch))
goto unwrap_failed;
- mic.data = gsd->gsd_scratch;
- if (read_bytes_from_xdr_buf(buf, integ_len + 4, mic.data, mic.len))
+ checksum.data = gsd->gsd_scratch;
+ if (read_bytes_from_xdr_buf(buf, offset + XDR_UNIT, checksum.data,
+ checksum.len))
goto unwrap_failed;
- maj_stat = gss_verify_mic(ctx, &integ_buf, &mic);
+
+ maj_stat = gss_verify_mic(ctx, &databody_integ, &checksum);
if (maj_stat != GSS_S_COMPLETE)
goto bad_mic;
- rseqno = svc_getnl(&buf->head[0]);
- if (rseqno != seq)
+
+ /* 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;
- /* trim off the mic and padding at the end before returning */
- xdr_buf_trim(buf, round_up_to_quad(mic.len) + 4);
+
+ xdr_truncate_decode(xdr, XDR_UNIT + checksum.len);
return 0;
unwrap_failed:
trace_rpcgss_svc_unwrap_failed(rqstp);
return -EINVAL;
bad_seqno:
- trace_rpcgss_svc_seqno_bad(rqstp, seq, rseqno);
+ trace_rpcgss_svc_seqno_bad(rqstp, seq, seq_num);
return -EINVAL;
bad_mic:
trace_rpcgss_svc_mic(rqstp, maj_stat);
return -EINVAL;
}
-static inline int
-total_buf_len(struct xdr_buf *buf)
-{
- return buf->head[0].iov_len + buf->page_len + buf->tail[0].iov_len;
-}
-
-static void
-fix_priv_head(struct xdr_buf *buf, int pad)
-{
- if (buf->page_len == 0) {
- /* We need to adjust head and buf->len in tandem in this
- * case to make svc_defer() work--it finds the original
- * buffer start using buf->len - buf->head[0].iov_len. */
- buf->head[0].iov_len -= pad;
- }
-}
-
-static int
-unwrap_priv_data(struct svc_rqst *rqstp, struct xdr_buf *buf, u32 seq, struct gss_ctx *ctx)
+/*
+ * 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;
+ * };
+ */
+static noinline_for_stack int
+svcauth_gss_unwrap_priv(struct svc_rqst *rqstp, u32 seq, struct gss_ctx *ctx)
{
- u32 priv_len, maj_stat;
- int pad, remaining_len, offset;
- u32 rseqno;
+ struct xdr_stream *xdr = &rqstp->rq_arg_stream;
+ u32 len, maj_stat, seq_num, offset;
+ struct xdr_buf *buf = xdr->buf;
+ unsigned int saved_len;
clear_bit(RQ_SPLICE_OK, &rqstp->rq_flags);
- priv_len = svc_getnl(&buf->head[0]);
+ 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;
}
- /* buf->len is the number of bytes from the original start of the
- * request to the end, where head[0].iov_len is just the bytes
- * not yet read from the head, so these two values are different: */
- remaining_len = total_buf_len(buf);
- if (priv_len > remaining_len)
+ if (len > xdr_stream_remaining(xdr))
goto unwrap_failed;
- pad = remaining_len - priv_len;
- buf->len -= pad;
- fix_priv_head(buf, pad);
-
- maj_stat = gss_unwrap(ctx, 0, priv_len, buf);
- pad = priv_len - buf->len;
- /* The upper layers assume the buffer is aligned on 4-byte boundaries.
- * In the krb5p case, at least, the data ends up offset, so we need to
- * move it around. */
- /* XXX: This is very inefficient. It would be better to either do
- * this while we encrypt, or maybe in the receive code, if we can peak
- * ahead and work out the service and mechanism there. */
- offset = xdr_pad_size(buf->head[0].iov_len);
- if (offset) {
- buf->buflen = RPCSVC_MAXPAYLOAD;
- xdr_shift_buf(buf, offset);
- fix_priv_head(buf, pad);
- }
+ offset = xdr_stream_pos(xdr);
+
+ saved_len = buf->len;
+ maj_stat = gss_unwrap(ctx, offset, offset + len, buf);
if (maj_stat != GSS_S_COMPLETE)
goto bad_unwrap;
+ xdr->nwords -= XDR_QUADLEN(saved_len - buf->len);
+
out_seq:
- rseqno = svc_getnl(&buf->head[0]);
- if (rseqno != seq)
+ /* gss_unwrap() decrypted the sequence number. */
+ if (xdr_stream_decode_u32(xdr, &seq_num) < 0)
+ goto unwrap_failed;
+ if (seq_num != seq)
goto bad_seqno;
return 0;
@@ -1034,7 +979,7 @@ unwrap_failed:
trace_rpcgss_svc_unwrap_failed(rqstp);
return -EINVAL;
bad_seqno:
- trace_rpcgss_svc_seqno_bad(rqstp, seq, rseqno);
+ trace_rpcgss_svc_seqno_bad(rqstp, seq, seq_num);
return -EINVAL;
bad_unwrap:
trace_rpcgss_svc_unwrap(rqstp, maj_stat);
@@ -1071,72 +1016,29 @@ svcauth_gss_set_client(struct svc_rqst *rqstp)
return SVC_OK;
}
-static inline int
-gss_write_init_verf(struct cache_detail *cd, struct svc_rqst *rqstp,
- struct xdr_netobj *out_handle, int *major_status)
+static bool
+svcauth_gss_proc_init_verf(struct cache_detail *cd, struct svc_rqst *rqstp,
+ struct xdr_netobj *out_handle, int *major_status,
+ u32 seq_num)
{
+ struct xdr_stream *xdr = &rqstp->rq_res_stream;
struct rsc *rsci;
- int rc;
+ bool rc;
if (*major_status != GSS_S_COMPLETE)
- return gss_write_null_verf(rqstp);
+ goto null_verifier;
rsci = gss_svc_searchbyctx(cd, out_handle);
if (rsci == NULL) {
*major_status = GSS_S_NO_CONTEXT;
- return gss_write_null_verf(rqstp);
+ goto null_verifier;
}
- rc = gss_write_verf(rqstp, rsci->mechctx, GSS_SEQ_WIN);
+
+ rc = svcauth_gss_encode_verf(rqstp, rsci->mechctx, seq_num);
cache_put(&rsci->h, cd);
return rc;
-}
-
-static inline int
-gss_read_common_verf(struct rpc_gss_wire_cred *gc,
- struct kvec *argv, __be32 *authp,
- struct xdr_netobj *in_handle)
-{
- /* Read the verifier; should be NULL: */
- *authp = rpc_autherr_badverf;
- if (argv->iov_len < 2 * 4)
- return SVC_DENIED;
- if (svc_getnl(argv) != RPC_AUTH_NULL)
- return SVC_DENIED;
- if (svc_getnl(argv) != 0)
- return SVC_DENIED;
- /* Martial context handle and token for upcall: */
- *authp = rpc_autherr_badcred;
- if (gc->gc_proc == RPC_GSS_PROC_INIT && gc->gc_ctx.len != 0)
- return SVC_DENIED;
- if (dup_netobj(in_handle, &gc->gc_ctx))
- return SVC_CLOSE;
- *authp = rpc_autherr_badverf;
- return 0;
-}
-
-static inline int
-gss_read_verf(struct rpc_gss_wire_cred *gc,
- struct kvec *argv, __be32 *authp,
- struct xdr_netobj *in_handle,
- struct xdr_netobj *in_token)
-{
- struct xdr_netobj tmpobj;
- int res;
-
- res = gss_read_common_verf(gc, argv, authp, in_handle);
- if (res)
- return res;
-
- if (svc_safe_getnetobj(argv, &tmpobj)) {
- kfree(in_handle->data);
- return SVC_DENIED;
- }
- if (dup_netobj(in_token, &tmpobj)) {
- kfree(in_handle->data);
- return SVC_CLOSE;
- }
-
- return 0;
+null_verifier:
+ return xdr_stream_encode_opaque_auth(xdr, RPC_AUTH_NULL, NULL, 0) > 0;
}
static void gss_free_in_token_pages(struct gssp_in_token *in_token)
@@ -1161,40 +1063,43 @@ static int gss_read_proxy_verf(struct svc_rqst *rqstp,
struct xdr_netobj *in_handle,
struct gssp_in_token *in_token)
{
- struct kvec *argv = &rqstp->rq_arg.head[0];
+ struct xdr_stream *xdr = &rqstp->rq_arg_stream;
unsigned int length, pgto_offs, pgfrom_offs;
- int pages, i, res, pgto, pgfrom;
- size_t inlen, to_offs, from_offs;
+ int pages, i, pgto, pgfrom;
+ size_t to_offs, from_offs;
+ u32 inlen;
- res = gss_read_common_verf(gc, argv, &rqstp->rq_auth_stat, in_handle);
- if (res)
- return res;
+ if (dup_netobj(in_handle, &gc->gc_ctx))
+ return SVC_CLOSE;
- inlen = svc_getnl(argv);
- if (inlen > (argv->iov_len + rqstp->rq_arg.page_len)) {
- kfree(in_handle->data);
- return SVC_DENIED;
- }
+ /*
+ * RFC 2203 Section 5.2.2
+ *
+ * struct rpc_gss_init_arg {
+ * opaque gss_token<>;
+ * };
+ */
+ if (xdr_stream_decode_u32(xdr, &inlen) < 0)
+ goto out_denied_free;
+ if (inlen > xdr_stream_remaining(xdr))
+ goto out_denied_free;
pages = DIV_ROUND_UP(inlen, PAGE_SIZE);
in_token->pages = kcalloc(pages, sizeof(struct page *), GFP_KERNEL);
- if (!in_token->pages) {
- kfree(in_handle->data);
- return SVC_DENIED;
- }
+ if (!in_token->pages)
+ goto out_denied_free;
in_token->page_base = 0;
in_token->page_len = inlen;
for (i = 0; i < pages; i++) {
in_token->pages[i] = alloc_page(GFP_KERNEL);
if (!in_token->pages[i]) {
- kfree(in_handle->data);
gss_free_in_token_pages(in_token);
- return SVC_DENIED;
+ goto out_denied_free;
}
}
- length = min_t(unsigned int, inlen, argv->iov_len);
- memcpy(page_address(in_token->pages[0]), argv->iov_base, length);
+ length = min_t(unsigned int, inlen, (char *)xdr->end - (char *)xdr->p);
+ memcpy(page_address(in_token->pages[0]), xdr->p, length);
inlen -= length;
to_offs = length;
@@ -1217,26 +1122,41 @@ static int gss_read_proxy_verf(struct svc_rqst *rqstp,
inlen -= length;
}
return 0;
+
+out_denied_free:
+ kfree(in_handle->data);
+ return SVC_DENIED;
}
-static inline int
-gss_write_resv(struct kvec *resv, size_t size_limit,
- struct xdr_netobj *out_handle, struct xdr_netobj *out_token,
- int major_status, int minor_status)
-{
- if (resv->iov_len + 4 > size_limit)
- return -1;
- svc_putnl(resv, RPC_SUCCESS);
- if (svc_safe_putnetobj(resv, out_handle))
- return -1;
- if (resv->iov_len + 3 * 4 > size_limit)
- return -1;
- svc_putnl(resv, major_status);
- svc_putnl(resv, minor_status);
- svc_putnl(resv, GSS_SEQ_WIN);
- if (svc_safe_putnetobj(resv, out_token))
- return -1;
- return 0;
+/*
+ * RFC 2203, Section 5.2.3.1.
+ *
+ * struct rpc_gss_init_res {
+ * opaque handle<>;
+ * unsigned int gss_major;
+ * unsigned int gss_minor;
+ * unsigned int seq_window;
+ * opaque gss_token<>;
+ * };
+ */
+static bool
+svcxdr_encode_gss_init_res(struct xdr_stream *xdr,
+ struct xdr_netobj *handle,
+ struct xdr_netobj *gss_token,
+ unsigned int major_status,
+ unsigned int minor_status, u32 seq_num)
+{
+ if (xdr_stream_encode_opaque(xdr, handle->data, handle->len) < 0)
+ return false;
+ if (xdr_stream_encode_u32(xdr, major_status) < 0)
+ return false;
+ if (xdr_stream_encode_u32(xdr, minor_status) < 0)
+ return false;
+ if (xdr_stream_encode_u32(xdr, seq_num) < 0)
+ return false;
+ if (xdr_stream_encode_opaque(xdr, gss_token->data, gss_token->len) < 0)
+ return false;
+ return true;
}
/*
@@ -1246,20 +1166,44 @@ gss_write_resv(struct kvec *resv, size_t size_limit,
* the upcall results are available, write the verifier and result.
* Otherwise, drop the request pending an answer to the upcall.
*/
-static int svcauth_gss_legacy_init(struct svc_rqst *rqstp,
- struct rpc_gss_wire_cred *gc)
+static int
+svcauth_gss_legacy_init(struct svc_rqst *rqstp,
+ struct rpc_gss_wire_cred *gc)
{
- struct kvec *argv = &rqstp->rq_arg.head[0];
- struct kvec *resv = &rqstp->rq_res.head[0];
+ 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));
- ret = gss_read_verf(gc, argv, &rqstp->rq_auth_stat,
- &rsikey.in_handle, &rsikey.in_token);
- if (ret)
- return ret;
+ if (dup_netobj(&rsikey.in_handle, &gc->gc_ctx))
+ return SVC_CLOSE;
+
+ /*
+ * RFC 2203 Section 5.2.2
+ *
+ * struct rpc_gss_init_arg {
+ * opaque gss_token<>;
+ * };
+ */
+ if (xdr_stream_decode_u32(xdr, &len) < 0) {
+ kfree(rsikey.in_handle.data);
+ return SVC_DENIED;
+ }
+ p = xdr_inline_decode(xdr, len);
+ if (!p) {
+ kfree(rsikey.in_handle.data);
+ return SVC_DENIED;
+ }
+ rsikey.in_token.data = kmalloc(len, GFP_KERNEL);
+ if (ZERO_OR_NULL_PTR(rsikey.in_token.data)) {
+ kfree(rsikey.in_handle.data);
+ return SVC_CLOSE;
+ }
+ memcpy(rsikey.in_token.data, p, len);
+ rsikey.in_token.len = len;
/* Perform upcall, or find upcall result: */
rsip = rsi_lookup(sn->rsi_cache, &rsikey);
@@ -1271,13 +1215,14 @@ static int svcauth_gss_legacy_init(struct svc_rqst *rqstp,
return SVC_CLOSE;
ret = SVC_CLOSE;
- /* Got an answer to the upcall; use it: */
- if (gss_write_init_verf(sn->rsc_cache, rqstp,
- &rsip->out_handle, &rsip->major_status))
+ if (!svcauth_gss_proc_init_verf(sn->rsc_cache, rqstp, &rsip->out_handle,
+ &rsip->major_status, GSS_SEQ_WIN))
+ goto out;
+ if (!svcxdr_set_accept_stat(rqstp))
goto out;
- if (gss_write_resv(resv, PAGE_SIZE,
- &rsip->out_handle, &rsip->out_token,
- rsip->major_status, rsip->minor_status))
+ if (!svcxdr_encode_gss_init_res(&rqstp->rq_res_stream, &rsip->out_handle,
+ &rsip->out_token, rsip->major_status,
+ rsip->minor_status, GSS_SEQ_WIN))
goto out;
ret = SVC_COMPLETE;
@@ -1361,7 +1306,6 @@ out:
static int svcauth_gss_proxy_init(struct svc_rqst *rqstp,
struct rpc_gss_wire_cred *gc)
{
- struct kvec *resv = &rqstp->rq_res.head[0];
struct xdr_netobj cli_handle;
struct gssp_upcall_data ud;
uint64_t handle;
@@ -1399,13 +1343,14 @@ static int svcauth_gss_proxy_init(struct svc_rqst *rqstp,
goto out;
}
- /* Got an answer to the upcall; use it: */
- if (gss_write_init_verf(sn->rsc_cache, rqstp,
- &cli_handle, &ud.major_status))
+ if (!svcauth_gss_proc_init_verf(sn->rsc_cache, rqstp, &cli_handle,
+ &ud.major_status, GSS_SEQ_WIN))
goto out;
- if (gss_write_resv(resv, PAGE_SIZE,
- &cli_handle, &ud.out_token,
- ud.major_status, ud.minor_status))
+ 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;
@@ -1442,6 +1387,31 @@ static bool use_gss_proxy(struct net *net)
return sn->use_gss_proxy;
}
+static noinline_for_stack int
+svcauth_gss_proc_init(struct svc_rqst *rqstp, struct rpc_gss_wire_cred *gc)
+{
+ struct xdr_stream *xdr = &rqstp->rq_arg_stream;
+ u32 flavor, len;
+ void *body;
+
+ /* Call's verf field: */
+ if (xdr_stream_decode_opaque_auth(xdr, &flavor, &body, &len) < 0)
+ return SVC_GARBAGE;
+ if (flavor != RPC_AUTH_NULL || len != 0) {
+ rqstp->rq_auth_stat = rpc_autherr_badverf;
+ return SVC_DENIED;
+ }
+
+ if (gc->gc_proc == RPC_GSS_PROC_INIT && gc->gc_ctx.len != 0) {
+ rqstp->rq_auth_stat = rpc_autherr_badcred;
+ return SVC_DENIED;
+ }
+
+ if (!use_gss_proxy(SVC_NET(rqstp)))
+ return svcauth_gss_legacy_init(rqstp, gc);
+ return svcauth_gss_proxy_init(rqstp, gc);
+}
+
#ifdef CONFIG_PROC_FS
static ssize_t write_gssp(struct file *file, const char __user *buf,
@@ -1524,6 +1494,56 @@ static void destroy_use_gss_proxy_proc_entry(struct net *net)
clear_gssp_clnt(sn);
}
}
+
+static ssize_t read_gss_krb5_enctypes(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct rpcsec_gss_oid oid = {
+ .len = 9,
+ .data = "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02",
+ };
+ struct gss_api_mech *mech;
+ ssize_t ret;
+
+ mech = gss_mech_get_by_OID(&oid);
+ if (!mech)
+ return 0;
+ if (!mech->gm_upcall_enctypes) {
+ gss_mech_put(mech);
+ return 0;
+ }
+
+ ret = simple_read_from_buffer(buf, count, ppos,
+ mech->gm_upcall_enctypes,
+ strlen(mech->gm_upcall_enctypes));
+ gss_mech_put(mech);
+ return ret;
+}
+
+static const struct proc_ops gss_krb5_enctypes_proc_ops = {
+ .proc_open = nonseekable_open,
+ .proc_read = read_gss_krb5_enctypes,
+};
+
+static int create_krb5_enctypes_proc_entry(struct net *net)
+{
+ struct sunrpc_net *sn = net_generic(net, sunrpc_net_id);
+
+ sn->gss_krb5_enctypes =
+ proc_create_data("gss_krb5_enctypes", S_IFREG | 0444,
+ sn->proc_net_rpc, &gss_krb5_enctypes_proc_ops,
+ net);
+ return sn->gss_krb5_enctypes ? 0 : -ENOMEM;
+}
+
+static void destroy_krb5_enctypes_proc_entry(struct net *net)
+{
+ struct sunrpc_net *sn = net_generic(net, sunrpc_net_id);
+
+ if (sn->gss_krb5_enctypes)
+ remove_proc_entry("gss_krb5_enctypes", sn->proc_net_rpc);
+}
+
#else /* CONFIG_PROC_FS */
static int create_use_gss_proxy_proc_entry(struct net *net)
@@ -1533,27 +1553,94 @@ static int create_use_gss_proxy_proc_entry(struct net *net)
static void destroy_use_gss_proxy_proc_entry(struct net *net) {}
+static int create_krb5_enctypes_proc_entry(struct net *net)
+{
+ return 0;
+}
+
+static void destroy_krb5_enctypes_proc_entry(struct net *net) {}
+
#endif /* CONFIG_PROC_FS */
/*
- * Accept an rpcsec packet.
- * If context establishment, punt to user space
- * If data exchange, verify/decrypt
- * If context destruction, handle here
- * In the context establishment and destruction case we encode
- * response here and return SVC_COMPLETE.
+ * 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;
+ * }
+ * };
+ */
+static bool
+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)
+ return false;
+ /*
+ * 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)
+ return false;
+
+ /* struct rpc_gss_cred_t */
+ if (xdr_stream_decode_u32(xdr, &gc->gc_v) < 0)
+ return false;
+ if (xdr_stream_decode_u32(xdr, &gc->gc_proc) < 0)
+ return false;
+ if (xdr_stream_decode_u32(xdr, &gc->gc_seq) < 0)
+ return false;
+ if (xdr_stream_decode_u32(xdr, &gc->gc_svc) < 0)
+ return false;
+ handle_len = xdr_stream_decode_opaque_inline(xdr,
+ (void **)&gc->gc_ctx.data,
+ body_len);
+ if (handle_len < 0)
+ return false;
+ if (body_len != XDR_UNIT * 5 + xdr_align_size(handle_len))
+ return false;
+
+ gc->gc_ctx.len = handle_len;
+ return true;
+}
+
+/**
+ * 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).
*/
static int
svcauth_gss_accept(struct svc_rqst *rqstp)
{
- struct kvec *argv = &rqstp->rq_arg.head[0];
- struct kvec *resv = &rqstp->rq_res.head[0];
- u32 crlen;
struct gss_svc_data *svcdata = rqstp->rq_auth_data;
+ __be32 *rpcstart;
struct rpc_gss_wire_cred *gc;
struct rsc *rsci = NULL;
- __be32 *rpcstart;
- __be32 *reject_stat = resv->iov_base + resv->iov_len;
int ret;
struct sunrpc_net *sn = net_generic(SVC_NET(rqstp), sunrpc_net_id);
@@ -1563,53 +1650,31 @@ svcauth_gss_accept(struct svc_rqst *rqstp)
if (!svcdata)
goto auth_err;
rqstp->rq_auth_data = svcdata;
- svcdata->verf_start = NULL;
+ svcdata->gsd_databody_offset = 0;
svcdata->rsci = NULL;
gc = &svcdata->clcred;
- /* start of rpc packet is 7 u32's back from here:
- * xid direction rpcversion prog vers proc flavour
- */
- rpcstart = argv->iov_base;
- rpcstart -= 7;
-
- /* credential is:
- * version(==1), proc(0,1,2,3), seq, service (1,2,3), handle
- * at least 5 u32s, and is preceded by length, so that makes 6.
- */
-
- if (argv->iov_len < 5 * 4)
+ if (!svcauth_gss_decode_credbody(&rqstp->rq_arg_stream, gc, &rpcstart))
goto auth_err;
- crlen = svc_getnl(argv);
- if (svc_getnl(argv) != RPC_GSS_VERSION)
- goto auth_err;
- gc->gc_proc = svc_getnl(argv);
- gc->gc_seq = svc_getnl(argv);
- gc->gc_svc = svc_getnl(argv);
- if (svc_safe_getnetobj(argv, &gc->gc_ctx))
- goto auth_err;
- if (crlen != round_up_to_quad(gc->gc_ctx.len) + 5 * 4)
- goto auth_err;
-
- if ((gc->gc_proc != RPC_GSS_PROC_DATA) && (rqstp->rq_proc != 0))
+ if (gc->gc_v != RPC_GSS_VERSION)
goto auth_err;
- rqstp->rq_auth_stat = rpc_autherr_badverf;
switch (gc->gc_proc) {
case RPC_GSS_PROC_INIT:
case RPC_GSS_PROC_CONTINUE_INIT:
- if (use_gss_proxy(SVC_NET(rqstp)))
- return svcauth_gss_proxy_init(rqstp, gc);
- else
- return svcauth_gss_legacy_init(rqstp, gc);
- case RPC_GSS_PROC_DATA:
+ if (rqstp->rq_proc != 0)
+ goto auth_err;
+ return svcauth_gss_proc_init(rqstp, gc);
case RPC_GSS_PROC_DESTROY:
- /* Look up the context, and check the verifier: */
+ if (rqstp->rq_proc != 0)
+ goto auth_err;
+ fallthrough;
+ case RPC_GSS_PROC_DATA:
rqstp->rq_auth_stat = rpcsec_gsserr_credproblem;
rsci = gss_svc_searchbyctx(sn->rsc_cache, &gc->gc_ctx);
if (!rsci)
goto auth_err;
- switch (gss_verify_header(rqstp, rsci, rpcstart, gc)) {
+ switch (svcauth_gss_verify_header(rqstp, rsci, rpcstart, gc)) {
case SVC_OK:
break;
case SVC_DENIED:
@@ -1619,6 +1684,8 @@ svcauth_gss_accept(struct svc_rqst *rqstp)
}
break;
default:
+ if (rqstp->rq_proc != 0)
+ goto auth_err;
rqstp->rq_auth_stat = rpc_autherr_rejectedcred;
goto auth_err;
}
@@ -1626,19 +1693,20 @@ svcauth_gss_accept(struct svc_rqst *rqstp)
/* now act upon the command: */
switch (gc->gc_proc) {
case RPC_GSS_PROC_DESTROY:
- if (gss_write_verf(rqstp, rsci->mechctx, gc->gc_seq))
+ if (!svcauth_gss_encode_verf(rqstp, rsci->mechctx, gc->gc_seq))
+ goto auth_err;
+ if (!svcxdr_set_accept_stat(rqstp))
goto auth_err;
/* Delete the entry from the cache_list and call cache_put */
sunrpc_cache_unhash(sn->rsc_cache, &rsci->h);
- if (resv->iov_len + 4 > PAGE_SIZE)
- goto drop;
- svc_putnl(resv, RPC_SUCCESS);
goto complete;
case RPC_GSS_PROC_DATA:
rqstp->rq_auth_stat = rpcsec_gsserr_ctxproblem;
- svcdata->verf_start = resv->iov_base + resv->iov_len;
- if (gss_write_verf(rqstp, rsci->mechctx, gc->gc_seq))
+ if (!svcauth_gss_encode_verf(rqstp, rsci->mechctx, gc->gc_seq))
+ goto auth_err;
+ if (!svcxdr_set_accept_stat(rqstp))
goto auth_err;
+ svcdata->gsd_databody_offset = xdr_stream_pos(&rqstp->rq_res_stream);
rqstp->rq_cred = rsci->cred;
get_group_info(rsci->cred.cr_group_info);
rqstp->rq_auth_stat = rpc_autherr_badcred;
@@ -1646,22 +1714,20 @@ svcauth_gss_accept(struct svc_rqst *rqstp)
case RPC_GSS_SVC_NONE:
break;
case RPC_GSS_SVC_INTEGRITY:
- /* placeholders for length and seq. number: */
- svc_putnl(resv, 0);
- svc_putnl(resv, 0);
- if (unwrap_integ_data(rqstp, &rqstp->rq_arg,
- gc->gc_seq, rsci->mechctx))
+ /* placeholders for body length and seq. number: */
+ xdr_reserve_space(&rqstp->rq_res_stream, XDR_UNIT * 2);
+ if (svcauth_gss_unwrap_integ(rqstp, gc->gc_seq,
+ rsci->mechctx))
goto garbage_args;
- rqstp->rq_auth_slack = RPC_MAX_AUTH_SIZE;
+ svcxdr_set_auth_slack(rqstp, RPC_MAX_AUTH_SIZE);
break;
case RPC_GSS_SVC_PRIVACY:
- /* placeholders for length and seq. number: */
- svc_putnl(resv, 0);
- svc_putnl(resv, 0);
- if (unwrap_priv_data(rqstp, &rqstp->rq_arg,
- gc->gc_seq, rsci->mechctx))
+ /* placeholders for body length and seq. number: */
+ xdr_reserve_space(&rqstp->rq_res_stream, XDR_UNIT * 2);
+ if (svcauth_gss_unwrap_priv(rqstp, gc->gc_seq,
+ rsci->mechctx))
goto garbage_args;
- rqstp->rq_auth_slack = RPC_MAX_AUTH_SIZE * 2;
+ svcxdr_set_auth_slack(rqstp, RPC_MAX_AUTH_SIZE * 2);
break;
default:
goto auth_err;
@@ -1680,8 +1746,7 @@ garbage_args:
ret = SVC_GARBAGE;
goto out;
auth_err:
- /* Restore write pointer to its original value: */
- xdr_ressize_check(rqstp, reject_stat);
+ xdr_truncate_encode(&rqstp->rq_res_stream, XDR_UNIT * 2);
ret = SVC_DENIED;
goto out;
complete:
@@ -1695,104 +1760,125 @@ out:
return ret;
}
-static __be32 *
-svcauth_gss_prepare_to_wrap(struct xdr_buf *resbuf, struct gss_svc_data *gsd)
+static u32
+svcauth_gss_prepare_to_wrap(struct svc_rqst *rqstp, struct gss_svc_data *gsd)
{
- __be32 *p;
- u32 verf_len;
+ u32 offset;
- p = gsd->verf_start;
- gsd->verf_start = NULL;
+ /* Release can be called twice, but we only wrap once. */
+ offset = gsd->gsd_databody_offset;
+ gsd->gsd_databody_offset = 0;
- /* If the reply stat is nonzero, don't wrap: */
- if (*(p-1) != rpc_success)
- return NULL;
- /* Skip the verifier: */
- p += 1;
- verf_len = ntohl(*p++);
- p += XDR_QUADLEN(verf_len);
- /* move accept_stat to right place: */
- memcpy(p, p + 2, 4);
- /* Also don't wrap if the accept stat is nonzero: */
- if (*p != rpc_success) {
- resbuf->head[0].iov_len -= 2 * 4;
- return NULL;
- }
- p++;
- return p;
+ /* 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;
}
-static inline int
-svcauth_gss_wrap_resp_integ(struct svc_rqst *rqstp)
+/*
+ * 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.
+ */
+static int svcauth_gss_wrap_integ(struct svc_rqst *rqstp)
{
- struct gss_svc_data *gsd = (struct gss_svc_data *)rqstp->rq_auth_data;
+ 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 *resbuf = &rqstp->rq_res;
- struct xdr_buf integ_buf;
- struct xdr_netobj mic;
- struct kvec *resv;
- __be32 *p;
- int integ_offset, integ_len;
- int stat = -EINVAL;
+ struct xdr_buf *buf = xdr->buf;
+ struct xdr_buf databody_integ;
+ struct xdr_netobj checksum;
+ u32 offset, maj_stat;
- p = svcauth_gss_prepare_to_wrap(resbuf, gsd);
- if (p == NULL)
- goto out;
- integ_offset = (u8 *)(p + 1) - (u8 *)resbuf->head[0].iov_base;
- integ_len = resbuf->len - integ_offset;
- if (integ_len & 3)
+ offset = svcauth_gss_prepare_to_wrap(rqstp, gsd);
+ if (!offset)
goto out;
- *p++ = htonl(integ_len);
- *p++ = htonl(gc->gc_seq);
- if (xdr_buf_subsegment(resbuf, &integ_buf, integ_offset, integ_len)) {
- WARN_ON_ONCE(1);
- goto out_err;
- }
- if (resbuf->tail[0].iov_base == NULL) {
- if (resbuf->head[0].iov_len + RPC_MAX_AUTH_SIZE > PAGE_SIZE)
- goto out_err;
- resbuf->tail[0].iov_base = resbuf->head[0].iov_base
- + resbuf->head[0].iov_len;
- resbuf->tail[0].iov_len = 0;
- }
- resv = &resbuf->tail[0];
- mic.data = (u8 *)resv->iov_base + resv->iov_len + 4;
- if (gss_get_mic(gsd->rsci->mechctx, &integ_buf, &mic))
- goto out_err;
- svc_putnl(resv, mic.len);
- memset(mic.data + mic.len, 0,
- round_up_to_quad(mic.len) - mic.len);
- resv->iov_len += XDR_QUADLEN(mic.len) << 2;
- /* not strictly required: */
- resbuf->len += XDR_QUADLEN(mic.len) << 2;
- if (resv->iov_len > PAGE_SIZE)
- goto out_err;
+
+ 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;
+
+ checksum.data = gsd->gsd_scratch;
+ maj_stat = gss_get_mic(gsd->rsci->mechctx, &databody_integ, &checksum);
+ if (maj_stat != GSS_S_COMPLETE)
+ goto bad_mic;
+
+ if (xdr_stream_encode_opaque(xdr, checksum.data, checksum.len) < 0)
+ goto wrap_failed;
+ xdr_commit_encode(xdr);
+
out:
- stat = 0;
-out_err:
- return stat;
+ return 0;
+
+bad_mic:
+ trace_rpcgss_svc_get_mic(rqstp, maj_stat);
+ return -EINVAL;
+wrap_failed:
+ trace_rpcgss_svc_wrap_failed(rqstp);
+ return -EINVAL;
}
-static inline int
-svcauth_gss_wrap_resp_priv(struct svc_rqst *rqstp)
+/*
+ * 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.
+ */
+static int svcauth_gss_wrap_priv(struct svc_rqst *rqstp)
{
- struct gss_svc_data *gsd = (struct gss_svc_data *)rqstp->rq_auth_data;
+ struct gss_svc_data *gsd = rqstp->rq_auth_data;
struct rpc_gss_wire_cred *gc = &gsd->clcred;
- struct xdr_buf *resbuf = &rqstp->rq_res;
- struct page **inpages = NULL;
- __be32 *p, *len;
- int offset;
- int pad;
-
- p = svcauth_gss_prepare_to_wrap(resbuf, gsd);
- if (p == NULL)
+ 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;
- len = p++;
- offset = (u8 *)p - (u8 *)resbuf->head[0].iov_base;
- *p++ = htonl(gc->gc_seq);
- inpages = resbuf->pages;
- /* XXX: Would be better to write some xdr helper functions for
- * nfs{2,3,4}xdr.c that place the data right, instead of copying: */
+
+ /*
+ * 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
@@ -1801,19 +1887,17 @@ svcauth_gss_wrap_resp_priv(struct svc_rqst *rqstp)
* there is RPC_MAX_AUTH_SIZE slack space available in
* both the head and tail.
*/
- if (resbuf->tail[0].iov_base) {
- if (resbuf->tail[0].iov_base >=
- resbuf->head[0].iov_base + PAGE_SIZE)
- return -EINVAL;
- if (resbuf->tail[0].iov_base < resbuf->head[0].iov_base)
- return -EINVAL;
- if (resbuf->tail[0].iov_len + resbuf->head[0].iov_len
+ 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)
- return -ENOMEM;
- memmove(resbuf->tail[0].iov_base + RPC_MAX_AUTH_SIZE,
- resbuf->tail[0].iov_base,
- resbuf->tail[0].iov_len);
- resbuf->tail[0].iov_base += RPC_MAX_AUTH_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
@@ -1822,55 +1906,73 @@ svcauth_gss_wrap_resp_priv(struct svc_rqst *rqstp)
* is RPC_MAX_AUTH_SIZE slack space available in both the
* head and tail.
*/
- if (resbuf->tail[0].iov_base == NULL) {
- if (resbuf->head[0].iov_len + 2*RPC_MAX_AUTH_SIZE > PAGE_SIZE)
- return -ENOMEM;
- resbuf->tail[0].iov_base = resbuf->head[0].iov_base
- + resbuf->head[0].iov_len + RPC_MAX_AUTH_SIZE;
- resbuf->tail[0].iov_len = 0;
+ 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;
}
- if (gss_wrap(gsd->rsci->mechctx, offset, resbuf, inpages))
- return -ENOMEM;
- *len = htonl(resbuf->len - offset);
- pad = 3 - ((resbuf->len - offset - 1)&3);
- p = (__be32 *)(resbuf->tail[0].iov_base + resbuf->tail[0].iov_len);
+
+ maj_stat = gss_wrap(gsd->rsci->mechctx, offset + XDR_UNIT, buf,
+ buf->pages);
+ if (maj_stat != GSS_S_COMPLETE)
+ goto bad_wrap;
+
+ /* Wrapping can change the size of databody_priv. */
+ if (xdr_encode_word(buf, offset, buf->len - offset - XDR_UNIT))
+ goto wrap_failed;
+ pad = xdr_pad_size(buf->len - offset - XDR_UNIT);
+ p = (__be32 *)(tail->iov_base + tail->iov_len);
memset(p, 0, pad);
- resbuf->tail[0].iov_len += pad;
- resbuf->len += pad;
+ tail->iov_len += pad;
+ buf->len += pad;
+
return 0;
+wrap_failed:
+ trace_rpcgss_svc_wrap_failed(rqstp);
+ return -EINVAL;
+bad_wrap:
+ trace_rpcgss_svc_wrap(rqstp, maj_stat);
+ return -ENOMEM;
}
+/**
+ * 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
+ *
+ * XXX: These return values do not match the return values documented
+ * for the auth_ops ->release method in linux/sunrpc/svcauth.h.
+ */
static int
svcauth_gss_release(struct svc_rqst *rqstp)
{
- struct gss_svc_data *gsd = (struct gss_svc_data *)rqstp->rq_auth_data;
- struct rpc_gss_wire_cred *gc;
- struct xdr_buf *resbuf = &rqstp->rq_res;
- int stat = -EINVAL;
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;
- /* Release can be called twice, but we only wrap once. */
- if (gsd->verf_start == NULL)
- goto out;
- /* normally not set till svc_send, but we need it here: */
- /* XXX: what for? Do we mess it up the moment we call svc_putu32
- * or whatever? */
- resbuf->len = total_buf_len(resbuf);
+
switch (gc->gc_svc) {
case RPC_GSS_SVC_NONE:
break;
case RPC_GSS_SVC_INTEGRITY:
- stat = svcauth_gss_wrap_resp_integ(rqstp);
+ stat = svcauth_gss_wrap_integ(rqstp);
if (stat)
goto out_err;
break;
case RPC_GSS_SVC_PRIVACY:
- stat = svcauth_gss_wrap_resp_priv(rqstp);
+ stat = svcauth_gss_wrap_priv(rqstp);
if (stat)
goto out_err;
break;
@@ -1997,7 +2099,15 @@ gss_svc_init_net(struct net *net)
rv = create_use_gss_proxy_proc_entry(net);
if (rv)
goto out2;
+
+ rv = create_krb5_enctypes_proc_entry(net);
+ if (rv)
+ goto out3;
+
return 0;
+
+out3:
+ destroy_use_gss_proxy_proc_entry(net);
out2:
rsi_cache_destroy_net(net);
out1:
@@ -2008,6 +2118,7 @@ out1:
void
gss_svc_shutdown_net(struct net *net)
{
+ destroy_krb5_enctypes_proc_entry(net);
destroy_use_gss_proxy_proc_entry(net);
rsi_cache_destroy_net(net);
rsc_cache_destroy_net(net);