Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

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 by mtx. No threads can be blocked waiting for the mutex pointed to by mtx.behavior of a program is undefined if it destroys a mutex object owned by any thread or a thread terminates while owning a mutex object.

Similar wording exists for std::recursive_mutexstd::timed_mutexstd::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.

...

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.

Code Block
bgColor#ffcccc
langc
#include <stdatomic.h><mutex>
#include <threads.h><thread>
 
mtxconst size_t lock;
/* Atomic so multiple threads can modify safely */
atomic_int completed = ATOMIC_VAR_INIT(0);
enum { max_threads = 5 }; 

intmaxThreads = 10;

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
bgColor#ccccff
langc
#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 */
    }
  }
  return 0;
}
&m);
  }
}

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
bgColor#ccccff
langc
#include <stdatomic.h><mutex>
#include <threads.h><thread>
 
mtxconst size_t lock;
/* Atomic so multiple threads can increment safely */
atomic_int completed = ATOMIC_VAR_INIT(0);
enum { max_threads = 5 }; 

intmaxThreads = 10;

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).
}
void run_threads() {
    /* Handle error */
  }

  return 0;
}

int main(void) {
  thrd_t threads[max_threadsstd::thread threads[maxThreads];
  
  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

Fortify SCA

5.0

 

Can detect violations of this rule with CERT C Rule Pack
CodeSonar
Include Page
CodeSonar_V
CodeSonar_V

CONCURRENCY.LOCALARG

Local Variable Passed to Thread

Helix QAC

Include Page
Helix QAC_V
Helix QAC_V

DF961, DF4962
Klocwork
Include Page
Klocwork_V
Klocwork_V
CERT.CONC.MUTEX.DESTROY_WHILE_LOCKED
Parasoft C/C++test
9.5BD-RES-FREE, BD-RES-INVFREE

Include Page
Parasoft_V
Parasoft_V

CERT_CPP-CON50-a

Do not destroy another thread's mutex

Polyspace Bug Finder

Include Page
Polyspace Bug Finder_V
Polyspace Bug Finder_V

CERT C++: CON50-CPPChecks 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

Bibliography

[ISO/IEC
9899:2011
14882-2014]
7
Subclause 30.
26.
4.1, "
The mtx_destroy Function
Mutex Requirements"


...

Image Added Image Modified Image Removed  Image Modified