CertC-CON31ΒΆ
Do not destroy a mutex while it is locked
Mutexes 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 and shared data are no longer protected.
The C Standard, 7.26.4.1, paragraph 2 [ ISO/IEC 9899:2011], states
The
mtx_destroyfunction releases any resources used by the mutex pointed to bymtx. No threads can be blocked waiting for the mutex pointed to bymtx.
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 race conditions, allowing the
mutex to be destroyed before 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
undefined.
#include <stdatomic.h>
#include <stddef.h>
#include <threads.h>
mtx_t lock;
/* Atomic so multiple threads can modify safely */
atomic_int completed = ATOMIC_VAR_INIT(0);
enum { max_threads = 5 };
int do_work(void *arg) {
int *i = (int *)arg;
if (*i == 0) { /* Creation thread */
if (thrd_success != mtx_init(&lock, mtx_plain)) {
/* Handle error */
}
atomic_store(&completed, 1);
} else if (*i < max_threads - 1) { /* 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];
for (size_t i = 0; i < max_threads; i++) {
if (thrd_success != thrd_create(&threads[i], do_work, &i)) {
/* Handle error */
}
}
for (size_t i = 0; i < max_threads; i++) {
if (thrd_success != thrd_join(threads[i], 0)) {
/* Handle error */
}
}
return 0;
}
Compliant Solution
This compliant solution eliminates the race conditions by initializing the
mutex in
main() before creating the threads and by destroying the mutex in
main() after joining the threads:
#include <stdatomic.h>
#include <stddef.h>
#include <threads.h>
mtx_t lock;
/* Atomic so multiple threads can increment safely */
atomic_int completed = ATOMIC_VAR_INIT(0);
enum { max_threads = 5 };
int do_work(void *dummy) {
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 */
}
return 0;
}
int main(void) {
thrd_t threads[max_threads];
if (thrd_success != mtx_init(&lock, mtx_plain)) {
/* Handle error */
}
for (size_t i = 0; i < max_threads; i++) {
if (thrd_success != thrd_create(&threads[i], do_work, NULL)) {
/* Handle error */
}
}
for (size_t i = 0; i < max_threads; i++) {
if (thrd_success != thrd_join(threads[i], 0)) {
/* Handle error */
}
}
mtx_destroy(&lock);
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 |
|---|---|---|---|---|---|
| CON31-C | Medium | Probable | High | P4 | L3 |
Related Guidelines
| Taxonomy | Taxonomy item | Relationship |
|---|---|---|
| CWE 2.11 | CWE-667, Improper Locking | 2017-07-10: CERT: Rule subset of CWE |
Bibliography
| [ ISO/IEC 9899:2011] | 7.26.4.1, "The
mtx_destroy Function"
|
Possible Messages
Key |
Text |
Severity |
Disabled |
|---|---|---|---|
destroy_in_use_mutex |
Do not destroy a mutex while it is locked. |
None |
False |
Options
This rule shares the following common options: exclude_in_macros, exclude_messages_in_system_headers, excludes, extend_exclude_to_macro_invocations, includes, justification_checker, languages, post_processing, provider, report_at, severity
The following places define options that affect this rule: Stylechecks, Analysis-GlobalOptions
This rule has no individual options.