Commit message: Fix IAKERB context export/import [CVE-2015-2698]

The patches for CVE-2015-2696 contained a regression in the newly
added IAKERB iakerb_gss_export_sec_context() function, which could
cause it to corrupt memory.  Fix the regression by properly
dereferencing the context_handle pointer before casting it.

Also, the patches did not implement an IAKERB gss_import_sec_context()
function, under the erroneous belief that an exported IAKERB context
would be tagged as a krb5 context.  Implement it now to allow IAKERB
contexts to be successfully exported and imported after establishment.

CVE-2015-2698:

In any MIT krb5 release with the patches for CVE-2015-2696 applied, an
application which calls gss_export_sec_context() may experience memory
corruption if the context was established using the IAKERB mechanism.
Historically, some vulnerabilities of this nature can be translated
into remote code execution, though the necessary exploits must be
tailored to the individual application and are usually quite
complicated.

    CVSSv2 Vector: AV:N/AC:H/Au:S/C:C/I:C/A:C/E:POC/RL:OF/RC:C

ticket: 8273 (new)
target_version: 1.14
tags: pullup
CVE: CVE-2015-2698
CWE: CWE-119
Vulnerability description: The iakerb_gss_export_sec_context function in lib/gssapi/krb5/iakerb.c in MIT Kerberos 5 (aka krb5) 1.14 pre-release 2015-09-14 improperly accesses a certain pointer, which allows remote authenticated users to cause a denial of service (memory corruption) or possibly have unspecified other impact by interacting with an application that calls the gss_export_sec_context function.  NOTE: this vulnerability exists because of an incorrect fix for CVE-2015-2696.

Project: krb5
File: gssapi_krb5.c

kg_set_ccache_name (OM_uint32 *minor_status, const char *name)
{
    char *new_name = NULL;
    char *swap = NULL;
    char *kg_ccache_name;
    krb5_error_code kerr;

    if (name) {
        new_name = strdup(name);
        if (new_name == NULL) {
            *minor_status = ENOMEM;
            return GSS_S_FAILURE;
        }
    }

    kg_ccache_name = k5_getspecific(K5_KEY_GSS_KRB5_CCACHE_NAME);
    swap = kg_ccache_name;
    kg_ccache_name = new_name;
    new_name = swap;
    kerr = k5_setspecific(K5_KEY_GSS_KRB5_CCACHE_NAME, kg_ccache_name);
    if (kerr != 0) {
        /* Can't store, so free up the storage.  */
        free(kg_ccache_name);
        /* ??? free(new_name); */
        *minor_status = kerr;
        return GSS_S_FAILURE;
    }

    free (new_name);
    *minor_status = 0;
    return GSS_S_COMPLETE;
}

Project: krb5
File: gssapi_krb5.c

krb5_gss_localname(OM_uint32 *minor,
                   const gss_name_t pname,
                   const gss_const_OID mech_type,
                   gss_buffer_t localname)
{
    krb5_context context;
    krb5_error_code code;
    krb5_gss_name_t kname;
    char lname[BUFSIZ];

    code = krb5_gss_init_context(&context);
    if (code != 0) {
        *minor = code;
        return GSS_S_FAILURE;
    }

    kname = (krb5_gss_name_t)pname;

    code = krb5_aname_to_localname(context, kname->princ,
                                   sizeof(lname), lname);
    if (code != 0) {
        *minor = KRB5_NO_LOCALNAME;
        krb5_free_context(context);
        return GSS_S_FAILURE;
    }


    krb5_free_context(context);
    localname->value = gssalloc_strdup(lname);
    localname->length = strlen(lname);

    return (code == 0) ? GSS_S_COMPLETE : GSS_S_FAILURE;
}

Project: krb5
File: gssapi_krb5.c

krb5_gssspi_set_cred_option(OM_uint32 *minor_status,
                            gss_cred_id_t *cred_handle,
                            const gss_OID desired_object,
                            const gss_buffer_t value)
{
    OM_uint32 major_status = GSS_S_FAILURE;
    size_t i;

    if (minor_status == NULL)
        return GSS_S_CALL_INACCESSIBLE_WRITE;

    if (cred_handle == NULL)
        return GSS_S_CALL_INACCESSIBLE_WRITE;

    *minor_status = 0;

    if (desired_object == GSS_C_NO_OID)
        return GSS_S_CALL_INACCESSIBLE_READ;

    if (*cred_handle != GSS_C_NO_CREDENTIAL) {
        major_status = krb5_gss_validate_cred(minor_status, *cred_handle);
        if (GSS_ERROR(major_status))
            return major_status;
    }

    for (i = 0; i < sizeof(krb5_gssspi_set_cred_option_ops)/
             sizeof(krb5_gssspi_set_cred_option_ops[0]); i++) {
        if (g_OID_prefix_equal(desired_object, &krb5_gssspi_set_cred_option_ops[i].oid)) {
            return (*krb5_gssspi_set_cred_option_ops[i].func)(minor_status,
                                                              cred_handle,
                                                              desired_object,
                                                              value);
        }
    }

    *minor_status = EINVAL;

    return GSS_S_UNAVAILABLE;
}

Project: krb5
File: iakerb.c

iakerb_get_initial_state(iakerb_ctx_id_t ctx,
                         krb5_gss_cred_id_t cred,
                         krb5_gss_name_t target,
                         OM_uint32 time_req,
                         enum iakerb_state *state)
{
    krb5_creds in_creds, *out_creds = NULL;
    krb5_error_code code;

    memset(&in_creds, 0, sizeof(in_creds));

    in_creds.client = cred->name->princ;
    in_creds.server = target->princ;

    if (cred->name->ad_context != NULL) {
        code = krb5_authdata_export_authdata(ctx->k5c,
                                             cred->name->ad_context,
                                             AD_USAGE_TGS_REQ,
                                             &in_creds.authdata);
        if (code != 0)
            goto cleanup;
    }

    if (time_req != 0 && time_req != GSS_C_INDEFINITE) {
        krb5_timestamp now;

        code = krb5_timeofday(ctx->k5c, &now);
        if (code != 0)
            goto cleanup;

        in_creds.times.endtime = now + time_req;
    }

    /* Make an AS request if we have no creds or it's time to refresh them. */
    if (cred->expire == 0 || kg_cred_time_to_refresh(ctx->k5c, cred)) {
        *state = IAKERB_AS_REQ;
        code = 0;
        goto cleanup;
    }

    code = krb5_get_credentials(ctx->k5c, KRB5_GC_CACHED, cred->ccache,
                                &in_creds, &out_creds);
    if (code == KRB5_CC_NOTFOUND || code == KRB5_CC_NOT_KTYPE) {
        *state = cred->have_tgt ? IAKERB_TGS_REQ : IAKERB_AS_REQ;
        code = 0;
    } else if (code == 0) {
        *state = IAKERB_AP_REQ;
        krb5_free_creds(ctx->k5c, out_creds);
    }

cleanup:
    krb5_free_authdata(ctx->k5c, in_creds.authdata);

    return code;
}

Project: krb5
File: iakerb.c

iakerb_gss_verify_mic_iov(OM_uint32 *minor_status, gss_ctx_id_t context_handle,
                          gss_qop_t *qop_state, gss_iov_buffer_desc *iov,
                          int iov_count)
{
    iakerb_ctx_id_t ctx = (iakerb_ctx_id_t)context_handle;

    if (ctx->gssc == GSS_C_NO_CONTEXT)
        return GSS_S_NO_CONTEXT;

    return krb5_gss_verify_mic_iov(minor_status, ctx->gssc, qop_state, iov,
                                   iov_count);
}

Project: krb5
File: iakerb.c

iakerb_initiator_step(iakerb_ctx_id_t ctx,
                      krb5_gss_cred_id_t cred,
                      krb5_gss_name_t name,
                      OM_uint32 time_req,
                      const gss_buffer_t input_token,
                      gss_buffer_t output_token)
{
    krb5_error_code code = 0;
    krb5_data in = empty_data(), out = empty_data(), realm = empty_data();
    krb5_data *cookie = NULL;
    OM_uint32 tmp;
    unsigned int flags = 0;
    krb5_ticket_times times;

    output_token->length = 0;
    output_token->value = NULL;

    if (input_token != GSS_C_NO_BUFFER) {
        code = iakerb_parse_token(ctx, 0, input_token, NULL, &cookie, &in);
        if (code != 0)
            goto cleanup;

        code = iakerb_save_token(ctx, input_token);
        if (code != 0)
            goto cleanup;
    }

    switch (ctx->state) {
    case IAKERB_AS_REQ:
        if (ctx->icc == NULL) {
            code = iakerb_init_creds_ctx(ctx, cred, time_req);
            if (code != 0)
                goto cleanup;
        }

        code = krb5_init_creds_step(ctx->k5c, ctx->icc, &in, &out, &realm,
                                    &flags);
        if (code != 0) {
            if (cred->have_tgt) {
                /* We were trying to refresh; keep going with current creds. */
                ctx->state = IAKERB_TGS_REQ;
                krb5_clear_error_message(ctx->k5c);
            } else {
                goto cleanup;
            }
        } else if (!(flags & KRB5_INIT_CREDS_STEP_FLAG_CONTINUE)) {
            krb5_init_creds_get_times(ctx->k5c, ctx->icc, &times);
            kg_cred_set_initial_refresh(ctx->k5c, cred, &times);
            cred->expire = times.endtime;

            krb5_init_creds_free(ctx->k5c, ctx->icc);
            ctx->icc = NULL;

            ctx->state = IAKERB_TGS_REQ;
        } else
            break;
        in = empty_data();
        /* Done with AS request; fall through to TGS request. */
    case IAKERB_TGS_REQ:
        if (ctx->tcc == NULL) {
            code = iakerb_tkt_creds_ctx(ctx, cred, name, time_req);
            if (code != 0)
                goto cleanup;
        }

        code = krb5_tkt_creds_step(ctx->k5c, ctx->tcc, &in, &out, &realm,
                                   &flags);
        if (code != 0)
            goto cleanup;
        if (!(flags & KRB5_TKT_CREDS_STEP_FLAG_CONTINUE)) {
            krb5_tkt_creds_get_times(ctx->k5c, ctx->tcc, &times);
            cred->expire = times.endtime;

            krb5_tkt_creds_free(ctx->k5c, ctx->tcc);
            ctx->tcc = NULL;

            ctx->state = IAKERB_AP_REQ;
        } else
            break;
        /* Done with TGS request; fall through to AP request. */
    case IAKERB_AP_REQ:
        break;
    }

    if (out.length != 0) {
        assert(ctx->state != IAKERB_AP_REQ);

        code = iakerb_make_token(ctx, &realm, cookie, &out,
                                 (input_token == GSS_C_NO_BUFFER),
                                 output_token);
        if (code != 0)
            goto cleanup;

        /* Save the token for generating a future checksum */
        code = iakerb_save_token(ctx, output_token);
        if (code != 0)
            goto cleanup;

        ctx->count++;
    }

cleanup:
    if (code != 0)
        gss_release_buffer(&tmp, output_token);
    krb5_free_data(ctx->k5c, cookie);
    krb5_free_data_contents(ctx->k5c, &out);
    krb5_free_data_contents(ctx->k5c, &realm);

    return code;
}

Project: krb5
File: iakerb.c

iakerb_make_finished(krb5_context context,
                     krb5_key key,
                     const krb5_data *conv,
                     krb5_data **finished)
{
    krb5_error_code code;
    krb5_iakerb_finished iaf;

    *finished = NULL;

    memset(&iaf, 0, sizeof(iaf));

    if (key == NULL)
        return KRB5KDC_ERR_NULL_KEY;

    code = krb5_k_make_checksum(context, 0, key, KRB5_KEYUSAGE_IAKERB_FINISHED,
                                conv, &iaf.checksum);
    if (code != 0)
        return code;

    code = encode_krb5_iakerb_finished(&iaf, finished);

    krb5_free_checksum_contents(context, &iaf.checksum);

    return code;
}