Repository: Mbed-TLS/mbedtls (C)

Issue:
Our mutex abstraction layer is not entirely thread safe
### Summary

I say 'entirely', because to a certain extent we will get away with it (and have done due to the use of only global mutexs so far), but there are going to be cases where we do not.

The problem revolves around the `is_valid` flag in the `mbedtls_threading_mutex_t` struct. Given that this is not behind a mutex, it is clearly not thread safe. When I mean by "We will get away with this", if the valid flag is set prior to the mutex being used by any thread, then clearly this will be ok. However if the mutex is destroyed, there is a chance that another thread (for example waiting on the mutex to exit the thread) would potentially use the mutex after the first thread had killed the mutex. Although its unlikely the `is_valid` flag would corrupted, you cannot guarantee it being seen by all threads at the same time, it depends on where the two threads are and the level of memory cohesion between them, even if the field is marked as valid. Its a fairly classic race condition, so the results will be pretty random.

There are a couple of alternatives here, as I see it:

## Option 1 - Leave it.

Leave things as they are and document the shortcomings somewhere. I'm fundamentally not happy with this option, as I don't think we can even make sure the unsupported circumstances don't happen in our own code base. The layout of threads is going to be dependant on the end user.

## Option 1 - Remove `is_valid`

The main problem with this is that we can no longer check if a mutex is valid, so if `pthread_mutex_init()` fails, then we are not going to be able to report this. We do however make use of the static initialiser in most places currently, which would mean no error reporting anyway.

There is also a school of thought employed by other libraries that if a mutex creation fails, then its not going to be a recoverable situation. The first attempt to lock this mutex will error, anyway, so do we *need* to catch the error at creation time?

## Option 2 - Make it Safe.

This would be simple, wrap access to `is_valid` in a mutex. However this would mean that we would have to lock a mutex in order to.. lock a mutex, which is not considered a particularly good thing to do. For starters the performance of this is going to be terrible, and for seconds it is obviously going to change the order of execution fairly majorly.

Our Mutex Thread testing layer is not thread safe
### Summary

As per #8472  the `isvalid` field in the `mbedtls_threading_mutex_t` struct is not protected by a mutex, so the wrappers in `threading_helpers.c` reading and writing it on lock or unlock is going to cause a race condition on the `isvalid` field, which means that this test is very unlikely to work as expected in a multithreaded environment. Worse still, malfunction of these tests could cause a mutex to stop working.

The new TSAN compiler sanitiser mode flags this race condition immediately. I have a couple of ideas as to how to fix this, and obviously this is also dependant on how we decide to fix #8472

## Option 1. Remove the mutex testing layer.

I would hope that TSAN would catch most of the cases that are tested here, and I'm pretty sure pthreads itself will error on a few of the others.

## Option 2. Make it safe.

By wrapping all of the reading and writing of `isvalid` in a mutex we could easily make this safe, but I think it should be behind its own flag. I know these wrappers will only be used in a test environment, so performance isn't particularly a concern here here but the need to lock a mutex to lock or unlock a mutex could very much hide some timing issues from TSAN if the two are only run together. Care will also have to be taken to avoid deadlocking / negative interaction with the new test_info mutexes.




Discussion: > if a mutex creation fails, then its not going to be a recoverable situation

This is not true in general. If _taking_ a mutex fails, something is seriously wrong. But _creating_ a mutex can fail, because it might allocate resources. With pthreads, `pthread_mutex_init` may not fail. But on some other platforms (including Win32 IIRC), creating a mutex allocates a resource that can be exahusted.

>  But on some other platforms (including Win32 IIRC), creating a mutex allocates a resource that can be exahusted.

If we use SRWLOCKs on Windows, these are much more lightweight structures, and would allow us to do static initialisation as well. Again, it seems a little odd to be worrying about error codes from mutex creation when we are using static initialisers for any global mutex - in this case any error would be deferred to the first attempt to use the mutex as well.
> If we use SRWLOCKs on Windows, these are much more lightweight structures

SRWLOCKs are just pointers, so no system resource allocation is necessary, so creation can only fail if `malloc(sizeof(void *))` would fail.

And we've even been gifted an implementation in #7180 (based on work in #2306), but honestly, the actual implementation is trivial
Can you please share a problematic trace? AFAICT there is no possible race on architectures with memory consistency, which is the case on most common architectures.

The only possible race I see is:

1. Thread A calls `mbedtls_mutex_init`. This calls `pthread_mutex_init` which initializes the system mutex object, and ensures that all other threads see the system mutex object in the initialized state (otherwise system mutexes wouldn't work).
2. Thread A sets `is_valid = 1` and returns from `mbedtls_mutex_init`, but the write to `is_valid` is not yet committed to memory.
3. Thread A communicates with thread B, indicating that the mutex object is ready to use.
4. Thread B calls `mbedtls_mutex_lock`.
5. Thread B sees `is_valid == 0`, so `mbedtls_mutex_lock` fails.
6. Thread A commits the write of `is_valid = 1` to memory.

> If we use SRWLOCKs on Windows

Windows is not relevant here except as an example. We can't solve the resource allocation requirement on all embedded platforms.
> Can you please share a problematic trace? AFAICT there is no possible race on architectures with memory consistency, which is the case on most common architectures.
> 
> The only possible race I see is:
> 
> 1. Thread A calls `mbedtls_mutex_init`. This calls `pthread_mutex_init` which initializes the system mutex object, and ensures that all other threads see the system mutex object in the initialized state (otherwise system mutexes wouldn't work).
> 2. Thread A sets `is_valid = 1` and returns from `mbedtls_mutex_init`, but the write to `is_valid` is not yet committed to memory.
> 3. Thread A communicates with thread B, indicating that the mutex object is ready to use.
> 4. Thread B calls `mbedtls_mutex_lock`.
> 5. Thread B sees `is_valid == 0`, so `mbedtls_mutex_lock` fails.
> 6. Thread A commits the write of `is_valid = 1` to memory.

That really makes a lot of presumptions, mainly that Thread B is not going to call `mbedtls_mutex_lock()` until after thread A has called `mbedtls_mutex_init()` or used the static initialiser, or at least until after `isvalid` has made its way to the other thread, which may be running on another CPU.

The first is quite likely to happen (see  "we're currently getting away with this"), but the second is not an assumption that can easily be made, however we can _sometimes_ avoid this, by not sharing the mutex with the other thread until initialisation is complete. This doesn't work for global mutexes though, of which we already have two.

There is a much more problematic situation, though:

1. Thread A creates and shares a mutex with another Thread B , `isvalid` is now 1, all good.
2. Thread A locks the mutex, and works with something protected by the mutex.
3. Thread B waits for the mutex.
4. Thread A unlocks the mutex, and proceeds to destroy the mutex - 'isvalid' is now zero from the point of view of Thread A
5. Thread B gets the mutex, does some work and goes to unlock the mutex. This should fail because 'isvalid' should now be zero, however as they see it, 'isvalid' is still 1 - again, memory cohesion is complex especially when multiple CPUs are involved, and we cannot make presumptions if the correct guards are not used. This will now error, as its attempting to lock an invalid mutex.

This is again, a fairly contrived example, but there are a few points here:

1. We can only partially guarantee that 'isvalid' is going to be valid prior to another thread using it - this is not documented anywhere, and given the nature of threading, its going to be impossible to ensure that this is the case everywhere.
2. We can definitely NOT guarantee that the clearing of  `isvalid` will be safe for multiple threads holding the same mutex, so we potentially should not be doing it all. 
3. This is a reinvention of the wheel. The mutex itself knows if it is valid or not, in a thread safe manner, and the call to lock / unlock will error if you do this. We should perhaps be wrapping returns in our own error codes, but apart from that, what do we gain by 'isvalid' here?
> Windows is not relevant here except as an example. We can't solve the resource allocation requirement on all embedded platforms.

Its not the resource depletion that is the problem here, we are already using static initialisers which will not return any error if something went wrong. If those will not work on some platforms, we should stop using them, as `MBEDTLS_THREADING_ALT` already does.
> Thread B is not going to call mbedtls_mutex_lock() until after thread A has called mbedtls_mutex_init() or used the static initialiser

That would be a usage violation: thread B would be using an object that isn't initialized. If this happens, it's a major bug and it isn't related to the `is_valid` field.

> until after isvalid has made its way to the other thread, which may be running on another CPU.

Yes, that's the problematic case.

> 4\. Thread A unlocks the mutex, and proceeds to destroy the mutex - 'isvalid' is now zero from the point of view of Thread A  
> 5\. Thread B gets the mutex, does some work and goes to unlock the mutex.  

That too would be a usage violation: using an object that's already destroyed. Again, that's irrespective of the existence of the `is_valid` field.

> We can only partially guarantee that 'isvalid' is going to be valid prior to another thread using it - this is not documented anywhere, and given the nature of threading, its going to be impossible to ensure that this is the case everywhere.

Yes, that is a problem. `is_valid` is only used for testing though, and [we don't have to run that testing in an environment where there are actually multiple threads](https://github.com/Mbed-TLS/mbedtls/issues/8473#issuecomment-1792636966).

> We can definitely NOT guarantee that the clearing of isvalid will be safe for multiple threads holding the same mutex, so we potentially should not be doing it all.

`is_valid` is only cleared on destruction, so it is safe (except if the mutex was initialized in a different thread and the initialization's setting of `is_valid` hasn't propagated to the destruction thread, in which case there can be a resource leak).

> This is a reinvention of the wheel. The mutex itself knows if it is valid or not, in a thread safe manner, and the call to lock / unlock will error if you do this. We should perhaps be wrapping returns in our own error codes, but apart from that, what do we gain by 'isvalid' here?

No, the mutex does not know if it's valid or not! The point of `is_valid` is to keep track of calls to mutex initialization and destruction. We can't use the mutex object for that because it doesn't exist before initialization or after destruction. Please read the [documentation in `threading_helpers.c`](https://github.com/Mbed-TLS/mbedtls/blob/mbedtls-3.5.0/tests/src/threading_helpers.c#L27).

It may be a usage violation, but the whole point of a mutex is that its there to prevent usage violations. If it cannot be used itself in certain circumstances, then they are not going to be of a whole load of use.

`isvalid` is not cleared on destruction - there is no destruction. The other thread will still hold the object, and could potentially use it.

I agree  mutexes should **never** be destroyed until all the threads have confirmed as having exited, but that doesn't change the fundamental problem, you are using something not thread safe to try and ensure safety in a multithreaded environment. Saying that it is thread safe if you use it correctly is not going to cut it here, there will always be a situation or edge case where this will fail, and one of them involves global initialisation, which is something we already do. Do we document anywhere that you should not attempt to use the global mutexes in global initialisation? Seems like an easy mistake to make...

I have read the documentation, I can assure you that the pthread library knows whether or not one of its own mutexes is valid or not, from almost a decade of using it. Our abstraction / wrappers might not, but it does, and will error if you attempt to do something like this. Again, we should be wrapping these errors if we want an actual platform layer, but thats beside the point.
Once again: the goal of the current mutex usage test framework is to catch **errors in how the mutex object themselves are used**. It is not about what the mutexes might be protecting.

> you are using something not thread safe to try and ensure safety in a multithreaded environment.

As I've already written elsewhere, the `is_valid` field is meant for testing, and that testing does not need to be carried out in a multithreading environment. It only needs to be an environment where the mutexes exist. A solution could be to remove the `is_valid` checks from the lock and unlock function, and only keep their use in the test code.

> Saying that it is thread safe if you use it correctly is not going to cut it here, there will always be a situation or edge case where this will fail

Mutex **initialization** is not thread-safe (in general — on some platforms it is, but on others it isn't). If two threads run `mbedtls_mutex_init` on the same object concurrently, the behavior is undefined. It's not an edge case, it's one of the zillion cases of undefined behavior in C programming, like reading a stack variable without initializing it. We do, and must, assume that callers call this function correctly.

>  I can assure you that the pthread library knows whether or not one of its own mutexes is valid or not

That is not relevant. We're testing things like using a `mbedtls_threading_mutex_t` (calling lock/unlock on it) without having called `mbedtls_mutex_init`. At this point, a valid mutex object does not exist. If you run that on Linux/glibc (and probably many other pthread implementations) and the memory of the `mbedtls_threading_mutex_t` happens to be all-bits-zero, it just works, because all-bits-zero is a valid representation of a mutex that is in the unlocked state. That same code would fail on other threading implementations where all-bits-zero is not a valid representation. The goal of the mutex usage test framework that we have now is to catch this kind of usage violation that is not caught on the platform where we run tests.

After offline discussion, we are currently trending towards moving `isvalid` so its defined under `MBEDTLS_TEST_MUTEX_USAGE`, and all checks and tests are done in `threading_helpers.c`, which we will make thread safe. Testing is therefore completely preserved (in single and multithread environments) - see also #8473 

By mutex testing layer, do you mean `MBEDTLS_TEST_MUTEX_USAGE`? If so: I think we should keep it. It doesn't check much, but it's fully deterministic. If it's not thread-safe, that's not really a concern. We'll just run it in a configuration where we have threading enabled (so that the mutex objects exist) but we don't actually create other threads.
Sorry, to explain, this refers to the wrappers in `threading_helpers.c` (I have edited the body to clear this up). 

Given these hook into the operation of the mutex, I'm not really sure how to test these in a single threaded mode - Many of the mutexes won't actually exist unless you turn on the relevant flags, or certainly won't be used, given the performance impact of doing so. I'm not sure we would get any meaningful results like this, put it that way

These tests would have to be actively disabled if any threading was actually turned on, given they trip TSAN, and would cause the CI to fail.
Currently trending towards Option 2, but also moving `isvalid` so it only exists (and is checked / manipulated) under `MBEDTLS_TEST_MUTEX_USAGE`, thus retaining full test coverage, but thread-safe.



Fix (patch):
diff --git a/include/mbedtls/threading.h b/include/mbedtls/threading.h
index ed16a23b1ab0..b504233bdc50 100644
--- a/include/mbedtls/threading.h
+++ b/include/mbedtls/threading.h
@@ -28,10 +28,14 @@ extern "C" {
 #include <pthread.h>
 typedef struct mbedtls_threading_mutex_t {
     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
-     * API of Mbed TLS and may change without notice. */
-    char MBEDTLS_PRIVATE(is_valid);
+
+    /* WARNING - state should only be accessed when holding the mutex lock in
+     * tests/src/threading_helpers.c, otherwise corruption can occur.
+     * 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;
 #endif
 
diff --git a/library/threading.c b/library/threading.c
index 52fe8fca99f0..873b5077b80b 100644
--- a/library/threading.c
+++ b/library/threading.c
@@ -56,28 +56,27 @@ static void threading_mutex_init_pthread(mbedtls_threading_mutex_t *mutex)
         return;
     }
 
-    /* A nonzero value of is_valid indicates a successfully initialized
-     * mutex. This is a workaround for not being able to return an error
-     * code for this function. The lock/unlock functions return an error
-     * if is_valid is nonzero. The Mbed TLS unit test code uses this field
-     * to distinguish more states of the mutex; see
-     * tests/src/threading_helpers for details. */
-    mutex->is_valid = pthread_mutex_init(&mutex->mutex, NULL) == 0;
+    /* One problem here is that calling lock on a pthread mutex without first
+     * having initialised it is undefined behaviour. Obviously we cannot check
+     * this here in a thread safe manner without a significant performance
+     * hit, so state transitions are checked in tests only via the state
+     * variable. Please make sure any new mutex that gets added is exercised in
+     * tests; see tests/src/threading_helpers.c for more details. */
+    (void) pthread_mutex_init(&mutex->mutex, NULL);
 }
 
 static void threading_mutex_free_pthread(mbedtls_threading_mutex_t *mutex)
 {
-    if (mutex == NULL || !mutex->is_valid) {
+    if (mutex == NULL) {
         return;
     }
 
     (void) pthread_mutex_destroy(&mutex->mutex);
-    mutex->is_valid = 0;
 }
 
 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;
     }
 
@@ -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)
 {
-    if (mutex == NULL || !mutex->is_valid) {
+    if (mutex == NULL) {
         return MBEDTLS_ERR_THREADING_BAD_INPUT_DATA;
     }