The C++ Standard Library supplies both recursive and non-recursive mutex classes used to protect critical sections. The recursive mutex classes (std::recursive_mutex and std::recursive_timed_mutex) differ from the non-recursive mutex classes (std::mutex, std::timed_mutex, and std::shared_timed_mutex) in that a recursive mutex may be locked recursively by the thread that currently owns the mutex. All mutex classes support the ability to speculatively lock the mutex through functions such as try_lock(), try_lock_for(), try_lock_until(), try_lock_shared_for(), and try_lock_shared_until(). These speculative locking functions attempt to obtain ownership of the mutex for the calling thread, but will not block in the event the ownership cannot be obtained. Instead, they return a Boolean value specifying whether the ownership of the mutex was obtained or not.
The C++ Standard, [thread.mutex.requirements.mutex], paragraphs 14 and 15 [ISO/IEC 14882-2014], state the following:
The expression
m.try_lock()shall be well-formed and have the following semantics:
Requires: Ifmis of typestd::mutex,std::timed_mutex, orstd::shared_timed_mutex, the calling thread does not own the mutex.
Further, [thread.timedmutex.class], paragraph 3, in part, states the following:
The behavior of a program is undefined if:
— a thread that owns atimed_mutexobject callslock(),try_lock(),try_lock_for(), ortry_lock_until()on that object
Finally, [thread.sharedtimedmutex.class], paragraph 3, in part, states the following:
The behavior of a program is undefined if:
— a thread attempts to recursively gain any ownership of ashared_timed_mutex.
Thus, attempting to speculatively lock a non-recursive mutex object that is already owned by the calling thread is undefined behavior. Do not call try_lock(), try_lock_for(), try_lock_until(), try_lock_shared_for(), or try_lock_shared_until() on a non-recursive mutex object from a thread that already owns that mutex object.
Noncompliant Code Example
In this noncompliant code example, the mutex m is locked by the thread's initial entry point and is speculatively locked in the do_work() function from the same thread, resulting in undefined behavior because it is not a recursive mutex. With common implementations, this may result in deadlock.
#include <mutex>
#include <thread>
std::mutex m;
void do_thread_safe_work();
void do_work() {
while (!m.try_lock()) {
// The lock is not owned yet, do other work while waiting.
do_thread_safe_work();
}
try {
// The mutex is now locked; perform work on shared resources.
// ...
// Release the mutex.
catch (...) {
m.unlock();
throw;
}
m.unlock();
}
void start_func() {
std::lock_guard<std::mutex> lock(m);
do_work();
}
int main() {
std::thread t(start_func);
do_work();
t.join();
}
Compliant Solution
This compliant solution removes the lock from the thread's initial entry point, allowing the mutex to be speculatively locked, but not recursively.
#include <mutex>
#include <thread>
std::mutex m;
void do_thread_safe_work();
void do_work() {
while (!m.try_lock()) {
// The lock is not owned yet, do other work while waiting.
do_thread_safe_work();
}
try {
// The mutex is now locked; perform work on shared resources.
// ...
// Release the mutex.
catch (...) {
m.unlock();
throw;
}
m.unlock();
}
void start_func() {
do_work();
}
int main() {
std::thread t(start_func);
do_work();
t.join();
}
Risk Assessment
Speculatively locking a non-recursive mutex in a recursive manner is undefined behavior that can lead to deadlock.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
|---|---|---|---|---|---|
CON56-CPP | Low | Unlikely | High | P1 | L3 |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
Bibliography
| [ISO/IEC 14882-2014] | Subclause 30.4.1, "Mutex Requirements" |


