From 6f2e733bab69a5c853a929a96671736458b8d4c1 Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Fri, 15 May 2015 12:01:29 -0700 Subject: [PATCH] Add infrastructure for reference counts. OpenSSL has traditionally done reference counting with |int|s and the |CRYPTO_add| function. Unless a special callback is installed (rare), this is implemented by doing the reference count operations under a lock. This change adds infrastructure for handling reference counts and uses atomic operations when C11 support is available. Change-Id: Ia023ce432319efd00f77a7340da27d16ee4b63c3 Reviewed-on: https://boringssl-review.googlesource.com/4771 Reviewed-by: Adam Langley --- CMakeLists.txt | 5 ++++ crypto/CMakeLists.txt | 16 ++++++++-- crypto/internal.h | 26 ++++++++++++++++ crypto/refcount_c11.c | 65 ++++++++++++++++++++++++++++++++++++++++ crypto/refcount_lock.c | 53 ++++++++++++++++++++++++++++++++ crypto/refcount_test.c | 59 ++++++++++++++++++++++++++++++++++++ include/openssl/thread.h | 9 ++++++ util/all_tests.go | 1 + 8 files changed, 231 insertions(+), 3 deletions(-) create mode 100644 crypto/refcount_c11.c create mode 100644 crypto/refcount_lock.c create mode 100644 crypto/refcount_test.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e41ee9f..8ab22f5d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,11 @@ if((CMAKE_COMPILER_IS_GNUCXX AND CMAKE_C_COMPILER_VERSION VERSION_GREATER "4.7.9 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wshadow") endif() +if((CMAKE_COMPILER_IS_GNUCXX AND CMAKE_C_COMPILER_VERSION VERSION_GREATER "4.8.99") OR + CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11 -D_XOPEN_SOURCE=700") +endif() + add_definitions(-DBORINGSSL_IMPLEMENTATION) if (BUILD_SHARED_LIBS) diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index 6433dc6b..7430b620 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -145,15 +145,17 @@ add_library( crypto crypto.c + directory_posix.c + directory_win.c + ex_data.c mem.c + refcount_c11.c + refcount_lock.c thread.c thread_none.c thread_pthread.c thread_win.c - ex_data.c time_support.c - directory_posix.c - directory_win.c ${CRYPTO_ARCH_SOURCES} @@ -217,5 +219,13 @@ add_executable( target_link_libraries(thread_test crypto) +add_executable( + refcount_test + + refcount_test.c +) + +target_link_libraries(refcount_test crypto) + perlasm(cpu-x86_64-asm.${ASM_EXT} cpu-x86_64-asm.pl) perlasm(cpu-x86-asm.${ASM_EXT} cpu-x86-asm.pl) diff --git a/crypto/internal.h b/crypto/internal.h index 42125dbc..2a11ab5b 100644 --- a/crypto/internal.h +++ b/crypto/internal.h @@ -354,6 +354,32 @@ typedef pthread_once_t CRYPTO_once_t; OPENSSL_EXPORT void CRYPTO_once(CRYPTO_once_t *once, void (*init)(void)); +/* Reference counting. */ + +#if __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_ATOMICS__) +#define OPENSSL_C11_ATOMIC +#endif + +/* CRYPTO_REFCOUNT_MAX is the value at which the reference count saturates. */ +#define CRYPTO_REFCOUNT_MAX 0xffffffff + +/* CRYPTO_refcount_inc atomically increments the value at |*count| unless the + * value would overflow. It's safe for multiple threads to concurrently call + * this or |CRYPTO_refcount_dec_and_test_zero| on the same + * |CRYPTO_refcount_t|. */ +OPENSSL_EXPORT void CRYPTO_refcount_inc(CRYPTO_refcount_t *count); + +/* CRYPTO_refcount_dec_and_test_zero tests the value at |*count|: + * if it's zero, it crashes the address space. + * if it's the maximum value, it returns zero. + * otherwise, it atomically decrements it and returns one iff the resulting + * value is zero. + * + * It's safe for multiple threads to concurrently call this or + * |CRYPTO_refcount_inc| on the same |CRYPTO_refcount_t|. */ +OPENSSL_EXPORT int CRYPTO_refcount_dec_and_test_zero(CRYPTO_refcount_t *count); + + /* Locks. * * Two types of locks are defined: |CRYPTO_MUTEX|, which can be used in diff --git a/crypto/refcount_c11.c b/crypto/refcount_c11.c new file mode 100644 index 00000000..a77473f7 --- /dev/null +++ b/crypto/refcount_c11.c @@ -0,0 +1,65 @@ +/* Copyright (c) 2015, Google Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ + +#include "internal.h" + + +#if defined(OPENSSL_C11_ATOMIC) + +#include +#include +#include +#include + +#include + + +/* See comment above the typedef of CRYPTO_refcount_t about these tests. */ +static_assert(alignof(CRYPTO_refcount_t) == alignof(_Atomic CRYPTO_refcount_t), + "_Atomic alters the needed alignment of a reference count"); +static_assert(sizeof(CRYPTO_refcount_t) == sizeof(_Atomic CRYPTO_refcount_t), + "_Atomic alters the size of a reference count"); + +static_assert((CRYPTO_refcount_t)-1 == CRYPTO_REFCOUNT_MAX, + "CRYPTO_REFCOUNT_MAX is incorrect"); + +void CRYPTO_refcount_inc(CRYPTO_refcount_t *count) { + uint32_t expected = atomic_load(count); + + while (expected != CRYPTO_REFCOUNT_MAX) { + uint32_t new_value = expected + 1; + if (atomic_compare_exchange_weak(count, &expected, new_value)) { + break; + } + } +} + +int CRYPTO_refcount_dec_and_test_zero(CRYPTO_refcount_t *count) { + uint32_t expected = atomic_load(count); + + for (;;) { + if (expected == 0) { + abort(); + } else if (expected == CRYPTO_REFCOUNT_MAX) { + return 0; + } else { + const uint32_t new_value = expected - 1; + if (atomic_compare_exchange_weak(count, &expected, new_value)) { + return new_value == 0; + } + } + } +} + +#endif /* OPENSSL_C11_ATOMIC */ diff --git a/crypto/refcount_lock.c b/crypto/refcount_lock.c new file mode 100644 index 00000000..bb8ef86b --- /dev/null +++ b/crypto/refcount_lock.c @@ -0,0 +1,53 @@ +/* Copyright (c) 2015, Google Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ + +#include "internal.h" + +#include + +#include + + +#if !defined(OPENSSL_C11_ATOMIC) + +OPENSSL_COMPILE_ASSERT((CRYPTO_refcount_t)-1 == CRYPTO_REFCOUNT_MAX, + CRYPTO_REFCOUNT_MAX_is_incorrect); + +static struct CRYPTO_STATIC_MUTEX g_refcount_lock = CRYPTO_STATIC_MUTEX_INIT; + +void CRYPTO_refcount_inc(CRYPTO_refcount_t *count) { + CRYPTO_STATIC_MUTEX_lock_write(&g_refcount_lock); + if (*count < CRYPTO_REFCOUNT_MAX) { + (*count)++; + } + CRYPTO_STATIC_MUTEX_unlock(&g_refcount_lock); +} + +int CRYPTO_refcount_dec_and_test_zero(CRYPTO_refcount_t *count) { + int ret; + + CRYPTO_STATIC_MUTEX_lock_write(&g_refcount_lock); + if (*count == 0) { + abort(); + } + if (*count < CRYPTO_REFCOUNT_MAX) { + (*count)--; + } + ret = (*count == 0); + CRYPTO_STATIC_MUTEX_unlock(&g_refcount_lock); + + return ret; +} + +#endif /* OPENSSL_C11_ATOMIC */ diff --git a/crypto/refcount_test.c b/crypto/refcount_test.c new file mode 100644 index 00000000..53db90f6 --- /dev/null +++ b/crypto/refcount_test.c @@ -0,0 +1,59 @@ +/* Copyright (c) 2015, Google Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ + +#include "internal.h" + +#include + +#include + + +int main() { + CRYPTO_refcount_t count = 0; + + CRYPTO_refcount_inc(&count); + if (count != 1) { + fprintf(stderr, "Incrementing reference count did not work.\n"); + return 1; + } + if (!CRYPTO_refcount_dec_and_test_zero(&count) || count != 0) { + fprintf(stderr, "Decrementing reference count to zero did not work.\n"); + return 1; + } + + count = CRYPTO_REFCOUNT_MAX; + CRYPTO_refcount_inc(&count); + if (count != CRYPTO_REFCOUNT_MAX) { + fprintf(stderr, "Count did not saturate correctly when incrementing.\n"); + return 1; + } + if (CRYPTO_refcount_dec_and_test_zero(&count) || + count != CRYPTO_REFCOUNT_MAX) { + fprintf(stderr, "Count did not saturate correctly when decrementing.\n"); + return 1; + } + + count = 2; + if (CRYPTO_refcount_dec_and_test_zero(&count)) { + fprintf(stderr, "Decrementing two resulted in zero!\n"); + return 1; + } + if (count != 1) { + fprintf(stderr, "Decrementing two did not produce one!"); + return 1; + } + + printf("PASS\n"); + return 0; +} diff --git a/include/openssl/thread.h b/include/openssl/thread.h index f6e75296..01df6ea5 100644 --- a/include/openssl/thread.h +++ b/include/openssl/thread.h @@ -90,6 +90,15 @@ typedef union crypto_mutex_st { } CRYPTO_MUTEX; #endif +/* CRYPTO_refcount_t is the type of a reference count. + * + * Since some platforms use C11 atomics to access this, it should have the + * _Atomic qualifier. However, this header is included by C++ programs as well + * as C code that might not set -std=c11. So, in practice, it's not possible to + * do that. Instead we statically assert that the size and native alignment of + * a plain uint32_t and an _Atomic uint32_t are equal in refcount_c11.c. */ +typedef uint32_t CRYPTO_refcount_t; + /* Functions to support multithreading. * diff --git a/util/all_tests.go b/util/all_tests.go index 91822d15..5b6dd885 100644 --- a/util/all_tests.go +++ b/util/all_tests.go @@ -83,6 +83,7 @@ var tests = []test{ {"crypto/lhash/lhash_test"}, {"crypto/modes/gcm_test"}, {"crypto/pkcs8/pkcs12_test"}, + {"crypto/refcount_test"}, {"crypto/rsa/rsa_test"}, {"crypto/thread_test"}, {"crypto/x509/pkcs7_test"},