CVE-2014-5352: Other (NVD-CWE-Other)
value: 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.
CWE description: NVD is only using a subset of CWE for mapping instead of the entire CWE, and the weakness type is not covered by that subset.
Published: 2015-02-19T11:59Z
Repository: https://github.com/krb5/krb5
Language: C
Files changed: src/lib/gssapi/krb5/context_time.c, src/lib/gssapi/krb5/export_sec_context.c, src/lib/gssapi/krb5/gssapiP_krb5.h, src/lib/gssapi/krb5/gssapi_krb5.c, src/lib/gssapi/krb5/inq_context.c, src/lib/gssapi/krb5/k5seal.c, src/lib/gssapi/krb5/k5sealiov.c, src/lib/gssapi/krb5/k5unseal.c, src/lib/gssapi/krb5/k5unsealiov.c, src/lib/gssapi/krb5/lucid_context.c, src/lib/gssapi/krb5/prf.c, src/lib/gssapi/krb5/process_context_token.c, src/lib/gssapi/krb5/wrap_size_limit.c, src/tests/gssapi/t_prf.c
Commit date: 2015-02-04 14:26:15 -0500
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

Diff: diff --git a/src/lib/gssapi/krb5/context_time.c b/src/lib/gssapi/krb5/context_time.c
index b3d1db0075..a18cfb05b7 100644
--- a/src/lib/gssapi/krb5/context_time.c
+++ b/src/lib/gssapi/krb5/context_time.c
@@ -38,11 +38,11 @@ krb5_gss_context_time(minor_status, context_handle, time_rec)
     krb5_timestamp now;
     krb5_deltat lifetime;
 
     ctx = (krb5_gss_ctx_id_rec *) context_handle;
 
-    if (! ctx->established) {
+    if (ctx->terminated || !ctx->established) {
         *minor_status = KG_CTX_INCOMPLETE;
         return(GSS_S_NO_CONTEXT);
     }
 
     if ((code = krb5_timeofday(ctx->k5_context, &now))) {
diff --git a/src/lib/gssapi/krb5/export_sec_context.c b/src/lib/gssapi/krb5/export_sec_context.c
index 18a3a34cf6..1b3de68188 100644
--- a/src/lib/gssapi/krb5/export_sec_context.c
+++ b/src/lib/gssapi/krb5/export_sec_context.c
@@ -43,10 +43,15 @@ krb5_gss_export_sec_context(minor_status, context_handle, interprocess_token)
     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;
 
diff --git a/src/lib/gssapi/krb5/gssapiP_krb5.h b/src/lib/gssapi/krb5/gssapiP_krb5.h
index 7e807cc059..a0e8625d05 100644
--- a/src/lib/gssapi/krb5/gssapiP_krb5.h
+++ b/src/lib/gssapi/krb5/gssapiP_krb5.h
@@ -204,10 +204,11 @@ typedef struct _krb5_gss_ctx_id_rec {
     krb5_magic magic;
     unsigned int initiate : 1;   /* nonzero if initiating, zero if accepting */
     unsigned int established : 1;
     unsigned int have_acceptor_subkey : 1;
     unsigned int seed_init : 1;  /* XXX tested but never actually set */
+    unsigned int terminated : 1;
     OM_uint32 gss_flags;
     unsigned char seed[16];
     krb5_gss_name_t here;
     krb5_gss_name_t there;
     krb5_key subkey; /* One of two potential keys to use with RFC 4121
diff --git a/src/lib/gssapi/krb5/gssapi_krb5.c b/src/lib/gssapi/krb5/gssapi_krb5.c
index 6456b238e4..77b7fff026 100644
--- a/src/lib/gssapi/krb5/gssapi_krb5.c
+++ b/src/lib/gssapi/krb5/gssapi_krb5.c
@@ -367,11 +367,11 @@ krb5_gss_inquire_sec_context_by_oid (OM_uint32 *minor_status,
 
     *data_set = GSS_C_NO_BUFFER_SET;
 
     ctx = (krb5_gss_ctx_id_rec *) context_handle;
 
-    if (!ctx->established)
+    if (ctx->terminated || !ctx->established)
         return GSS_S_NO_CONTEXT;
 
     for (i = 0; i < sizeof(krb5_gss_inquire_sec_context_by_oid_ops)/
              sizeof(krb5_gss_inquire_sec_context_by_oid_ops[0]); i++) {
         if (g_OID_prefix_equal(desired_object, &krb5_gss_inquire_sec_context_by_oid_ops[i].oid)) {
diff --git a/src/lib/gssapi/krb5/inq_context.c b/src/lib/gssapi/krb5/inq_context.c
index eacb0fd378..096df2a895 100644
--- a/src/lib/gssapi/krb5/inq_context.c
+++ b/src/lib/gssapi/krb5/inq_context.c
@@ -103,11 +103,11 @@ krb5_gss_inquire_context(minor_status, context_handle, initiator_name,
     if (acceptor_name)
         *acceptor_name = (gss_name_t) NULL;
 
     ctx = (krb5_gss_ctx_id_rec *) context_handle;
 
-    if (! ctx->established) {
+    if (ctx->terminated || !ctx->established) {
         *minor_status = KG_CTX_INCOMPLETE;
         return(GSS_S_NO_CONTEXT);
     }
 
     initiator = NULL;
diff --git a/src/lib/gssapi/krb5/k5seal.c b/src/lib/gssapi/krb5/k5seal.c
index 7665cbae98..f1c74dd522 100644
--- a/src/lib/gssapi/krb5/k5seal.c
+++ b/src/lib/gssapi/krb5/k5seal.c
@@ -340,11 +340,11 @@ kg_seal(minor_status, context_handle, conf_req_flag, qop_req,
         return GSS_S_FAILURE;
     }
 
     ctx = (krb5_gss_ctx_id_rec *) context_handle;
 
-    if (! ctx->established) {
+    if (ctx->terminated || !ctx->established) {
         *minor_status = KG_CTX_INCOMPLETE;
         return(GSS_S_NO_CONTEXT);
     }
 
     context = ctx->k5_context;
diff --git a/src/lib/gssapi/krb5/k5sealiov.c b/src/lib/gssapi/krb5/k5sealiov.c
index a1296700ec..b53e348f09 100644
--- a/src/lib/gssapi/krb5/k5sealiov.c
+++ b/src/lib/gssapi/krb5/k5sealiov.c
@@ -279,11 +279,11 @@ kg_seal_iov(OM_uint32 *minor_status,
         *minor_status = (OM_uint32)G_UNKNOWN_QOP;
         return GSS_S_FAILURE;
     }
 
     ctx = (krb5_gss_ctx_id_rec *)context_handle;
-    if (!ctx->established) {
+    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)) {
diff --git a/src/lib/gssapi/krb5/k5unseal.c b/src/lib/gssapi/krb5/k5unseal.c
index 0573958787..673c883e63 100644
--- a/src/lib/gssapi/krb5/k5unseal.c
+++ b/src/lib/gssapi/krb5/k5unseal.c
@@ -490,11 +490,11 @@ kg_unseal(minor_status, context_handle, input_token_buffer,
     int vfyflags = 0;
     OM_uint32 ret;
 
     ctx = (krb5_gss_ctx_id_rec *) context_handle;
 
-    if (! ctx->established) {
+    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 */
diff --git a/src/lib/gssapi/krb5/k5unsealiov.c b/src/lib/gssapi/krb5/k5unsealiov.c
index f34d802bd9..8b6704274d 100644
--- a/src/lib/gssapi/krb5/k5unsealiov.c
+++ b/src/lib/gssapi/krb5/k5unsealiov.c
@@ -623,11 +623,11 @@ kg_unseal_iov(OM_uint32 *minor_status,
 {
     krb5_gss_ctx_id_rec *ctx;
     OM_uint32 code;
 
     ctx = (krb5_gss_ctx_id_rec *)context_handle;
-    if (!ctx->established) {
+    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) {
diff --git a/src/lib/gssapi/krb5/lucid_context.c b/src/lib/gssapi/krb5/lucid_context.c
index 85df7fda59..449e71fed4 100644
--- a/src/lib/gssapi/krb5/lucid_context.c
+++ b/src/lib/gssapi/krb5/lucid_context.c
@@ -73,10 +73,15 @@ gss_krb5int_export_lucid_sec_context(
     /* 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);
diff --git a/src/lib/gssapi/krb5/prf.c b/src/lib/gssapi/krb5/prf.c
index e19291f348..e897074fc1 100644
--- a/src/lib/gssapi/krb5/prf.c
+++ b/src/lib/gssapi/krb5/prf.c
@@ -56,10 +56,14 @@ krb5_gss_pseudo_random(OM_uint32 *minor_status,
 
     ns.length = 0;
     ns.data = NULL;
 
     ctx = (krb5_gss_ctx_id_t)context;
+    if (ctx->terminated || !ctx->established) {
+        *minor_status = KG_CTX_INCOMPLETE;
+        return GSS_S_NO_CONTEXT;
+    }
 
     switch (prf_key) {
     case GSS_C_PRF_KEY_FULL:
         if (ctx->have_acceptor_subkey) {
             key = ctx->acceptor_subkey;
diff --git a/src/lib/gssapi/krb5/process_context_token.c b/src/lib/gssapi/krb5/process_context_token.c
index ae33180a75..a672f48c85 100644
--- a/src/lib/gssapi/krb5/process_context_token.c
+++ b/src/lib/gssapi/krb5/process_context_token.c
@@ -37,23 +37,30 @@ krb5_gss_process_context_token(minor_status, context_handle,
     krb5_gss_ctx_id_rec *ctx;
     OM_uint32 majerr;
 
     ctx = (krb5_gss_ctx_id_t) context_handle;
 
-    if (! ctx->established) {
+    if (ctx->terminated || !ctx->established) {
         *minor_status = KG_CTX_INCOMPLETE;
         return(GSS_S_NO_CONTEXT);
     }
 
+    /* We only support context deletion tokens for now, and RFC 4121 does not
+     * define a context deletion token. */
+    if (ctx->proto) {
+        *minor_status = 0;
+        return(GSS_S_DEFECTIVE_TOKEN);
+    }
+
     /* "unseal" the token */
 
     if (GSS_ERROR(majerr = kg_unseal(minor_status, context_handle,
                                      token_buffer,
                                      GSS_C_NO_BUFFER, NULL, NULL,
                                      KG_TOK_DEL_CTX)))
         return(majerr);
 
-    /* that's it.  delete the context */
-
-    return(krb5_gss_delete_sec_context(minor_status, &context_handle,
-                                       GSS_C_NO_BUFFER));
+    /* Mark the context as terminated, but do not delete it (as that would
+     * leave the caller with a dangling context handle). */
+    ctx->terminated = 1;
+    return(GSS_S_COMPLETE);
 }
diff --git a/src/lib/gssapi/krb5/wrap_size_limit.c b/src/lib/gssapi/krb5/wrap_size_limit.c
index 7bc4221daf..ed5c599951 100644
--- a/src/lib/gssapi/krb5/wrap_size_limit.c
+++ b/src/lib/gssapi/krb5/wrap_size_limit.c
@@ -93,11 +93,11 @@ krb5_gss_wrap_size_limit(minor_status, context_handle, conf_req_flag,
         *minor_status = (OM_uint32) G_UNKNOWN_QOP;
         return(GSS_S_FAILURE);
     }
 
     ctx = (krb5_gss_ctx_id_rec *) context_handle;
-    if (! ctx->established) {
+    if (ctx->terminated || !ctx->established) {
         *minor_status = KG_CTX_INCOMPLETE;
         return(GSS_S_NO_CONTEXT);
     }
 
     if (ctx->proto == 1) {
diff --git a/src/tests/gssapi/t_prf.c b/src/tests/gssapi/t_prf.c
index 254f8fb5eb..7f0489995e 100644
--- a/src/tests/gssapi/t_prf.c
+++ b/src/tests/gssapi/t_prf.c
@@ -125,10 +125,11 @@ main(int argc, char *argv[])
      */
     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. */

Vulnerable code:     if (! ctx->established) {
    if (!ctx->established)
    if (! ctx->established) {
    if (! ctx->established) {
    if (!ctx->established) {
    if (! ctx->established) {
    if (!ctx->established) {
    if (! ctx->established) {
    /* that's it.  delete the context */

    return(krb5_gss_delete_sec_context(minor_status, &context_handle,
                                       GSS_C_NO_BUFFER));
    if (! ctx->established) {
Fixed code:     if (ctx->terminated || !ctx->established) {
    if (ctx->terminated) {
        *minor_status = KG_CTX_INCOMPLETE;
        return (GSS_S_NO_CONTEXT);
    }

    unsigned int terminated : 1;
    if (ctx->terminated || !ctx->established)
    if (ctx->terminated || !ctx->established) {
    if (ctx->terminated || !ctx->established) {
    if (ctx->terminated || !ctx->established) {
    if (ctx->terminated || !ctx->established) {
    if (ctx->terminated || !ctx->established) {
    if (ctx->terminated || !ctx->established) {
        *minor_status = KG_CTX_INCOMPLETE;
        return GSS_S_NO_CONTEXT;
    }

    if (ctx->terminated || !ctx->established) {
        *minor_status = KG_CTX_INCOMPLETE;
        return GSS_S_NO_CONTEXT;
    }
    if (ctx->terminated || !ctx->established) {
    /* We only support context deletion tokens for now, and RFC 4121 does not
     * define a context deletion token. */
    if (ctx->proto) {
        *minor_status = 0;
        return(GSS_S_DEFECTIVE_TOKEN);
    }

    /* Mark the context as terminated, but do not delete it (as that would
     * leave the caller with a dangling context handle). */
    ctx->terminated = 1;
    return(GSS_S_COMPLETE);
    if (ctx->terminated || !ctx->established) {
    kgctx.established = 1;