UNDER CONStRUCTION
Mutexes that are used to protect shared data structures being concurrently accessed. If a mutex is destroyed while a thread is blocked waiting for that mutex, critical sections (shared data that would otherwise be protected from data races) are no longer protected.
The C++ Standard, [thread.mutex.class], paragraph 5 [ISO/IEC 14882-2014], states:
The behavior of a program is undefined if it destroys a
mutex
object owned by any thread or a thread terminates while owning amutex
object.
accesses to shared data may be locked using the lock()
member function and unlocked using the unlock()
member function. If an exception occurs between the call to lock()
and the call to unlock()
, and the exception changes control flow such that unlock()
is not called, the mutex will be left in the locked state and no critical sections protected by that mutex will be allowed to execute. This is likely to lead to deadlock.
The throwing of an exception must not allow a mutex to remain locked indefinitely. If a mutex was locked and an exception occurs within the critical section protected by that mutex, the mutex must be unlocked as part of exception handling before rethrowing the exception or continuing execution unless subsequent control flow will unlock the mutex.
C++ supplies the lock classes lock_guard
, unique_lock
, and shared_lock
, which can be initialized with a mutex. In its constructor, the lock object locks the mutex, and in its destructor, it unlocks the mutex. The lock_guard
class provides a simple RAII wrapper around a mutex. The unique_lock
and shared_lock
classes also use RAII and provide additional functionality, such as manual control over the locking strategy. The unique_lock
class prevents the lock from being copied, although it allows the lock ownership to be moved to another lock. The shared_lock
class allows the mutex to be shared by several locks. For all three classes, if an exception occurs and takes control flow out of the scope of the lock, the destructor will unlock the mutex and the program can continue working normally. These lock objects are the preferred way to ensure that a mutex is properly released when an exception is thrownThis 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.
...
manipulates shared data and protects the critical section by locking the mutex. When it is finished, it unlocks the mutex. However, if an exception occurs while manipulating the shared data, the mutex will remain locked.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <mutex> #include <thread> constvoid sizemanipulate_t max_threads = 10; void do_work(size_t i, shared_data(std::mutex *lockp&pm) { std::lock_guard<std::mutex> guard(*lockppm.lock(); // AccessPerform datawork protectedon byshared the lockdata. } void start_threads(void) { std::thread threads[max_threads]; std::mutex lock; for (size_t i = 0; i < max_threads; ++i) { threads[i] = std::thread(do_work, i, &lock); } } |
Compliant Solution
pm.unlock();
}
|
Compliant Solution (Manual Unlock)
This compliant solution catches any exceptions thrown when performing work on the shared data and unlocks the mutex before rethrowing the exception.This compliant solution eliminates the race condition by extending the lifetime of the lock:
Code Block | ||||
---|---|---|---|---|
| ||||
#include <mutex> #include <thread> constvoid sizemanipulate_t max_threads = 10; void do_work(size_t i, shared_data(std::mutex *lockp&pm) { std::lock_guard<std::mutex> guard(*lockppm.lock(); try { // AccessPerform datawork protectedon byshared the lockdata. } std::mutex lock; void start_threads(void) } catch (...) { std::thread threads[max_threads]; for (size_t i = 0; i < max_threads; ++i) { threads[i] = std::thread(do_work, i, &lock); } } |
Compliant Solution
pm.unlock();
throw;
}
pm.unlock(); // in case no exceptions occur
} |
Compliant Solution (Lock Object)
This compliant solution uses a lock_guard
object to ensure that the mutex will be unlocked, even if an exception occurs, without relying on exception handling machinery and manual resource management.This compliant solution eliminates the race condition by joining the threads before the lock's destructor is invoked:
Code Block | ||||
---|---|---|---|---|
| ||||
#include <mutex> #include <thread> constvoid sizemanipulate_t max_threads = 10; void do_work(size_t i, shared_data(std::mutex *lockp&pm) { std::lock_guard<std::mutex> guardlk(*lockppm); // Access data protected by the lock. } void run_threads(void) { std::thread threads[max_threads]; std::mutex lock; for (size_t i = 0; i < max_threads; ++i) { threads[i] = std::thread(do_work, i, &lock); } for (size_t i = 0; i < max_threads; ++i) { threads[i].join(); } } |
Risk Assessment
Perform work on shared data.
}
|
Risk Assessment
If an exception occurs while a mutex is locked, deadlock may resultDestroying a mutex while it is locked may result in invalid control flow and data corruption.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|
CON51-CPP |
Low | Probable |
Low |
P6 |
L2 |
Automated Detection
Tool | Version | Checker | Description |
---|
5.0
CodeSonar |
| CONCURRENCY.LOCK.NOUNLOCK | Missing Lock Release | ||||||
Helix QAC |
| C++5018 | |||||||
Parasoft C/C++test |
| CERT_CPP-CON51-a | Do not call lock() directly on a mutex | |||||||
Polyspace Bug Finder |
| CERT C++: CON51-CPP | Checks for lock possibly not released on exception (rule fully covered) |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
This rule is a subset of ERR56-CPP. Guarantee exception safety.
MITRE CWE | CWE-667, Improper Locking |
SEI CERT Oracle Coding Standard for Java | LCK08-J. Ensure actively held locks are released on exceptional conditions |
Bibliography
...