UNDER CONSTRUCTION
Mutexes Mutex objects are used to protect shared data structures from being concurrently accessed. If a mutex object is destroyed while a thread is blocked waiting for that mutexthe lock, critical sections (shared data that would otherwise be protected from data races) and shared data are no longer protected.
The C++ Standard, 7 [thread.26.4.1mutex.class], paragraph 2 5 [ISO/IEC 9899:201114882-2014], states the following:
The
mtx_destroy
function releases any resources used by the mutex pointed to bymtx
. No threads can be blocked waiting for the mutex pointed to bymtx
.behavior of a program is undefined if it destroys amutex
object owned by any thread or a thread terminates while owning amutex
object.
Similar wording exists for std::recursive_mutex
, std::timed_mutex
, std::recursive_timed_mutex
, and std::shared_timed_mutex
. These statements imply that destroying a mutex object This statement implies that destroying a mutex while a thread is waiting on it is undefined behavior.
Noncompliant Code Example
This noncompliant code example creates several threads that each invoke the do_work()
function, passing a unique number as an ID. The do_work()
function initializes the lock
mutex if the argument is 0 and destroys the mutex if the argument is max_threads - 1
. In all other cases, the do_work()
function provides normal processing. Each thread, except the final cleanup thread, increments the atomic completed
variable when it is finished.
Unfortunately, this code contains several contains a race conditionscondition, allowing the mutex to be destroyed before while it is unlocked. Additionally, there is no guarantee that lock
will be initialized before it is passed to mtx_lock()
. Each of these behaviors is undefinedstill owned, because start_threads()
may invoke the mutex's destructor before all of the threads have exited.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdatomic.h><mutex> #include <threads.h><thread> mtxconst size_t lock; /* Atomic so multiple threads can modify safely */ atomic_int completedmaxThreads = ATOMIC_VAR_INIT(0); enum { max_threads = 5 }; int10; void do_work(voidsize_t i, std::mutex *argpm) { int *i = (int *)argstd::lock_guard<std::mutex> lk(*pm); if (*i == 0) { /* Creation thread */ Access data protected if (thrd_success != mtx_init(&lock, mtx_plain))by the lock. } void start_threads() { std::thread threads[maxThreads]; /* Handle error */ }std::mutex m; for atomic_store(&completed, 1); } else if (*(size_t i = 0; i < max_threads - 1maxThreads; ++i) { /* Worker thread */ if (thrd_success != mtx_lock(&lock)) { /* Handle error */ } /* Access data protected by the lock */ atomic_fetch_add(&completed, 1); if (thrd_success != mtx_unlock(&lock)) { /* Handle error */ } } else { /* Destruction thread */ mtx_destroy(&lock); } return 0; } int main(void) { thrd_t threads[max_threads]; threads[i] = std::thread(do_work, i, &m); } } |
Compliant Solution
This compliant solution eliminates the race condition by extending the lifetime of the mutex.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <mutex> #include <thread> const size_t maxThreads = 10; void do_work(size_t i, std::mutex *pm) { std::lock_guard<std::mutex> lk(*pm); // Access data protected by the lock. } std::mutex m; void start_threads() { std::thread threads[maxThreads]; for (size_t i = 0; i < max_threadsmaxThreads; i++i) { if (thrd_success != thrd_create(&threads[i], = std::thread(do_work, &i)) { /* Handle error */ } } for (size_t i = 0; i < max_threads; i++) { if (thrd_success != thrd_join(threads[i], 0)) { /* Handle error */&m); } } return 0; } |
Compliant Solution
This compliant solution eliminates the race conditions condition by initializing the mutex in main()
before creating the threads and by destroying the mutex in main()
after joining the threads:joining the threads before the mutex's destructor is invoked.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdatomic.h><mutex> #include <threads.h><thread> mtxconst size_t lock; /* Atomic so multiple threads can increment safely */ atomic_int completedmaxThreads = ATOMIC_VAR_INIT(0); enum { max_threads = 5 }; int10; void do_work(voidsize_t i, std::mutex *dummypm) { if (thrd_success != mtx_lock(&lock)) { /* Handle error */ } /*std::lock_guard<std::mutex> lk(*pm); // Access data protected by the lock */ atomic_fetch_add(&completed, 1); if (thrd_success != mtx_unlock(&lock)) { /* Handle error */ } return 0; } int main(void. } void run_threads() { thrd_tstd::thread threads[max_threadsmaxThreads]; if (thrd_success != mtx_init(&lock, mtx_plain)) { /* Handle error */ }std::mutex m; for (size_t i = 0; i < max_threadsmaxThreads; i++i) { if (thrd_success != thrd_create(&threads[i], = std::thread(do_work, NULL)) { /* Handle error */ } }i, &m); } for (size_t i = 0; i < max_threadsmaxThreads; i++i) { if (thrd_success != thrd_join(threads[i], 0)) { /* Handle error */ } } mtx_destroy(&lock.join(); return 0;} } |
Risk Assessment
Destroying a mutex while it is locked may result in invalid control flow and data corruption.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|
CON50- |
CPP | Medium | Probable | High | P4 | L3 |
Automated Detection
Tool | Version | Checker | Description |
---|
5.0
CodeSonar |
| CONCURRENCY.LOCALARG | Local Variable Passed to Thread | ||||||
Helix QAC |
| DF961, DF4962 | |||||||
Klocwork |
| CERT.CONC.MUTEX.DESTROY_WHILE_LOCKED | |||||||
Parasoft C/C++test |
| CERT_CPP-CON50-a | Do not destroy another thread's mutex | |||||||
Polyspace Bug Finder |
| CERT C++: CON50-CPP | Checks for destruction of locked mutex (rule partially covered) |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
MITRE CWE | CWE-667, Improper Locking |
SEI CERT C Coding Standard | CON31-C. Do not destroy a mutex while it is locked |
Bibliography
...