krb5 commit [krb5-1.16]: Fix memory bugs in gss_add_cred() extension case

Greg Hudson ghudson at mit.edu
Tue Oct 30 12:25:55 EDT 2018


https://github.com/krb5/krb5/commit/71d28787a8f70186909141c8b6de2e7798ab5e41
commit 71d28787a8f70186909141c8b6de2e7798ab5e41
Author: Greg Hudson <ghudson at mit.edu>
Date:   Thu Sep 13 16:31:36 2018 -0400

    Fix memory bugs in gss_add_cred() extension case
    
    If gss_add_cred() is called with both an input_cred_handle and an
    output_cred_handle, it creates a new credential with the elements of
    the input credential plus the requested element.  Making a shallow
    copy of mechs_array and cred_array from the old credential creates
    aliased pointers which become invalid when one of the two credentials
    is released, leading to use-after-free and double-free errors.
    
    Instead, make a full copy of the input cred for this case.  Make this
    copy at the beginning so that union_cred can always be modified in
    place (and freed on error using gss_release_cred() if we created it),
    removing the need for new_union_cred, new_mechs_array, and
    new_cred_array.  Use a stack object for target_mechs to simplify
    cleanup and reduce the number of failure cases.
    
    GSSAPI provides no facility for copying a credential; since we mostly
    use the GSSAPI as our SPI for mechanisms, we have no simple way to
    copy mechanism creds when copying the union cred.  Use
    gss_export_cred() and gss_import_cred() if the mechanism provides
    them; otherwise fall back to gss_inquire_cred() and
    gss_acquire_cred().
    
    (cherry picked from commit 288cbada833dc6af7d43dd308563b48b73347dfb)
    
    ticket: 8734
    version_fixed: 1.16.2

 src/lib/gssapi/mechglue/g_acquire_cred.c |  207 ++++++++++++++++++++----------
 src/tests/gssapi/t_add_cred.c            |   31 +++++-
 2 files changed, 167 insertions(+), 71 deletions(-)

diff --git a/src/lib/gssapi/mechglue/g_acquire_cred.c b/src/lib/gssapi/mechglue/g_acquire_cred.c
index e7b3ed6..f03ab9e 100644
--- a/src/lib/gssapi/mechglue/g_acquire_cred.c
+++ b/src/lib/gssapi/mechglue/g_acquire_cred.c
@@ -308,6 +308,92 @@ val_add_cred_args(
     return (GSS_S_COMPLETE);
 }
 
+/* Copy a mechanism credential (with the mechanism given by mech_oid) as
+ * faithfully as possible. */
+static OM_uint32
+copy_mech_cred(OM_uint32 *minor_status, gss_cred_id_t cred_in,
+	       gss_OID mech_oid, gss_cred_id_t *cred_out)
+{
+    OM_uint32 status, tmpmin;
+    gss_mechanism mech;
+    gss_buffer_desc buf;
+    gss_name_t name;
+    OM_uint32 life;
+    gss_cred_usage_t usage;
+    gss_OID_set_desc oidset;
+
+    mech = gssint_get_mechanism(mech_oid);
+    if (mech == NULL)
+	return (GSS_S_BAD_MECH);
+    if (mech->gss_export_cred != NULL && mech->gss_import_cred != NULL) {
+	status = mech->gss_export_cred(minor_status, cred_in, &buf);
+	if (status != GSS_S_COMPLETE)
+	    return (status);
+	status = mech->gss_import_cred(minor_status, &buf, cred_out);
+	(void) gss_release_buffer(&tmpmin, &buf);
+    } else if (mech->gss_inquire_cred != NULL &&
+	       mech->gss_acquire_cred != NULL) {
+	status = mech->gss_inquire_cred(minor_status, cred_in, &name, &life,
+					&usage, NULL);
+	if (status != GSS_S_COMPLETE)
+	    return (status);
+	oidset.count = 1;
+	oidset.elements = gssint_get_public_oid(mech_oid);
+	status = mech->gss_acquire_cred(minor_status, name, life, &oidset,
+					usage, cred_out, NULL, NULL);
+	gss_release_name(&tmpmin, &name);
+    } else {
+	status = GSS_S_UNAVAILABLE;
+    }
+    return (status);
+}
+
+/* Copy a union credential from cred_in to *cred_out. */
+static OM_uint32
+copy_union_cred(OM_uint32 *minor_status, gss_cred_id_t cred_in,
+		gss_union_cred_t *cred_out)
+{
+    OM_uint32 status, tmpmin;
+    gss_union_cred_t cred = (gss_union_cred_t)cred_in;
+    gss_union_cred_t ncred = NULL;
+    gss_cred_id_t tmpcred;
+    int i;
+
+    ncred = calloc(1, sizeof (*ncred));
+    if (ncred == NULL)
+	goto oom;
+    ncred->mechs_array = calloc(cred->count, sizeof (*ncred->mechs_array));
+    ncred->cred_array = calloc(cred->count, sizeof (*ncred->cred_array));
+    if (ncred->mechs_array == NULL || ncred->cred_array == NULL)
+	goto oom;
+    ncred->count = cred->count;
+
+    for (i = 0; i < cred->count; i++) {
+	/* Copy this element's mechanism OID. */
+	ncred->mechs_array[i].elements = malloc(cred->mechs_array[i].length);
+	if (ncred->mechs_array[i].elements == NULL)
+	    goto oom;
+	g_OID_copy(&ncred->mechs_array[i], &cred->mechs_array[i]);
+
+	/* Copy this element's mechanism cred. */
+	status = copy_mech_cred(minor_status, cred->cred_array[i],
+				&cred->mechs_array[i], &ncred->cred_array[i]);
+	if (status != GSS_S_COMPLETE)
+	    goto error;
+    }
+
+    ncred->loopback = ncred;
+    *cred_out = ncred;
+    return GSS_S_COMPLETE;
+
+oom:
+    status = GSS_S_FAILURE;
+    *minor_status = ENOMEM;
+error:
+    tmpcred = (gss_cred_id_t)ncred;
+    (void) gss_release_cred(&tmpmin, &tmpcred);
+    return status;
+}
 
 /* V2 KRB5_CALLCONV */
 OM_uint32 KRB5_CALLCONV
@@ -359,14 +445,13 @@ gss_add_cred_from(minor_status, input_cred_handle,
     OM_uint32		status, temp_minor_status;
     OM_uint32		time_req, time_rec = 0, *time_recp = NULL;
     gss_union_name_t	union_name;
-    gss_union_cred_t	new_union_cred, union_cred;
+    gss_union_cred_t	union_cred;
     gss_name_t		internal_name = GSS_C_NO_NAME;
     gss_name_t		allocated_name = GSS_C_NO_NAME;
     gss_mechanism	mech;
-    gss_cred_id_t	cred = NULL;
-    gss_OID		new_mechs_array = NULL;
-    gss_cred_id_t *	new_cred_array = NULL;
-    gss_OID_set		target_mechs = GSS_C_NO_OID_SET;
+    gss_cred_id_t	cred = NULL, tmpcred;
+    void		*newptr, *oidbuf = NULL;
+    gss_OID_set_desc	target_mechs;
     gss_OID		selected_mech = GSS_C_NO_OID;
 
     status = val_add_cred_args(minor_status,
@@ -396,16 +481,25 @@ gss_add_cred_from(minor_status, input_cred_handle,
 	return (GSS_S_UNAVAILABLE);
 
     if (input_cred_handle == GSS_C_NO_CREDENTIAL) {
+	/* Create a new credential handle. */
 	union_cred = malloc(sizeof (gss_union_cred_desc));
 	if (union_cred == NULL)
 	    return (GSS_S_FAILURE);
 
 	(void) memset(union_cred, 0, sizeof (gss_union_cred_desc));
-    } else {
+	union_cred->loopback = union_cred;
+    } else if (output_cred_handle == NULL) {
+	/* Add to the existing handle. */
 	union_cred = (gss_union_cred_t)input_cred_handle;
 	if (gssint_get_mechanism_cred(union_cred, selected_mech) !=
 	    GSS_C_NO_CREDENTIAL)
 	    return (GSS_S_DUPLICATE_ELEMENT);
+    } else {
+	/* Create a new credential handle with the mechanism credentials of the
+	 * input handle plus the acquired mechanism credential. */
+	status = copy_union_cred(minor_status, input_cred_handle, &union_cred);
+	if (status != GSS_S_COMPLETE)
+	    return (status);
     }
 
     /* We may need to create a mechanism specific name. */
@@ -436,30 +530,28 @@ gss_add_cred_from(minor_status, input_cred_handle,
     else
 	time_req = 0;
 
-    status = gss_create_empty_oid_set(minor_status, &target_mechs);
-    if (status != GSS_S_COMPLETE)
-	goto errout;
-
-    status = gss_add_oid_set_member(minor_status,
-				    gssint_get_public_oid(selected_mech),
-				    &target_mechs);
-    if (status != GSS_S_COMPLETE)
+    target_mechs.count = 1;
+    target_mechs.elements = gssint_get_public_oid(selected_mech);
+    if (target_mechs.elements == NULL) {
+	status = GSS_S_FAILURE;
 	goto errout;
+    }
 
     if (initiator_time_rec != NULL || acceptor_time_rec != NULL)
 	time_recp = &time_rec;
 
     if (mech->gss_acquire_cred_from) {
 	status = mech->gss_acquire_cred_from(minor_status, internal_name,
-					     time_req, target_mechs,
+					     time_req, &target_mechs,
 					     cred_usage, cred_store, &cred,
 					     NULL, time_recp);
     } else if (cred_store == GSS_C_NO_CRED_STORE) {
 	status = mech->gss_acquire_cred(minor_status, internal_name, time_req,
-					target_mechs, cred_usage, &cred, NULL,
+					&target_mechs, cred_usage, &cred, NULL,
 					time_recp);
     } else {
-	return GSS_S_UNAVAILABLE;
+	status = GSS_S_UNAVAILABLE;
+	goto errout;
     }
 
     if (status != GSS_S_COMPLETE) {
@@ -467,17 +559,23 @@ gss_add_cred_from(minor_status, input_cred_handle,
 	goto errout;
     }
 
-    /* now add the new credential elements */
-    new_mechs_array = (gss_OID)
-	malloc(sizeof (gss_OID_desc) * (union_cred->count+1));
+    /* Extend the arrays in the union cred. */
 
-    new_cred_array = (gss_cred_id_t *)
-	malloc(sizeof (gss_cred_id_t) * (union_cred->count+1));
+    newptr = realloc(union_cred->mechs_array,
+		     (union_cred->count + 1) * sizeof (gss_OID_desc));
+    if (newptr == NULL) {
+	status = GSS_S_FAILURE;
+	goto errout;
+    }
+    union_cred->mechs_array = newptr;
 
-    if (!new_mechs_array || !new_cred_array) {
+    newptr = realloc(union_cred->cred_array,
+		     (union_cred->count + 1) * sizeof (gss_cred_id_t));
+    if (newptr == NULL) {
 	status = GSS_S_FAILURE;
 	goto errout;
     }
+    union_cred->cred_array = newptr;
 
     if (acceptor_time_rec)
 	if (cred_usage == GSS_C_ACCEPT || cred_usage == GSS_C_BOTH)
@@ -486,52 +584,25 @@ gss_add_cred_from(minor_status, input_cred_handle,
 	if (cred_usage == GSS_C_INITIATE || cred_usage == GSS_C_BOTH)
 	    *initiator_time_rec = time_rec;
 
-    /*
-     * OK, expand the mechanism array and the credential array
-     */
-    (void) memcpy(new_mechs_array, union_cred->mechs_array,
-		  sizeof (gss_OID_desc) * union_cred->count);
-    (void) memcpy(new_cred_array, union_cred->cred_array,
-		  sizeof (gss_cred_id_t) * union_cred->count);
-
-    new_cred_array[union_cred->count] = cred;
-    if ((new_mechs_array[union_cred->count].elements =
-	 malloc(selected_mech->length)) == NULL)
+    oidbuf = malloc(selected_mech->length);
+    if (oidbuf == NULL)
 	goto errout;
-
-    g_OID_copy(&new_mechs_array[union_cred->count], selected_mech);
+    union_cred->mechs_array[union_cred->count].elements = oidbuf;
+    g_OID_copy(&union_cred->mechs_array[union_cred->count], selected_mech);
 
     if (actual_mechs != NULL) {
-	status = gssint_make_public_oid_set(minor_status, new_mechs_array,
+	status = gssint_make_public_oid_set(minor_status,
+					    union_cred->mechs_array,
 					    union_cred->count + 1,
 					    actual_mechs);
-	if (GSS_ERROR(status)) {
-	    free(new_mechs_array[union_cred->count].elements);
+	if (GSS_ERROR(status))
 	    goto errout;
-	}
     }
 
-    if (output_cred_handle == NULL) {
-	free(union_cred->mechs_array);
-	free(union_cred->cred_array);
-	new_union_cred = union_cred;
-    } else if (input_cred_handle == GSS_C_NO_CREDENTIAL) {
-	new_union_cred = union_cred;
-	*output_cred_handle = (gss_cred_id_t)new_union_cred;
-    } else {
-	new_union_cred = malloc(sizeof (gss_union_cred_desc));
-	if (new_union_cred == NULL) {
-	    free(new_mechs_array[union_cred->count].elements);
-	    goto errout;
-	}
-	*new_union_cred = *union_cred;
-	*output_cred_handle = (gss_cred_id_t)new_union_cred;
-    }
-
-    new_union_cred->mechs_array = new_mechs_array;
-    new_union_cred->cred_array = new_cred_array;
-    new_union_cred->count++;
-    new_union_cred->loopback = new_union_cred;
+    union_cred->cred_array[union_cred->count] = cred;
+    union_cred->count++;
+    if (output_cred_handle != NULL)
+	*output_cred_handle = (gss_cred_id_t)union_cred;
 
     /* We're done with the internal name. Free it if we allocated it. */
 
@@ -539,16 +610,10 @@ gss_add_cred_from(minor_status, input_cred_handle,
 	(void) gssint_release_internal_name(&temp_minor_status,
 					   selected_mech,
 					   &allocated_name);
-    (void) generic_gss_release_oid_set(&temp_minor_status, &target_mechs);
 
     return (GSS_S_COMPLETE);
 
 errout:
-    if (new_mechs_array)
-	free(new_mechs_array);
-    if (new_cred_array)
-	free(new_cred_array);
-
     if (cred != NULL && mech->gss_release_cred)
 	mech->gss_release_cred(&temp_minor_status, &cred);
 
@@ -557,10 +622,12 @@ errout:
 					   selected_mech,
 					   &allocated_name);
 
-    if (input_cred_handle == GSS_C_NO_CREDENTIAL && union_cred)
-	free(union_cred);
+    if (output_cred_handle != NULL && union_cred != NULL) {
+	tmpcred = union_cred;
+	(void) gss_release_cred(&temp_minor_status, &tmpcred);
+    }
 
-    (void) generic_gss_release_oid_set(&temp_minor_status, &target_mechs);
+    free(oidbuf);
 
     return (status);
 }
diff --git a/src/tests/gssapi/t_add_cred.c b/src/tests/gssapi/t_add_cred.c
index 1407e67..b1142b6 100644
--- a/src/tests/gssapi/t_add_cred.c
+++ b/src/tests/gssapi/t_add_cred.c
@@ -46,7 +46,7 @@ int
 main()
 {
     OM_uint32 minor, major;
-    gss_cred_id_t cred1;
+    gss_cred_id_t cred1, cred2;
     gss_cred_usage_t usage;
     gss_name_t name;
 
@@ -102,7 +102,36 @@ main()
                                      NULL, &usage);
     assert(major == GSS_S_COMPLETE && usage == GSS_C_ACCEPT);
 
+    /* Start over with another new cred. */
     gss_release_cred(&minor, &cred1);
+    major = gss_add_cred(&minor, GSS_C_NO_CREDENTIAL, GSS_C_NO_NAME,
+                         &mech_krb5, GSS_C_ACCEPT, GSS_C_INDEFINITE,
+                         GSS_C_INDEFINITE, &cred1, NULL, NULL, NULL);
+    assert(major == GSS_S_COMPLETE);
+
+    /* Create an expanded cred by passing both an output handle and an input
+     * handle. */
+    major = gss_add_cred(&minor, cred1, GSS_C_NO_NAME, &mech_iakerb,
+                         GSS_C_INITIATE, GSS_C_INDEFINITE, GSS_C_INDEFINITE,
+                         &cred2, NULL, NULL, NULL);
+    assert(major == GSS_S_COMPLETE);
+
+    /* Verify mechanism creds in cred1 and cred2. */
+    major = gss_inquire_cred_by_mech(&minor, cred1, &mech_krb5, NULL, NULL,
+                                     NULL, &usage);
+    assert(major == GSS_S_COMPLETE && usage == GSS_C_ACCEPT);
+    major = gss_inquire_cred_by_mech(&minor, cred1, &mech_iakerb, NULL, NULL,
+                                     NULL, &usage);
+    assert(major == GSS_S_NO_CRED);
+    major = gss_inquire_cred_by_mech(&minor, cred2, &mech_krb5, NULL, NULL,
+                                     NULL, &usage);
+    assert(major == GSS_S_COMPLETE && usage == GSS_C_ACCEPT);
+    major = gss_inquire_cred_by_mech(&minor, cred2, &mech_iakerb, NULL, NULL,
+                                     NULL, &usage);
+    assert(major == GSS_S_COMPLETE && usage == GSS_C_INITIATE);
+
+    gss_release_cred(&minor, &cred1);
+    gss_release_cred(&minor, &cred2);
 
     return 0;
 }


More information about the cvs-krb5 mailing list