From ff91ec934ed52294cddcd7dcfacc04721a0487bf Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 9 Jan 2014 19:14:09 +0100 Subject: tests: Add a test for the internal locking * src/global.c (external_lock_test): New. (_gcry_vcontrol): Call new function with formerly reserved code 61. * tests/t-common.h: New. Taken from current libgpg-error. * tests/t-lock.c: New. Based on t-lock.c from libgpg-error. * configure.ac (HAVE_PTHREAD): Set macro to 1 if defined. (AC_CHECK_FUNCS): Check for flockfile. * tests/Makefile.am (tests_bin): Add t-lock. (noinst_HEADERS): Add t-common.h (LDADD): Move value to ... (default_ldadd): new. (t_lock_LDADD): New. -- Signed-off-by: Werner Koch (cherry picked from commit fa42c61a84996b6a7574c32233dfd8d9f254d93a) Resolved conflicts: * src/ath.c: Remove as not anymore used in 1.7. * tests/Makefile.am: Merge. Changes: * src/global.c (external_lock_test): Use the gpgrt function for locking. Changed subject because here we are only adding the test case. --- configure.ac | 4 +- src/global.c | 50 +++++- tests/Makefile.am | 10 +- tests/t-common.h | 99 ++++++++++++ tests/t-lock.c | 460 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 618 insertions(+), 5 deletions(-) create mode 100644 tests/t-common.h create mode 100644 tests/t-lock.c diff --git a/configure.ac b/configure.ac index 1d5027ad..4d16fc69 100644 --- a/configure.ac +++ b/configure.ac @@ -731,7 +731,7 @@ AC_SUBST(PTH_LIBS) # AC_CHECK_LIB(pthread,pthread_create,have_pthread=yes) if test "$have_pthread" = yes; then - AC_DEFINE(HAVE_PTHREAD, ,[Define if we have pthread.]) + AC_DEFINE(HAVE_PTHREAD, 1 ,[Define if we have pthread.]) fi @@ -1364,7 +1364,7 @@ AC_CHECK_FUNCS(strtoul memmove stricmp atexit raise) # Other checks AC_CHECK_FUNCS(strerror rand mmap getpagesize sysconf waitpid wait4) AC_CHECK_FUNCS(gettimeofday getrusage gethrtime clock_gettime syslog) -AC_CHECK_FUNCS(fcntl ftruncate) +AC_CHECK_FUNCS(fcntl ftruncate flockfile) GNUPG_CHECK_MLOCK diff --git a/src/global.c b/src/global.c index b2b1de6e..ec0cc3fa 100644 --- a/src/global.c +++ b/src/global.c @@ -66,6 +66,8 @@ static gcry_handler_no_mem_t outofcore_handler; static void *outofcore_handler_value; static int no_secure_memory; +/* Prototypes. */ +static gpg_err_code_t external_lock_test (int cmd); @@ -607,7 +609,8 @@ _gcry_vcontrol (enum gcry_ctl_cmds cmd, va_list arg_ptr) _gcry_random_deinit_external_test (ctx); } break; - case 61: /* RFU */ + case 61: /* Run external lock test */ + rc = external_lock_test (va_arg (arg_ptr, int)); break; case 62: /* RFU */ break; @@ -1108,3 +1111,48 @@ _gcry_set_progress_handler (void (*cb)(void *,const char*,int, int, int), _gcry_register_primegen_progress (cb, cb_data); _gcry_register_random_progress (cb, cb_data); } + + + +/* This is a helper for the regression test suite to test Libgcrypt's locks. + It works using a one test lock with CMD controlling what to do: + + 30111 - Allocate and init lock + 30112 - Take lock + 30113 - Release lock + 30114 - Destroy lock. + + This function is used by tests/t-lock.c - it is not part of the + public API! + */ +static gpg_err_code_t +external_lock_test (int cmd) +{ + GPGRT_LOCK_DEFINE (testlock); + gpg_err_code_t rc = 0; + + switch (cmd) + { + case 30111: /* Init Lock. */ + rc = gpgrt_lock_init (&testlock); + break; + + case 30112: /* Take Lock. */ + rc = gpgrt_lock_lock (&testlock); + break; + + case 30113: /* Release Lock. */ + rc = gpgrt_lock_unlock (&testlock); + break; + + case 30114: /* Destroy Lock. */ + rc = gpgrt_lock_destroy (&testlock); + break; + + default: + rc = GPG_ERR_INV_OP; + break; + } + + return rc; +} diff --git a/tests/Makefile.am b/tests/Makefile.am index 884fb3d9..f0a46338 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -20,7 +20,7 @@ tests_bin = \ version mpitests t-sexp t-convert \ - t-mpi-bit t-mpi-point curves \ + t-mpi-bit t-mpi-point curves t-lock \ prime basic keygen pubkey hmac hashtest t-kdf keygrip \ fips186-dsa aeswrap pkcs1v2 random dsa-rfc6979 t-ed25519 @@ -45,11 +45,17 @@ TESTS_ENVIRONMENT = GCRYPT_IN_REGRESSION_TEST=1 AM_CPPFLAGS = -I../src -I$(top_srcdir)/src AM_CFLAGS = $(GPG_ERROR_CFLAGS) -LDADD = ../src/libgcrypt.la $(DL_LIBS) ../compat/libcompat.la $(GPG_ERROR_LIBS) +default_ldadd = \ + ../src/libgcrypt.la $(DL_LIBS) \ + ../compat/libcompat.la $(GPG_ERROR_LIBS) EXTRA_PROGRAMS = testapi pkbench noinst_PROGRAMS = $(tests_bin) $(tests_bin_last) fipsdrv rsacvt genhashdata +noinst_HEADERS = t-common.h EXTRA_DIST = README rsa-16k.key cavs_tests.sh cavs_driver.pl \ pkcs1v2-oaep.h pkcs1v2-pss.h pkcs1v2-v15c.h pkcs1v2-v15s.h \ t-ed25519.inp stopwatch.h hashtest-256g.in + +LDADD = $(default_ldadd) +t_lock_LDADD = $(default_ldadd) $(LIBMULTITHREAD) diff --git a/tests/t-common.h b/tests/t-common.h new file mode 100644 index 00000000..288963df --- /dev/null +++ b/tests/t-common.h @@ -0,0 +1,99 @@ +/* t-common.h - Common code for the tests. + * Copyright (C) 2013 g10 Code GmbH + * + * This file is part of libgpg-error. + * + * libgpg-error is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * libgpg-error is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see . + */ + +#include + +#include "../src/gcrypt.h" + +#ifndef PGM +# error Macro PGM not defined. +#endif + + +static int verbose; +static int debug; +static int errorcount; + + +static void +die (const char *format, ...) +{ + va_list arg_ptr ; + + fflush (stdout); +#ifdef HAVE_FLOCKFILE + flockfile (stderr); +#endif + fprintf (stderr, "%s: ", PGM); + va_start (arg_ptr, format) ; + vfprintf (stderr, format, arg_ptr); + va_end (arg_ptr); + if (*format && format[strlen(format)-1] != '\n') + putc ('\n', stderr); +#ifdef HAVE_FLOCKFILE + funlockfile (stderr); +#endif + exit (1); +} + + +static void +fail (const char *format, ...) +{ + va_list arg_ptr; + + fflush (stdout); +#ifdef HAVE_FLOCKFILE + flockfile (stderr); +#endif + fprintf (stderr, "%s: ", PGM); + va_start (arg_ptr, format); + vfprintf (stderr, format, arg_ptr); + va_end (arg_ptr); + if (*format && format[strlen(format)-1] != '\n') + putc ('\n', stderr); +#ifdef HAVE_FLOCKFILE + funlockfile (stderr); +#endif + errorcount++; + if (errorcount >= 50) + die ("stopped after 50 errors."); +} + + +static void +show (const char *format, ...) +{ + va_list arg_ptr; + + if (!verbose) + return; +#ifdef HAVE_FLOCKFILE + flockfile (stderr); +#endif + fprintf (stderr, "%s: ", PGM); + va_start (arg_ptr, format); + vfprintf (stderr, format, arg_ptr); + if (*format && format[strlen(format)-1] != '\n') + putc ('\n', stderr); + va_end (arg_ptr); +#ifdef HAVE_FLOCKFILE + funlockfile (stderr); +#endif +} diff --git a/tests/t-lock.c b/tests/t-lock.c new file mode 100644 index 00000000..c6c1e418 --- /dev/null +++ b/tests/t-lock.c @@ -0,0 +1,460 @@ +/* t-lock.c - Check the lock functions + * Copyright (C) 2014 g10 Code GmbH + * + * This file is part of Libgcrypt. + * + * Libgcrypt is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * Libgcrypt is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see . + */ + +#if HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include +#include +#include +#if HAVE_PTHREAD +# include +#endif + +#define PGM "t-lock" + +#include "t-common.h" + +/* Mingw requires us to include windows.h after winsock2.h which is + included by gcrypt.h. */ +#ifdef _WIN32 +# include +#endif + +#ifdef _WIN32 +# define THREAD_RET_TYPE DWORD WINAPI +# define THREAD_RET_VALUE 0 +#else +# define THREAD_RET_TYPE void * +# define THREAD_RET_VALUE NULL +#endif + +#define PRIV_CTL_EXTERNAL_LOCK_TEST 61 +#define EXTERNAL_LOCK_TEST_INIT 30111 +#define EXTERNAL_LOCK_TEST_LOCK 30112 +#define EXTERNAL_LOCK_TEST_UNLOCK 30113 +#define EXTERNAL_LOCK_TEST_DESTROY 30114 + + +/* Number of threads to run. */ +#define N_NONCE_THREADS 8 +/* Number of interations. */ +#define N_NONCE_ITERATIONS 1000 +/* Requested nonce size. */ +#define NONCE_SIZE 11 + + +/* This tests works by having a a couple of accountant threads which do + random transactions between accounts and a revision threads which + checks that the balance of all accounts is invariant. The idea for + this check is due to Bruno Haible. */ +#define N_ACCOUNT 8 +#define ACCOUNT_VALUE 42 +static int account[N_ACCOUNT]; + +/* Number of transactions done by each accountant. */ +#define N_TRANSACTIONS 1000 + +/* Number of accountants to run. */ +#define N_ACCOUNTANTS 5 + +/* Maximum transaction value. A quite low value is used so that we + would get an integer overflow. */ +#define MAX_TRANSACTION_VALUE 50 + +/* Flag to tell the revision thread to finish. */ +static volatile int stop_revision_thread; + + +struct thread_arg_s +{ + int no; +}; + + + + +/* Wrapper functions to access Libgcrypt's internal test lock. */ +static void +external_lock_test_init (int line) +{ + gpg_error_t err; + + err = gcry_control (PRIV_CTL_EXTERNAL_LOCK_TEST, EXTERNAL_LOCK_TEST_INIT); + if (err) + fail ("init lock failed at %d: %s", line, gpg_strerror (err)); +} + +static void +external_lock_test_lock (int line) +{ + gpg_error_t err; + + err = gcry_control (PRIV_CTL_EXTERNAL_LOCK_TEST, EXTERNAL_LOCK_TEST_LOCK); + if (err) + fail ("taking lock failed at %d: %s", line, gpg_strerror (err)); +} + +static void +external_lock_test_unlock (int line) +{ + gpg_error_t err; + + err = gcry_control (PRIV_CTL_EXTERNAL_LOCK_TEST, EXTERNAL_LOCK_TEST_UNLOCK); + if (err) + fail ("releasing lock failed at %d: %s", line, gpg_strerror (err)); + +} + +static void +external_lock_test_destroy (int line) +{ + gpg_error_t err; + + err = gcry_control (PRIV_CTL_EXTERNAL_LOCK_TEST, EXTERNAL_LOCK_TEST_DESTROY); + if (err) + fail ("destroying lock failed at %d: %s", line, gpg_strerror (err)); +} + + + + +/* The nonce thread. We simply request a couple of nonces and + return. */ +static THREAD_RET_TYPE +nonce_thread (void *argarg) +{ + struct thread_arg_s *arg = argarg; + int i; + char nonce[NONCE_SIZE]; + + for (i = 0; i < N_NONCE_ITERATIONS; i++) + { + gcry_create_nonce (nonce, sizeof nonce); + if (i && !(i%100)) + show ("thread %d created %d nonces so far", arg->no, i); + } + + gcry_free (arg); + return THREAD_RET_VALUE; +} + + +/* To check our locking function we run several threads all accessing + the nonce functions. If this function returns we know that there + are no obvious deadlocks or failed lock initialization. */ +static void +check_nonce_lock (void) +{ + struct thread_arg_s *arg; +#ifdef _WIN32 + HANDLE threads[N_NONCE_THREADS]; + int i; + int rc; + + for (i=0; i < N_NONCE_THREADS; i++) + { + arg = gcry_xmalloc (sizeof *arg); + arg->no = i; + threads[i] = CreateThread (NULL, 0, nonce_thread, arg, 0, NULL); + if (!threads[i]) + die ("error creating nonce thread %d: rc=%d", + i, (int)GetLastError ()); + } + + for (i=0; i < N_NONCE_THREADS; i++) + { + rc = WaitForSingleObject (threads[i], INFINITE); + if (rc == WAIT_OBJECT_0) + show ("nonce thread %d has terminated", i); + else + fail ("waiting for nonce thread %d failed: %d", + i, (int)GetLastError ()); + CloseHandle (threads[i]); + } + +#elif HAVE_PTHREAD + pthread_t threads[N_NONCE_THREADS]; + int rc, i; + + for (i=0; i < N_NONCE_THREADS; i++) + { + arg = gcry_xmalloc (sizeof *arg); + arg->no = i; + pthread_create (&threads[i], NULL, nonce_thread, arg); + } + + for (i=0; i < N_NONCE_THREADS; i++) + { + rc = pthread_join (threads[i], NULL); + if (rc) + fail ("pthread_join failed for nonce thread %d: %s", + i, strerror (errno)); + else + show ("nonce thread %d has terminated", i); + } + +#endif /*!_WIN32*/ +} + + +/* Initialze all accounts. */ +static void +init_accounts (void) +{ + int i; + + for (i=0; i < N_ACCOUNT; i++) + account[i] = ACCOUNT_VALUE; +} + + +/* Check that the sum of all accounts matches the intial sum. */ +static void +check_accounts (void) +{ + int i, sum; + + sum = 0; + for (i = 0; i < N_ACCOUNT; i++) + sum += account[i]; + if (sum != N_ACCOUNT * ACCOUNT_VALUE) + die ("accounts out of balance"); +} + + +static void +print_accounts (void) +{ + int i; + + for (i=0; i < N_ACCOUNT; i++) + printf ("account %d: %6d\n", i, account[i]); +} + + +/* Get a a random integer value in the range 0 to HIGH. */ +static unsigned int +get_rand (int high) +{ + return (unsigned int)(1+(int)((double)(high+1)*rand ()/(RAND_MAX+1.0))) - 1; +} + + +/* Pick a random account. Note that this fucntion is not + thread-safe. */ +static int +pick_account (void) +{ + return get_rand (N_ACCOUNT - 1); +} + + +/* Pick a random value for a transaction. This is not thread-safe. */ +static int +pick_value (void) +{ + return get_rand (MAX_TRANSACTION_VALUE); +} + + +/* This is the revision department. */ +static THREAD_RET_TYPE +revision_thread (void *arg) +{ + (void)arg; + + while (!stop_revision_thread) + { + external_lock_test_lock (__LINE__); + check_accounts (); + external_lock_test_unlock (__LINE__); + } + return THREAD_RET_VALUE; +} + + +/* This is one of our accountants. */ +static THREAD_RET_TYPE +accountant_thread (void *arg) +{ + int i; + int acc1, acc2; + int value; + + (void)arg; + + for (i = 0; i < N_TRANSACTIONS; i++) + { + external_lock_test_lock (__LINE__); + acc1 = pick_account (); + acc2 = pick_account (); + value = pick_value (); + account[acc1] += value; + account[acc2] -= value; + external_lock_test_unlock (__LINE__); + } + return THREAD_RET_VALUE; +} + + +static void +run_test (void) +{ +#ifdef _WIN32 + HANDLE rthread; + HANDLE athreads[N_ACCOUNTANTS]; + int i; + int rc; + + external_lock_test_init (__LINE__); + stop_revision_thread = 0; + rthread = CreateThread (NULL, 0, revision_thread, NULL, 0, NULL); + if (!rthread) + die ("error creating revision thread: rc=%d", (int)GetLastError ()); + + for (i=0; i < N_ACCOUNTANTS; i++) + { + athreads[i] = CreateThread (NULL, 0, accountant_thread, NULL, 0, NULL); + if (!athreads[i]) + die ("error creating accountant thread %d: rc=%d", + i, (int)GetLastError ()); + } + + for (i=0; i < N_ACCOUNTANTS; i++) + { + rc = WaitForSingleObject (athreads[i], INFINITE); + if (rc == WAIT_OBJECT_0) + show ("accountant thread %d has terminated", i); + else + fail ("waiting for accountant thread %d failed: %d", + i, (int)GetLastError ()); + CloseHandle (athreads[i]); + } + stop_revision_thread = 1; + + rc = WaitForSingleObject (rthread, INFINITE); + if (rc == WAIT_OBJECT_0) + show ("revision thread has terminated"); + else + fail ("waiting for revision thread failed: %d", (int)GetLastError ()); + CloseHandle (rthread); + +#else /*!_WIN32*/ + pthread_t rthread; + pthread_t athreads[N_ACCOUNTANTS]; + int rc, i; + + external_lock_test_init (__LINE__); + stop_revision_thread = 0; + pthread_create (&rthread, NULL, revision_thread, NULL); + + for (i=0; i < N_ACCOUNTANTS; i++) + pthread_create (&athreads[i], NULL, accountant_thread, NULL); + + for (i=0; i < N_ACCOUNTANTS; i++) + { + rc = pthread_join (athreads[i], NULL); + if (rc) + fail ("pthread_join failed for accountant thread %d: %s", + i, strerror (errno)); + else + show ("accountant thread %d has terminated", i); + } + + stop_revision_thread = 1; + rc = pthread_join (rthread, NULL); + if (rc) + fail ("pthread_join failed for the revision thread: %s", strerror (errno)); + else + show ("revision thread has terminated"); + +#endif /*!_WIN32*/ + + external_lock_test_destroy (__LINE__); +} + + + +int +main (int argc, char **argv) +{ + int last_argc = -1; + + if (argc) + { + argc--; argv++; + } + while (argc && last_argc != argc ) + { + last_argc = argc; + if (!strcmp (*argv, "--help")) + { + puts ( +"usage: ./t-lock [options]\n" +"\n" +"Options:\n" +" --verbose Show what is going on\n" +" --debug Flyswatter\n" +); + exit (0); + } + if (!strcmp (*argv, "--verbose")) + { + verbose = 1; + argc--; argv++; + } + else if (!strcmp (*argv, "--debug")) + { + verbose = debug = 1; + argc--; argv++; + } + } + + srand (time(NULL)*getpid()); + + if (debug) + gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1u, 0); + gcry_control (GCRYCTL_DISABLE_SECMEM, 0); + if (!gcry_check_version (GCRYPT_VERSION)) + die ("version mismatch"); + gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0); + gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); + + check_nonce_lock (); + + init_accounts (); + check_accounts (); + + run_test (); + check_accounts (); + + /* Run a second time to check deinit code. */ + run_test (); + check_accounts (); + + if (verbose) + print_accounts (); + + return errorcount ? 1 : 0; +} -- cgit v1.2.1