From 6dc1508e8c23df8571bf5ce67ee73dea07ad723a Mon Sep 17 00:00:00 2001 From: Nicolas Williams Date: Sun, 21 Dec 2025 01:47:12 -0600 Subject: [PATCH] gss: Add threaded testing of GSS-API! --- lib/gssapi/test_context.c | 111 +++++++++++++++++++++++++++++++++++++ tests/gss/check-context.in | 20 +++++++ 2 files changed, 131 insertions(+) diff --git a/lib/gssapi/test_context.c b/lib/gssapi/test_context.c index e6d505033..d2d37d3c6 100644 --- a/lib/gssapi/test_context.c +++ b/lib/gssapi/test_context.c @@ -82,6 +82,8 @@ static int token_split = 0; static int version_flag = 0; static int verbose_flag = 0; static int help_flag = 0; +static int threaded_flag = 0; +static int num_threads = 8; static int i_channel_bound = 0; static char *i_channel_bindings = NULL; static char *a_channel_bindings = NULL; @@ -932,6 +934,8 @@ static struct getargs args[] = { {"token-split", 0, arg_integer, &token_split, "bytes", NULL }, {"on-behalf-of", 0, arg_string, &on_behalf_of_string, "principal", "send authenticator authz-data AD-ON-BEHALF-OF" }, + {"threaded", 0, arg_flag, &threaded_flag, "run threaded test", NULL }, + {"num-threads", 0, arg_integer, &num_threads, "number of threads for --threaded", NULL }, {"version", 0, arg_flag, &version_flag, "print version", NULL }, {"verbose", 'v', arg_flag, &verbose_flag, "verbose", NULL }, {"help", 0, arg_flag, &help_flag, NULL, NULL } @@ -945,6 +949,98 @@ usage (int ret) exit (ret); } +/* + * Threaded test for exercising concurrent GSS context establishment. + * This helps detect race conditions in context/credential handling. + */ + +struct threaded_test_args { + gss_OID mechoid; + gss_OID nameoid; + const char *target; + gss_cred_id_t client_cred; + int thread_num; + int iterations; + int failed; +}; + +static void * +threaded_test_worker(void *arg) +{ + struct threaded_test_args *ta = arg; + gss_ctx_id_t sctx = GSS_C_NO_CONTEXT; + gss_ctx_id_t cctx = GSS_C_NO_CONTEXT; + gss_OID actual_mech = GSS_C_NO_OID; + gss_cred_id_t deleg_cred = GSS_C_NO_CREDENTIAL; + OM_uint32 min_stat; + int i; + + for (i = 0; i < ta->iterations; i++) { + if (verbose_flag) + printf("thread %d: iteration %d/%d\n", + ta->thread_num, i + 1, ta->iterations); + + sctx = GSS_C_NO_CONTEXT; + cctx = GSS_C_NO_CONTEXT; + deleg_cred = GSS_C_NO_CREDENTIAL; + + loop(ta->mechoid, ta->nameoid, ta->target, ta->client_cred, + &sctx, &cctx, &actual_mech, &deleg_cred); + + gss_delete_sec_context(&min_stat, &cctx, NULL); + gss_delete_sec_context(&min_stat, &sctx, NULL); + gss_release_cred(&min_stat, &deleg_cred); + } + + return NULL; +} + +static int +run_threaded_test(gss_OID mechoid, gss_OID nameoid, const char *target, + gss_cred_id_t client_cred, int nthreads, int iterations) +{ + HEIMDAL_THREAD_ID *threads; + struct threaded_test_args *thread_args; + int i, ret = 0; + + if (verbose_flag) + printf("running threaded test: %d threads, %d iterations each\n", + nthreads, iterations); + + threads = calloc(nthreads, sizeof(*threads)); + thread_args = calloc(nthreads, sizeof(*thread_args)); + if (threads == NULL || thread_args == NULL) + errx(1, "out of memory"); + + for (i = 0; i < nthreads; i++) { + thread_args[i].mechoid = mechoid; + thread_args[i].nameoid = nameoid; + thread_args[i].target = target; + thread_args[i].client_cred = client_cred; + thread_args[i].thread_num = i; + thread_args[i].iterations = iterations; + thread_args[i].failed = 0; + + if (HEIMDAL_THREAD_create(&threads[i], threaded_test_worker, + &thread_args[i]) != 0) + errx(1, "HEIMDAL_THREAD_create failed for thread %d", i); + } + + for (i = 0; i < nthreads; i++) { + HEIMDAL_THREAD_join(threads[i], NULL); + if (thread_args[i].failed) + ret = 1; + } + + free(threads); + free(thread_args); + + if (verbose_flag) + printf("threaded test %s\n", ret ? "FAILED" : "completed"); + + return ret; +} + int main(int argc, char **argv) { @@ -1177,6 +1273,21 @@ main(int argc, char **argv) gssapi_err(maj_stat, min_stat, GSS_C_NO_OID)); } + if (threaded_flag) { + int iterations = max_loops > 0 ? max_loops : 10; + int ret; + + ret = run_threaded_test(mechoid, nameoid, argv[0], client_cred, + num_threads, iterations); + gss_release_cred(&min_stat, &client_cred); + gss_release_oid_set(&min_stat, &actual_mechs); + if (mechoids != GSS_C_NO_OID_SET && mechoids != &mechoid_descs) + gss_release_oid_set(&min_stat, &mechoids); + empty_release(); + krb5_free_context(context); + return ret; + } + loop(mechoid, nameoid, argv[0], client_cred, &sctx, &cctx, &actual_mech, &deleg_cred); diff --git a/tests/gss/check-context.in b/tests/gss/check-context.in index 5a836b9dc..a3cb8eb70 100644 --- a/tests/gss/check-context.in +++ b/tests/gss/check-context.in @@ -607,6 +607,26 @@ ${context} \ host/long.test.h5l.se || \ { eval "$testfailed"; } +test_section "threaded context establishment" +test_section "Getting client initial tickets for threaded test" +test_run ${kinit} --password-file=${objdir}/foopassword user1@${R} + +test_section "threaded gss context (krb5)" +test_run ${context} \ + --mech-type=krb5 \ + --threaded \ + --num-threads=4 \ + --max-loops=5 \ + --name-type=hostbased-service host@lucid.test.h5l.se + +test_section "threaded gss context (spnego)" +test_run ${context} \ + --mech-type=spnego \ + --threaded \ + --num-threads=4 \ + --max-loops=5 \ + --name-type=hostbased-service host@lucid.test.h5l.se + trap "" EXIT echo "killing kdc (${kdcpid})"