|
|
d7027e |
From c0873e9b9de0570c97e88598f17b72bf51fd7f5d Mon Sep 17 00:00:00 2001
|
|
|
d7027e |
From: Greg Hudson <ghudson@mit.edu>
|
|
|
d7027e |
Date: Sun, 1 Jul 2018 00:12:25 -0400
|
|
|
d7027e |
Subject: [PATCH] Fix bugs with concurrent use of MEMORY ccaches
|
|
|
d7027e |
|
|
|
d7027e |
A memory ccache iterator stores an alias into the cache object's
|
|
|
d7027e |
linked list of credentials. If the cache is reinitialized while the
|
|
|
d7027e |
iterator is active, the alias becomes invalid. Also, multiple handles
|
|
|
d7027e |
referencing the same memory ccache all use aliases to the same data
|
|
|
d7027e |
object; if one of the handles is destroyed, the other contains a
|
|
|
d7027e |
dangling pointer.
|
|
|
d7027e |
|
|
|
d7027e |
Fix the first issue by adding a generation counter to the cache and to
|
|
|
d7027e |
cursors, incremented each time the cache is initialized or destroyed.
|
|
|
d7027e |
Check the generation on each cursor step and end the iteration if the
|
|
|
d7027e |
list was invalidated. Fix the second issue by adding a reference
|
|
|
d7027e |
count to the cache object, counting one reference for the table slot
|
|
|
d7027e |
and one for each open handle. Empty the cache object on each destroy
|
|
|
d7027e |
operation, but only release the object when the last handle to it is
|
|
|
d7027e |
destroyed or closed.
|
|
|
d7027e |
|
|
|
d7027e |
Add regression tests for the two issues to t_cc.c.
|
|
|
d7027e |
|
|
|
d7027e |
The first issue was reported by Sorin Manolache.
|
|
|
d7027e |
|
|
|
d7027e |
ticket: 8202
|
|
|
d7027e |
tags: pullup
|
|
|
d7027e |
target_version: 1.16-next
|
|
|
d7027e |
target_version: 1.15-next
|
|
|
d7027e |
|
|
|
d7027e |
(cherry picked from commit 146dadec8fe7ccc4149eb2e3f577cc320aee6efb)
|
|
|
d7027e |
---
|
|
|
d7027e |
src/lib/krb5/ccache/cc_memory.c | 164 ++++++++++++++++++++------------
|
|
|
d7027e |
src/lib/krb5/ccache/t_cc.c | 51 ++++++++++
|
|
|
d7027e |
2 files changed, 154 insertions(+), 61 deletions(-)
|
|
|
d7027e |
|
|
|
d7027e |
diff --git a/src/lib/krb5/ccache/cc_memory.c b/src/lib/krb5/ccache/cc_memory.c
|
|
|
d7027e |
index c5425eb3a..8cdaff7fb 100644
|
|
|
d7027e |
--- a/src/lib/krb5/ccache/cc_memory.c
|
|
|
d7027e |
+++ b/src/lib/krb5/ccache/cc_memory.c
|
|
|
d7027e |
@@ -102,18 +102,20 @@ extern krb5_error_code krb5_change_cache (void);
|
|
|
d7027e |
typedef struct _krb5_mcc_link {
|
|
|
d7027e |
struct _krb5_mcc_link *next;
|
|
|
d7027e |
krb5_creds *creds;
|
|
|
d7027e |
-} krb5_mcc_link, *krb5_mcc_cursor;
|
|
|
d7027e |
+} krb5_mcc_link;
|
|
|
d7027e |
|
|
|
d7027e |
/* Per-cache data header. */
|
|
|
d7027e |
typedef struct _krb5_mcc_data {
|
|
|
d7027e |
char *name;
|
|
|
d7027e |
k5_cc_mutex lock;
|
|
|
d7027e |
krb5_principal prin;
|
|
|
d7027e |
- krb5_mcc_cursor link;
|
|
|
d7027e |
+ krb5_mcc_link *link;
|
|
|
d7027e |
krb5_timestamp changetime;
|
|
|
d7027e |
/* Time offsets for clock-skewed clients. */
|
|
|
d7027e |
krb5_int32 time_offset;
|
|
|
d7027e |
krb5_int32 usec_offset;
|
|
|
d7027e |
+ int refcount; /* One for the table slot, one per handle */
|
|
|
d7027e |
+ int generation; /* Incremented at each initialize */
|
|
|
d7027e |
} krb5_mcc_data;
|
|
|
d7027e |
|
|
|
d7027e |
/* List of memory caches. */
|
|
|
d7027e |
@@ -122,6 +124,12 @@ typedef struct krb5_mcc_list_node {
|
|
|
d7027e |
krb5_mcc_data *cache;
|
|
|
d7027e |
} krb5_mcc_list_node;
|
|
|
d7027e |
|
|
|
d7027e |
+/* Iterator over credentials in a memory cache. */
|
|
|
d7027e |
+struct mcc_cursor {
|
|
|
d7027e |
+ int generation;
|
|
|
d7027e |
+ krb5_mcc_link *next_link;
|
|
|
d7027e |
+};
|
|
|
d7027e |
+
|
|
|
d7027e |
/* Iterator over memory caches. */
|
|
|
d7027e |
struct krb5_mcc_ptcursor_data {
|
|
|
d7027e |
struct krb5_mcc_list_node *cur;
|
|
|
d7027e |
@@ -132,7 +140,23 @@ static krb5_mcc_list_node *mcc_head = 0;
|
|
|
d7027e |
|
|
|
d7027e |
static void update_mcc_change_time(krb5_mcc_data *);
|
|
|
d7027e |
|
|
|
d7027e |
-static void krb5_mcc_free (krb5_context context, krb5_ccache id);
|
|
|
d7027e |
+/* Remove creds from d, invalidate any existing cursors, and unset the client
|
|
|
d7027e |
+ * principal. The caller is responsible for locking. */
|
|
|
d7027e |
+static void
|
|
|
d7027e |
+empty_mcc_cache(krb5_context context, krb5_mcc_data *d)
|
|
|
d7027e |
+{
|
|
|
d7027e |
+ krb5_mcc_link *curr, *next;
|
|
|
d7027e |
+
|
|
|
d7027e |
+ for (curr = d->link; curr != NULL; curr = next) {
|
|
|
d7027e |
+ next = curr->next;
|
|
|
d7027e |
+ krb5_free_creds(context, curr->creds);
|
|
|
d7027e |
+ free(curr);
|
|
|
d7027e |
+ }
|
|
|
d7027e |
+ d->link = NULL;
|
|
|
d7027e |
+ d->generation++;
|
|
|
d7027e |
+ krb5_free_principal(context, d->prin);
|
|
|
d7027e |
+ d->prin = NULL;
|
|
|
d7027e |
+}
|
|
|
d7027e |
|
|
|
d7027e |
/*
|
|
|
d7027e |
* Modifies:
|
|
|
d7027e |
@@ -150,16 +174,12 @@ krb5_mcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ)
|
|
|
d7027e |
{
|
|
|
d7027e |
krb5_os_context os_ctx = &context->os_context;
|
|
|
d7027e |
krb5_error_code ret;
|
|
|
d7027e |
- krb5_mcc_data *d;
|
|
|
d7027e |
+ krb5_mcc_data *d = id->data;
|
|
|
d7027e |
|
|
|
d7027e |
- d = (krb5_mcc_data *)id->data;
|
|
|
d7027e |
k5_cc_mutex_lock(context, &d->lock);
|
|
|
d7027e |
+ empty_mcc_cache(context, d);
|
|
|
d7027e |
|
|
|
d7027e |
- krb5_mcc_free(context, id);
|
|
|
d7027e |
-
|
|
|
d7027e |
- d = (krb5_mcc_data *)id->data;
|
|
|
d7027e |
- ret = krb5_copy_principal(context, princ,
|
|
|
d7027e |
- &d->prin);
|
|
|
d7027e |
+ ret = krb5_copy_principal(context, princ, &d->prin);
|
|
|
d7027e |
update_mcc_change_time(d);
|
|
|
d7027e |
|
|
|
d7027e |
if (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID) {
|
|
|
d7027e |
@@ -185,61 +205,59 @@ krb5_mcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ)
|
|
|
d7027e |
krb5_error_code KRB5_CALLCONV
|
|
|
d7027e |
krb5_mcc_close(krb5_context context, krb5_ccache id)
|
|
|
d7027e |
{
|
|
|
d7027e |
+ krb5_mcc_data *d = id->data;
|
|
|
d7027e |
+ int count;
|
|
|
d7027e |
+
|
|
|
d7027e |
free(id);
|
|
|
d7027e |
- return KRB5_OK;
|
|
|
d7027e |
-}
|
|
|
d7027e |
-
|
|
|
d7027e |
-static void
|
|
|
d7027e |
-krb5_mcc_free(krb5_context context, krb5_ccache id)
|
|
|
d7027e |
-{
|
|
|
d7027e |
- krb5_mcc_cursor curr,next;
|
|
|
d7027e |
- krb5_mcc_data *d;
|
|
|
d7027e |
-
|
|
|
d7027e |
- d = (krb5_mcc_data *) id->data;
|
|
|
d7027e |
- for (curr = d->link; curr;) {
|
|
|
d7027e |
- krb5_free_creds(context, curr->creds);
|
|
|
d7027e |
- next = curr->next;
|
|
|
d7027e |
- free(curr);
|
|
|
d7027e |
- curr = next;
|
|
|
d7027e |
+ k5_cc_mutex_lock(context, &d->lock);
|
|
|
d7027e |
+ count = --d->refcount;
|
|
|
d7027e |
+ k5_cc_mutex_unlock(context, &d->lock);
|
|
|
d7027e |
+ if (count == 0) {
|
|
|
d7027e |
+ /* This is the last active handle referencing d and d has been removed
|
|
|
d7027e |
+ * from the table, so we can release it. */
|
|
|
d7027e |
+ empty_mcc_cache(context, d);
|
|
|
d7027e |
+ free(d->name);
|
|
|
d7027e |
+ k5_cc_mutex_destroy(&d->lock);
|
|
|
d7027e |
+ free(d);
|
|
|
d7027e |
}
|
|
|
d7027e |
- d->link = NULL;
|
|
|
d7027e |
- krb5_free_principal(context, d->prin);
|
|
|
d7027e |
+ return KRB5_OK;
|
|
|
d7027e |
}
|
|
|
d7027e |
|
|
|
d7027e |
/*
|
|
|
d7027e |
* Effects:
|
|
|
d7027e |
* Destroys the contents of id. id is invalid after call.
|
|
|
d7027e |
- *
|
|
|
d7027e |
- * Errors:
|
|
|
d7027e |
- * system errors (locks related)
|
|
|
d7027e |
*/
|
|
|
d7027e |
krb5_error_code KRB5_CALLCONV
|
|
|
d7027e |
krb5_mcc_destroy(krb5_context context, krb5_ccache id)
|
|
|
d7027e |
{
|
|
|
d7027e |
krb5_mcc_list_node **curr, *node;
|
|
|
d7027e |
- krb5_mcc_data *d;
|
|
|
d7027e |
+ krb5_mcc_data *d = id->data;
|
|
|
d7027e |
+ krb5_boolean removed_from_table = FALSE;
|
|
|
d7027e |
|
|
|
d7027e |
k5_cc_mutex_lock(context, &krb5int_mcc_mutex);
|
|
|
d7027e |
|
|
|
d7027e |
- d = (krb5_mcc_data *)id->data;
|
|
|
d7027e |
for (curr = &mcc_head; *curr; curr = &(*curr)->next) {
|
|
|
d7027e |
if ((*curr)->cache == d) {
|
|
|
d7027e |
node = *curr;
|
|
|
d7027e |
*curr = node->next;
|
|
|
d7027e |
free(node);
|
|
|
d7027e |
+ removed_from_table = TRUE;
|
|
|
d7027e |
break;
|
|
|
d7027e |
}
|
|
|
d7027e |
}
|
|
|
d7027e |
k5_cc_mutex_unlock(context, &krb5int_mcc_mutex);
|
|
|
d7027e |
|
|
|
d7027e |
+ /* Empty the cache and remove the reference for the table slot. There will
|
|
|
d7027e |
+ * always be at least one reference left for the handle being destroyed. */
|
|
|
d7027e |
k5_cc_mutex_lock(context, &d->lock);
|
|
|
d7027e |
-
|
|
|
d7027e |
- krb5_mcc_free(context, id);
|
|
|
d7027e |
- free(d->name);
|
|
|
d7027e |
+ empty_mcc_cache(context, d);
|
|
|
d7027e |
+ if (removed_from_table)
|
|
|
d7027e |
+ d->refcount--;
|
|
|
d7027e |
k5_cc_mutex_unlock(context, &d->lock);
|
|
|
d7027e |
- k5_cc_mutex_destroy(&d->lock);
|
|
|
d7027e |
- free(d);
|
|
|
d7027e |
- free(id);
|
|
|
d7027e |
+
|
|
|
d7027e |
+ /* Invalidate the handle, possibly removing the last reference to d and
|
|
|
d7027e |
+ * freeing it. */
|
|
|
d7027e |
+ krb5_mcc_close(context, id);
|
|
|
d7027e |
|
|
|
d7027e |
krb5_change_cache ();
|
|
|
d7027e |
return KRB5_OK;
|
|
|
d7027e |
@@ -279,9 +297,12 @@ krb5_mcc_resolve (krb5_context context, krb5_ccache *id, const char *residual)
|
|
|
d7027e |
for (ptr = mcc_head; ptr; ptr=ptr->next)
|
|
|
d7027e |
if (!strcmp(ptr->cache->name, residual))
|
|
|
d7027e |
break;
|
|
|
d7027e |
- if (ptr)
|
|
|
d7027e |
+ if (ptr != NULL) {
|
|
|
d7027e |
d = ptr->cache;
|
|
|
d7027e |
- else {
|
|
|
d7027e |
+ k5_cc_mutex_lock(context, &d->lock);
|
|
|
d7027e |
+ d->refcount++;
|
|
|
d7027e |
+ k5_cc_mutex_unlock(context, &d->lock);
|
|
|
d7027e |
+ } else {
|
|
|
d7027e |
err = new_mcc_data(residual, &d);
|
|
|
d7027e |
if (err) {
|
|
|
d7027e |
k5_cc_mutex_unlock(context, &krb5int_mcc_mutex);
|
|
|
d7027e |
@@ -326,14 +347,18 @@ krb5_error_code KRB5_CALLCONV
|
|
|
d7027e |
krb5_mcc_start_seq_get(krb5_context context, krb5_ccache id,
|
|
|
d7027e |
krb5_cc_cursor *cursor)
|
|
|
d7027e |
{
|
|
|
d7027e |
- krb5_mcc_cursor mcursor;
|
|
|
d7027e |
+ struct mcc_cursor *mcursor;
|
|
|
d7027e |
krb5_mcc_data *d;
|
|
|
d7027e |
|
|
|
d7027e |
+ mcursor = malloc(sizeof(*mcursor));
|
|
|
d7027e |
+ if (mcursor == NULL)
|
|
|
d7027e |
+ return KRB5_CC_NOMEM;
|
|
|
d7027e |
d = id->data;
|
|
|
d7027e |
k5_cc_mutex_lock(context, &d->lock);
|
|
|
d7027e |
- mcursor = d->link;
|
|
|
d7027e |
+ mcursor->generation = d->generation;
|
|
|
d7027e |
+ mcursor->next_link = d->link;
|
|
|
d7027e |
k5_cc_mutex_unlock(context, &d->lock);
|
|
|
d7027e |
- *cursor = (krb5_cc_cursor) mcursor;
|
|
|
d7027e |
+ *cursor = mcursor;
|
|
|
d7027e |
return KRB5_OK;
|
|
|
d7027e |
}
|
|
|
d7027e |
|
|
|
d7027e |
@@ -361,23 +386,34 @@ krb5_error_code KRB5_CALLCONV
|
|
|
d7027e |
krb5_mcc_next_cred(krb5_context context, krb5_ccache id,
|
|
|
d7027e |
krb5_cc_cursor *cursor, krb5_creds *creds)
|
|
|
d7027e |
{
|
|
|
d7027e |
- krb5_mcc_cursor mcursor;
|
|
|
d7027e |
+ struct mcc_cursor *mcursor;
|
|
|
d7027e |
krb5_error_code retval;
|
|
|
d7027e |
+ krb5_mcc_data *d = id->data;
|
|
|
d7027e |
|
|
|
d7027e |
- /* Once the node in the linked list is created, it's never
|
|
|
d7027e |
- modified, so we don't need to worry about locking here. (Note
|
|
|
d7027e |
- that we don't support _remove_cred.) */
|
|
|
d7027e |
- mcursor = (krb5_mcc_cursor) *cursor;
|
|
|
d7027e |
- if (mcursor == NULL)
|
|
|
d7027e |
- return KRB5_CC_END;
|
|
|
d7027e |
memset(creds, 0, sizeof(krb5_creds));
|
|
|
d7027e |
- if (mcursor->creds) {
|
|
|
d7027e |
- retval = k5_copy_creds_contents(context, mcursor->creds, creds);
|
|
|
d7027e |
- if (retval)
|
|
|
d7027e |
- return retval;
|
|
|
d7027e |
+ mcursor = *cursor;
|
|
|
d7027e |
+ if (mcursor->next_link == NULL)
|
|
|
d7027e |
+ return KRB5_CC_END;
|
|
|
d7027e |
+
|
|
|
d7027e |
+ /*
|
|
|
d7027e |
+ * Check the cursor generation against the cache generation in case the
|
|
|
d7027e |
+ * cache has been reinitialized or destroyed, freeing the pointer in the
|
|
|
d7027e |
+ * cursor. Keep the cache locked while we copy the creds and advance the
|
|
|
d7027e |
+ * pointer, in case another thread reinitializes the cache after we check
|
|
|
d7027e |
+ * the generation.
|
|
|
d7027e |
+ */
|
|
|
d7027e |
+ k5_cc_mutex_lock(context, &d->lock);
|
|
|
d7027e |
+ if (mcursor->generation != d->generation) {
|
|
|
d7027e |
+ k5_cc_mutex_unlock(context, &d->lock);
|
|
|
d7027e |
+ return KRB5_CC_END;
|
|
|
d7027e |
}
|
|
|
d7027e |
- *cursor = (krb5_cc_cursor)mcursor->next;
|
|
|
d7027e |
- return KRB5_OK;
|
|
|
d7027e |
+
|
|
|
d7027e |
+ retval = k5_copy_creds_contents(context, mcursor->next_link->creds, creds);
|
|
|
d7027e |
+ if (retval == 0)
|
|
|
d7027e |
+ mcursor->next_link = mcursor->next_link->next;
|
|
|
d7027e |
+
|
|
|
d7027e |
+ k5_cc_mutex_unlock(context, &d->lock);
|
|
|
d7027e |
+ return retval;
|
|
|
d7027e |
}
|
|
|
d7027e |
|
|
|
d7027e |
/*
|
|
|
d7027e |
@@ -396,14 +432,18 @@ krb5_mcc_next_cred(krb5_context context, krb5_ccache id,
|
|
|
d7027e |
krb5_error_code KRB5_CALLCONV
|
|
|
d7027e |
krb5_mcc_end_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor)
|
|
|
d7027e |
{
|
|
|
d7027e |
- *cursor = 0L;
|
|
|
d7027e |
+ free(*cursor);
|
|
|
d7027e |
+ *cursor = NULL;
|
|
|
d7027e |
return KRB5_OK;
|
|
|
d7027e |
}
|
|
|
d7027e |
|
|
|
d7027e |
-/* Utility routine: Creates the back-end data for a memory cache, and
|
|
|
d7027e |
- threads it into the global linked list.
|
|
|
d7027e |
-
|
|
|
d7027e |
- Call with the global list lock held. */
|
|
|
d7027e |
+/*
|
|
|
d7027e |
+ * Utility routine: Creates the back-end data for a memory cache, and threads
|
|
|
d7027e |
+ * it into the global linked list. Give the new object two references, one for
|
|
|
d7027e |
+ * the table slot and one for the caller's handle.
|
|
|
d7027e |
+ *
|
|
|
d7027e |
+ * Call with the global list lock held.
|
|
|
d7027e |
+ */
|
|
|
d7027e |
static krb5_error_code
|
|
|
d7027e |
new_mcc_data (const char *name, krb5_mcc_data **dataptr)
|
|
|
d7027e |
{
|
|
|
d7027e |
@@ -432,6 +472,8 @@ new_mcc_data (const char *name, krb5_mcc_data **dataptr)
|
|
|
d7027e |
d->changetime = 0;
|
|
|
d7027e |
d->time_offset = 0;
|
|
|
d7027e |
d->usec_offset = 0;
|
|
|
d7027e |
+ d->refcount = 2;
|
|
|
d7027e |
+ d->generation = 0;
|
|
|
d7027e |
update_mcc_change_time(d);
|
|
|
d7027e |
|
|
|
d7027e |
n = malloc(sizeof(krb5_mcc_list_node));
|
|
|
d7027e |
diff --git a/src/lib/krb5/ccache/t_cc.c b/src/lib/krb5/ccache/t_cc.c
|
|
|
d7027e |
index 6069cabd3..cd4569c4c 100644
|
|
|
d7027e |
--- a/src/lib/krb5/ccache/t_cc.c
|
|
|
d7027e |
+++ b/src/lib/krb5/ccache/t_cc.c
|
|
|
d7027e |
@@ -386,6 +386,55 @@ test_misc(krb5_context context)
|
|
|
d7027e |
krb5_cc_dfl_ops = ops_save;
|
|
|
d7027e |
|
|
|
d7027e |
}
|
|
|
d7027e |
+
|
|
|
d7027e |
+/*
|
|
|
d7027e |
+ * Regression tests for #8202. Because memory ccaches share objects between
|
|
|
d7027e |
+ * different handles to the same cache and between iterators and caches,
|
|
|
d7027e |
+ * historically there have been some bugs when those objects are released.
|
|
|
d7027e |
+ */
|
|
|
d7027e |
+static void
|
|
|
d7027e |
+test_memory_concurrent(krb5_context context)
|
|
|
d7027e |
+{
|
|
|
d7027e |
+ krb5_error_code kret;
|
|
|
d7027e |
+ krb5_ccache id1, id2;
|
|
|
d7027e |
+ krb5_cc_cursor cursor;
|
|
|
d7027e |
+ krb5_creds creds;
|
|
|
d7027e |
+
|
|
|
d7027e |
+ /* Create two handles to the same memory ccache and destroy them. */
|
|
|
d7027e |
+ kret = krb5_cc_resolve(context, "MEMORY:x", &id1);
|
|
|
d7027e |
+ CHECK(kret, "resolve 1");
|
|
|
d7027e |
+ kret = krb5_cc_resolve(context, "MEMORY:x", &id2);
|
|
|
d7027e |
+ CHECK(kret, "resolve 2");
|
|
|
d7027e |
+ kret = krb5_cc_destroy(context, id1);
|
|
|
d7027e |
+ CHECK(kret, "destroy 1");
|
|
|
d7027e |
+ kret = krb5_cc_destroy(context, id2);
|
|
|
d7027e |
+ CHECK(kret, "destroy 2");
|
|
|
d7027e |
+
|
|
|
d7027e |
+ kret = init_test_cred(context);
|
|
|
d7027e |
+ CHECK(kret, "init_creds");
|
|
|
d7027e |
+
|
|
|
d7027e |
+ /* Reinitialize the cache after creating an iterator for it, and verify
|
|
|
d7027e |
+ * that the iterator ends gracefully. */
|
|
|
d7027e |
+ kret = krb5_cc_resolve(context, "MEMORY:x", &id1);
|
|
|
d7027e |
+ CHECK(kret, "resolve");
|
|
|
d7027e |
+ kret = krb5_cc_initialize(context, id1, test_creds.client);
|
|
|
d7027e |
+ CHECK(kret, "initialize");
|
|
|
d7027e |
+ kret = krb5_cc_store_cred(context, id1, &test_creds);
|
|
|
d7027e |
+ CHECK(kret, "store");
|
|
|
d7027e |
+ kret = krb5_cc_start_seq_get(context, id1, &cursor);
|
|
|
d7027e |
+ CHECK(kret, "start_seq_get");
|
|
|
d7027e |
+ kret = krb5_cc_initialize(context, id1, test_creds.client);
|
|
|
d7027e |
+ CHECK(kret, "initialize again");
|
|
|
d7027e |
+ kret = krb5_cc_next_cred(context, id1, &cursor, &creds);
|
|
|
d7027e |
+ CHECK_BOOL(kret != KRB5_CC_END, "iterator should end", "next_cred");
|
|
|
d7027e |
+ kret = krb5_cc_end_seq_get(context, id1, &cursor);
|
|
|
d7027e |
+ CHECK(kret, "end_seq_get");
|
|
|
d7027e |
+ kret = krb5_cc_destroy(context, id1);
|
|
|
d7027e |
+ CHECK(kret, "destroy");
|
|
|
d7027e |
+
|
|
|
d7027e |
+ free_test_cred(context);
|
|
|
d7027e |
+}
|
|
|
d7027e |
+
|
|
|
d7027e |
extern const krb5_cc_ops krb5_mcc_ops;
|
|
|
d7027e |
extern const krb5_cc_ops krb5_fcc_ops;
|
|
|
d7027e |
|
|
|
d7027e |
@@ -434,6 +483,8 @@ main(void)
|
|
|
d7027e |
do_test(context, "MEMORY:");
|
|
|
d7027e |
do_test(context, "FILE:");
|
|
|
d7027e |
|
|
|
d7027e |
+ test_memory_concurrent(context);
|
|
|
d7027e |
+
|
|
|
d7027e |
krb5_free_context(context);
|
|
|
d7027e |
return 0;
|
|
|
d7027e |
}
|