mirror of
https://github.com/ARMmbed/mbedtls.git
synced 2025-05-10 08:59:05 +08:00
Merge pull request #8534 from paul-elliott-arm/fix_mutex_abstraction
Make mutex abstraction and tests thread safe
This commit is contained in:
commit
c6f1637f8c
@ -28,10 +28,14 @@ extern "C" {
|
|||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
typedef struct mbedtls_threading_mutex_t {
|
typedef struct mbedtls_threading_mutex_t {
|
||||||
pthread_mutex_t MBEDTLS_PRIVATE(mutex);
|
pthread_mutex_t MBEDTLS_PRIVATE(mutex);
|
||||||
/* is_valid is 0 after a failed init or a free, and nonzero after a
|
|
||||||
* successful init. This field is not considered part of the public
|
/* WARNING - state should only be accessed when holding the mutex lock in
|
||||||
* API of Mbed TLS and may change without notice. */
|
* tests/src/threading_helpers.c, otherwise corruption can occur.
|
||||||
char MBEDTLS_PRIVATE(is_valid);
|
* state will be 0 after a failed init or a free, and nonzero after a
|
||||||
|
* successful init. This field is for testing only and thus not considered
|
||||||
|
* part of the public API of Mbed TLS and may change without notice.*/
|
||||||
|
char MBEDTLS_PRIVATE(state);
|
||||||
|
|
||||||
} mbedtls_threading_mutex_t;
|
} mbedtls_threading_mutex_t;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -56,28 +56,27 @@ static void threading_mutex_init_pthread(mbedtls_threading_mutex_t *mutex)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* A nonzero value of is_valid indicates a successfully initialized
|
/* One problem here is that calling lock on a pthread mutex without first
|
||||||
* mutex. This is a workaround for not being able to return an error
|
* having initialised it is undefined behaviour. Obviously we cannot check
|
||||||
* code for this function. The lock/unlock functions return an error
|
* this here in a thread safe manner without a significant performance
|
||||||
* if is_valid is nonzero. The Mbed TLS unit test code uses this field
|
* hit, so state transitions are checked in tests only via the state
|
||||||
* to distinguish more states of the mutex; see
|
* variable. Please make sure any new mutex that gets added is exercised in
|
||||||
* tests/src/threading_helpers for details. */
|
* tests; see tests/src/threading_helpers.c for more details. */
|
||||||
mutex->is_valid = pthread_mutex_init(&mutex->mutex, NULL) == 0;
|
(void) pthread_mutex_init(&mutex->mutex, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void threading_mutex_free_pthread(mbedtls_threading_mutex_t *mutex)
|
static void threading_mutex_free_pthread(mbedtls_threading_mutex_t *mutex)
|
||||||
{
|
{
|
||||||
if (mutex == NULL || !mutex->is_valid) {
|
if (mutex == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
(void) pthread_mutex_destroy(&mutex->mutex);
|
(void) pthread_mutex_destroy(&mutex->mutex);
|
||||||
mutex->is_valid = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int threading_mutex_lock_pthread(mbedtls_threading_mutex_t *mutex)
|
static int threading_mutex_lock_pthread(mbedtls_threading_mutex_t *mutex)
|
||||||
{
|
{
|
||||||
if (mutex == NULL || !mutex->is_valid) {
|
if (mutex == NULL) {
|
||||||
return MBEDTLS_ERR_THREADING_BAD_INPUT_DATA;
|
return MBEDTLS_ERR_THREADING_BAD_INPUT_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +89,7 @@ static int threading_mutex_lock_pthread(mbedtls_threading_mutex_t *mutex)
|
|||||||
|
|
||||||
static int threading_mutex_unlock_pthread(mbedtls_threading_mutex_t *mutex)
|
static int threading_mutex_unlock_pthread(mbedtls_threading_mutex_t *mutex)
|
||||||
{
|
{
|
||||||
if (mutex == NULL || !mutex->is_valid) {
|
if (mutex == NULL) {
|
||||||
return MBEDTLS_ERR_THREADING_BAD_INPUT_DATA;
|
return MBEDTLS_ERR_THREADING_BAD_INPUT_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -435,6 +435,9 @@ int test_hooks_failure_detected(void)
|
|||||||
|
|
||||||
void test_hooks_free(void)
|
void test_hooks_free(void)
|
||||||
{
|
{
|
||||||
|
#if defined(MBEDTLS_TEST_MUTEX_USAGE)
|
||||||
|
mbedtls_test_mutex_usage_end();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* MBEDTLS_TEST_HOOKS */
|
#endif /* MBEDTLS_TEST_HOOKS */
|
||||||
|
@ -255,10 +255,18 @@ int mbedtls_test_hexcmp(uint8_t *a, uint8_t *b,
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(MBEDTLS_TEST_MUTEX_USAGE)
|
#if defined(MBEDTLS_TEST_MUTEX_USAGE)
|
||||||
/** Permanently activate the mutex usage verification framework. See
|
/**
|
||||||
* threading_helpers.c for information. */
|
* Activate the mutex usage verification framework. See threading_helpers.c for
|
||||||
|
* information.
|
||||||
|
* */
|
||||||
void mbedtls_test_mutex_usage_init(void);
|
void mbedtls_test_mutex_usage_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deactivate the mutex usage verification framework. See threading_helpers.c
|
||||||
|
* for information.
|
||||||
|
*/
|
||||||
|
void mbedtls_test_mutex_usage_end(void);
|
||||||
|
|
||||||
/** Call this function after executing a test case to check for mutex usage
|
/** Call this function after executing a test case to check for mutex usage
|
||||||
* errors. */
|
* errors. */
|
||||||
void mbedtls_test_mutex_usage_check(void);
|
void mbedtls_test_mutex_usage_check(void);
|
||||||
|
@ -58,15 +58,15 @@
|
|||||||
* indicate the exact location of the problematic call. To locate the error,
|
* indicate the exact location of the problematic call. To locate the error,
|
||||||
* use a debugger and set a breakpoint on mbedtls_test_mutex_usage_error().
|
* use a debugger and set a breakpoint on mbedtls_test_mutex_usage_error().
|
||||||
*/
|
*/
|
||||||
enum value_of_mutex_is_valid_field {
|
enum value_of_mutex_state_field {
|
||||||
/* Potential values for the is_valid field of mbedtls_threading_mutex_t.
|
/* Potential values for the state field of mbedtls_threading_mutex_t.
|
||||||
* Note that MUTEX_FREED must be 0 and MUTEX_IDLE must be 1 for
|
* Note that MUTEX_FREED must be 0 and MUTEX_IDLE must be 1 for
|
||||||
* compatibility with threading_mutex_init_pthread() and
|
* compatibility with threading_mutex_init_pthread() and
|
||||||
* threading_mutex_free_pthread(). MUTEX_LOCKED could be any nonzero
|
* threading_mutex_free_pthread(). MUTEX_LOCKED could be any nonzero
|
||||||
* value. */
|
* value. */
|
||||||
MUTEX_FREED = 0, //!< Set by threading_mutex_free_pthread
|
MUTEX_FREED = 0, //! < Set by mbedtls_test_wrap_mutex_free
|
||||||
MUTEX_IDLE = 1, //!< Set by threading_mutex_init_pthread and by our unlock
|
MUTEX_IDLE = 1, //! < Set by mbedtls_test_wrap_mutex_init and by mbedtls_test_wrap_mutex_unlock
|
||||||
MUTEX_LOCKED = 2, //!< Set by our lock
|
MUTEX_LOCKED = 2, //! < Set by mbedtls_test_wrap_mutex_lock
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@ -77,10 +77,30 @@ typedef struct {
|
|||||||
} mutex_functions_t;
|
} mutex_functions_t;
|
||||||
static mutex_functions_t mutex_functions;
|
static mutex_functions_t mutex_functions;
|
||||||
|
|
||||||
/** The total number of calls to mbedtls_mutex_init(), minus the total number
|
/**
|
||||||
* of calls to mbedtls_mutex_free().
|
* The mutex used to guard live_mutexes below and access to the status variable
|
||||||
|
* in every mbedtls_threading_mutex_t.
|
||||||
|
* Note that we are not reporting any errors when locking and unlocking this
|
||||||
|
* mutex. This is for a couple of reasons:
|
||||||
*
|
*
|
||||||
* Reset to 0 after each test case.
|
* 1. We have no real way of reporting any errors with this mutex - we cannot
|
||||||
|
* report it back to the caller, as the failure was not that of the mutex
|
||||||
|
* passed in. We could fail the test, but again this would indicate a problem
|
||||||
|
* with the test code that did not exist.
|
||||||
|
*
|
||||||
|
* 2. Any failure to lock is unlikely to be intermittent, and will thus not
|
||||||
|
* give false test results - the overall result would be to turn off the
|
||||||
|
* testing. This is not a situation that is likely to happen with normal
|
||||||
|
* testing and we still have TSan to fall back on should this happen.
|
||||||
|
*/
|
||||||
|
mbedtls_threading_mutex_t mbedtls_test_mutex_mutex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The total number of calls to mbedtls_mutex_init(), minus the total number
|
||||||
|
* of calls to mbedtls_mutex_free().
|
||||||
|
*
|
||||||
|
* Do not read or write without holding mbedtls_test_mutex_mutex (above). Reset
|
||||||
|
* to 0 after each test case.
|
||||||
*/
|
*/
|
||||||
static int live_mutexes;
|
static int live_mutexes;
|
||||||
|
|
||||||
@ -88,6 +108,7 @@ static void mbedtls_test_mutex_usage_error(mbedtls_threading_mutex_t *mutex,
|
|||||||
const char *msg)
|
const char *msg)
|
||||||
{
|
{
|
||||||
(void) mutex;
|
(void) mutex;
|
||||||
|
|
||||||
if (mbedtls_test_info.mutex_usage_error == NULL) {
|
if (mbedtls_test_info.mutex_usage_error == NULL) {
|
||||||
mbedtls_test_info.mutex_usage_error = msg;
|
mbedtls_test_info.mutex_usage_error = msg;
|
||||||
}
|
}
|
||||||
@ -101,76 +122,92 @@ static void mbedtls_test_mutex_usage_error(mbedtls_threading_mutex_t *mutex,
|
|||||||
static void mbedtls_test_wrap_mutex_init(mbedtls_threading_mutex_t *mutex)
|
static void mbedtls_test_wrap_mutex_init(mbedtls_threading_mutex_t *mutex)
|
||||||
{
|
{
|
||||||
mutex_functions.init(mutex);
|
mutex_functions.init(mutex);
|
||||||
if (mutex->is_valid) {
|
|
||||||
|
if (mutex_functions.lock(&mbedtls_test_mutex_mutex) == 0) {
|
||||||
|
mutex->state = MUTEX_IDLE;
|
||||||
++live_mutexes;
|
++live_mutexes;
|
||||||
|
|
||||||
|
mutex_functions.unlock(&mbedtls_test_mutex_mutex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void mbedtls_test_wrap_mutex_free(mbedtls_threading_mutex_t *mutex)
|
static void mbedtls_test_wrap_mutex_free(mbedtls_threading_mutex_t *mutex)
|
||||||
{
|
{
|
||||||
switch (mutex->is_valid) {
|
if (mutex_functions.lock(&mbedtls_test_mutex_mutex) == 0) {
|
||||||
case MUTEX_FREED:
|
|
||||||
mbedtls_test_mutex_usage_error(mutex, "free without init or double free");
|
switch (mutex->state) {
|
||||||
break;
|
case MUTEX_FREED:
|
||||||
case MUTEX_IDLE:
|
mbedtls_test_mutex_usage_error(mutex, "free without init or double free");
|
||||||
/* Do nothing. The underlying free function will reset is_valid
|
break;
|
||||||
* to 0. */
|
case MUTEX_IDLE:
|
||||||
break;
|
mutex->state = MUTEX_FREED;
|
||||||
case MUTEX_LOCKED:
|
--live_mutexes;
|
||||||
mbedtls_test_mutex_usage_error(mutex, "free without unlock");
|
break;
|
||||||
break;
|
case MUTEX_LOCKED:
|
||||||
default:
|
mbedtls_test_mutex_usage_error(mutex, "free without unlock");
|
||||||
mbedtls_test_mutex_usage_error(mutex, "corrupted state");
|
break;
|
||||||
break;
|
default:
|
||||||
}
|
mbedtls_test_mutex_usage_error(mutex, "corrupted state");
|
||||||
if (mutex->is_valid) {
|
break;
|
||||||
--live_mutexes;
|
}
|
||||||
|
|
||||||
|
mutex_functions.unlock(&mbedtls_test_mutex_mutex);
|
||||||
}
|
}
|
||||||
mutex_functions.free(mutex);
|
mutex_functions.free(mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int mbedtls_test_wrap_mutex_lock(mbedtls_threading_mutex_t *mutex)
|
static int mbedtls_test_wrap_mutex_lock(mbedtls_threading_mutex_t *mutex)
|
||||||
{
|
{
|
||||||
|
/* Lock the passed in mutex first, so that the only way to change the state
|
||||||
|
* is to hold the passed in and internal mutex - otherwise we create a race
|
||||||
|
* condition. */
|
||||||
int ret = mutex_functions.lock(mutex);
|
int ret = mutex_functions.lock(mutex);
|
||||||
switch (mutex->is_valid) {
|
if (mutex_functions.lock(&mbedtls_test_mutex_mutex) == 0) {
|
||||||
case MUTEX_FREED:
|
switch (mutex->state) {
|
||||||
mbedtls_test_mutex_usage_error(mutex, "lock without init");
|
case MUTEX_FREED:
|
||||||
break;
|
mbedtls_test_mutex_usage_error(mutex, "lock without init");
|
||||||
case MUTEX_IDLE:
|
break;
|
||||||
if (ret == 0) {
|
case MUTEX_IDLE:
|
||||||
mutex->is_valid = 2;
|
if (ret == 0) {
|
||||||
}
|
mutex->state = MUTEX_LOCKED;
|
||||||
break;
|
}
|
||||||
case MUTEX_LOCKED:
|
break;
|
||||||
mbedtls_test_mutex_usage_error(mutex, "double lock");
|
case MUTEX_LOCKED:
|
||||||
break;
|
mbedtls_test_mutex_usage_error(mutex, "double lock");
|
||||||
default:
|
break;
|
||||||
mbedtls_test_mutex_usage_error(mutex, "corrupted state");
|
default:
|
||||||
break;
|
mbedtls_test_mutex_usage_error(mutex, "corrupted state");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_functions.unlock(&mbedtls_test_mutex_mutex);
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int mbedtls_test_wrap_mutex_unlock(mbedtls_threading_mutex_t *mutex)
|
static int mbedtls_test_wrap_mutex_unlock(mbedtls_threading_mutex_t *mutex)
|
||||||
{
|
{
|
||||||
int ret = mutex_functions.unlock(mutex);
|
/* Lock the internal mutex first and change state, so that the only way to
|
||||||
switch (mutex->is_valid) {
|
* change the state is to hold the passed in and internal mutex - otherwise
|
||||||
case MUTEX_FREED:
|
* we create a race condition. */
|
||||||
mbedtls_test_mutex_usage_error(mutex, "unlock without init");
|
if (mutex_functions.lock(&mbedtls_test_mutex_mutex) == 0) {
|
||||||
break;
|
switch (mutex->state) {
|
||||||
case MUTEX_IDLE:
|
case MUTEX_FREED:
|
||||||
mbedtls_test_mutex_usage_error(mutex, "unlock without lock");
|
mbedtls_test_mutex_usage_error(mutex, "unlock without init");
|
||||||
break;
|
break;
|
||||||
case MUTEX_LOCKED:
|
case MUTEX_IDLE:
|
||||||
if (ret == 0) {
|
mbedtls_test_mutex_usage_error(mutex, "unlock without lock");
|
||||||
mutex->is_valid = MUTEX_IDLE;
|
break;
|
||||||
}
|
case MUTEX_LOCKED:
|
||||||
break;
|
mutex->state = MUTEX_IDLE;
|
||||||
default:
|
break;
|
||||||
mbedtls_test_mutex_usage_error(mutex, "corrupted state");
|
default:
|
||||||
break;
|
mbedtls_test_mutex_usage_error(mutex, "corrupted state");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mutex_functions.unlock(&mbedtls_test_mutex_mutex);
|
||||||
}
|
}
|
||||||
return ret;
|
return mutex_functions.unlock(mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void mbedtls_test_mutex_usage_init(void)
|
void mbedtls_test_mutex_usage_init(void)
|
||||||
@ -183,6 +220,8 @@ void mbedtls_test_mutex_usage_init(void)
|
|||||||
mbedtls_mutex_free = &mbedtls_test_wrap_mutex_free;
|
mbedtls_mutex_free = &mbedtls_test_wrap_mutex_free;
|
||||||
mbedtls_mutex_lock = &mbedtls_test_wrap_mutex_lock;
|
mbedtls_mutex_lock = &mbedtls_test_wrap_mutex_lock;
|
||||||
mbedtls_mutex_unlock = &mbedtls_test_wrap_mutex_unlock;
|
mbedtls_mutex_unlock = &mbedtls_test_wrap_mutex_unlock;
|
||||||
|
|
||||||
|
mutex_functions.init(&mbedtls_test_mutex_mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void mbedtls_test_mutex_usage_check(void)
|
void mbedtls_test_mutex_usage_check(void)
|
||||||
@ -207,4 +246,14 @@ void mbedtls_test_mutex_usage_check(void)
|
|||||||
mbedtls_test_info.mutex_usage_error = NULL;
|
mbedtls_test_info.mutex_usage_error = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void mbedtls_test_mutex_usage_end(void)
|
||||||
|
{
|
||||||
|
mbedtls_mutex_init = mutex_functions.init;
|
||||||
|
mbedtls_mutex_free = mutex_functions.free;
|
||||||
|
mbedtls_mutex_lock = mutex_functions.lock;
|
||||||
|
mbedtls_mutex_unlock = mutex_functions.unlock;
|
||||||
|
|
||||||
|
mutex_functions.free(&mbedtls_test_mutex_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
#endif /* MBEDTLS_TEST_MUTEX_USAGE */
|
#endif /* MBEDTLS_TEST_MUTEX_USAGE */
|
||||||
|
@ -772,6 +772,10 @@ int execute_tests(int argc, const char **argv)
|
|||||||
mbedtls_fprintf(stdout, " (%u / %u tests (%u skipped))\n",
|
mbedtls_fprintf(stdout, " (%u / %u tests (%u skipped))\n",
|
||||||
total_tests - total_errors, total_tests, total_skipped);
|
total_tests - total_errors, total_tests, total_skipped);
|
||||||
|
|
||||||
|
#if defined(MBEDTLS_TEST_MUTEX_USAGE)
|
||||||
|
mbedtls_test_mutex_usage_end();
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(MBEDTLS_MEMORY_BUFFER_ALLOC_C) && \
|
#if defined(MBEDTLS_MEMORY_BUFFER_ALLOC_C) && \
|
||||||
!defined(TEST_SUITE_MEMORY_BUFFER_ALLOC)
|
!defined(TEST_SUITE_MEMORY_BUFFER_ALLOC)
|
||||||
#if defined(MBEDTLS_MEMORY_DEBUG)
|
#if defined(MBEDTLS_MEMORY_DEBUG)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user