| /* |
| * Modifications for Lustre |
| * |
| * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. |
| * |
| * Copyright (c) 2011, 2012, Intel Corporation. |
| * |
| * Author: Eric Mei <ericm@clusterfs.com> |
| */ |
| |
| /* |
| * linux/net/sunrpc/auth_gss.c |
| * |
| * RPCSEC_GSS client authentication. |
| * |
| * Copyright (c) 2000 The Regents of the University of Michigan. |
| * All rights reserved. |
| * |
| * Dug Song <dugsong@monkey.org> |
| * Andy Adamson <andros@umich.edu> |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of the University nor the names of its |
| * contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR |
| * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
| * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
| * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| * |
| */ |
| |
| #define DEBUG_SUBSYSTEM S_SEC |
| #include <linux/init.h> |
| #include <linux/module.h> |
| #include <linux/slab.h> |
| #include <linux/dcache.h> |
| #include <linux/fs.h> |
| #include <linux/mutex.h> |
| #include <asm/atomic.h> |
| |
| #include <obd.h> |
| #include <obd_class.h> |
| #include <obd_support.h> |
| #include <obd_cksum.h> |
| #include <lustre/lustre_idl.h> |
| #include <lustre_net.h> |
| #include <lustre_import.h> |
| #include <lustre_sec.h> |
| |
| #include "gss_err.h" |
| #include "gss_internal.h" |
| #include "gss_api.h" |
| |
| #include <linux/crypto.h> |
| #include <linux/crc32.h> |
| |
| /* |
| * early reply have fixed size, respectively in privacy and integrity mode. |
| * so we calculate them only once. |
| */ |
| static int gss_at_reply_off_integ; |
| static int gss_at_reply_off_priv; |
| |
| |
| static inline int msg_last_segidx(struct lustre_msg *msg) |
| { |
| LASSERT(msg->lm_bufcount > 0); |
| return msg->lm_bufcount - 1; |
| } |
| static inline int msg_last_seglen(struct lustre_msg *msg) |
| { |
| return msg->lm_buflens[msg_last_segidx(msg)]; |
| } |
| |
| /******************************************** |
| * wire data swabber * |
| ********************************************/ |
| |
| static |
| void gss_header_swabber(struct gss_header *ghdr) |
| { |
| __swab32s(&ghdr->gh_flags); |
| __swab32s(&ghdr->gh_proc); |
| __swab32s(&ghdr->gh_seq); |
| __swab32s(&ghdr->gh_svc); |
| __swab32s(&ghdr->gh_pad1); |
| __swab32s(&ghdr->gh_handle.len); |
| } |
| |
| struct gss_header *gss_swab_header(struct lustre_msg *msg, int segment, |
| int swabbed) |
| { |
| struct gss_header *ghdr; |
| |
| ghdr = lustre_msg_buf(msg, segment, sizeof(*ghdr)); |
| if (ghdr == NULL) |
| return NULL; |
| |
| if (swabbed) |
| gss_header_swabber(ghdr); |
| |
| if (sizeof(*ghdr) + ghdr->gh_handle.len > msg->lm_buflens[segment]) { |
| CERROR("gss header has length %d, now %u received\n", |
| (int) sizeof(*ghdr) + ghdr->gh_handle.len, |
| msg->lm_buflens[segment]); |
| return NULL; |
| } |
| |
| return ghdr; |
| } |
| |
| #if 0 |
| static |
| void gss_netobj_swabber(netobj_t *obj) |
| { |
| __swab32s(&obj->len); |
| } |
| |
| netobj_t *gss_swab_netobj(struct lustre_msg *msg, int segment) |
| { |
| netobj_t *obj; |
| |
| obj = lustre_swab_buf(msg, segment, sizeof(*obj), gss_netobj_swabber); |
| if (obj && sizeof(*obj) + obj->len > msg->lm_buflens[segment]) { |
| CERROR("netobj require length %u but only %u received\n", |
| (unsigned int) sizeof(*obj) + obj->len, |
| msg->lm_buflens[segment]); |
| return NULL; |
| } |
| |
| return obj; |
| } |
| #endif |
| |
| /* |
| * payload should be obtained from mechanism. but currently since we |
| * only support kerberos, we could simply use fixed value. |
| * krb5 "meta" data: |
| * - krb5 header: 16 |
| * - krb5 checksum: 20 |
| * |
| * for privacy mode, payload also include the cipher text which has the same |
| * size as plain text, plus possible confounder, padding both at maximum cipher |
| * block size. |
| */ |
| #define GSS_KRB5_INTEG_MAX_PAYLOAD (40) |
| |
| static inline |
| int gss_mech_payload(struct gss_ctx *mechctx, int msgsize, int privacy) |
| { |
| if (privacy) |
| return GSS_KRB5_INTEG_MAX_PAYLOAD + 16 + 16 + 16 + msgsize; |
| else |
| return GSS_KRB5_INTEG_MAX_PAYLOAD; |
| } |
| |
| /* |
| * return signature size, otherwise < 0 to indicate error |
| */ |
| static int gss_sign_msg(struct lustre_msg *msg, |
| struct gss_ctx *mechctx, |
| enum lustre_sec_part sp, |
| __u32 flags, __u32 proc, __u32 seq, __u32 svc, |
| rawobj_t *handle) |
| { |
| struct gss_header *ghdr; |
| rawobj_t text[4], mic; |
| int textcnt, max_textcnt, mic_idx; |
| __u32 major; |
| |
| LASSERT(msg->lm_bufcount >= 2); |
| |
| /* gss hdr */ |
| LASSERT(msg->lm_buflens[0] >= |
| sizeof(*ghdr) + (handle ? handle->len : 0)); |
| ghdr = lustre_msg_buf(msg, 0, 0); |
| |
| ghdr->gh_version = PTLRPC_GSS_VERSION; |
| ghdr->gh_sp = (__u8) sp; |
| ghdr->gh_flags = flags; |
| ghdr->gh_proc = proc; |
| ghdr->gh_seq = seq; |
| ghdr->gh_svc = svc; |
| if (!handle) { |
| /* fill in a fake one */ |
| ghdr->gh_handle.len = 0; |
| } else { |
| ghdr->gh_handle.len = handle->len; |
| memcpy(ghdr->gh_handle.data, handle->data, handle->len); |
| } |
| |
| /* no actual signature for null mode */ |
| if (svc == SPTLRPC_SVC_NULL) |
| return lustre_msg_size_v2(msg->lm_bufcount, msg->lm_buflens); |
| |
| /* MIC */ |
| mic_idx = msg_last_segidx(msg); |
| max_textcnt = (svc == SPTLRPC_SVC_AUTH) ? 1 : mic_idx; |
| |
| for (textcnt = 0; textcnt < max_textcnt; textcnt++) { |
| text[textcnt].len = msg->lm_buflens[textcnt]; |
| text[textcnt].data = lustre_msg_buf(msg, textcnt, 0); |
| } |
| |
| mic.len = msg->lm_buflens[mic_idx]; |
| mic.data = lustre_msg_buf(msg, mic_idx, 0); |
| |
| major = lgss_get_mic(mechctx, textcnt, text, 0, NULL, &mic); |
| if (major != GSS_S_COMPLETE) { |
| CERROR("fail to generate MIC: %08x\n", major); |
| return -EPERM; |
| } |
| LASSERT(mic.len <= msg->lm_buflens[mic_idx]); |
| |
| return lustre_shrink_msg(msg, mic_idx, mic.len, 0); |
| } |
| |
| /* |
| * return gss error |
| */ |
| static |
| __u32 gss_verify_msg(struct lustre_msg *msg, |
| struct gss_ctx *mechctx, |
| __u32 svc) |
| { |
| rawobj_t text[4], mic; |
| int textcnt, max_textcnt; |
| int mic_idx; |
| __u32 major; |
| |
| LASSERT(msg->lm_bufcount >= 2); |
| |
| if (svc == SPTLRPC_SVC_NULL) |
| return GSS_S_COMPLETE; |
| |
| mic_idx = msg_last_segidx(msg); |
| max_textcnt = (svc == SPTLRPC_SVC_AUTH) ? 1 : mic_idx; |
| |
| for (textcnt = 0; textcnt < max_textcnt; textcnt++) { |
| text[textcnt].len = msg->lm_buflens[textcnt]; |
| text[textcnt].data = lustre_msg_buf(msg, textcnt, 0); |
| } |
| |
| mic.len = msg->lm_buflens[mic_idx]; |
| mic.data = lustre_msg_buf(msg, mic_idx, 0); |
| |
| major = lgss_verify_mic(mechctx, textcnt, text, 0, NULL, &mic); |
| if (major != GSS_S_COMPLETE) |
| CERROR("mic verify error: %08x\n", major); |
| |
| return major; |
| } |
| |
| /* |
| * return gss error code |
| */ |
| static |
| __u32 gss_unseal_msg(struct gss_ctx *mechctx, |
| struct lustre_msg *msgbuf, |
| int *msg_len, int msgbuf_len) |
| { |
| rawobj_t clear_obj, hdrobj, token; |
| __u8 *clear_buf; |
| int clear_buflen; |
| __u32 major; |
| |
| if (msgbuf->lm_bufcount != 2) { |
| CERROR("invalid bufcount %d\n", msgbuf->lm_bufcount); |
| return GSS_S_FAILURE; |
| } |
| |
| /* allocate a temporary clear text buffer, same sized as token, |
| * we assume the final clear text size <= token size */ |
| clear_buflen = lustre_msg_buflen(msgbuf, 1); |
| OBD_ALLOC_LARGE(clear_buf, clear_buflen); |
| if (!clear_buf) |
| return GSS_S_FAILURE; |
| |
| /* buffer objects */ |
| hdrobj.len = lustre_msg_buflen(msgbuf, 0); |
| hdrobj.data = lustre_msg_buf(msgbuf, 0, 0); |
| token.len = lustre_msg_buflen(msgbuf, 1); |
| token.data = lustre_msg_buf(msgbuf, 1, 0); |
| clear_obj.len = clear_buflen; |
| clear_obj.data = clear_buf; |
| |
| major = lgss_unwrap(mechctx, &hdrobj, &token, &clear_obj); |
| if (major != GSS_S_COMPLETE) { |
| CERROR("unwrap message error: %08x\n", major); |
| GOTO(out_free, major = GSS_S_FAILURE); |
| } |
| LASSERT(clear_obj.len <= clear_buflen); |
| LASSERT(clear_obj.len <= msgbuf_len); |
| |
| /* now the decrypted message */ |
| memcpy(msgbuf, clear_obj.data, clear_obj.len); |
| *msg_len = clear_obj.len; |
| |
| major = GSS_S_COMPLETE; |
| out_free: |
| OBD_FREE_LARGE(clear_buf, clear_buflen); |
| return major; |
| } |
| |
| /******************************************** |
| * gss client context manipulation helpers * |
| ********************************************/ |
| |
| int cli_ctx_expire(struct ptlrpc_cli_ctx *ctx) |
| { |
| LASSERT(atomic_read(&ctx->cc_refcount)); |
| |
| if (!test_and_set_bit(PTLRPC_CTX_DEAD_BIT, &ctx->cc_flags)) { |
| if (!ctx->cc_early_expire) |
| clear_bit(PTLRPC_CTX_UPTODATE_BIT, &ctx->cc_flags); |
| |
| CWARN("ctx %p(%u->%s) get expired: %lu(%+lds)\n", |
| ctx, ctx->cc_vcred.vc_uid, sec2target_str(ctx->cc_sec), |
| ctx->cc_expire, |
| ctx->cc_expire == 0 ? 0 : |
| cfs_time_sub(ctx->cc_expire, cfs_time_current_sec())); |
| |
| sptlrpc_cli_ctx_wakeup(ctx); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * return 1 if the context is dead. |
| */ |
| int cli_ctx_check_death(struct ptlrpc_cli_ctx *ctx) |
| { |
| if (unlikely(cli_ctx_is_dead(ctx))) |
| return 1; |
| |
| /* expire is 0 means never expire. a newly created gss context |
| * which during upcall may has 0 expiration */ |
| if (ctx->cc_expire == 0) |
| return 0; |
| |
| /* check real expiration */ |
| if (cfs_time_after(ctx->cc_expire, cfs_time_current_sec())) |
| return 0; |
| |
| cli_ctx_expire(ctx); |
| return 1; |
| } |
| |
| void gss_cli_ctx_uptodate(struct gss_cli_ctx *gctx) |
| { |
| struct ptlrpc_cli_ctx *ctx = &gctx->gc_base; |
| unsigned long ctx_expiry; |
| |
| if (lgss_inquire_context(gctx->gc_mechctx, &ctx_expiry)) { |
| CERROR("ctx %p(%u): unable to inquire, expire it now\n", |
| gctx, ctx->cc_vcred.vc_uid); |
| ctx_expiry = 1; /* make it expired now */ |
| } |
| |
| ctx->cc_expire = gss_round_ctx_expiry(ctx_expiry, |
| ctx->cc_sec->ps_flvr.sf_flags); |
| |
| /* At this point this ctx might have been marked as dead by |
| * someone else, in which case nobody will make further use |
| * of it. we don't care, and mark it UPTODATE will help |
| * destroying server side context when it be destroied. */ |
| set_bit(PTLRPC_CTX_UPTODATE_BIT, &ctx->cc_flags); |
| |
| if (sec_is_reverse(ctx->cc_sec)) { |
| CWARN("server installed reverse ctx %p idx "LPX64", " |
| "expiry %lu(%+lds)\n", ctx, |
| gss_handle_to_u64(&gctx->gc_handle), |
| ctx->cc_expire, ctx->cc_expire - cfs_time_current_sec()); |
| } else { |
| CWARN("client refreshed ctx %p idx "LPX64" (%u->%s), " |
| "expiry %lu(%+lds)\n", ctx, |
| gss_handle_to_u64(&gctx->gc_handle), |
| ctx->cc_vcred.vc_uid, sec2target_str(ctx->cc_sec), |
| ctx->cc_expire, ctx->cc_expire - cfs_time_current_sec()); |
| |
| /* install reverse svc ctx for root context */ |
| if (ctx->cc_vcred.vc_uid == 0) |
| gss_sec_install_rctx(ctx->cc_sec->ps_import, |
| ctx->cc_sec, ctx); |
| } |
| |
| sptlrpc_cli_ctx_wakeup(ctx); |
| } |
| |
| static void gss_cli_ctx_finalize(struct gss_cli_ctx *gctx) |
| { |
| LASSERT(gctx->gc_base.cc_sec); |
| |
| if (gctx->gc_mechctx) { |
| lgss_delete_sec_context(&gctx->gc_mechctx); |
| gctx->gc_mechctx = NULL; |
| } |
| |
| if (!rawobj_empty(&gctx->gc_svc_handle)) { |
| /* forward ctx: mark buddy reverse svcctx soon-expire. */ |
| if (!sec_is_reverse(gctx->gc_base.cc_sec) && |
| !rawobj_empty(&gctx->gc_svc_handle)) |
| gss_svc_upcall_expire_rvs_ctx(&gctx->gc_svc_handle); |
| |
| rawobj_free(&gctx->gc_svc_handle); |
| } |
| |
| rawobj_free(&gctx->gc_handle); |
| } |
| |
| /* |
| * Based on sequence number algorithm as specified in RFC 2203. |
| * |
| * modified for our own problem: arriving request has valid sequence number, |
| * but unwrapping request might cost a long time, after that its sequence |
| * are not valid anymore (fall behind the window). It rarely happen, mostly |
| * under extreme load. |
| * |
| * note we should not check sequence before verify the integrity of incoming |
| * request, because just one attacking request with high sequence number might |
| * cause all following request be dropped. |
| * |
| * so here we use a multi-phase approach: prepare 2 sequence windows, |
| * "main window" for normal sequence and "back window" for fall behind sequence. |
| * and 3-phase checking mechanism: |
| * 0 - before integrity verification, perform a initial sequence checking in |
| * main window, which only try and don't actually set any bits. if the |
| * sequence is high above the window or fit in the window and the bit |
| * is 0, then accept and proceed to integrity verification. otherwise |
| * reject this sequence. |
| * 1 - after integrity verification, check in main window again. if this |
| * sequence is high above the window or fit in the window and the bit |
| * is 0, then set the bit and accept; if it fit in the window but bit |
| * already set, then reject; if it fall behind the window, then proceed |
| * to phase 2. |
| * 2 - check in back window. if it is high above the window or fit in the |
| * window and the bit is 0, then set the bit and accept. otherwise reject. |
| * |
| * return value: |
| * 1: looks like a replay |
| * 0: is ok |
| * -1: is a replay |
| * |
| * note phase 0 is necessary, because otherwise replay attacking request of |
| * sequence which between the 2 windows can't be detected. |
| * |
| * this mechanism can't totally solve the problem, but could help much less |
| * number of valid requests be dropped. |
| */ |
| static |
| int gss_do_check_seq(unsigned long *window, __u32 win_size, __u32 *max_seq, |
| __u32 seq_num, int phase) |
| { |
| LASSERT(phase >= 0 && phase <= 2); |
| |
| if (seq_num > *max_seq) { |
| /* |
| * 1. high above the window |
| */ |
| if (phase == 0) |
| return 0; |
| |
| if (seq_num >= *max_seq + win_size) { |
| memset(window, 0, win_size / 8); |
| *max_seq = seq_num; |
| } else { |
| while (*max_seq < seq_num) { |
| (*max_seq)++; |
| __clear_bit((*max_seq) % win_size, window); |
| } |
| } |
| __set_bit(seq_num % win_size, window); |
| } else if (seq_num + win_size <= *max_seq) { |
| /* |
| * 2. low behind the window |
| */ |
| if (phase == 0 || phase == 2) |
| goto replay; |
| |
| CWARN("seq %u is %u behind (size %d), check backup window\n", |
| seq_num, *max_seq - win_size - seq_num, win_size); |
| return 1; |
| } else { |
| /* |
| * 3. fit into the window |
| */ |
| switch (phase) { |
| case 0: |
| if (test_bit(seq_num % win_size, window)) |
| goto replay; |
| break; |
| case 1: |
| case 2: |
| if (__test_and_set_bit(seq_num % win_size, window)) |
| goto replay; |
| break; |
| } |
| } |
| |
| return 0; |
| |
| replay: |
| CERROR("seq %u (%s %s window) is a replay: max %u, winsize %d\n", |
| seq_num, |
| seq_num + win_size > *max_seq ? "in" : "behind", |
| phase == 2 ? "backup " : "main", |
| *max_seq, win_size); |
| return -1; |
| } |
| |
| /* |
| * Based on sequence number algorithm as specified in RFC 2203. |
| * |
| * if @set == 0: initial check, don't set any bit in window |
| * if @sec == 1: final check, set bit in window |
| */ |
| int gss_check_seq_num(struct gss_svc_seq_data *ssd, __u32 seq_num, int set) |
| { |
| int rc = 0; |
| |
| spin_lock(&ssd->ssd_lock); |
| |
| if (set == 0) { |
| /* |
| * phase 0 testing |
| */ |
| rc = gss_do_check_seq(ssd->ssd_win_main, GSS_SEQ_WIN_MAIN, |
| &ssd->ssd_max_main, seq_num, 0); |
| if (unlikely(rc)) |
| gss_stat_oos_record_svc(0, 1); |
| } else { |
| /* |
| * phase 1 checking main window |
| */ |
| rc = gss_do_check_seq(ssd->ssd_win_main, GSS_SEQ_WIN_MAIN, |
| &ssd->ssd_max_main, seq_num, 1); |
| switch (rc) { |
| case -1: |
| gss_stat_oos_record_svc(1, 1); |
| /* fall through */ |
| case 0: |
| goto exit; |
| } |
| /* |
| * phase 2 checking back window |
| */ |
| rc = gss_do_check_seq(ssd->ssd_win_back, GSS_SEQ_WIN_BACK, |
| &ssd->ssd_max_back, seq_num, 2); |
| if (rc) |
| gss_stat_oos_record_svc(2, 1); |
| else |
| gss_stat_oos_record_svc(2, 0); |
| } |
| exit: |
| spin_unlock(&ssd->ssd_lock); |
| return rc; |
| } |
| |
| /*************************************** |
| * cred APIs * |
| ***************************************/ |
| |
| static inline int gss_cli_payload(struct ptlrpc_cli_ctx *ctx, |
| int msgsize, int privacy) |
| { |
| return gss_mech_payload(NULL, msgsize, privacy); |
| } |
| |
| static int gss_cli_bulk_payload(struct ptlrpc_cli_ctx *ctx, |
| struct sptlrpc_flavor *flvr, |
| int reply, int read) |
| { |
| int payload = sizeof(struct ptlrpc_bulk_sec_desc); |
| |
| LASSERT(SPTLRPC_FLVR_BULK_TYPE(flvr->sf_rpc) == SPTLRPC_BULK_DEFAULT); |
| |
| if ((!reply && !read) || (reply && read)) { |
| switch (SPTLRPC_FLVR_BULK_SVC(flvr->sf_rpc)) { |
| case SPTLRPC_BULK_SVC_NULL: |
| break; |
| case SPTLRPC_BULK_SVC_INTG: |
| payload += gss_cli_payload(ctx, 0, 0); |
| break; |
| case SPTLRPC_BULK_SVC_PRIV: |
| payload += gss_cli_payload(ctx, 0, 1); |
| break; |
| case SPTLRPC_BULK_SVC_AUTH: |
| default: |
| LBUG(); |
| } |
| } |
| |
| return payload; |
| } |
| |
| int gss_cli_ctx_match(struct ptlrpc_cli_ctx *ctx, struct vfs_cred *vcred) |
| { |
| return (ctx->cc_vcred.vc_uid == vcred->vc_uid); |
| } |
| |
| void gss_cli_ctx_flags2str(unsigned long flags, char *buf, int bufsize) |
| { |
| buf[0] = '\0'; |
| |
| if (flags & PTLRPC_CTX_NEW) |
| strncat(buf, "new,", bufsize); |
| if (flags & PTLRPC_CTX_UPTODATE) |
| strncat(buf, "uptodate,", bufsize); |
| if (flags & PTLRPC_CTX_DEAD) |
| strncat(buf, "dead,", bufsize); |
| if (flags & PTLRPC_CTX_ERROR) |
| strncat(buf, "error,", bufsize); |
| if (flags & PTLRPC_CTX_CACHED) |
| strncat(buf, "cached,", bufsize); |
| if (flags & PTLRPC_CTX_ETERNAL) |
| strncat(buf, "eternal,", bufsize); |
| if (buf[0] == '\0') |
| strncat(buf, "-,", bufsize); |
| |
| buf[strlen(buf) - 1] = '\0'; |
| } |
| |
| int gss_cli_ctx_sign(struct ptlrpc_cli_ctx *ctx, |
| struct ptlrpc_request *req) |
| { |
| struct gss_cli_ctx *gctx = ctx2gctx(ctx); |
| __u32 flags = 0, seq, svc; |
| int rc; |
| |
| LASSERT(req->rq_reqbuf); |
| LASSERT(req->rq_reqbuf->lm_bufcount >= 2); |
| LASSERT(req->rq_cli_ctx == ctx); |
| |
| /* nothing to do for context negotiation RPCs */ |
| if (req->rq_ctx_init) |
| return 0; |
| |
| svc = SPTLRPC_FLVR_SVC(req->rq_flvr.sf_rpc); |
| if (req->rq_pack_bulk) |
| flags |= LUSTRE_GSS_PACK_BULK; |
| if (req->rq_pack_udesc) |
| flags |= LUSTRE_GSS_PACK_USER; |
| |
| redo: |
| seq = atomic_inc_return(&gctx->gc_seq); |
| |
| rc = gss_sign_msg(req->rq_reqbuf, gctx->gc_mechctx, |
| ctx->cc_sec->ps_part, |
| flags, gctx->gc_proc, seq, svc, |
| &gctx->gc_handle); |
| if (rc < 0) |
| return rc; |
| |
| /* gss_sign_msg() msg might take long time to finish, in which period |
| * more rpcs could be wrapped up and sent out. if we found too many |
| * of them we should repack this rpc, because sent it too late might |
| * lead to the sequence number fall behind the window on server and |
| * be dropped. also applies to gss_cli_ctx_seal(). |
| * |
| * Note: null mode dosen't check sequence number. */ |
| if (svc != SPTLRPC_SVC_NULL && |
| atomic_read(&gctx->gc_seq) - seq > GSS_SEQ_REPACK_THRESHOLD) { |
| int behind = atomic_read(&gctx->gc_seq) - seq; |
| |
| gss_stat_oos_record_cli(behind); |
| CWARN("req %p: %u behind, retry signing\n", req, behind); |
| goto redo; |
| } |
| |
| req->rq_reqdata_len = rc; |
| return 0; |
| } |
| |
| static |
| int gss_cli_ctx_handle_err_notify(struct ptlrpc_cli_ctx *ctx, |
| struct ptlrpc_request *req, |
| struct gss_header *ghdr) |
| { |
| struct gss_err_header *errhdr; |
| int rc; |
| |
| LASSERT(ghdr->gh_proc == PTLRPC_GSS_PROC_ERR); |
| |
| errhdr = (struct gss_err_header *) ghdr; |
| |
| CWARN("req x"LPU64"/t"LPU64", ctx %p idx "LPX64"(%u->%s): " |
| "%sserver respond (%08x/%08x)\n", |
| req->rq_xid, req->rq_transno, ctx, |
| gss_handle_to_u64(&ctx2gctx(ctx)->gc_handle), |
| ctx->cc_vcred.vc_uid, sec2target_str(ctx->cc_sec), |
| sec_is_reverse(ctx->cc_sec) ? "reverse" : "", |
| errhdr->gh_major, errhdr->gh_minor); |
| |
| /* context fini rpc, let it failed */ |
| if (req->rq_ctx_fini) { |
| CWARN("context fini rpc failed\n"); |
| return -EINVAL; |
| } |
| |
| /* reverse sec, just return error, don't expire this ctx because it's |
| * crucial to callback rpcs. note if the callback rpc failed because |
| * of bit flip during network transfer, the client will be evicted |
| * directly. so more gracefully we probably want let it retry for |
| * number of times. */ |
| if (sec_is_reverse(ctx->cc_sec)) |
| return -EINVAL; |
| |
| if (errhdr->gh_major != GSS_S_NO_CONTEXT && |
| errhdr->gh_major != GSS_S_BAD_SIG) |
| return -EACCES; |
| |
| /* server return NO_CONTEXT might be caused by context expire |
| * or server reboot/failover. we try to refresh a new ctx which |
| * be transparent to upper layer. |
| * |
| * In some cases, our gss handle is possible to be incidentally |
| * identical to another handle since the handle itself is not |
| * fully random. In krb5 case, the GSS_S_BAD_SIG will be |
| * returned, maybe other gss error for other mechanism. |
| * |
| * if we add new mechanism, make sure the correct error are |
| * returned in this case. */ |
| CWARN("%s: server might lost the context, retrying\n", |
| errhdr->gh_major == GSS_S_NO_CONTEXT ? "NO_CONTEXT" : "BAD_SIG"); |
| |
| sptlrpc_cli_ctx_expire(ctx); |
| |
| /* we need replace the ctx right here, otherwise during |
| * resent we'll hit the logic in sptlrpc_req_refresh_ctx() |
| * which keep the ctx with RESEND flag, thus we'll never |
| * get rid of this ctx. */ |
| rc = sptlrpc_req_replace_dead_ctx(req); |
| if (rc == 0) |
| req->rq_resend = 1; |
| |
| return rc; |
| } |
| |
| int gss_cli_ctx_verify(struct ptlrpc_cli_ctx *ctx, |
| struct ptlrpc_request *req) |
| { |
| struct gss_cli_ctx *gctx; |
| struct gss_header *ghdr, *reqhdr; |
| struct lustre_msg *msg = req->rq_repdata; |
| __u32 major; |
| int pack_bulk, swabbed, rc = 0; |
| |
| LASSERT(req->rq_cli_ctx == ctx); |
| LASSERT(msg); |
| |
| gctx = container_of(ctx, struct gss_cli_ctx, gc_base); |
| |
| /* special case for context negotiation, rq_repmsg/rq_replen actually |
| * are not used currently. but early reply always be treated normally */ |
| if (req->rq_ctx_init && !req->rq_early) { |
| req->rq_repmsg = lustre_msg_buf(msg, 1, 0); |
| req->rq_replen = msg->lm_buflens[1]; |
| return 0; |
| } |
| |
| if (msg->lm_bufcount < 2 || msg->lm_bufcount > 4) { |
| CERROR("unexpected bufcount %u\n", msg->lm_bufcount); |
| return -EPROTO; |
| } |
| |
| swabbed = ptlrpc_rep_need_swab(req); |
| |
| ghdr = gss_swab_header(msg, 0, swabbed); |
| if (ghdr == NULL) { |
| CERROR("can't decode gss header\n"); |
| return -EPROTO; |
| } |
| |
| /* sanity checks */ |
| reqhdr = lustre_msg_buf(msg, 0, sizeof(*reqhdr)); |
| LASSERT(reqhdr); |
| |
| if (ghdr->gh_version != reqhdr->gh_version) { |
| CERROR("gss version %u mismatch, expect %u\n", |
| ghdr->gh_version, reqhdr->gh_version); |
| return -EPROTO; |
| } |
| |
| switch (ghdr->gh_proc) { |
| case PTLRPC_GSS_PROC_DATA: |
| pack_bulk = ghdr->gh_flags & LUSTRE_GSS_PACK_BULK; |
| |
| if (!req->rq_early && |
| !equi(req->rq_pack_bulk == 1, pack_bulk)) { |
| CERROR("%s bulk flag in reply\n", |
| req->rq_pack_bulk ? "missing" : "unexpected"); |
| return -EPROTO; |
| } |
| |
| if (ghdr->gh_seq != reqhdr->gh_seq) { |
| CERROR("seqnum %u mismatch, expect %u\n", |
| ghdr->gh_seq, reqhdr->gh_seq); |
| return -EPROTO; |
| } |
| |
| if (ghdr->gh_svc != reqhdr->gh_svc) { |
| CERROR("svc %u mismatch, expect %u\n", |
| ghdr->gh_svc, reqhdr->gh_svc); |
| return -EPROTO; |
| } |
| |
| if (swabbed) |
| gss_header_swabber(ghdr); |
| |
| major = gss_verify_msg(msg, gctx->gc_mechctx, reqhdr->gh_svc); |
| if (major != GSS_S_COMPLETE) { |
| CERROR("failed to verify reply: %x\n", major); |
| return -EPERM; |
| } |
| |
| if (req->rq_early && reqhdr->gh_svc == SPTLRPC_SVC_NULL) { |
| __u32 cksum; |
| |
| cksum = crc32_le(!(__u32) 0, |
| lustre_msg_buf(msg, 1, 0), |
| lustre_msg_buflen(msg, 1)); |
| if (cksum != msg->lm_cksum) { |
| CWARN("early reply checksum mismatch: " |
| "%08x != %08x\n", cksum, msg->lm_cksum); |
| return -EPROTO; |
| } |
| } |
| |
| if (pack_bulk) { |
| /* bulk checksum is right after the lustre msg */ |
| if (msg->lm_bufcount < 3) { |
| CERROR("Invalid reply bufcount %u\n", |
| msg->lm_bufcount); |
| return -EPROTO; |
| } |
| |
| rc = bulk_sec_desc_unpack(msg, 2, swabbed); |
| if (rc) { |
| CERROR("unpack bulk desc: %d\n", rc); |
| return rc; |
| } |
| } |
| |
| req->rq_repmsg = lustre_msg_buf(msg, 1, 0); |
| req->rq_replen = msg->lm_buflens[1]; |
| break; |
| case PTLRPC_GSS_PROC_ERR: |
| if (req->rq_early) { |
| CERROR("server return error with early reply\n"); |
| rc = -EPROTO; |
| } else { |
| rc = gss_cli_ctx_handle_err_notify(ctx, req, ghdr); |
| } |
| break; |
| default: |
| CERROR("unknown gss proc %d\n", ghdr->gh_proc); |
| rc = -EPROTO; |
| } |
| |
| return rc; |
| } |
| |
| int gss_cli_ctx_seal(struct ptlrpc_cli_ctx *ctx, |
| struct ptlrpc_request *req) |
| { |
| struct gss_cli_ctx *gctx; |
| rawobj_t hdrobj, msgobj, token; |
| struct gss_header *ghdr; |
| __u32 buflens[2], major; |
| int wiresize, rc; |
| |
| LASSERT(req->rq_clrbuf); |
| LASSERT(req->rq_cli_ctx == ctx); |
| LASSERT(req->rq_reqlen); |
| |
| gctx = container_of(ctx, struct gss_cli_ctx, gc_base); |
| |
| /* final clear data length */ |
| req->rq_clrdata_len = lustre_msg_size_v2(req->rq_clrbuf->lm_bufcount, |
| req->rq_clrbuf->lm_buflens); |
| |
| /* calculate wire data length */ |
| buflens[0] = PTLRPC_GSS_HEADER_SIZE; |
| buflens[1] = gss_cli_payload(&gctx->gc_base, req->rq_clrdata_len, 1); |
| wiresize = lustre_msg_size_v2(2, buflens); |
| |
| /* allocate wire buffer */ |
| if (req->rq_pool) { |
| /* pre-allocated */ |
| LASSERT(req->rq_reqbuf); |
| LASSERT(req->rq_reqbuf != req->rq_clrbuf); |
| LASSERT(req->rq_reqbuf_len >= wiresize); |
| } else { |
| OBD_ALLOC_LARGE(req->rq_reqbuf, wiresize); |
| if (!req->rq_reqbuf) |
| return -ENOMEM; |
| req->rq_reqbuf_len = wiresize; |
| } |
| |
| lustre_init_msg_v2(req->rq_reqbuf, 2, buflens, NULL); |
| req->rq_reqbuf->lm_secflvr = req->rq_flvr.sf_rpc; |
| |
| /* gss header */ |
| ghdr = lustre_msg_buf(req->rq_reqbuf, 0, 0); |
| ghdr->gh_version = PTLRPC_GSS_VERSION; |
| ghdr->gh_sp = (__u8) ctx->cc_sec->ps_part; |
| ghdr->gh_flags = 0; |
| ghdr->gh_proc = gctx->gc_proc; |
| ghdr->gh_svc = SPTLRPC_SVC_PRIV; |
| ghdr->gh_handle.len = gctx->gc_handle.len; |
| memcpy(ghdr->gh_handle.data, gctx->gc_handle.data, gctx->gc_handle.len); |
| if (req->rq_pack_bulk) |
| ghdr->gh_flags |= LUSTRE_GSS_PACK_BULK; |
| if (req->rq_pack_udesc) |
| ghdr->gh_flags |= LUSTRE_GSS_PACK_USER; |
| |
| redo: |
| ghdr->gh_seq = atomic_inc_return(&gctx->gc_seq); |
| |
| /* buffer objects */ |
| hdrobj.len = PTLRPC_GSS_HEADER_SIZE; |
| hdrobj.data = (__u8 *) ghdr; |
| msgobj.len = req->rq_clrdata_len; |
| msgobj.data = (__u8 *) req->rq_clrbuf; |
| token.len = lustre_msg_buflen(req->rq_reqbuf, 1); |
| token.data = lustre_msg_buf(req->rq_reqbuf, 1, 0); |
| |
| major = lgss_wrap(gctx->gc_mechctx, &hdrobj, &msgobj, |
| req->rq_clrbuf_len, &token); |
| if (major != GSS_S_COMPLETE) { |
| CERROR("priv: wrap message error: %08x\n", major); |
| GOTO(err_free, rc = -EPERM); |
| } |
| LASSERT(token.len <= buflens[1]); |
| |
| /* see explain in gss_cli_ctx_sign() */ |
| if (unlikely(atomic_read(&gctx->gc_seq) - ghdr->gh_seq > |
| GSS_SEQ_REPACK_THRESHOLD)) { |
| int behind = atomic_read(&gctx->gc_seq) - ghdr->gh_seq; |
| |
| gss_stat_oos_record_cli(behind); |
| CWARN("req %p: %u behind, retry sealing\n", req, behind); |
| |
| ghdr->gh_seq = atomic_inc_return(&gctx->gc_seq); |
| goto redo; |
| } |
| |
| /* now set the final wire data length */ |
| req->rq_reqdata_len = lustre_shrink_msg(req->rq_reqbuf, 1, token.len,0); |
| return 0; |
| |
| err_free: |
| if (!req->rq_pool) { |
| OBD_FREE_LARGE(req->rq_reqbuf, req->rq_reqbuf_len); |
| req->rq_reqbuf = NULL; |
| req->rq_reqbuf_len = 0; |
| } |
| return rc; |
| } |
| |
| int gss_cli_ctx_unseal(struct ptlrpc_cli_ctx *ctx, |
| struct ptlrpc_request *req) |
| { |
| struct gss_cli_ctx *gctx; |
| struct gss_header *ghdr; |
| struct lustre_msg *msg = req->rq_repdata; |
| int msglen, pack_bulk, swabbed, rc; |
| __u32 major; |
| |
| LASSERT(req->rq_cli_ctx == ctx); |
| LASSERT(req->rq_ctx_init == 0); |
| LASSERT(msg); |
| |
| gctx = container_of(ctx, struct gss_cli_ctx, gc_base); |
| swabbed = ptlrpc_rep_need_swab(req); |
| |
| ghdr = gss_swab_header(msg, 0, swabbed); |
| if (ghdr == NULL) { |
| CERROR("can't decode gss header\n"); |
| return -EPROTO; |
| } |
| |
| /* sanity checks */ |
| if (ghdr->gh_version != PTLRPC_GSS_VERSION) { |
| CERROR("gss version %u mismatch, expect %u\n", |
| ghdr->gh_version, PTLRPC_GSS_VERSION); |
| return -EPROTO; |
| } |
| |
| switch (ghdr->gh_proc) { |
| case PTLRPC_GSS_PROC_DATA: |
| pack_bulk = ghdr->gh_flags & LUSTRE_GSS_PACK_BULK; |
| |
| if (!req->rq_early && |
| !equi(req->rq_pack_bulk == 1, pack_bulk)) { |
| CERROR("%s bulk flag in reply\n", |
| req->rq_pack_bulk ? "missing" : "unexpected"); |
| return -EPROTO; |
| } |
| |
| if (swabbed) |
| gss_header_swabber(ghdr); |
| |
| /* use rq_repdata_len as buffer size, which assume unseal |
| * doesn't need extra memory space. for precise control, we'd |
| * better calculate out actual buffer size as |
| * (repbuf_len - offset - repdata_len) */ |
| major = gss_unseal_msg(gctx->gc_mechctx, msg, |
| &msglen, req->rq_repdata_len); |
| if (major != GSS_S_COMPLETE) { |
| CERROR("failed to unwrap reply: %x\n", major); |
| rc = -EPERM; |
| break; |
| } |
| |
| swabbed = __lustre_unpack_msg(msg, msglen); |
| if (swabbed < 0) { |
| CERROR("Failed to unpack after decryption\n"); |
| return -EPROTO; |
| } |
| |
| if (msg->lm_bufcount < 1) { |
| CERROR("Invalid reply buffer: empty\n"); |
| return -EPROTO; |
| } |
| |
| if (pack_bulk) { |
| if (msg->lm_bufcount < 2) { |
| CERROR("bufcount %u: missing bulk sec desc\n", |
| msg->lm_bufcount); |
| return -EPROTO; |
| } |
| |
| /* bulk checksum is the last segment */ |
| if (bulk_sec_desc_unpack(msg, msg->lm_bufcount - 1, |
| swabbed)) |
| return -EPROTO; |
| } |
| |
| req->rq_repmsg = lustre_msg_buf(msg, 0, 0); |
| req->rq_replen = msg->lm_buflens[0]; |
| |
| rc = 0; |
| break; |
| case PTLRPC_GSS_PROC_ERR: |
| if (req->rq_early) { |
| CERROR("server return error with early reply\n"); |
| rc = -EPROTO; |
| } else { |
| rc = gss_cli_ctx_handle_err_notify(ctx, req, ghdr); |
| } |
| break; |
| default: |
| CERROR("unexpected proc %d\n", ghdr->gh_proc); |
| rc = -EPERM; |
| } |
| |
| return rc; |
| } |
| |
| /********************************************* |
| * reverse context installation * |
| *********************************************/ |
| |
| static inline |
| int gss_install_rvs_svc_ctx(struct obd_import *imp, |
| struct gss_sec *gsec, |
| struct gss_cli_ctx *gctx) |
| { |
| return gss_svc_upcall_install_rvs_ctx(imp, gsec, gctx); |
| } |
| |
| /********************************************* |
| * GSS security APIs * |
| *********************************************/ |
| int gss_sec_create_common(struct gss_sec *gsec, |
| struct ptlrpc_sec_policy *policy, |
| struct obd_import *imp, |
| struct ptlrpc_svc_ctx *svcctx, |
| struct sptlrpc_flavor *sf) |
| { |
| struct ptlrpc_sec *sec; |
| |
| LASSERT(imp); |
| LASSERT(SPTLRPC_FLVR_POLICY(sf->sf_rpc) == SPTLRPC_POLICY_GSS); |
| |
| gsec->gs_mech = lgss_subflavor_to_mech( |
| SPTLRPC_FLVR_BASE_SUB(sf->sf_rpc)); |
| if (!gsec->gs_mech) { |
| CERROR("gss backend 0x%x not found\n", |
| SPTLRPC_FLVR_BASE_SUB(sf->sf_rpc)); |
| return -EOPNOTSUPP; |
| } |
| |
| spin_lock_init(&gsec->gs_lock); |
| gsec->gs_rvs_hdl = 0ULL; |
| |
| /* initialize upper ptlrpc_sec */ |
| sec = &gsec->gs_base; |
| sec->ps_policy = policy; |
| atomic_set(&sec->ps_refcount, 0); |
| atomic_set(&sec->ps_nctx, 0); |
| sec->ps_id = sptlrpc_get_next_secid(); |
| sec->ps_flvr = *sf; |
| sec->ps_import = class_import_get(imp); |
| spin_lock_init(&sec->ps_lock); |
| INIT_LIST_HEAD(&sec->ps_gc_list); |
| |
| if (!svcctx) { |
| sec->ps_gc_interval = GSS_GC_INTERVAL; |
| } else { |
| LASSERT(sec_is_reverse(sec)); |
| |
| /* never do gc on reverse sec */ |
| sec->ps_gc_interval = 0; |
| } |
| |
| if (SPTLRPC_FLVR_BULK_SVC(sec->ps_flvr.sf_rpc) == SPTLRPC_BULK_SVC_PRIV) |
| sptlrpc_enc_pool_add_user(); |
| |
| CDEBUG(D_SEC, "create %s%s@%p\n", (svcctx ? "reverse " : ""), |
| policy->sp_name, gsec); |
| return 0; |
| } |
| |
| void gss_sec_destroy_common(struct gss_sec *gsec) |
| { |
| struct ptlrpc_sec *sec = &gsec->gs_base; |
| |
| LASSERT(sec->ps_import); |
| LASSERT(atomic_read(&sec->ps_refcount) == 0); |
| LASSERT(atomic_read(&sec->ps_nctx) == 0); |
| |
| if (gsec->gs_mech) { |
| lgss_mech_put(gsec->gs_mech); |
| gsec->gs_mech = NULL; |
| } |
| |
| class_import_put(sec->ps_import); |
| |
| if (SPTLRPC_FLVR_BULK_SVC(sec->ps_flvr.sf_rpc) == SPTLRPC_BULK_SVC_PRIV) |
| sptlrpc_enc_pool_del_user(); |
| } |
| |
| void gss_sec_kill(struct ptlrpc_sec *sec) |
| { |
| sec->ps_dying = 1; |
| } |
| |
| int gss_cli_ctx_init_common(struct ptlrpc_sec *sec, |
| struct ptlrpc_cli_ctx *ctx, |
| struct ptlrpc_ctx_ops *ctxops, |
| struct vfs_cred *vcred) |
| { |
| struct gss_cli_ctx *gctx = ctx2gctx(ctx); |
| |
| gctx->gc_win = 0; |
| atomic_set(&gctx->gc_seq, 0); |
| |
| INIT_HLIST_NODE(&ctx->cc_cache); |
| atomic_set(&ctx->cc_refcount, 0); |
| ctx->cc_sec = sec; |
| ctx->cc_ops = ctxops; |
| ctx->cc_expire = 0; |
| ctx->cc_flags = PTLRPC_CTX_NEW; |
| ctx->cc_vcred = *vcred; |
| spin_lock_init(&ctx->cc_lock); |
| INIT_LIST_HEAD(&ctx->cc_req_list); |
| INIT_LIST_HEAD(&ctx->cc_gc_chain); |
| |
| /* take a ref on belonging sec, balanced in ctx destroying */ |
| atomic_inc(&sec->ps_refcount); |
| /* statistic only */ |
| atomic_inc(&sec->ps_nctx); |
| |
| CDEBUG(D_SEC, "%s@%p: create ctx %p(%u->%s)\n", |
| sec->ps_policy->sp_name, ctx->cc_sec, |
| ctx, ctx->cc_vcred.vc_uid, sec2target_str(ctx->cc_sec)); |
| return 0; |
| } |
| |
| /* |
| * return value: |
| * 1: the context has been taken care of by someone else |
| * 0: proceed to really destroy the context locally |
| */ |
| int gss_cli_ctx_fini_common(struct ptlrpc_sec *sec, |
| struct ptlrpc_cli_ctx *ctx) |
| { |
| struct gss_cli_ctx *gctx = ctx2gctx(ctx); |
| |
| LASSERT(atomic_read(&sec->ps_nctx) > 0); |
| LASSERT(atomic_read(&ctx->cc_refcount) == 0); |
| LASSERT(ctx->cc_sec == sec); |
| |
| /* |
| * remove UPTODATE flag of reverse ctx thus we won't send fini rpc, |
| * this is to avoid potential problems of client side reverse svc ctx |
| * be mis-destroyed in various recovery senarios. anyway client can |
| * manage its reverse ctx well by associating it with its buddy ctx. |
| */ |
| if (sec_is_reverse(sec)) |
| ctx->cc_flags &= ~PTLRPC_CTX_UPTODATE; |
| |
| if (gctx->gc_mechctx) { |
| /* the final context fini rpc will use this ctx too, and it's |
| * asynchronous which finished by request_out_callback(). so |
| * we add refcount, whoever drop finally drop the refcount to |
| * 0 should responsible for the rest of destroy. */ |
| atomic_inc(&ctx->cc_refcount); |
| |
| gss_do_ctx_fini_rpc(gctx); |
| gss_cli_ctx_finalize(gctx); |
| |
| if (!atomic_dec_and_test(&ctx->cc_refcount)) |
| return 1; |
| } |
| |
| if (sec_is_reverse(sec)) |
| CWARN("reverse sec %p: destroy ctx %p\n", |
| ctx->cc_sec, ctx); |
| else |
| CWARN("%s@%p: destroy ctx %p(%u->%s)\n", |
| sec->ps_policy->sp_name, ctx->cc_sec, |
| ctx, ctx->cc_vcred.vc_uid, sec2target_str(ctx->cc_sec)); |
| |
| return 0; |
| } |
| |
| static |
| int gss_alloc_reqbuf_intg(struct ptlrpc_sec *sec, |
| struct ptlrpc_request *req, |
| int svc, int msgsize) |
| { |
| int bufsize, txtsize; |
| int bufcnt = 2; |
| __u32 buflens[5]; |
| |
| /* |
| * on-wire data layout: |
| * - gss header |
| * - lustre message |
| * - user descriptor (optional) |
| * - bulk sec descriptor (optional) |
| * - signature (optional) |
| * - svc == NULL: NULL |
| * - svc == AUTH: signature of gss header |
| * - svc == INTG: signature of all above |
| * |
| * if this is context negotiation, reserver fixed space |
| * at the last (signature) segment regardless of svc mode. |
| */ |
| |
| buflens[0] = PTLRPC_GSS_HEADER_SIZE; |
| txtsize = buflens[0]; |
| |
| buflens[1] = msgsize; |
| if (svc == SPTLRPC_SVC_INTG) |
| txtsize += buflens[1]; |
| |
| if (req->rq_pack_udesc) { |
| buflens[bufcnt] = sptlrpc_current_user_desc_size(); |
| if (svc == SPTLRPC_SVC_INTG) |
| txtsize += buflens[bufcnt]; |
| bufcnt++; |
| } |
| |
| if (req->rq_pack_bulk) { |
| buflens[bufcnt] = gss_cli_bulk_payload(req->rq_cli_ctx, |
| &req->rq_flvr, |
| 0, req->rq_bulk_read); |
| if (svc == SPTLRPC_SVC_INTG) |
| txtsize += buflens[bufcnt]; |
| bufcnt++; |
| } |
| |
| if (req->rq_ctx_init) |
| buflens[bufcnt++] = GSS_CTX_INIT_MAX_LEN; |
| else if (svc != SPTLRPC_SVC_NULL) |
| buflens[bufcnt++] = gss_cli_payload(req->rq_cli_ctx, txtsize,0); |
| |
| bufsize = lustre_msg_size_v2(bufcnt, buflens); |
| |
| if (!req->rq_reqbuf) { |
| bufsize = size_roundup_power2(bufsize); |
| |
| OBD_ALLOC_LARGE(req->rq_reqbuf, bufsize); |
| if (!req->rq_reqbuf) |
| return -ENOMEM; |
| |
| req->rq_reqbuf_len = bufsize; |
| } else { |
| LASSERT(req->rq_pool); |
| LASSERT(req->rq_reqbuf_len >= bufsize); |
| memset(req->rq_reqbuf, 0, bufsize); |
| } |
| |
| lustre_init_msg_v2(req->rq_reqbuf, bufcnt, buflens, NULL); |
| req->rq_reqbuf->lm_secflvr = req->rq_flvr.sf_rpc; |
| |
| req->rq_reqmsg = lustre_msg_buf(req->rq_reqbuf, 1, msgsize); |
| LASSERT(req->rq_reqmsg); |
| |
| /* pack user desc here, later we might leave current user's process */ |
| if (req->rq_pack_udesc) |
| sptlrpc_pack_user_desc(req->rq_reqbuf, 2); |
| |
| return 0; |
| } |
| |
| static |
| int gss_alloc_reqbuf_priv(struct ptlrpc_sec *sec, |
| struct ptlrpc_request *req, |
| int msgsize) |
| { |
| __u32 ibuflens[3], wbuflens[2]; |
| int ibufcnt; |
| int clearsize, wiresize; |
| |
| LASSERT(req->rq_clrbuf == NULL); |
| LASSERT(req->rq_clrbuf_len == 0); |
| |
| /* Inner (clear) buffers |
| * - lustre message |
| * - user descriptor (optional) |
| * - bulk checksum (optional) |
| */ |
| ibufcnt = 1; |
| ibuflens[0] = msgsize; |
| |
| if (req->rq_pack_udesc) |
| ibuflens[ibufcnt++] = sptlrpc_current_user_desc_size(); |
| if (req->rq_pack_bulk) |
| ibuflens[ibufcnt++] = gss_cli_bulk_payload(req->rq_cli_ctx, |
| &req->rq_flvr, 0, |
| req->rq_bulk_read); |
| |
| clearsize = lustre_msg_size_v2(ibufcnt, ibuflens); |
| /* to allow append padding during encryption */ |
| clearsize += GSS_MAX_CIPHER_BLOCK; |
| |
| /* Wrapper (wire) buffers |
| * - gss header |
| * - cipher text |
| */ |
| wbuflens[0] = PTLRPC_GSS_HEADER_SIZE; |
| wbuflens[1] = gss_cli_payload(req->rq_cli_ctx, clearsize, 1); |
| wiresize = lustre_msg_size_v2(2, wbuflens); |
| |
| if (req->rq_pool) { |
| /* rq_reqbuf is preallocated */ |
| LASSERT(req->rq_reqbuf); |
| LASSERT(req->rq_reqbuf_len >= wiresize); |
| |
| memset(req->rq_reqbuf, 0, req->rq_reqbuf_len); |
| |
| /* if the pre-allocated buffer is big enough, we just pack |
| * both clear buf & request buf in it, to avoid more alloc. */ |
| if (clearsize + wiresize <= req->rq_reqbuf_len) { |
| req->rq_clrbuf = |
| (void *) (((char *) req->rq_reqbuf) + wiresize); |
| } else { |
| CWARN("pre-allocated buf size %d is not enough for " |
| "both clear (%d) and cipher (%d) text, proceed " |
| "with extra allocation\n", req->rq_reqbuf_len, |
| clearsize, wiresize); |
| } |
| } |
| |
| if (!req->rq_clrbuf) { |
| clearsize = size_roundup_power2(clearsize); |
| |
| OBD_ALLOC_LARGE(req->rq_clrbuf, clearsize); |
| if (!req->rq_clrbuf) |
| return -ENOMEM; |
| } |
| req->rq_clrbuf_len = clearsize; |
| |
| lustre_init_msg_v2(req->rq_clrbuf, ibufcnt, ibuflens, NULL); |
| req->rq_reqmsg = lustre_msg_buf(req->rq_clrbuf, 0, msgsize); |
| |
| if (req->rq_pack_udesc) |
| sptlrpc_pack_user_desc(req->rq_clrbuf, 1); |
| |
| return 0; |
| } |
| |
| /* |
| * NOTE: any change of request buffer allocation should also consider |
| * changing enlarge_reqbuf() series functions. |
| */ |
| int gss_alloc_reqbuf(struct ptlrpc_sec *sec, |
| struct ptlrpc_request *req, |
| int msgsize) |
| { |
| int svc = SPTLRPC_FLVR_SVC(req->rq_flvr.sf_rpc); |
| |
| LASSERT(!req->rq_pack_bulk || |
| (req->rq_bulk_read || req->rq_bulk_write)); |
| |
| switch (svc) { |
| case SPTLRPC_SVC_NULL: |
| case SPTLRPC_SVC_AUTH: |
| case SPTLRPC_SVC_INTG: |
| return gss_alloc_reqbuf_intg(sec, req, svc, msgsize); |
| case SPTLRPC_SVC_PRIV: |
| return gss_alloc_reqbuf_priv(sec, req, msgsize); |
| default: |
| LASSERTF(0, "bad rpc flavor %x\n", req->rq_flvr.sf_rpc); |
| return 0; |
| } |
| } |
| |
| void gss_free_reqbuf(struct ptlrpc_sec *sec, |
| struct ptlrpc_request *req) |
| { |
| int privacy; |
| |
| LASSERT(!req->rq_pool || req->rq_reqbuf); |
| privacy = SPTLRPC_FLVR_SVC(req->rq_flvr.sf_rpc) == SPTLRPC_SVC_PRIV; |
| |
| if (!req->rq_clrbuf) |
| goto release_reqbuf; |
| |
| /* release clear buffer */ |
| LASSERT(privacy); |
| LASSERT(req->rq_clrbuf_len); |
| |
| if (req->rq_pool == NULL || |
| req->rq_clrbuf < req->rq_reqbuf || |
| (char *) req->rq_clrbuf >= |
| (char *) req->rq_reqbuf + req->rq_reqbuf_len) |
| OBD_FREE_LARGE(req->rq_clrbuf, req->rq_clrbuf_len); |
| |
| req->rq_clrbuf = NULL; |
| req->rq_clrbuf_len = 0; |
| |
| release_reqbuf: |
| if (!req->rq_pool && req->rq_reqbuf) { |
| LASSERT(req->rq_reqbuf_len); |
| |
| OBD_FREE_LARGE(req->rq_reqbuf, req->rq_reqbuf_len); |
| req->rq_reqbuf = NULL; |
| req->rq_reqbuf_len = 0; |
| } |
| } |
| |
| static int do_alloc_repbuf(struct ptlrpc_request *req, int bufsize) |
| { |
| bufsize = size_roundup_power2(bufsize); |
| |
| OBD_ALLOC_LARGE(req->rq_repbuf, bufsize); |
| if (!req->rq_repbuf) |
| return -ENOMEM; |
| |
| req->rq_repbuf_len = bufsize; |
| return 0; |
| } |
| |
| static |
| int gss_alloc_repbuf_intg(struct ptlrpc_sec *sec, |
| struct ptlrpc_request *req, |
| int svc, int msgsize) |
| { |
| int txtsize; |
| __u32 buflens[4]; |
| int bufcnt = 2; |
| int alloc_size; |
| |
| /* |
| * on-wire data layout: |
| * - gss header |
| * - lustre message |
| * - bulk sec descriptor (optional) |
| * - signature (optional) |
| * - svc == NULL: NULL |
| * - svc == AUTH: signature of gss header |
| * - svc == INTG: signature of all above |
| * |
| * if this is context negotiation, reserver fixed space |
| * at the last (signature) segment regardless of svc mode. |
| */ |
| |
| buflens[0] = PTLRPC_GSS_HEADER_SIZE; |
| txtsize = buflens[0]; |
| |
| buflens[1] = msgsize; |
| if (svc == SPTLRPC_SVC_INTG) |
| txtsize += buflens[1]; |
| |
| if (req->rq_pack_bulk) { |
| buflens[bufcnt] = gss_cli_bulk_payload(req->rq_cli_ctx, |
| &req->rq_flvr, |
| 1, req->rq_bulk_read); |
| if (svc == SPTLRPC_SVC_INTG) |
| txtsize += buflens[bufcnt]; |
| bufcnt++; |
| } |
| |
| if (req->rq_ctx_init) |
| buflens[bufcnt++] = GSS_CTX_INIT_MAX_LEN; |
| else if (svc != SPTLRPC_SVC_NULL) |
| buflens[bufcnt++] = gss_cli_payload(req->rq_cli_ctx, txtsize,0); |
| |
| alloc_size = lustre_msg_size_v2(bufcnt, buflens); |
| |
| /* add space for early reply */ |
| alloc_size += gss_at_reply_off_integ; |
| |
| return do_alloc_repbuf(req, alloc_size); |
| } |
| |
| static |
| int gss_alloc_repbuf_priv(struct ptlrpc_sec *sec, |
| struct ptlrpc_request *req, |
| int msgsize) |
| { |
| int txtsize; |
| __u32 buflens[2]; |
| int bufcnt; |
| int alloc_size; |
| |
| /* inner buffers */ |
| bufcnt = 1; |
| buflens[0] = msgsize; |
| |
| if (req->rq_pack_bulk) |
| buflens[bufcnt++] = gss_cli_bulk_payload(req->rq_cli_ctx, |
| &req->rq_flvr, |
| 1, req->rq_bulk_read); |
| txtsize = lustre_msg_size_v2(bufcnt, buflens); |
| txtsize += GSS_MAX_CIPHER_BLOCK; |
| |
| /* wrapper buffers */ |
| bufcnt = 2; |
| buflens[0] = PTLRPC_GSS_HEADER_SIZE; |
| buflens[1] = gss_cli_payload(req->rq_cli_ctx, txtsize, 1); |
| |
| alloc_size = lustre_msg_size_v2(bufcnt, buflens); |
| /* add space for early reply */ |
| alloc_size += gss_at_reply_off_priv; |
| |
| return do_alloc_repbuf(req, alloc_size); |
| } |
| |
| int gss_alloc_repbuf(struct ptlrpc_sec *sec, |
| struct ptlrpc_request *req, |
| int msgsize) |
| { |
| int svc = SPTLRPC_FLVR_SVC(req->rq_flvr.sf_rpc); |
| |
| LASSERT(!req->rq_pack_bulk || |
| (req->rq_bulk_read || req->rq_bulk_write)); |
| |
| switch (svc) { |
| case SPTLRPC_SVC_NULL: |
| case SPTLRPC_SVC_AUTH: |
| case SPTLRPC_SVC_INTG: |
| return gss_alloc_repbuf_intg(sec, req, svc, msgsize); |
| case SPTLRPC_SVC_PRIV: |
| return gss_alloc_repbuf_priv(sec, req, msgsize); |
| default: |
| LASSERTF(0, "bad rpc flavor %x\n", req->rq_flvr.sf_rpc); |
| return 0; |
| } |
| } |
| |
| void gss_free_repbuf(struct ptlrpc_sec *sec, |
| struct ptlrpc_request *req) |
| { |
| OBD_FREE_LARGE(req->rq_repbuf, req->rq_repbuf_len); |
| req->rq_repbuf = NULL; |
| req->rq_repbuf_len = 0; |
| req->rq_repdata = NULL; |
| req->rq_repdata_len = 0; |
| } |
| |
| static int get_enlarged_msgsize(struct lustre_msg *msg, |
| int segment, int newsize) |
| { |
| int save, newmsg_size; |
| |
| LASSERT(newsize >= msg->lm_buflens[segment]); |
| |
| save = msg->lm_buflens[segment]; |
| msg->lm_buflens[segment] = newsize; |
| newmsg_size = lustre_msg_size_v2(msg->lm_bufcount, msg->lm_buflens); |
| msg->lm_buflens[segment] = save; |
| |
| return newmsg_size; |
| } |
| |
| static int get_enlarged_msgsize2(struct lustre_msg *msg, |
| int segment1, int newsize1, |
| int segment2, int newsize2) |
| { |
| int save1, save2, newmsg_size; |
| |
| LASSERT(newsize1 >= msg->lm_buflens[segment1]); |
| LASSERT(newsize2 >= msg->lm_buflens[segment2]); |
| |
| save1 = msg->lm_buflens[segment1]; |
| save2 = msg->lm_buflens[segment2]; |
| msg->lm_buflens[segment1] = newsize1; |
| msg->lm_buflens[segment2] = newsize2; |
| newmsg_size = lustre_msg_size_v2(msg->lm_bufcount, msg->lm_buflens); |
| msg->lm_buflens[segment1] = save1; |
| msg->lm_buflens[segment2] = save2; |
| |
| return newmsg_size; |
| } |
| |
| static |
| int gss_enlarge_reqbuf_intg(struct ptlrpc_sec *sec, |
| struct ptlrpc_request *req, |
| int svc, |
| int segment, int newsize) |
| { |
| struct lustre_msg *newbuf; |
| int txtsize, sigsize = 0, i; |
| int newmsg_size, newbuf_size; |
| |
| /* |
| * gss header is at seg 0; |
| * embedded msg is at seg 1; |
| * signature (if any) is at the last seg |
| */ |
| LASSERT(req->rq_reqbuf); |
| LASSERT(req->rq_reqbuf_len > req->rq_reqlen); |
| LASSERT(req->rq_reqbuf->lm_bufcount >= 2); |
| LASSERT(lustre_msg_buf(req->rq_reqbuf, 1, 0) == req->rq_reqmsg); |
| |
| /* 1. compute new embedded msg size */ |
| newmsg_size = get_enlarged_msgsize(req->rq_reqmsg, segment, newsize); |
| LASSERT(newmsg_size >= req->rq_reqbuf->lm_buflens[1]); |
| |
| /* 2. compute new wrapper msg size */ |
| if (svc == SPTLRPC_SVC_NULL) { |
| /* no signature, get size directly */ |
| newbuf_size = get_enlarged_msgsize(req->rq_reqbuf, |
| 1, newmsg_size); |
| } else { |
| txtsize = req->rq_reqbuf->lm_buflens[0]; |
| |
| if (svc == SPTLRPC_SVC_INTG) { |
| for (i = 1; i < req->rq_reqbuf->lm_bufcount; i++) |
| txtsize += req->rq_reqbuf->lm_buflens[i]; |
| txtsize += newmsg_size - req->rq_reqbuf->lm_buflens[1]; |
| } |
| |
| sigsize = gss_cli_payload(req->rq_cli_ctx, txtsize, 0); |
| LASSERT(sigsize >= msg_last_seglen(req->rq_reqbuf)); |
| |
| newbuf_size = get_enlarged_msgsize2( |
| req->rq_reqbuf, |
| 1, newmsg_size, |
| msg_last_segidx(req->rq_reqbuf), |
| sigsize); |
| } |
| |
| /* request from pool should always have enough buffer */ |
| LASSERT(!req->rq_pool || req->rq_reqbuf_len >= newbuf_size); |
| |
| if (req->rq_reqbuf_len < newbuf_size) { |
| newbuf_size = size_roundup_power2(newbuf_size); |
| |
| OBD_ALLOC_LARGE(newbuf, newbuf_size); |
| if (newbuf == NULL) |
| return -ENOMEM; |
| |
| memcpy(newbuf, req->rq_reqbuf, req->rq_reqbuf_len); |
| |
| OBD_FREE_LARGE(req->rq_reqbuf, req->rq_reqbuf_len); |
| req->rq_reqbuf = newbuf; |
| req->rq_reqbuf_len = newbuf_size; |
| req->rq_reqmsg = lustre_msg_buf(req->rq_reqbuf, 1, 0); |
| } |
| |
| /* do enlargement, from wrapper to embedded, from end to begin */ |
| if (svc != SPTLRPC_SVC_NULL) |
| _sptlrpc_enlarge_msg_inplace(req->rq_reqbuf, |
| msg_last_segidx(req->rq_reqbuf), |
| sigsize); |
| |
| _sptlrpc_enlarge_msg_inplace(req->rq_reqbuf, 1, newmsg_size); |
| _sptlrpc_enlarge_msg_inplace(req->rq_reqmsg, segment, newsize); |
| |
| req->rq_reqlen = newmsg_size; |
| return 0; |
| } |
| |
| static |
| int gss_enlarge_reqbuf_priv(struct ptlrpc_sec *sec, |
| struct ptlrpc_request *req, |
| int segment, int newsize) |
| { |
| struct lustre_msg *newclrbuf; |
| int newmsg_size, newclrbuf_size, newcipbuf_size; |
| __u32 buflens[3]; |
| |
| /* |
| * embedded msg is at seg 0 of clear buffer; |
| * cipher text is at seg 2 of cipher buffer; |
| */ |
| LASSERT(req->rq_pool || |
| (req->rq_reqbuf == NULL && req->rq_reqbuf_len == 0)); |
| LASSERT(req->rq_reqbuf == NULL || |
| (req->rq_pool && req->rq_reqbuf->lm_bufcount == 3)); |
| LASSERT(req->rq_clrbuf); |
| LASSERT(req->rq_clrbuf_len > req->rq_reqlen); |
| LASSERT(lustre_msg_buf(req->rq_clrbuf, 0, 0) == req->rq_reqmsg); |
| |
| /* compute new embedded msg size */ |
| newmsg_size = get_enlarged_msgsize(req->rq_reqmsg, segment, newsize); |
| |
| /* compute new clear buffer size */ |
| newclrbuf_size = get_enlarged_msgsize(req->rq_clrbuf, 0, newmsg_size); |
| newclrbuf_size += GSS_MAX_CIPHER_BLOCK; |
| |
| /* compute new cipher buffer size */ |
| buflens[0] = PTLRPC_GSS_HEADER_SIZE; |
| buflens[1] = gss_cli_payload(req->rq_cli_ctx, buflens[0], 0); |
| buflens[2] = gss_cli_payload(req->rq_cli_ctx, newclrbuf_size, 1); |
| newcipbuf_size = lustre_msg_size_v2(3, buflens); |
| |
| /* handle the case that we put both clear buf and cipher buf into |
| * pre-allocated single buffer. */ |
| if (unlikely(req->rq_pool) && |
| req->rq_clrbuf >= req->rq_reqbuf && |
| (char *) req->rq_clrbuf < |
| (char *) req->rq_reqbuf + req->rq_reqbuf_len) { |
| /* it couldn't be better we still fit into the |
| * pre-allocated buffer. */ |
| if (newclrbuf_size + newcipbuf_size <= req->rq_reqbuf_len) { |
| void *src, *dst; |
| |
| /* move clear text backward. */ |
| src = req->rq_clrbuf; |
| dst = (char *) req->rq_reqbuf + newcipbuf_size; |
| |
| memmove(dst, src, req->rq_clrbuf_len); |
| |
| req->rq_clrbuf = (struct lustre_msg *) dst; |
| req->rq_clrbuf_len = newclrbuf_size; |
| req->rq_reqmsg = lustre_msg_buf(req->rq_clrbuf, 0, 0); |
| } else { |
| /* sadly we have to split out the clear buffer */ |
| LASSERT(req->rq_reqbuf_len >= newcipbuf_size); |
| LASSERT(req->rq_clrbuf_len < newclrbuf_size); |
| } |
| } |
| |
| if (req->rq_clrbuf_len < newclrbuf_size) { |
| newclrbuf_size = size_roundup_power2(newclrbuf_size); |
| |
| OBD_ALLOC_LARGE(newclrbuf, newclrbuf_size); |
| if (newclrbuf == NULL) |
| return -ENOMEM; |
| |
| memcpy(newclrbuf, req->rq_clrbuf, req->rq_clrbuf_len); |
| |
| if (req->rq_reqbuf == NULL || |
| req->rq_clrbuf < req->rq_reqbuf || |
| (char *) req->rq_clrbuf >= |
| (char *) req->rq_reqbuf + req->rq_reqbuf_len) { |
| OBD_FREE_LARGE(req->rq_clrbuf, req->rq_clrbuf_len); |
| } |
| |
| req->rq_clrbuf = newclrbuf; |
| req->rq_clrbuf_len = newclrbuf_size; |
| req->rq_reqmsg = lustre_msg_buf(req->rq_clrbuf, 0, 0); |
| } |
| |
| _sptlrpc_enlarge_msg_inplace(req->rq_clrbuf, 0, newmsg_size); |
| _sptlrpc_enlarge_msg_inplace(req->rq_reqmsg, segment, newsize); |
| req->rq_reqlen = newmsg_size; |
| |
| return 0; |
| } |
| |
| int gss_enlarge_reqbuf(struct ptlrpc_sec *sec, |
| struct ptlrpc_request *req, |
| int segment, int newsize) |
| { |
| int svc = SPTLRPC_FLVR_SVC(req->rq_flvr.sf_rpc); |
| |
| LASSERT(!req->rq_ctx_init && !req->rq_ctx_fini); |
| |
| switch (svc) { |
| case SPTLRPC_SVC_NULL: |
| case SPTLRPC_SVC_AUTH: |
| case SPTLRPC_SVC_INTG: |
| return gss_enlarge_reqbuf_intg(sec, req, svc, segment, newsize); |
| case SPTLRPC_SVC_PRIV: |
| return gss_enlarge_reqbuf_priv(sec, req, segment, newsize); |
| default: |
| LASSERTF(0, "bad rpc flavor %x\n", req->rq_flvr.sf_rpc); |
| return 0; |
| } |
| } |
| |
| int gss_sec_install_rctx(struct obd_import *imp, |
| struct ptlrpc_sec *sec, |
| struct ptlrpc_cli_ctx *ctx) |
| { |
| struct gss_sec *gsec; |
| struct gss_cli_ctx *gctx; |
| int rc; |
| |
| gsec = container_of(sec, struct gss_sec, gs_base); |
| gctx = container_of(ctx, struct gss_cli_ctx, gc_base); |
| |
| rc = gss_install_rvs_svc_ctx(imp, gsec, gctx); |
| return rc; |
| } |
| |
| /******************************************** |
| * server side API * |
| ********************************************/ |
| |
| static inline |
| int gss_svc_reqctx_is_special(struct gss_svc_reqctx *grctx) |
| { |
| LASSERT(grctx); |
| return (grctx->src_init || grctx->src_init_continue || |
| grctx->src_err_notify); |
| } |
| |
| static |
| void gss_svc_reqctx_free(struct gss_svc_reqctx *grctx) |
| { |
| if (grctx->src_ctx) |
| gss_svc_upcall_put_ctx(grctx->src_ctx); |
| |
| sptlrpc_policy_put(grctx->src_base.sc_policy); |
| OBD_FREE_PTR(grctx); |
| } |
| |
| static inline |
| void gss_svc_reqctx_addref(struct gss_svc_reqctx *grctx) |
| { |
| LASSERT(atomic_read(&grctx->src_base.sc_refcount) > 0); |
| atomic_inc(&grctx->src_base.sc_refcount); |
| } |
| |
| static inline |
| void gss_svc_reqctx_decref(struct gss_svc_reqctx *grctx) |
| { |
| LASSERT(atomic_read(&grctx->src_base.sc_refcount) > 0); |
| |
| if (atomic_dec_and_test(&grctx->src_base.sc_refcount)) |
| gss_svc_reqctx_free(grctx); |
| } |
| |
| static |
| int gss_svc_sign(struct ptlrpc_request *req, |
| struct ptlrpc_reply_state *rs, |
| struct gss_svc_reqctx *grctx, |
| __u32 svc) |
| { |
| __u32 flags = 0; |
| int rc; |
| |
| LASSERT(rs->rs_msg == lustre_msg_buf(rs->rs_repbuf, 1, 0)); |
| |
| /* embedded lustre_msg might have been shrinked */ |
| if (req->rq_replen != rs->rs_repbuf->lm_buflens[1]) |
| lustre_shrink_msg(rs->rs_repbuf, 1, req->rq_replen, 1); |
| |
| if (req->rq_pack_bulk) |
| flags |= LUSTRE_GSS_PACK_BULK; |
| |
| rc = gss_sign_msg(rs->rs_repbuf, grctx->src_ctx->gsc_mechctx, |
| LUSTRE_SP_ANY, flags, PTLRPC_GSS_PROC_DATA, |
| grctx->src_wirectx.gw_seq, svc, NULL); |
| if (rc < 0) |
| return rc; |
| |
| rs->rs_repdata_len = rc; |
| |
| if (likely(req->rq_packed_final)) { |
| if (lustre_msghdr_get_flags(req->rq_reqmsg) & MSGHDR_AT_SUPPORT) |
| req->rq_reply_off = gss_at_reply_off_integ; |
| else |
| req->rq_reply_off = 0; |
| } else { |
| if (svc == SPTLRPC_SVC_NULL) |
| rs->rs_repbuf->lm_cksum = crc32_le(!(__u32) 0, |
| lustre_msg_buf(rs->rs_repbuf, 1, 0), |
| lustre_msg_buflen(rs->rs_repbuf, 1)); |
| req->rq_reply_off = 0; |
| } |
| |
| return 0; |
| } |
| |
| int gss_pack_err_notify(struct ptlrpc_request *req, __u32 major, __u32 minor) |
| { |
| struct gss_svc_reqctx *grctx = gss_svc_ctx2reqctx(req->rq_svc_ctx); |
| struct ptlrpc_reply_state *rs; |
| struct gss_err_header *ghdr; |
| int replen = sizeof(struct ptlrpc_body); |
| int rc; |
| |
| //if (OBD_FAIL_CHECK_ORSET(OBD_FAIL_SVCGSS_ERR_NOTIFY, OBD_FAIL_ONCE)) |
| // return -EINVAL; |
| |
| grctx->src_err_notify = 1; |
| grctx->src_reserve_len = 0; |
| |
| rc = lustre_pack_reply_v2(req, 1, &replen, NULL, 0); |
| if (rc) { |
| CERROR("could not pack reply, err %d\n", rc); |
| return rc; |
| } |
| |
| /* gss hdr */ |
| rs = req->rq_reply_state; |
| LASSERT(rs->rs_repbuf->lm_buflens[1] >= sizeof(*ghdr)); |
| ghdr = lustre_msg_buf(rs->rs_repbuf, 0, 0); |
| ghdr->gh_version = PTLRPC_GSS_VERSION; |
| ghdr->gh_flags = 0; |
| ghdr->gh_proc = PTLRPC_GSS_PROC_ERR; |
| ghdr->gh_major = major; |
| ghdr->gh_minor = minor; |
| ghdr->gh_handle.len = 0; /* fake context handle */ |
| |
| rs->rs_repdata_len = lustre_msg_size_v2(rs->rs_repbuf->lm_bufcount, |
| rs->rs_repbuf->lm_buflens); |
| |
| CDEBUG(D_SEC, "prepare gss error notify(0x%x/0x%x) to %s\n", |
| major, minor, libcfs_nid2str(req->rq_peer.nid)); |
| return 0; |
| } |
| |
| static |
| int gss_svc_handle_init(struct ptlrpc_request *req, |
| struct gss_wire_ctx *gw) |
| { |
| struct gss_svc_reqctx *grctx = gss_svc_ctx2reqctx(req->rq_svc_ctx); |
| struct lustre_msg *reqbuf = req->rq_reqbuf; |
| struct obd_uuid *uuid; |
| struct obd_device *target; |
| rawobj_t uuid_obj, rvs_hdl, in_token; |
| __u32 lustre_svc; |
| __u32 *secdata, seclen; |
| int swabbed, rc; |
| |
| CDEBUG(D_SEC, "processing gss init(%d) request from %s\n", gw->gw_proc, |
| libcfs_nid2str(req->rq_peer.nid)); |
| |
| req->rq_ctx_init = 1; |
| |
| if (gw->gw_flags & LUSTRE_GSS_PACK_BULK) { |
| CERROR("unexpected bulk flag\n"); |
| return SECSVC_DROP; |
| } |
| |
| if (gw->gw_proc == PTLRPC_GSS_PROC_INIT && gw->gw_handle.len != 0) { |
| CERROR("proc %u: invalid handle length %u\n", |
| gw->gw_proc, gw->gw_handle.len); |
| return SECSVC_DROP; |
| } |
| |
| if (reqbuf->lm_bufcount < 3 || reqbuf->lm_bufcount > 4) { |
| CERROR("Invalid bufcount %d\n", reqbuf->lm_bufcount); |
| return SECSVC_DROP; |
| } |
| |
| swabbed = ptlrpc_req_need_swab(req); |
| |
| /* ctx initiate payload is in last segment */ |
| secdata = lustre_msg_buf(reqbuf, reqbuf->lm_bufcount - 1, 0); |
| seclen = reqbuf->lm_buflens[reqbuf->lm_bufcount - 1]; |
| |
| if (seclen < 4 + 4) { |
| CERROR("sec size %d too small\n", seclen); |
| return SECSVC_DROP; |
| } |
| |
| /* lustre svc type */ |
| lustre_svc = le32_to_cpu(*secdata++); |
| seclen -= 4; |
| |
| /* extract target uuid, note this code is somewhat fragile |
| * because touched internal structure of obd_uuid */ |
| if (rawobj_extract(&uuid_obj, &secdata, &seclen)) { |
| CERROR("failed to extract target uuid\n"); |
| return SECSVC_DROP; |
| } |
| uuid_obj.data[uuid_obj.len - 1] = '\0'; |
| |
| uuid = (struct obd_uuid *) uuid_obj.data; |
| target = class_uuid2obd(uuid); |
| if (!target || target->obd_stopping || !target->obd_set_up) { |
| CERROR("target '%s' is not available for context init (%s)\n", |
| uuid->uuid, target == NULL ? "no target" : |
| (target->obd_stopping ? "stopping" : "not set up")); |
| return SECSVC_DROP; |
| } |
| |
| /* extract reverse handle */ |
| if (rawobj_extract(&rvs_hdl, &secdata, &seclen)) { |
| CERROR("failed extract reverse handle\n"); |
| return SECSVC_DROP; |
| } |
| |
| /* extract token */ |
| if (rawobj_extract(&in_token, &secdata, &seclen)) { |
| CERROR("can't extract token\n"); |
| return SECSVC_DROP; |
| } |
| |
| rc = gss_svc_upcall_handle_init(req, grctx, gw, target, lustre_svc, |
| &rvs_hdl, &in_token); |
| if (rc != SECSVC_OK) |
| return rc; |
| |
| if (grctx->src_ctx->gsc_usr_mds || grctx->src_ctx->gsc_usr_oss || |
| grctx->src_ctx->gsc_usr_root) |
| CWARN("create svc ctx %p: user from %s authenticated as %s\n", |
| grctx->src_ctx, libcfs_nid2str(req->rq_peer.nid), |
| grctx->src_ctx->gsc_usr_mds ? "mds" : |
| (grctx->src_ctx->gsc_usr_oss ? "oss" : "root")); |
| else |
| CWARN("create svc ctx %p: accept user %u from %s\n", |
| grctx->src_ctx, grctx->src_ctx->gsc_uid, |
| libcfs_nid2str(req->rq_peer.nid)); |
| |
| if (gw->gw_flags & LUSTRE_GSS_PACK_USER) { |
| if (reqbuf->lm_bufcount < 4) { |
| CERROR("missing user descriptor\n"); |
| return SECSVC_DROP; |
| } |
| if (sptlrpc_unpack_user_desc(reqbuf, 2, swabbed)) { |
| CERROR("Mal-formed user descriptor\n"); |
| return SECSVC_DROP; |
| } |
| |
| req->rq_pack_udesc = 1; |
| req->rq_user_desc = lustre_msg_buf(reqbuf, 2, 0); |
| } |
| |
| req->rq_reqmsg = lustre_msg_buf(reqbuf, 1, 0); |
| req->rq_reqlen = lustre_msg_buflen(reqbuf, 1); |
| |
| return rc; |
| } |
| |
| /* |
| * last segment must be the gss signature. |
| */ |
| static |
| int gss_svc_verify_request(struct ptlrpc_request *req, |
| struct gss_svc_reqctx *grctx, |
| struct gss_wire_ctx *gw, |
| __u32 *major) |
| { |
| struct gss_svc_ctx *gctx = grctx->src_ctx; |
| struct lustre_msg *msg = req->rq_reqbuf; |
| int offset = 2; |
| int swabbed; |
| |
| *major = GSS_S_COMPLETE; |
| |
| if (msg->lm_bufcount < 2) { |
| CERROR("Too few segments (%u) in request\n", msg->lm_bufcount); |
| return -EINVAL; |
| } |
| |
| if (gw->gw_svc == SPTLRPC_SVC_NULL) |
| goto verified; |
| |
| if (gss_check_seq_num(&gctx->gsc_seqdata, gw->gw_seq, 0)) { |
| CERROR("phase 0: discard replayed req: seq %u\n", gw->gw_seq); |
| *major = GSS_S_DUPLICATE_TOKEN; |
| return -EACCES; |
| } |
| |
| *major = gss_verify_msg(msg, gctx->gsc_mechctx, gw->gw_svc); |
| if (*major != GSS_S_COMPLETE) { |
| CERROR("failed to verify request: %x\n", *major); |
| return -EACCES; |
| } |
| |
| if (gctx->gsc_reverse == 0 && |
| gss_check_seq_num(&gctx->gsc_seqdata, gw->gw_seq, 1)) { |
| CERROR("phase 1+: discard replayed req: seq %u\n", gw->gw_seq); |
| *major = GSS_S_DUPLICATE_TOKEN; |
| return -EACCES; |
| } |
| |
| verified: |
| swabbed = ptlrpc_req_need_swab(req); |
| |
| /* user descriptor */ |
| if (gw->gw_flags & LUSTRE_GSS_PACK_USER) { |
| if (msg->lm_bufcount < (offset + 1)) { |
| CERROR("no user desc included\n"); |
| return -EINVAL; |
| } |
| |
| if (sptlrpc_unpack_user_desc(msg, offset, swabbed)) { |
| CERROR("Mal-formed user descriptor\n"); |
| return -EINVAL; |
| } |
| |
| req->rq_pack_udesc = 1; |
| req->rq_user_desc = lustre_msg_buf(msg, offset, 0); |
| offset++; |
| } |
| |
| /* check bulk_sec_desc data */ |
| if (gw->gw_flags & LUSTRE_GSS_PACK_BULK) { |
| if (msg->lm_bufcount < (offset + 1)) { |
| CERROR("missing bulk sec descriptor\n"); |
| return -EINVAL; |
| } |
| |
| if (bulk_sec_desc_unpack(msg, offset, swabbed)) |
| return -EINVAL; |
| |
| req->rq_pack_bulk = 1; |
| grctx->src_reqbsd = lustre_msg_buf(msg, offset, 0); |
| grctx->src_reqbsd_size = lustre_msg_buflen(msg, offset); |
| } |
| |
| req->rq_reqmsg = lustre_msg_buf(msg, 1, 0); |
| req->rq_reqlen = msg->lm_buflens[1]; |
| return 0; |
| } |
| |
| static |
| int gss_svc_unseal_request(struct ptlrpc_request *req, |
| struct gss_svc_reqctx *grctx, |
| struct gss_wire_ctx *gw, |
| __u32 *major) |
| { |
| struct gss_svc_ctx *gctx = grctx->src_ctx; |
| struct lustre_msg *msg = req->rq_reqbuf; |
| int swabbed, msglen, offset = 1; |
| |
| if (gss_check_seq_num(&gctx->gsc_seqdata, gw->gw_seq, 0)) { |
| CERROR("phase 0: discard replayed req: seq %u\n", gw->gw_seq); |
| *major = GSS_S_DUPLICATE_TOKEN; |
| return -EACCES; |
| } |
| |
| *major = gss_unseal_msg(gctx->gsc_mechctx, msg, |
| &msglen, req->rq_reqdata_len); |
| if (*major != GSS_S_COMPLETE) { |
| CERROR("failed to unwrap request: %x\n", *major); |
| return -EACCES; |
| } |
| |
| if (gss_check_seq_num(&gctx->gsc_seqdata, gw->gw_seq, 1)) { |
| CERROR("phase 1+: discard replayed req: seq %u\n", gw->gw_seq); |
| *major = GSS_S_DUPLICATE_TOKEN; |
| return -EACCES; |
| } |
| |
| swabbed = __lustre_unpack_msg(msg, msglen); |
| if (swabbed < 0) { |
| CERROR("Failed to unpack after decryption\n"); |
| return -EINVAL; |
| } |
| req->rq_reqdata_len = msglen; |
| |
| if (msg->lm_bufcount < 1) { |
| CERROR("Invalid buffer: is empty\n"); |
| return -EINVAL; |
| } |
| |
| if (gw->gw_flags & LUSTRE_GSS_PACK_USER) { |
| if (msg->lm_bufcount < offset + 1) { |
| CERROR("no user descriptor included\n"); |
| return -EINVAL; |
| } |
| |
| if (sptlrpc_unpack_user_desc(msg, offset, swabbed)) { |
| CERROR("Mal-formed user descriptor\n"); |
| return -EINVAL; |
| } |
| |
| req->rq_pack_udesc = 1; |
| req->rq_user_desc = lustre_msg_buf(msg, offset, 0); |
| offset++; |
| } |
| |
| if (gw->gw_flags & LUSTRE_GSS_PACK_BULK) { |
| if (msg->lm_bufcount < offset + 1) { |
| CERROR("no bulk checksum included\n"); |
| return -EINVAL; |
| } |
| |
| if (bulk_sec_desc_unpack(msg, offset, swabbed)) |
| return -EINVAL; |
| |
| req->rq_pack_bulk = 1; |
| grctx->src_reqbsd = lustre_msg_buf(msg, offset, 0); |
| grctx->src_reqbsd_size = lustre_msg_buflen(msg, offset); |
| } |
| |
| req->rq_reqmsg = lustre_msg_buf(req->rq_reqbuf, 0, 0); |
| req->rq_reqlen = req->rq_reqbuf->lm_buflens[0]; |
| return 0; |
| } |
| |
| static |
| int gss_svc_handle_data(struct ptlrpc_request *req, |
| struct gss_wire_ctx *gw) |
| { |
| struct gss_svc_reqctx *grctx = gss_svc_ctx2reqctx(req->rq_svc_ctx); |
| __u32 major = 0; |
| int rc = 0; |
| |
| grctx->src_ctx = gss_svc_upcall_get_ctx(req, gw); |
| if (!grctx->src_ctx) { |
| major = GSS_S_NO_CONTEXT; |
| goto error; |
| } |
| |
| switch (gw->gw_svc) { |
| case SPTLRPC_SVC_NULL: |
| case SPTLRPC_SVC_AUTH: |
| case SPTLRPC_SVC_INTG: |
| rc = gss_svc_verify_request(req, grctx, gw, &major); |
| break; |
| case SPTLRPC_SVC_PRIV: |
| rc = gss_svc_unseal_request(req, grctx, gw, &major); |
| break; |
| default: |
| CERROR("unsupported gss service %d\n", gw->gw_svc); |
| rc = -EINVAL; |
| } |
| |
| if (rc == 0) |
| return SECSVC_OK; |
| |
| CERROR("svc %u failed: major 0x%08x: req xid "LPU64" ctx %p idx " |
| LPX64"(%u->%s)\n", gw->gw_svc, major, req->rq_xid, |
| grctx->src_ctx, gss_handle_to_u64(&gw->gw_handle), |
| grctx->src_ctx->gsc_uid, libcfs_nid2str(req->rq_peer.nid)); |
| error: |
| /* we only notify client in case of NO_CONTEXT/BAD_SIG, which |
| * might happen after server reboot, to allow recovery. */ |
| if ((major == GSS_S_NO_CONTEXT || major == GSS_S_BAD_SIG) && |
| gss_pack_err_notify(req, major, 0) == 0) |
| return SECSVC_COMPLETE; |
| |
| return SECSVC_DROP; |
| } |
| |
| static |
| int gss_svc_handle_destroy(struct ptlrpc_request *req, |
| struct gss_wire_ctx *gw) |
| { |
| struct gss_svc_reqctx *grctx = gss_svc_ctx2reqctx(req->rq_svc_ctx); |
| __u32 major; |
| |
| req->rq_ctx_fini = 1; |
| req->rq_no_reply = 1; |
| |
| grctx->src_ctx = gss_svc_upcall_get_ctx(req, gw); |
| if (!grctx->src_ctx) { |
| CDEBUG(D_SEC, "invalid gss context handle for destroy.\n"); |
| return SECSVC_DROP; |
| } |
| |
| if (gw->gw_svc != SPTLRPC_SVC_INTG) { |
| CERROR("svc %u is not supported in destroy.\n", gw->gw_svc); |
| return SECSVC_DROP; |
| } |
| |
| if (gss_svc_verify_request(req, grctx, gw, &major)) |
| return SECSVC_DROP; |
| |
| CWARN("destroy svc ctx %p idx "LPX64" (%u->%s)\n", |
| grctx->src_ctx, gss_handle_to_u64(&gw->gw_handle), |
| grctx->src_ctx->gsc_uid, libcfs_nid2str(req->rq_peer.nid)); |
| |
| gss_svc_upcall_destroy_ctx(grctx->src_ctx); |
| |
| if (gw->gw_flags & LUSTRE_GSS_PACK_USER) { |
| if (req->rq_reqbuf->lm_bufcount < 4) { |
| CERROR("missing user descriptor, ignore it\n"); |
| return SECSVC_OK; |
| } |
| if (sptlrpc_unpack_user_desc(req->rq_reqbuf, 2, |
| ptlrpc_req_need_swab(req))) { |
| CERROR("Mal-formed user descriptor, ignore it\n"); |
| return SECSVC_OK; |
| } |
| |
| req->rq_pack_udesc = 1; |
| req->rq_user_desc = lustre_msg_buf(req->rq_reqbuf, 2, 0); |
| } |
| |
| return SECSVC_OK; |
| } |
| |
| int gss_svc_accept(struct ptlrpc_sec_policy *policy, struct ptlrpc_request *req) |
| { |
| struct gss_header *ghdr; |
| struct gss_svc_reqctx *grctx; |
| struct gss_wire_ctx *gw; |
| int swabbed, rc; |
| |
| LASSERT(req->rq_reqbuf); |
| LASSERT(req->rq_svc_ctx == NULL); |
| |
| if (req->rq_reqbuf->lm_bufcount < 2) { |
| CERROR("buf count only %d\n", req->rq_reqbuf->lm_bufcount); |
| return SECSVC_DROP; |
| } |
| |
| swabbed = ptlrpc_req_need_swab(req); |
| |
| ghdr = gss_swab_header(req->rq_reqbuf, 0, swabbed); |
| if (ghdr == NULL) { |
| CERROR("can't decode gss header\n"); |
| return SECSVC_DROP; |
| } |
| |
| /* sanity checks */ |
| if (ghdr->gh_version != PTLRPC_GSS_VERSION) { |
| CERROR("gss version %u, expect %u\n", ghdr->gh_version, |
| PTLRPC_GSS_VERSION); |
| return SECSVC_DROP; |
| } |
| |
| req->rq_sp_from = ghdr->gh_sp; |
| |
| /* alloc grctx data */ |
| OBD_ALLOC_PTR(grctx); |
| if (!grctx) |
| return SECSVC_DROP; |
| |
| grctx->src_base.sc_policy = sptlrpc_policy_get(policy); |
| atomic_set(&grctx->src_base.sc_refcount, 1); |
| req->rq_svc_ctx = &grctx->src_base; |
| gw = &grctx->src_wirectx; |
| |
| /* save wire context */ |
| gw->gw_flags = ghdr->gh_flags; |
| gw->gw_proc = ghdr->gh_proc; |
| gw->gw_seq = ghdr->gh_seq; |
| gw->gw_svc = ghdr->gh_svc; |
| rawobj_from_netobj(&gw->gw_handle, &ghdr->gh_handle); |
| |
| /* keep original wire header which subject to checksum verification */ |
| if (swabbed) |
| gss_header_swabber(ghdr); |
| |
| switch (ghdr->gh_proc) { |
| case PTLRPC_GSS_PROC_INIT: |
| case PTLRPC_GSS_PROC_CONTINUE_INIT: |
| rc = gss_svc_handle_init(req, gw); |
| break; |
| case PTLRPC_GSS_PROC_DATA: |
| rc = gss_svc_handle_data(req, gw); |
| break; |
| case PTLRPC_GSS_PROC_DESTROY: |
| rc = gss_svc_handle_destroy(req, gw); |
| break; |
| default: |
| CERROR("unknown proc %u\n", gw->gw_proc); |
| rc = SECSVC_DROP; |
| break; |
| } |
| |
| switch (rc) { |
| case SECSVC_OK: |
| LASSERT(grctx->src_ctx); |
| |
| req->rq_auth_gss = 1; |
| req->rq_auth_remote = grctx->src_ctx->gsc_remote; |
| req->rq_auth_usr_mdt = grctx->src_ctx->gsc_usr_mds; |
| req->rq_auth_usr_ost = grctx->src_ctx->gsc_usr_oss; |
| req->rq_auth_usr_root = grctx->src_ctx->gsc_usr_root; |
| req->rq_auth_uid = grctx->src_ctx->gsc_uid; |
| req->rq_auth_mapped_uid = grctx->src_ctx->gsc_mapped_uid; |
| break; |
| case SECSVC_COMPLETE: |
| break; |
| case SECSVC_DROP: |
| gss_svc_reqctx_free(grctx); |
| req->rq_svc_ctx = NULL; |
| break; |
| } |
| |
| return rc; |
| } |
| |
| void gss_svc_invalidate_ctx(struct ptlrpc_svc_ctx *svc_ctx) |
| { |
| struct gss_svc_reqctx *grctx; |
| |
| if (svc_ctx == NULL) { |
| return; |
| } |
| |
| grctx = gss_svc_ctx2reqctx(svc_ctx); |
| |
| CWARN("gss svc invalidate ctx %p(%u)\n", |
| grctx->src_ctx, grctx->src_ctx->gsc_uid); |
| gss_svc_upcall_destroy_ctx(grctx->src_ctx); |
| } |
| |
| static inline |
| int gss_svc_payload(struct gss_svc_reqctx *grctx, int early, |
| int msgsize, int privacy) |
| { |
| /* we should treat early reply normally, but which is actually sharing |
| * the same ctx with original request, so in this case we should |
| * ignore the special ctx's special flags */ |
| if (early == 0 && gss_svc_reqctx_is_special(grctx)) |
| return grctx->src_reserve_len; |
| |
| return gss_mech_payload(NULL, msgsize, privacy); |
| } |
| |
| static int gss_svc_bulk_payload(struct gss_svc_ctx *gctx, |
| struct sptlrpc_flavor *flvr, |
| int read) |
| { |
| int payload = sizeof(struct ptlrpc_bulk_sec_desc); |
| |
| if (read) { |
| switch (SPTLRPC_FLVR_BULK_SVC(flvr->sf_rpc)) { |
| case SPTLRPC_BULK_SVC_NULL: |
| break; |
| case SPTLRPC_BULK_SVC_INTG: |
| payload += gss_mech_payload(NULL, 0, 0); |
| break; |
| case SPTLRPC_BULK_SVC_PRIV: |
| payload += gss_mech_payload(NULL, 0, 1); |
| break; |
| case SPTLRPC_BULK_SVC_AUTH: |
| default: |
| LBUG(); |
| } |
| } |
| |
| return payload; |
| } |
| |
| int gss_svc_alloc_rs(struct ptlrpc_request *req, int msglen) |
| { |
| struct gss_svc_reqctx *grctx; |
| struct ptlrpc_reply_state *rs; |
| int early, privacy, svc, bsd_off = 0; |
| __u32 ibuflens[2], buflens[4]; |
| int ibufcnt = 0, bufcnt; |
| int txtsize, wmsg_size, rs_size; |
| |
| LASSERT(msglen % 8 == 0); |
| |
| if (req->rq_pack_bulk && !req->rq_bulk_read && !req->rq_bulk_write) { |
| CERROR("client request bulk sec on non-bulk rpc\n"); |
| return -EPROTO; |
| } |
| |
| svc = SPTLRPC_FLVR_SVC(req->rq_flvr.sf_rpc); |
| early = (req->rq_packed_final == 0); |
| |
| grctx = gss_svc_ctx2reqctx(req->rq_svc_ctx); |
| if (!early && gss_svc_reqctx_is_special(grctx)) |
| privacy = 0; |
| else |
| privacy = (svc == SPTLRPC_SVC_PRIV); |
| |
| if (privacy) { |
| /* inner clear buffers */ |
| ibufcnt = 1; |
| ibuflens[0] = msglen; |
| |
| if (req->rq_pack_bulk) { |
| LASSERT(grctx->src_reqbsd); |
| |
| bsd_off = ibufcnt; |
| ibuflens[ibufcnt++] = gss_svc_bulk_payload( |
| grctx->src_ctx, |
| &req->rq_flvr, |
| req->rq_bulk_read); |
| } |
| |
| txtsize = lustre_msg_size_v2(ibufcnt, ibuflens); |
| txtsize += GSS_MAX_CIPHER_BLOCK; |
| |
| /* wrapper buffer */ |
| bufcnt = 2; |
| buflens[0] = PTLRPC_GSS_HEADER_SIZE; |
| buflens[1] = gss_svc_payload(grctx, early, txtsize, 1); |
| } else { |
| bufcnt = 2; |
| buflens[0] = PTLRPC_GSS_HEADER_SIZE; |
| buflens[1] = msglen; |
| |
| txtsize = buflens[0]; |
| if (svc == SPTLRPC_SVC_INTG) |
| txtsize += buflens[1]; |
| |
| if (req->rq_pack_bulk) { |
| LASSERT(grctx->src_reqbsd); |
| |
| bsd_off = bufcnt; |
| buflens[bufcnt] = gss_svc_bulk_payload( |
| grctx->src_ctx, |
| &req->rq_flvr, |
| req->rq_bulk_read); |
| if (svc == SPTLRPC_SVC_INTG) |
| txtsize += buflens[bufcnt]; |
| bufcnt++; |
| } |
| |
| if ((!early && gss_svc_reqctx_is_special(grctx)) || |
| svc != SPTLRPC_SVC_NULL) |
| buflens[bufcnt++] = gss_svc_payload(grctx, early, |
| txtsize, 0); |
| } |
| |
| wmsg_size = lustre_msg_size_v2(bufcnt, buflens); |
| |
| rs_size = sizeof(*rs) + wmsg_size; |
| rs = req->rq_reply_state; |
| |
| if (rs) { |
| /* pre-allocated */ |
| LASSERT(rs->rs_size >= rs_size); |
| } else { |
| OBD_ALLOC_LARGE(rs, rs_size); |
| if (rs == NULL) |
| return -ENOMEM; |
| |
| rs->rs_size = rs_size; |
| } |
| |
| rs->rs_repbuf = (struct lustre_msg *) (rs + 1); |
| rs->rs_repbuf_len = wmsg_size; |
| |
| /* initialize the buffer */ |
| if (privacy) { |
| lustre_init_msg_v2(rs->rs_repbuf, ibufcnt, ibuflens, NULL); |
| rs->rs_msg = lustre_msg_buf(rs->rs_repbuf, 0, msglen); |
| } else { |
| lustre_init_msg_v2(rs->rs_repbuf, bufcnt, buflens, NULL); |
| rs->rs_repbuf->lm_secflvr = req->rq_flvr.sf_rpc; |
| |
| rs->rs_msg = lustre_msg_buf(rs->rs_repbuf, 1, 0); |
| } |
| |
| if (bsd_off) { |
| grctx->src_repbsd = lustre_msg_buf(rs->rs_repbuf, bsd_off, 0); |
| grctx->src_repbsd_size = lustre_msg_buflen(rs->rs_repbuf, |
| bsd_off); |
| } |
| |
| gss_svc_reqctx_addref(grctx); |
| rs->rs_svc_ctx = req->rq_svc_ctx; |
| |
| LASSERT(rs->rs_msg); |
| req->rq_reply_state = rs; |
| return 0; |
| } |
| |
| static int gss_svc_seal(struct ptlrpc_request *req, |
| struct ptlrpc_reply_state *rs, |
| struct gss_svc_reqctx *grctx) |
| { |
| struct gss_svc_ctx *gctx = grctx->src_ctx; |
| rawobj_t hdrobj, msgobj, token; |
| struct gss_header *ghdr; |
| __u8 *token_buf; |
| int token_buflen; |
| __u32 buflens[2], major; |
| int msglen, rc; |
| |
| /* get clear data length. note embedded lustre_msg might |
| * have been shrinked */ |
| if (req->rq_replen != lustre_msg_buflen(rs->rs_repbuf, 0)) |
| msglen = lustre_shrink_msg(rs->rs_repbuf, 0, req->rq_replen, 1); |
| else |
| msglen = lustre_msg_size_v2(rs->rs_repbuf->lm_bufcount, |
| rs->rs_repbuf->lm_buflens); |
| |
| /* temporarily use tail of buffer to hold gss header data */ |
| LASSERT(msglen + PTLRPC_GSS_HEADER_SIZE <= rs->rs_repbuf_len); |
| ghdr = (struct gss_header *) ((char *) rs->rs_repbuf + |
| rs->rs_repbuf_len - PTLRPC_GSS_HEADER_SIZE); |
| ghdr->gh_version = PTLRPC_GSS_VERSION; |
| ghdr->gh_sp = LUSTRE_SP_ANY; |
| ghdr->gh_flags = 0; |
| ghdr->gh_proc = PTLRPC_GSS_PROC_DATA; |
| ghdr->gh_seq = grctx->src_wirectx.gw_seq; |
| ghdr->gh_svc = SPTLRPC_SVC_PRIV; |
| ghdr->gh_handle.len = 0; |
| if (req->rq_pack_bulk) |
| ghdr->gh_flags |= LUSTRE_GSS_PACK_BULK; |
| |
| /* allocate temporary cipher buffer */ |
| token_buflen = gss_mech_payload(gctx->gsc_mechctx, msglen, 1); |
| OBD_ALLOC_LARGE(token_buf, token_buflen); |
| if (token_buf == NULL) |
| return -ENOMEM; |
| |
| hdrobj.len = PTLRPC_GSS_HEADER_SIZE; |
| hdrobj.data = (__u8 *) ghdr; |
| msgobj.len = msglen; |
| msgobj.data = (__u8 *) rs->rs_repbuf; |
| token.len = token_buflen; |
| token.data = token_buf; |
| |
| major = lgss_wrap(gctx->gsc_mechctx, &hdrobj, &msgobj, |
| rs->rs_repbuf_len - PTLRPC_GSS_HEADER_SIZE, &token); |
| if (major != GSS_S_COMPLETE) { |
| CERROR("wrap message error: %08x\n", major); |
| GOTO(out_free, rc = -EPERM); |
| } |
| LASSERT(token.len <= token_buflen); |
| |
| /* we are about to override data at rs->rs_repbuf, nullify pointers |
| * to which to catch further illegal usage. */ |
| if (req->rq_pack_bulk) { |
| grctx->src_repbsd = NULL; |
| grctx->src_repbsd_size = 0; |
| } |
| |
| /* now fill the actual wire data |
| * - gss header |
| * - gss token |
| */ |
| buflens[0] = PTLRPC_GSS_HEADER_SIZE; |
| buflens[1] = token.len; |
| |
| rs->rs_repdata_len = lustre_msg_size_v2(2, buflens); |
| LASSERT(rs->rs_repdata_len <= rs->rs_repbuf_len); |
| |
| lustre_init_msg_v2(rs->rs_repbuf, 2, buflens, NULL); |
| rs->rs_repbuf->lm_secflvr = req->rq_flvr.sf_rpc; |
| |
| memcpy(lustre_msg_buf(rs->rs_repbuf, 0, 0), ghdr, |
| PTLRPC_GSS_HEADER_SIZE); |
| memcpy(lustre_msg_buf(rs->rs_repbuf, 1, 0), token.data, token.len); |
| |
| /* reply offset */ |
| if (req->rq_packed_final && |
| (lustre_msghdr_get_flags(req->rq_reqmsg) & MSGHDR_AT_SUPPORT)) |
| req->rq_reply_off = gss_at_reply_off_priv; |
| else |
| req->rq_reply_off = 0; |
| |
| /* to catch upper layer's further access */ |
| rs->rs_msg = NULL; |
| req->rq_repmsg = NULL; |
| req->rq_replen = 0; |
| |
| rc = 0; |
| out_free: |
| OBD_FREE_LARGE(token_buf, token_buflen); |
| return rc; |
| } |
| |
| int gss_svc_authorize(struct ptlrpc_request *req) |
| { |
| struct ptlrpc_reply_state *rs = req->rq_reply_state; |
| struct gss_svc_reqctx *grctx = gss_svc_ctx2reqctx(req->rq_svc_ctx); |
| struct gss_wire_ctx *gw = &grctx->src_wirectx; |
| int early, rc; |
| |
| early = (req->rq_packed_final == 0); |
| |
| if (!early && gss_svc_reqctx_is_special(grctx)) { |
| LASSERT(rs->rs_repdata_len != 0); |
| |
| req->rq_reply_off = gss_at_reply_off_integ; |
| return 0; |
| } |
| |
| /* early reply could happen in many cases */ |
| if (!early && |
| gw->gw_proc != PTLRPC_GSS_PROC_DATA && |
| gw->gw_proc != PTLRPC_GSS_PROC_DESTROY) { |
| CERROR("proc %d not support\n", gw->gw_proc); |
| return -EINVAL; |
| } |
| |
| LASSERT(grctx->src_ctx); |
| |
| switch (gw->gw_svc) { |
| case SPTLRPC_SVC_NULL: |
| case SPTLRPC_SVC_AUTH: |
| case SPTLRPC_SVC_INTG: |
| rc = gss_svc_sign(req, rs, grctx, gw->gw_svc); |
| break; |
| case SPTLRPC_SVC_PRIV: |
| rc = gss_svc_seal(req, rs, grctx); |
| break; |
| default: |
| CERROR("Unknown service %d\n", gw->gw_svc); |
| GOTO(out, rc = -EINVAL); |
| } |
| rc = 0; |
| |
| out: |
| return rc; |
| } |
| |
| void gss_svc_free_rs(struct ptlrpc_reply_state *rs) |
| { |
| struct gss_svc_reqctx *grctx; |
| |
| LASSERT(rs->rs_svc_ctx); |
| grctx = container_of(rs->rs_svc_ctx, struct gss_svc_reqctx, src_base); |
| |
| gss_svc_reqctx_decref(grctx); |
| rs->rs_svc_ctx = NULL; |
| |
| if (!rs->rs_prealloc) |
| OBD_FREE_LARGE(rs, rs->rs_size); |
| } |
| |
| void gss_svc_free_ctx(struct ptlrpc_svc_ctx *ctx) |
| { |
| LASSERT(atomic_read(&ctx->sc_refcount) == 0); |
| gss_svc_reqctx_free(gss_svc_ctx2reqctx(ctx)); |
| } |
| |
| int gss_copy_rvc_cli_ctx(struct ptlrpc_cli_ctx *cli_ctx, |
| struct ptlrpc_svc_ctx *svc_ctx) |
| { |
| struct gss_cli_ctx *cli_gctx = ctx2gctx(cli_ctx); |
| struct gss_svc_ctx *svc_gctx = gss_svc_ctx2gssctx(svc_ctx); |
| struct gss_ctx *mechctx = NULL; |
| |
| LASSERT(cli_gctx); |
| LASSERT(svc_gctx && svc_gctx->gsc_mechctx); |
| |
| cli_gctx->gc_proc = PTLRPC_GSS_PROC_DATA; |
| cli_gctx->gc_win = GSS_SEQ_WIN; |
| |
| /* The problem is the reverse ctx might get lost in some recovery |
| * situations, and the same svc_ctx will be used to re-create it. |
| * if there's callback be sentout before that, new reverse ctx start |
| * with sequence 0 will lead to future callback rpc be treated as |
| * replay. |
| * |
| * each reverse root ctx will record its latest sequence number on its |
| * buddy svcctx before be destroied, so here we continue use it. |
| */ |
| atomic_set(&cli_gctx->gc_seq, svc_gctx->gsc_rvs_seq); |
| |
| if (gss_svc_upcall_dup_handle(&cli_gctx->gc_svc_handle, svc_gctx)) { |
| CERROR("failed to dup svc handle\n"); |
| goto err_out; |
| } |
| |
| if (lgss_copy_reverse_context(svc_gctx->gsc_mechctx, &mechctx) != |
| GSS_S_COMPLETE) { |
| CERROR("failed to copy mech context\n"); |
| goto err_svc_handle; |
| } |
| |
| if (rawobj_dup(&cli_gctx->gc_handle, &svc_gctx->gsc_rvs_hdl)) { |
| CERROR("failed to dup reverse handle\n"); |
| goto err_ctx; |
| } |
| |
| cli_gctx->gc_mechctx = mechctx; |
| gss_cli_ctx_uptodate(cli_gctx); |
| |
| return 0; |
| |
| err_ctx: |
| lgss_delete_sec_context(&mechctx); |
| err_svc_handle: |
| rawobj_free(&cli_gctx->gc_svc_handle); |
| err_out: |
| return -ENOMEM; |
| } |
| |
| static void gss_init_at_reply_offset(void) |
| { |
| __u32 buflens[3]; |
| int clearsize; |
| |
| buflens[0] = PTLRPC_GSS_HEADER_SIZE; |
| buflens[1] = lustre_msg_early_size(); |
| buflens[2] = gss_cli_payload(NULL, buflens[1], 0); |
| gss_at_reply_off_integ = lustre_msg_size_v2(3, buflens); |
| |
| buflens[0] = lustre_msg_early_size(); |
| clearsize = lustre_msg_size_v2(1, buflens); |
| buflens[0] = PTLRPC_GSS_HEADER_SIZE; |
| buflens[1] = gss_cli_payload(NULL, clearsize, 0); |
| buflens[2] = gss_cli_payload(NULL, clearsize, 1); |
| gss_at_reply_off_priv = lustre_msg_size_v2(3, buflens); |
| } |
| |
| int __init sptlrpc_gss_init(void) |
| { |
| int rc; |
| |
| rc = gss_init_lproc(); |
| if (rc) |
| return rc; |
| |
| rc = gss_init_cli_upcall(); |
| if (rc) |
| goto out_lproc; |
| |
| rc = gss_init_svc_upcall(); |
| if (rc) |
| goto out_cli_upcall; |
| |
| rc = init_kerberos_module(); |
| if (rc) |
| goto out_svc_upcall; |
| |
| /* register policy after all other stuff be intialized, because it |
| * might be in used immediately after the registration. */ |
| |
| rc = gss_init_keyring(); |
| if (rc) |
| goto out_kerberos; |
| |
| #ifdef HAVE_GSS_PIPEFS |
| rc = gss_init_pipefs(); |
| if (rc) |
| goto out_keyring; |
| #endif |
| |
| gss_init_at_reply_offset(); |
| |
| return 0; |
| |
| #ifdef HAVE_GSS_PIPEFS |
| out_keyring: |
| gss_exit_keyring(); |
| #endif |
| |
| out_kerberos: |
| cleanup_kerberos_module(); |
| out_svc_upcall: |
| gss_exit_svc_upcall(); |
| out_cli_upcall: |
| gss_exit_cli_upcall(); |
| out_lproc: |
| gss_exit_lproc(); |
| return rc; |
| } |
| |
| static void __exit sptlrpc_gss_exit(void) |
| { |
| gss_exit_keyring(); |
| #ifdef HAVE_GSS_PIPEFS |
| gss_exit_pipefs(); |
| #endif |
| cleanup_kerberos_module(); |
| gss_exit_svc_upcall(); |
| gss_exit_cli_upcall(); |
| gss_exit_lproc(); |
| } |
| |
| MODULE_AUTHOR("Sun Microsystems, Inc. <http://www.lustre.org/>"); |
| MODULE_DESCRIPTION("GSS security policy for Lustre"); |
| MODULE_LICENSE("GPL"); |
| |
| module_init(sptlrpc_gss_init); |
| module_exit(sptlrpc_gss_exit); |