--- /dev/null
+/*
+ * Multithread exerciser - beat up on CSP, TP, and CL from multiple threads.
+ *
+ * Written by Doug Mitchell.
+ *
+ *
+ * Spawn a user-spec'd number of threads, each of which does the following:
+ *
+ * testThread(testParams) {
+ * roll the dice;
+ * depending on dieValue {
+ * cgVerify test;
+ * cgConstruct test;
+ * sslPing() test;
+ * etc....
+ * }
+ * }
+ */
+#include <utilLib/common.h>
+#include <utilLib/cspwrap.h>
+#include <clAppUtils/clutils.h>
+#include "testParams.h"
+#include <security_utilities/threading.h>
+#include <security_utilities/utilities.h>
+#include <security_utilities/devrandom.h>
+#include <pthread.h>
+#include <Security/Security.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+/*
+ * As of 3/15/2001, can't link apps which use Security.framework against BSAFE.
+ */
+#define BSAFE_ENABLE 0
+
+#define NUM_LOOPS 100
+#define NUM_THREADS 20
+
+/* function for both test init and test proper */
+typedef int (*testFcn)(TestParams *testParams);
+
+/* one test */
+typedef struct {
+ testFcn testInit;
+ testFcn testRun;
+ const char *testName;
+ char enable;
+} TestDef;
+
+/* the tests we know about */
+
+#define CG_CONSTRUCT_ENABLE 1 /* leak free 12/19 */
+#define CG_VERIFY_ENABLE 1 /* leak free 12/19 */
+#define SIGN_VFY_ENABLE 1 /* leak free */
+#define SYM_TEST_ENABLE 1 /* leak free */
+#define TIME_ENABLE 0 /* normally off */
+#define SSL_PING_ENABLE 0 /* leak free 12/19 */
+#define GET_FIELDS_ENABLE 1 /* leak free */
+#define GET_CACHED_FLDS_ENABLE 1 /* leak free */
+#define DER_DECODE_ENABLE 0
+#define ATTACH_ENABLE 1 /* leak free */
+#define SEC_TRUST_ENABLE 0 /* works but leaks per 3737232 */
+#define KC_STATUS_ENABLE 0 /* currently fails: see 6368768 */
+#define DIGEST_CLIENT_ENABLE 1
+#define MDS_LOOKUP_ENABLE 1 /* leak free */
+#define CSSM_ERR_STR_ENABLE 0 /* leak free */
+#define TRUST_SETTINGS_ENABLE 1
+#define DB_SETTINGS_ENABLE 0 /* not thread safe */
+#define COPY_ROOTS_ENABLE 1
+
+#if BSAFE_ENABLE
+#define RSA_SIGN_ENABLE 1
+#define DES_ENABLE 1
+#else
+#define RSA_SIGN_ENABLE 0
+#define DES_ENABLE 0
+#endif /* BSAFE_ENABLE */
+#define SSL_THRASH_ENABLE 0
+#define CSP_RAND_ENABLE 0
+
+/* when adding to this table be sure to update setTestEnables() as well */
+TestDef testArray[] = {
+ { cgConstructInit, cgConstruct, "cgConstruct", CG_CONSTRUCT_ENABLE },
+ { cgVerifyInit, cgVerify, "cgVerify", CG_VERIFY_ENABLE },
+ { signVerifyInit, signVerify, "signVerify", SIGN_VFY_ENABLE },
+ { symTestInit, symTest, "symTest", SYM_TEST_ENABLE },
+ { timeInit, timeThread, "timeThread", TIME_ENABLE },
+ { sslPingInit, sslPing, "sslPing", SSL_PING_ENABLE },
+ { getFieldsInit, getFields, "getFields", GET_FIELDS_ENABLE },
+ { getCachedFieldsInit, getCachedFields,"getCachedFields",GET_CACHED_FLDS_ENABLE},
+ { attachTestInit, attachTest, "attachTest", ATTACH_ENABLE },
+ { sslThrashInit, sslThrash, "sslThrash", SSL_THRASH_ENABLE },
+ { cspRandInit, cspRand, "cspRand", CSP_RAND_ENABLE },
+ { derDecodeInit, derDecodeTest, "derDecode", DER_DECODE_ENABLE },
+ { secTrustEvalInit, secTrustEval, "secTrustEval", SEC_TRUST_ENABLE },
+ { kcStatusInit, kcStatus, "kcStatus", KC_STATUS_ENABLE },
+ { digestClientInit, digestClient, "digestClient", DIGEST_CLIENT_ENABLE},
+ { mdsLookupInit, mdsLookup, "mdsLookup", MDS_LOOKUP_ENABLE },
+ { cssmErrStrInit, cssmErrStr, "cssmErrStr", CSSM_ERR_STR_ENABLE },
+ { trustSettingsInit, trustSettingsEval, "trustSettingsEval", TRUST_SETTINGS_ENABLE },
+ { dbOpenCloseInit, dbOpenCloseEval, "dbOpenClose", DB_SETTINGS_ENABLE },
+ { copyRootsInit, copyRootsTest, "copyRoots", COPY_ROOTS_ENABLE },
+ #if BSAFE_ENABLE
+ { desInit, desTest, "desTest", DES_ENABLE },
+ { rsaSignInit, rsaSignTest, "rsaSignTest", RSA_SIGN_ENABLE }
+ #endif
+};
+#define NUM_THREAD_TESTS (sizeof(testArray) / sizeof(TestDef))
+
+static void usage(char **argv)
+{
+ printf("Usage: %s [options]\n", argv[0]);
+ printf("Options:\n");
+ printf(" l=loopCount (default = %d)\n", NUM_LOOPS);
+ printf(" t=threadCount (default = %d)\n", NUM_THREADS);
+ printf(" e[cvsytpfabdFSrDTkmCer] - enable specific tests\n");
+ printf(" c=cgConstruct v=cgVerify s=signVerify y=symTest\n");
+ printf(" t=timeThread p=sslPing f=getFields a=attach\n");
+ printf(" b=bsafeSignVfy d=bsafeDES F=getCachedFields\n");
+ printf(" S=sslThrash r=cspRand D=derDecode T=SecTrustEval\n");
+ printf(" k=kcStatus m=mdsLookup C=digestClient e=cssmErrorStr\n");
+ printf(" R=TrustSetting B=DBOpenClose o=copyRoots\n");
+ printf(" o=test_specific_opts (see source for details)\n");
+ printf(" a(bort on error)\n");
+ printf(" r(un loop)\n");
+ printf(" q(uiet)\n");
+ printf(" v(erbose)\n");
+ printf(" s(ilent)\n");
+ printf(" h(elp)\n");
+ exit(1);
+}
+
+/* it happens from time to time on SSL ping */
+#include <signal.h>
+void sigpipe(int sig)
+{
+ fflush(stdin);
+ printf("***SIGPIPE***\n");
+}
+
+/* common thread-safe routines */
+static Security::DevRandomGenerator devRand;
+
+CSSM_RETURN threadGetRandData(
+ const TestParams *testParams,
+ CSSM_DATA_PTR data, // mallocd by caller
+ unsigned numBytes) // how much to fill
+{
+ devRand.random(data->Data, numBytes);
+ data->Length = numBytes;
+ return CSSM_OK;
+}
+
+/* delay a random amount, 0<delay<10ms */
+#define MAX_DELAY_US 10000
+void randomDelay()
+{
+ unsigned char usec;
+ devRand.random(&usec, 1);
+ usec %= 10000;
+ usleep(usec);
+}
+
+/* in case printf() is malevolently unsafe */
+
+static Mutex printLock;
+
+void printChar(char c)
+{
+ StLock<Mutex> _(printLock);
+ printf("%c", c);
+ fflush(stdout);
+}
+
+/*
+ * Optionally start up a CFRunLoop. This is needed to field keychain event callbacks, used
+ * to maintain root cert cache coherency.
+ */
+
+/* first we need something to register so we *have* a run loop */
+static OSStatus kcCacheCallback (
+ SecKeychainEvent keychainEvent,
+ SecKeychainCallbackInfo *info,
+ void *context)
+{
+ return noErr;
+}
+
+/* main thread has to wait for this to be set to know a run loop has been set up */
+static int runLoopInitialized = 0;
+
+/* this is the thread which actually runs the CFRunLoop */
+void *cfRunLoopThread(void *arg)
+{
+ OSStatus ortn = SecKeychainAddCallback(kcCacheCallback,
+ kSecTrustSettingsChangedEventMask, NULL);
+ if(ortn) {
+ printf("registerCacheCallbacks: SecKeychainAddCallback returned %d", (int32_t)ortn);
+ /* Not sure how this could ever happen - maybe if there is no run loop active? */
+ return NULL;
+ }
+ runLoopInitialized = 1;
+ CFRunLoopRun();
+ /* should not be reached */
+ printf("\n*** Hey! CFRunLoopRun() exited!***\n");
+ return NULL;
+}
+
+static int startCFRunLoop()
+{
+ pthread_t runLoopThread;
+
+ int result = pthread_create(&runLoopThread, NULL, cfRunLoopThread, NULL);
+ if(result) {
+ printf("***pthread_create returned %d, aborting\n", result);
+ return -1;
+ }
+ return 0;
+}
+
+/* main pthread body */
+void *testThread(void *arg)
+{
+ TestParams *testParams = (TestParams *)arg;
+ int status;
+
+ TestDef *thisTestDef = &testArray[testParams->testNum];
+ status = thisTestDef->testRun(testParams);
+ if(!testParams->quiet) {
+ printf("\n...thread %d test %s exiting with status %d\n",
+ testParams->threadNum, thisTestDef->testName, status);
+ }
+ pthread_exit((void*)status);
+ /* NOT REACHED */
+ return (void *)status;
+}
+
+/*
+ * Set enables in testArray[]
+ */
+static void setOneEnable(testFcn f)
+{
+ unsigned dex;
+ for(dex=0; dex<NUM_THREAD_TESTS; dex++) {
+ if(testArray[dex].testRun == f) {
+ testArray[dex].enable = 1;
+ return;
+ }
+ }
+ printf("****setOneEnable: test not found\n");
+ exit(1);
+}
+
+static void setTestEnables(const char *enables, char **argv)
+{
+ /* first turn 'em all off */
+ unsigned dex;
+ for(dex=0; dex<NUM_THREAD_TESTS; dex++) {
+ testArray[dex].enable = 0;
+ }
+
+ /* enable specific ones */
+ while(*enables != '\0') {
+ switch(*enables) {
+ case 'c': setOneEnable(cgConstruct); break;
+ case 'v': setOneEnable(cgVerify); break;
+ case 's': setOneEnable(signVerify); break;
+ case 'y': setOneEnable(symTest); break;
+ case 't': setOneEnable(timeThread); break;
+ case 'p': setOneEnable(sslPing); break;
+ case 'f': setOneEnable(getFields); break;
+ case 'F': setOneEnable(getCachedFields); break;
+ case 'a': setOneEnable(attachTest); break;
+ case 'S': setOneEnable(sslThrash); break;
+ case 'r': setOneEnable(cspRand); break;
+ case 'D': setOneEnable(derDecodeTest); break;
+ case 'T': setOneEnable(secTrustEval); break;
+ case 'k': setOneEnable(kcStatus); break;
+ case 'C': setOneEnable(digestClient); break;
+ case 'm': setOneEnable(mdsLookup); break;
+ case 'e': setOneEnable(cssmErrStr); break;
+ case 'R': setOneEnable(trustSettingsEval); break;
+ case 'B': setOneEnable(dbOpenCloseEval); break;
+ case 'o': setOneEnable(copyRootsTest); break;
+ #if BSAFE_ENABLE
+ case 'b': setOneEnable(rsaSignTest); break;
+ case 'd': setOneEnable(desTest); break;
+ #endif
+ default:
+ usage(argv);
+ }
+ enables++;
+ }
+}
+
+int main(int argc, char **argv)
+{
+ CSSM_CSP_HANDLE cspHand = 0;
+ CSSM_CL_HANDLE clHand = 0;
+ CSSM_TP_HANDLE tpHand = 0;
+ unsigned errCount = 0;
+ TestParams *testParams;
+ TestParams *thisTest;
+ unsigned dex;
+ pthread_t *threadList;
+ int arg;
+ char *argp;
+ int result;
+ TestDef *thisTestDef;
+ unsigned numValidTests;
+ unsigned i,j;
+
+ /* user-spec'd parameters */
+ char quiet = 0;
+ char verbose = 0;
+ unsigned numThreads = NUM_THREADS;
+ unsigned numLoops = NUM_LOOPS;
+ char *testOpts = NULL;
+ bool abortOnError = false;
+ bool silent = false;
+
+ for(arg=1; arg<argc; arg++) {
+ argp = argv[arg];
+ switch(argp[0]) {
+ case 'l':
+ numLoops = atoi(&argp[2]);
+ break;
+ case 't':
+ numThreads = atoi(&argp[2]);
+ break;
+ break;
+ case 'q':
+ quiet = 1;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ case 'o':
+ if((argp[1] != '=') || (argp[2] == '\0')) {
+ usage(argv);
+ }
+ testOpts = argp + 2;
+ break;
+ case 'e':
+ setTestEnables(argp + 1, argv);
+ break;
+ case 'a':
+ abortOnError = true;
+ break;
+ case 'r':
+ startCFRunLoop();
+ break;
+ case 's':
+ silent = true;
+ quiet = 1;
+ break;
+ default:
+ usage(argv);
+ }
+ }
+
+ /* attach to all three modules */
+ cspHand = cspStartup();
+ if(cspHand == 0) {
+ exit(1);
+ }
+ clHand = clStartup();
+ if(clHand == 0) {
+ goto abort;
+ }
+ tpHand = tpStartup();
+ if(tpHand == 0) {
+ goto abort;
+ }
+ signal(SIGPIPE, sigpipe);
+
+ /* malloc and init TestParams for all requested threads */
+ testParams = (TestParams *)malloc(numThreads * sizeof(TestParams));
+ for(dex=0; dex<numThreads; dex++) {
+ thisTest = &testParams[dex];
+ thisTest->numLoops = numLoops;
+ thisTest->verbose = verbose;
+ thisTest->quiet = quiet;
+ thisTest->threadNum = dex;
+ thisTest->cspHand = cspHand;
+ thisTest->clHand = clHand;
+ thisTest->tpHand = tpHand;
+ thisTest->testOpts = testOpts;
+
+ if(dex < 10) {
+ /* 0..9 */
+ thisTest->progressChar = '0' + dex;
+ }
+ else if(dex < 36) {
+ /* a..z */
+ thisTest->progressChar = 'a' + dex - 10;
+ }
+ else {
+ /* A..Z and if X can run more threads than that, I'll be surprised */
+ thisTest->progressChar = 'Z' + dex - 36;
+ }
+ }
+
+ /* Adjust testArray for tests which are actually enabled */
+ numValidTests = 0;
+ dex=0;
+ for(i=0; i<NUM_THREAD_TESTS; i++) {
+ if(testArray[dex].enable) {
+ numValidTests++;
+ dex++;
+ }
+ else {
+ /* delete this one, move remaining tests up */
+ for(j=dex; j<NUM_THREAD_TESTS-1; j++) {
+ testArray[j] = testArray[j+1];
+ }
+ /* and re-examine testArray[dex], which we just rewrote */
+ }
+ }
+
+ if(!silent) {
+ printf("Starting threadTest; args: ");
+ for(i=1; i<(unsigned)argc; i++) {
+ printf("%s ", argv[i]);
+ }
+ printf("\n");
+ }
+
+ /* assign a test module to each thread and run its init routine */
+ for(dex=0; dex<numThreads; dex++) {
+ /* roll the dice */
+ thisTest = &testParams[dex];
+ thisTest->testNum = genRand(0, numValidTests - 1);
+
+ thisTestDef = &testArray[thisTest->testNum];
+ if(!quiet) {
+ printf("...thread %d: test %s\n", dex, thisTestDef->testName);
+ }
+ result = thisTestDef->testInit(thisTest);
+ if(result) {
+ printf("***Error on %s init; aborting\n", thisTestDef->testName);
+ errCount++;
+ goto abort;
+ }
+ }
+
+
+ /* start up each thread */
+ threadList = (pthread_t *)malloc(numThreads * sizeof(pthread_t));
+ for(dex=0; dex<numThreads; dex++) {
+ int result = pthread_create(&threadList[dex], NULL,
+ testThread, &testParams[dex]);
+ if(result) {
+ printf("***pthread_create returned %d, aborting\n", result);
+ errCount++;
+ goto abort;
+ }
+ }
+
+ /* wait for each thread to complete */
+ for(dex=0; dex<numThreads; dex++) {
+ void *status;
+ result = pthread_join(threadList[dex], &status);
+ if(result) {
+ printf("***pthread_join returned %d, aborting\n", result);
+ goto abort;
+ }
+ if(!quiet) {
+ printf("\n...joined thread %d, status %d\n",
+ dex, status ? 1 : 0);
+ }
+ if(status != NULL) {
+ errCount++;
+ if(abortOnError) {
+ break;
+ }
+ }
+ }
+ if(errCount || !quiet) {
+ printf("threadTest complete; errCount %d\n", errCount);
+ }
+abort:
+ if(cspHand != 0) {
+ CSSM_ModuleDetach(cspHand);
+ }
+ if(clHand != 0) {
+ CSSM_ModuleDetach(clHand);
+ }
+ if(tpHand != 0) {
+ CSSM_ModuleDetach(tpHand);
+ }
+ return errCount;
+}
+
+