Commit message: Fix gss_process_context_token() [CVE-2014-5352]

[MITKRB5-SA-2015-001] The krb5 gss_process_context_token() should not
actually delete the context; that leaves the caller with a dangling
pointer and no way to know that it is invalid.  Instead, mark the
context as terminated, and check for terminated contexts in the GSS
functions which expect established contexts.  Also add checks in
export_sec_context and pseudo_random, and adjust t_prf.c for the
pseudo_random check.

ticket: 8055 (new)
target_version: 1.13.1
tags: pullup
CVE: CVE-2014-5352
CWE: CWE-703
Vulnerability description: The krb5_gss_process_context_token function in lib/gssapi/krb5/process_context_token.c in the libgssapi_krb5 library in MIT Kerberos 5 (aka krb5) through 1.11.5, 1.12.x through 1.12.2, and 1.13.x before 1.13.1 does not properly maintain security-context handles, which allows remote authenticated users to cause a denial of service (use-after-free and double free, and daemon crash) or possibly execute arbitrary code via crafted GSSAPI traffic, as demonstrated by traffic to kadmind.

Project: krb5
File: context_time.c

krb5_gss_context_time(minor_status, context_handle, time_rec)
    OM_uint32 *minor_status;
    gss_ctx_id_t context_handle;
    OM_uint32 *time_rec;
{
    krb5_error_code code;
    krb5_gss_ctx_id_rec *ctx;
    krb5_timestamp now;
    krb5_deltat lifetime;
 
     ctx = (krb5_gss_ctx_id_rec *) context_handle;
 
    if (ctx->terminated || !ctx->established) {
         *minor_status = KG_CTX_INCOMPLETE;
         return(GSS_S_NO_CONTEXT);
     }

    if ((code = krb5_timeofday(ctx->k5_context, &now))) {
        *minor_status = code;
        save_error_info(*minor_status, ctx->k5_context);
        return(GSS_S_FAILURE);
    }

    if ((lifetime = ctx->krb_times.endtime - now) <= 0) {
        *time_rec = 0;
        *minor_status = 0;
        return(GSS_S_CONTEXT_EXPIRED);
    } else {
        *time_rec = lifetime;
        *minor_status = 0;
        return(GSS_S_COMPLETE);
    }
}

Project: krb5
File: export_sec_context.c

krb5_gss_export_sec_context(minor_status, context_handle, interprocess_token)
    OM_uint32           *minor_status;
    gss_ctx_id_t        *context_handle;
    gss_buffer_t        interprocess_token;
{
    krb5_context        context = NULL;
    krb5_error_code     kret;
    OM_uint32           retval;
    size_t              bufsize, blen;
    krb5_gss_ctx_id_t   ctx;
    krb5_octet          *obuffer, *obp;

    /* Assume a tragic failure */
    obuffer = (krb5_octet *) NULL;
    retval = GSS_S_FAILURE;
     *minor_status = 0;
 
     ctx = (krb5_gss_ctx_id_t) *context_handle;
    if (ctx->terminated) {
        *minor_status = KG_CTX_INCOMPLETE;
        return (GSS_S_NO_CONTEXT);
    }

     context = ctx->k5_context;
     kret = krb5_gss_ser_init(context);
     if (kret)
        goto error_out;

    /* Determine size needed for externalization of context */
    bufsize = 0;
    if ((kret = kg_ctx_size(context, (krb5_pointer) ctx,
                            &bufsize)))
        goto error_out;

    /* Allocate the buffer */
    if ((obuffer = gssalloc_malloc(bufsize)) == NULL) {
        kret = ENOMEM;
        goto error_out;
    }

    obp = obuffer;
    blen = bufsize;
    /* Externalize the context */
    if ((kret = kg_ctx_externalize(context,
                                   (krb5_pointer) ctx, &obp, &blen)))
        goto error_out;

    /* Success!  Return the buffer */
    interprocess_token->length = bufsize - blen;
    interprocess_token->value = obuffer;
    *minor_status = 0;
    retval = GSS_S_COMPLETE;

    /* Now, clean up the context state */
    (void)krb5_gss_delete_sec_context(minor_status, context_handle, NULL);
    *context_handle = GSS_C_NO_CONTEXT;

    return (GSS_S_COMPLETE);

error_out:
    if (retval != GSS_S_COMPLETE)
        if (kret != 0 && context != 0)
            save_error_info((OM_uint32)kret, context);
    if (obuffer && bufsize) {
        memset(obuffer, 0, bufsize);
        xfree(obuffer);
    }
    if (*minor_status == 0)
        *minor_status = (OM_uint32) kret;
    return(retval);
}

Project: krb5
File: k5sealiov.c

kg_seal_iov(OM_uint32 *minor_status,
            gss_ctx_id_t context_handle,
            int conf_req_flag,
            gss_qop_t qop_req,
            int *conf_state,
            gss_iov_buffer_desc *iov,
            int iov_count,
            int toktype)
{
    krb5_gss_ctx_id_rec *ctx;
    krb5_error_code code;
    krb5_context context;

    if (qop_req != 0) {
        *minor_status = (OM_uint32)G_UNKNOWN_QOP;
        return GSS_S_FAILURE;
     }
 
     ctx = (krb5_gss_ctx_id_rec *)context_handle;
    if (ctx->terminated || !ctx->established) {
         *minor_status = KG_CTX_INCOMPLETE;
         return GSS_S_NO_CONTEXT;
     }

    if (conf_req_flag && kg_integ_only_iov(iov, iov_count)) {
        /* may be more sensible to return an error here */
        conf_req_flag = FALSE;
    }

    context = ctx->k5_context;
    switch (ctx->proto) {
    case 0:
        code = make_seal_token_v1_iov(context, ctx, conf_req_flag,
                                      conf_state, iov, iov_count, toktype);
        break;
    case 1:
        code = gss_krb5int_make_seal_token_v3_iov(context, ctx, conf_req_flag,
                                                  conf_state, iov, iov_count, toktype);
        break;
    default:
        code = G_UNKNOWN_QOP;
        break;
    }

    if (code != 0) {
        *minor_status = code;
        save_error_info(*minor_status, context);
        return GSS_S_FAILURE;
    }

    *minor_status = 0;

    return GSS_S_COMPLETE;
}

Project: krb5
File: k5unseal.c

kg_unseal(minor_status, context_handle, input_token_buffer,
          message_buffer, conf_state, qop_state, toktype)
    OM_uint32 *minor_status;
    gss_ctx_id_t context_handle;
    gss_buffer_t input_token_buffer;
    gss_buffer_t message_buffer;
    int *conf_state;
    gss_qop_t *qop_state;
    int toktype;
{
    krb5_gss_ctx_id_rec *ctx;
    unsigned char *ptr;
    unsigned int bodysize;
    int err;
    int toktype2;
    int vfyflags = 0;
    OM_uint32 ret;
 
     ctx = (krb5_gss_ctx_id_rec *) context_handle;
 
    if (ctx->terminated || !ctx->established) {
         *minor_status = KG_CTX_INCOMPLETE;
         return(GSS_S_NO_CONTEXT);
     }

    /* parse the token, leave the data in message_buffer, setting conf_state */

    /* verify the header */

    ptr = (unsigned char *) input_token_buffer->value;


    err = g_verify_token_header(ctx->mech_used,
                                &bodysize, &ptr, -1,
                                input_token_buffer->length,
                                vfyflags);
    if (err) {
        *minor_status = err;
        return GSS_S_DEFECTIVE_TOKEN;
    }

    if (bodysize < 2) {
        *minor_status = (OM_uint32)G_BAD_TOK_HEADER;
        return GSS_S_DEFECTIVE_TOKEN;
    }

    toktype2 = load_16_be(ptr);

    ptr += 2;
    bodysize -= 2;

    switch (toktype2) {
    case KG2_TOK_MIC_MSG:
    case KG2_TOK_WRAP_MSG:
    case KG2_TOK_DEL_CTX:
        ret = gss_krb5int_unseal_token_v3(&ctx->k5_context, minor_status, ctx,
                                          ptr, bodysize, message_buffer,
                                          conf_state, qop_state, toktype);
        break;
    case KG_TOK_MIC_MSG:
    case KG_TOK_WRAP_MSG:
    case KG_TOK_DEL_CTX:
        ret = kg_unseal_v1(ctx->k5_context, minor_status, ctx, ptr, bodysize,
                           message_buffer, conf_state, qop_state,
                           toktype);
        break;
    default:
        *minor_status = (OM_uint32)G_BAD_TOK_HEADER;
        ret = GSS_S_DEFECTIVE_TOKEN;
        break;
    }

    if (ret != 0)
        save_error_info (*minor_status, ctx->k5_context);

    return ret;
}

Project: krb5
File: k5unsealiov.c

kg_unseal_iov(OM_uint32 *minor_status,
              gss_ctx_id_t context_handle,
              int *conf_state,
              gss_qop_t *qop_state,
              gss_iov_buffer_desc *iov,
              int iov_count,
              int toktype)
{
    krb5_gss_ctx_id_rec *ctx;
     OM_uint32 code;
 
     ctx = (krb5_gss_ctx_id_rec *)context_handle;
    if (ctx->terminated || !ctx->established) {
         *minor_status = KG_CTX_INCOMPLETE;
         return GSS_S_NO_CONTEXT;
     }

    if (kg_locate_iov(iov, iov_count, GSS_IOV_BUFFER_TYPE_STREAM) != NULL) {
        code = kg_unseal_stream_iov(minor_status, ctx, conf_state, qop_state,
                                    iov, iov_count, toktype);
    } else {
        code = kg_unseal_iov_token(minor_status, ctx, conf_state, qop_state,
                                   iov, iov_count, toktype);
    }

    return code;
}

Project: krb5
File: lucid_context.c

gss_krb5int_export_lucid_sec_context(
    OM_uint32           *minor_status,
    const gss_ctx_id_t  context_handle,
    const gss_OID       desired_object,
    gss_buffer_set_t    *data_set)
{
    krb5_error_code     kret = 0;
    OM_uint32           retval;
    krb5_gss_ctx_id_t   ctx = (krb5_gss_ctx_id_t)context_handle;
    void                *lctx = NULL;
    int                 version = 0;
    gss_buffer_desc     rep;

    /* Assume failure */
    retval = GSS_S_FAILURE;
     *minor_status = 0;
     *data_set = GSS_C_NO_BUFFER_SET;
 
    if (ctx->terminated || !ctx->established) {
        *minor_status = KG_CTX_INCOMPLETE;
        return GSS_S_NO_CONTEXT;
    }

     retval = generic_gss_oid_decompose(minor_status,
                                        GSS_KRB5_EXPORT_LUCID_SEC_CONTEXT_OID,
                                        GSS_KRB5_EXPORT_LUCID_SEC_CONTEXT_OID_LENGTH,
                                       desired_object,
                                       &version);
    if (GSS_ERROR(retval))
        return retval;

    /* Externalize a structure of the right version */
    switch (version) {
    case 1:
        kret = make_external_lucid_ctx_v1((krb5_pointer)ctx,
                                          version, &lctx);
        break;
    default:
        kret = (OM_uint32) KG_LUCID_VERSION;
        break;
    }

    if (kret)
        goto error_out;

    rep.value = &lctx;
    rep.length = sizeof(lctx);

    retval = generic_gss_add_buffer_set_member(minor_status, &rep, data_set);
    if (GSS_ERROR(retval))
        goto error_out;

error_out:
    if (*minor_status == 0)
        *minor_status = (OM_uint32) kret;
    return(retval);
}

Project: krb5
File: wrap_size_limit.c

krb5_gss_wrap_size_limit(minor_status, context_handle, conf_req_flag,
                         qop_req, req_output_size, max_input_size)
    OM_uint32           *minor_status;
    gss_ctx_id_t        context_handle;
    int                 conf_req_flag;
    gss_qop_t           qop_req;
    OM_uint32           req_output_size;
    OM_uint32           *max_input_size;
{
    krb5_gss_ctx_id_rec *ctx;
    OM_uint32           data_size, conflen;
    OM_uint32           ohlen;
    int                 overhead;

    /* only default qop is allowed */
    if (qop_req != GSS_C_QOP_DEFAULT) {
        *minor_status = (OM_uint32) G_UNKNOWN_QOP;
        return(GSS_S_FAILURE);
     }
 
     ctx = (krb5_gss_ctx_id_rec *) context_handle;
    if (ctx->terminated || !ctx->established) {
         *minor_status = KG_CTX_INCOMPLETE;
         return(GSS_S_NO_CONTEXT);
     }

    if (ctx->proto == 1) {
        /* No pseudo-ASN.1 wrapper overhead, so no sequence length and
           OID.  */
        OM_uint32 sz = req_output_size;

        /* Token header: 16 octets.  */
        if (conf_req_flag) {
            krb5_key key;
            krb5_enctype enctype;

            key = ctx->have_acceptor_subkey ? ctx->acceptor_subkey
                : ctx->subkey;
            enctype = key->keyblock.enctype;

            while (sz > 0 && krb5_encrypt_size(sz, enctype) + 16 > req_output_size)
                sz--;
            /* Allow for encrypted copy of header.  */
            if (sz > 16)
                sz -= 16;
            else
                sz = 0;
#ifdef CFX_EXERCISE
            /* Allow for EC padding.  In the MIT implementation, only
               added while testing.  */
            if (sz > 65535)
                sz -= 65535;
            else
                sz = 0;
#endif
        } else {
            krb5_cksumtype cksumtype;
            krb5_error_code err;
            size_t cksumsize;

            cksumtype = ctx->have_acceptor_subkey ? ctx->acceptor_subkey_cksumtype
                : ctx->cksumtype;

            err = krb5_c_checksum_length(ctx->k5_context, cksumtype, &cksumsize);
            if (err) {
                *minor_status = err;
                return GSS_S_FAILURE;
            }

            /* Allow for token header and checksum.  */
            if (sz < 16 + cksumsize)
                sz = 0;
            else
                sz -= (16 + cksumsize);
        }

        *max_input_size = sz;
        *minor_status = 0;
        return GSS_S_COMPLETE;
    }

    /* Calculate the token size and subtract that from the output size */
    overhead = 7 + ctx->mech_used->length;
    data_size = req_output_size;
    conflen = kg_confounder_size(ctx->k5_context, ctx->enc->keyblock.enctype);
    data_size = (conflen + data_size + 8) & (~(OM_uint32)7);
    ohlen = g_token_size(ctx->mech_used,
                         (unsigned int) (data_size + ctx->cksum_size + 14))
        - req_output_size;

    if (ohlen+overhead < req_output_size)
        /*
         * Cannot have trailer length that will cause us to pad over our
         * length.
         */
        *max_input_size = (req_output_size - ohlen - overhead) & (~(OM_uint32)7);
    else
        *max_input_size = 0;

    *minor_status = 0;
    return(GSS_S_COMPLETE);
}

Project: krb5
File: t_prf.c

main(int argc, char *argv[])
{
    OM_uint32 minor, major;
    gss_ctx_id_t context;
    gss_union_ctx_id_desc uctx;
    krb5_gss_ctx_id_rec kgctx;
    krb5_key k1, k2;
    krb5_keyblock kb1, kb2;
    gss_buffer_desc in, out;
    unsigned char k1buf[32], k2buf[32], outbuf[44];
    size_t i;

    /*
     * Fake up just enough of a krb5 GSS context to make gss_pseudo_random
     * work, with chosen subkeys and acceptor subkeys.  If we implement
     * gss_import_lucid_sec_context, we can rewrite this to use public
     * interfaces and stop using private headers and internal knowledge of the
     * implementation.
     */
    context = (gss_ctx_id_t)&uctx;
     uctx.mech_type = &mech_krb5;
     uctx.internal_ctx_id = (gss_ctx_id_t)&kgctx;
     kgctx.k5_context = NULL;
    kgctx.established = 1;
     kgctx.have_acceptor_subkey = 1;
     kb1.contents = k1buf;
     kb2.contents = k2buf;
    for (i = 0; i < sizeof(tests) / sizeof(*tests); i++) {
        /* Set up the keys for this test. */
        kb1.enctype = tests[i].enctype;
        kb1.length = fromhex(tests[i].key1, k1buf);
        check_k5err(NULL, "create_key", krb5_k_create_key(NULL, &kb1, &k1));
        kgctx.subkey = k1;
        kb2.enctype = tests[i].enctype;
        kb2.length = fromhex(tests[i].key2, k2buf);
        check_k5err(NULL, "create_key", krb5_k_create_key(NULL, &kb2, &k2));
        kgctx.acceptor_subkey = k2;

        /* Generate a PRF value with the subkey and an empty input, and compare
         * it to the first expected output. */
        in.length = 0;
        in.value = NULL;
        major = gss_pseudo_random(&minor, context, GSS_C_PRF_KEY_PARTIAL, &in,
                                  44, &out);
        check_gsserr("gss_pseudo_random", major, minor);
        (void)fromhex(tests[i].out1, outbuf);
        assert(out.length == 44 && memcmp(out.value, outbuf, 44) == 0);
        (void)gss_release_buffer(&minor, &out);

        /* Generate a PRF value with the acceptor subkey and the 61-byte input
         * string, and compare it to the second expected output. */
        in.length = strlen(inputstr);
        in.value = (char *)inputstr;
        major = gss_pseudo_random(&minor, context, GSS_C_PRF_KEY_FULL, &in, 44,
                                  &out);
        check_gsserr("gss_pseudo_random", major, minor);
        (void)fromhex(tests[i].out2, outbuf);
        assert(out.length == 44 && memcmp(out.value, outbuf, 44) == 0);
        (void)gss_release_buffer(&minor, &out);

        /* Also check that generating zero bytes of output works. */
        major = gss_pseudo_random(&minor, context, GSS_C_PRF_KEY_FULL, &in, 0,
                                  &out);
        check_gsserr("gss_pseudo_random", major, minor);
        assert(out.length == 0);
        (void)gss_release_buffer(&minor, &out);

        krb5_k_free_key(NULL, k1);
        krb5_k_free_key(NULL, k2);
    }
    return 0;
}