From df1f5e796c537c84da445a9157c9c6727b4d8ee3 Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Mon, 13 Apr 2015 11:04:08 -0700 Subject: [PATCH] crypto: add mutexes. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prior to this, BoringSSL was using OpenSSL's technique of having users register a callback for locking operation. This change adds native mutex support. Since mutexes often need to be in objects that are exposed via public headers, the non-static mutexes are defined in thread.h. However, on Windows we don't want to #include windows.h for CRITICAL_SECTION and, on Linux, pthread.h doesn't define pthread_rwlock_t unless the feature flags are set correctly—something that we can't control in general for public header files. Thus, on both platforms, the mutex is defined as a uint8_t[] of equal or greater size and we depend on static asserts to ensure that everything works out ok. Change-Id: Iafec17ae7e3422325e587878a5384107ec6647ab Reviewed-on: https://boringssl-review.googlesource.com/4321 Reviewed-by: Adam Langley --- crypto/internal.h | 68 ++++++++++++++++++++++++++++++++++++++++ crypto/thread_pthread.c | 51 ++++++++++++++++++++++++++++++ crypto/thread_win.c | 67 +++++++++++++++++++++++++++++++++++++-- include/openssl/thread.h | 21 +++++++++++++ 4 files changed, 205 insertions(+), 2 deletions(-) diff --git a/crypto/internal.h b/crypto/internal.h index e8f4ef7a..6a8d5b2f 100644 --- a/crypto/internal.h +++ b/crypto/internal.h @@ -110,9 +110,14 @@ #define OPENSSL_HEADER_CRYPTO_INTERNAL_H #include +#include #if !defined(OPENSSL_WINDOWS) #include +#else +#pragma warning(push, 3) +#include +#pragma warning(pop) #endif #if defined(__cplusplus) @@ -360,6 +365,69 @@ typedef int32_t CRYPTO_once_t; OPENSSL_EXPORT void CRYPTO_once(CRYPTO_once_t *once, void (*init)(void)); +/* Locks. + * + * Two types of locks are defined: |CRYPTO_MUTEX|, which can be used in + * structures as normal, and |struct CRYPTO_STATIC_MUTEX|, which can be used as + * a global lock. A global lock must be initialised to the value + * |CRYPTO_STATIC_MUTEX_INIT|. + * + * |CRYPTO_MUTEX| can appear in public structures and so is defined in + * thread.h. + * + * The global lock is a different type because there's no static initialiser + * value on Windows for locks, so global locks have to be coupled with a + * |CRYPTO_once_t| to ensure that the lock is setup before use. This is done + * automatically by |CRYPTO_STATIC_MUTEX_lock_*|. */ + +#if !defined(OPENSSL_WINDOWS) +struct CRYPTO_STATIC_MUTEX { + pthread_rwlock_t lock; +}; +#define CRYPTO_STATIC_MUTEX_INIT { PTHREAD_RWLOCK_INITIALIZER } +#else +struct CRYPTO_STATIC_MUTEX { + CRYPTO_once_t once; + CRITICAL_SECTION lock; +}; +#define CRYPTO_STATIC_MUTEX_INIT { CRYPTO_ONCE_INIT, { 0 } } +#endif + +/* CRYPTO_MUTEX_init initialises |lock|. If |lock| is a static variable, use a + * |CRYPTO_STATIC_MUTEX|. */ +void CRYPTO_MUTEX_init(CRYPTO_MUTEX *lock); + +/* CRYPTO_MUTEX_lock_read locks |lock| such that other threads may also have a + * read lock, but none may have a write lock. (On Windows, read locks are + * actually fully exclusive.) */ +void CRYPTO_MUTEX_lock_read(CRYPTO_MUTEX *lock); + +/* CRYPTO_MUTEX_lock_write locks |lock| such that no other thread has any type + * of lock on it. */ +void CRYPTO_MUTEX_lock_write(CRYPTO_MUTEX *lock); + +/* CRYPTO_MUTEX_unlock unlocks |lock|. */ +void CRYPTO_MUTEX_unlock(CRYPTO_MUTEX *lock); + +/* CRYPTO_MUTEX_cleanup releases all resources held by |lock|. */ +void CRYPTO_MUTEX_cleanup(CRYPTO_MUTEX *lock); + +/* CRYPTO_STATIC_MUTEX_lock_read locks |lock| such that other threads may also + * have a read lock, but none may have a write lock. The |lock| variable does + * not need to be initialised by any function, but must have been statically + * initialised with |CRYPTO_STATIC_MUTEX_INIT|. */ +void CRYPTO_STATIC_MUTEX_lock_read(struct CRYPTO_STATIC_MUTEX *lock); + +/* CRYPTO_STATIC_MUTEX_lock_write locks |lock| such that no other thread has + * any type of lock on it. The |lock| variable does not need to be initialised + * by any function, but must have been statically initialised with + * |CRYPTO_STATIC_MUTEX_INIT|. */ +void CRYPTO_STATIC_MUTEX_lock_write(struct CRYPTO_STATIC_MUTEX *lock); + +/* CRYPTO_STATIC_MUTEX_unlock unlocks |lock|. */ +void CRYPTO_STATIC_MUTEX_unlock(struct CRYPTO_STATIC_MUTEX *lock); + + /* Thread local storage. */ /* thread_local_data_t enumerates the types of thread-local data that can be diff --git a/crypto/thread_pthread.c b/crypto/thread_pthread.c index 1516ea19..4188c4cc 100644 --- a/crypto/thread_pthread.c +++ b/crypto/thread_pthread.c @@ -17,11 +17,62 @@ #if !defined(OPENSSL_WINDOWS) #include +#include #include #include +#include +OPENSSL_COMPILE_ASSERT(sizeof(CRYPTO_MUTEX) >= sizeof(pthread_rwlock_t), + CRYPTO_MUTEX_too_small); + +void CRYPTO_MUTEX_init(CRYPTO_MUTEX *lock) { + if (pthread_rwlock_init((pthread_rwlock_t *) lock, NULL) != 0) { + abort(); + } +} + +void CRYPTO_MUTEX_lock_read(CRYPTO_MUTEX *lock) { + if (pthread_rwlock_rdlock((pthread_rwlock_t *) lock) != 0) { + abort(); + } +} + +void CRYPTO_MUTEX_lock_write(CRYPTO_MUTEX *lock) { + if (pthread_rwlock_wrlock((pthread_rwlock_t *) lock) != 0) { + abort(); + } +} + +void CRYPTO_MUTEX_unlock(CRYPTO_MUTEX *lock) { + if (pthread_rwlock_unlock((pthread_rwlock_t *) lock) != 0) { + abort(); + } +} + +void CRYPTO_MUTEX_cleanup(CRYPTO_MUTEX *lock) { + pthread_rwlock_destroy((pthread_rwlock_t *) lock); +} + +void CRYPTO_STATIC_MUTEX_lock_read(struct CRYPTO_STATIC_MUTEX *lock) { + if (pthread_rwlock_rdlock(&lock->lock) != 0) { + abort(); + } +} + +void CRYPTO_STATIC_MUTEX_lock_write(struct CRYPTO_STATIC_MUTEX *lock) { + if (pthread_rwlock_wrlock(&lock->lock) != 0) { + abort(); + } +} + +void CRYPTO_STATIC_MUTEX_unlock(struct CRYPTO_STATIC_MUTEX *lock) { + if (pthread_rwlock_unlock(&lock->lock) != 0) { + abort(); + } +} + void CRYPTO_once(CRYPTO_once_t *once, void (*init)(void)) { pthread_once(once, init); } diff --git a/crypto/thread_win.c b/crypto/thread_win.c index 240444f8..7a4f9bcb 100644 --- a/crypto/thread_win.c +++ b/crypto/thread_win.c @@ -21,12 +21,17 @@ #pragma warning(pop) #include +#include #include #include +#include -void CRYPTO_once(CRYPTO_once_t *in_once, void (*init)(void)) { +OPENSSL_COMPILE_ASSERT(sizeof(CRYPTO_MUTEX) >= sizeof(CRITICAL_SECTION), + CRYPTO_MUTEX_too_small); + +static void run_once(CRYPTO_once_t *in_once, void (*init)(void *), void *arg) { volatile LONG *once = (LONG*) in_once; assert(sizeof(LONG) == sizeof(CRYPTO_once_t)); @@ -48,7 +53,7 @@ void CRYPTO_once(CRYPTO_once_t *in_once, void (*init)(void)) { case 0: /* The value was zero so we are the first thread to call |CRYPTO_once| * on it. */ - init(); + init(arg); /* Write one to indicate that initialisation is complete. */ InterlockedExchange(once, 1); return; @@ -70,6 +75,64 @@ void CRYPTO_once(CRYPTO_once_t *in_once, void (*init)(void)) { } } +static void call_once_init(void *arg) { + void (*init_func)(void); + /* MSVC does not like casting between data and function pointers. */ + memcpy(&init_func, &arg, sizeof(void *)); + init_func(); +} + +void CRYPTO_once(CRYPTO_once_t *in_once, void (*init)(void)) { + void *arg; + /* MSVC does not like casting between data and function pointers. */ + memcpy(&arg, &init, sizeof(void *)); + run_once(in_once, call_once_init, arg); +} + +void CRYPTO_MUTEX_init(CRYPTO_MUTEX *lock) { + if (!InitializeCriticalSectionAndSpinCount((CRITICAL_SECTION *) lock, 0x400)) { + abort(); + } +} + +void CRYPTO_MUTEX_lock_read(CRYPTO_MUTEX *lock) { + /* Since we have to support Windows XP, read locks are actually exclusive. */ + EnterCriticalSection((CRITICAL_SECTION *) lock); +} + +void CRYPTO_MUTEX_lock_write(CRYPTO_MUTEX *lock) { + EnterCriticalSection((CRITICAL_SECTION *) lock); +} + +void CRYPTO_MUTEX_unlock(CRYPTO_MUTEX *lock) { + LeaveCriticalSection((CRITICAL_SECTION *) lock); +} + +void CRYPTO_MUTEX_cleanup(CRYPTO_MUTEX *lock) { + DeleteCriticalSection((CRITICAL_SECTION *) lock); +} + +static void static_lock_init(void *arg) { + struct CRYPTO_STATIC_MUTEX *lock = arg; + if (!InitializeCriticalSectionAndSpinCount(&lock->lock, 0x400)) { + abort(); + } +} + +void CRYPTO_STATIC_MUTEX_lock_read(struct CRYPTO_STATIC_MUTEX *lock) { + /* Since we have to support Windows XP, read locks are actually exclusive. */ + run_once(&lock->once, static_lock_init, lock); + EnterCriticalSection(&lock->lock); +} + +void CRYPTO_STATIC_MUTEX_lock_write(struct CRYPTO_STATIC_MUTEX *lock) { + CRYPTO_STATIC_MUTEX_lock_read(lock); +} + +void CRYPTO_STATIC_MUTEX_unlock(struct CRYPTO_STATIC_MUTEX *lock) { + LeaveCriticalSection(&lock->lock); +} + static CRITICAL_SECTION g_destructors_lock; static thread_local_destructor_t g_destructors[NUM_OPENSSL_THREAD_LOCALS]; diff --git a/include/openssl/thread.h b/include/openssl/thread.h index 1f8f524d..f9b9980d 100644 --- a/include/openssl/thread.h +++ b/include/openssl/thread.h @@ -64,6 +64,27 @@ extern "C" { #endif +#if defined(OPENSSL_WINDOWS) +/* CRYPTO_MUTEX can appear in public header files so we really don't want to + * pull in windows.h. It's statically asserted that this structure is large + * enough to contain a Windows CRITICAL_SECTION by thread_win.c. */ +typedef union crypto_mutex_st { + double alignment; + uint8_t padding[4*sizeof(void*) + 2*sizeof(int)]; +} CRYPTO_MUTEX; +#else +/* It is reasonable to include pthread.h on non-Windows systems, however the + * |pthread_rwlock_t| that we need is hidden under feature flags, and we can't + * ensure that we'll be able to get it. It's statically asserted that this + * structure is large enough to contain a |pthread_rwlock_t| by + * thread_pthread.c. */ +typedef union crypto_mutex_st { + double alignment; + uint8_t padding[3*sizeof(int) + 5*sizeof(unsigned) + 16 + 8]; +} CRYPTO_MUTEX; +#endif + + /* Functions to support multithreading. * * OpenSSL can safely be used in multi-threaded applications provided that at