...
In this noncompliant code example, mutex1 protects count1 and mutex2 protects count2. A race condition exists between the waiter1 and waiter2 threads because they use the same condition variable with different mutexes. If both threads attempt to call pthread_cond_wait() at the same time, one thread will succeed and the other thread will invoke undefined behavior.
| Code Block | ||||
|---|---|---|---|---|
| ||||
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <assert.h>
#include <unistd.h>
#include <errno.h>
pthread_mutex_t mutex1;
pthread_mutex_t mutex2;
pthread_mutexattr_t attr;
pthread_cond_t cv;
void *waiter1();
void *waiter2();
void *signaler();
int count1 = 0, count2 = 0;
#define COUNT_LIMIT 5
int main() {
int ret;
pthread_t thread1, thread2, thread3;
if ((ret = pthread_mutexattr_init( &attr)) != 0) {
/* handleHandle error */
}
if ((ret = pthread_mutexattr_settype( &attr, PTHREAD_MUTEX_ERRORCHECK)) != 0) {
/* handleHandle error */
}
if ((ret = pthread_mutex_init( &mutex1, &attr)) != 0) {
/* handleHandle error */
}
if ((ret = pthread_mutex_init( &mutex2, &attr)) != 0) {
/* handleHandle error */
}
if ((ret = pthread_cond_init( &cv, NULL)) != 0) {
/* handle error */
}
if ((ret = pthread_create( &thread1, NULL, &waiter1, NULL))) {
/* handleHandle error */
}
if ((ret = pthread_create( &thread2, NULL, &waiter2, NULL))) {
/* handle error */
}
if ((ret = pthread_create( &thread3, NULL, &signaler, NULL))) {
/* handleHandle error */
}
if ((ret = pthread_join( thread1, NULL)) != 0) {
/* handleHandle error */
}
if ((ret = pthread_join( thread2, NULL)) != 0) {
/* handleHandle error */
}
if ((ret = pthread_join( thread3, NULL)) != 0) {
/* handleHandle error */
}
return 0;
}
void *waiter1() {
int ret;
while (count1 < COUNT_LIMIT) {
if ((ret = pthread_mutex_lock(&mutex1)) != 0) {
/* handleHandle error */
}
if ((ret = pthread_cond_wait(&cv, &mutex1)) != 0) {
/* handleHandle error */
}
printf("count1 = %d\n", ++count1);
if ((ret = pthread_mutex_unlock(&mutex1)) != 0) {
/* handleHandle error */
}
}
return NULL;
}
void *waiter2() {
int ret;
while (count2 < COUNT_LIMIT) {
if ((ret = pthread_mutex_lock(&mutex2)) != 0) {
/* handleHandle error */
}
if ((ret = pthread_cond_wait(&cv, &mutex2)) != 0) {
/* handleHandle error */
}
printf("count2 = %d\n", ++count2);
if ((ret = pthread_mutex_unlock(&mutex2)) != 0) {
/* handleHandle error */
}
}
return NULL;
}
void *signaler() {
int ret;
while ((count1 < COUNT_LIMIT) || (count2 < COUNT_LIMIT)) {
sleep(1);
printf("signaling\n");
if ((ret = pthread_cond_signal(&cv)) != 0) {
/* handleHandle error */
}
}
return NULL;
}
|
...
pthread_cond_wait() returns EINVAL if it is called when another thread is waiting on the condition variable with a different mutex. This approach is arguably better because it forces the coder to fix the problem instead of allowing reliance on undefined behavior.
...
This problem can be solved either by always using the same mutex whenever a particular condition variable is used or by using separate condition variables, depending on how the code is expected to work. Here we use This compliant solution uses the €œsame-mutex€ solution.:
| Code Block | ||||
|---|---|---|---|---|
| ||||
#include <stdio.h> #include <pthread.h> pthread_mutex_t mutex1; /* initializedInitialized as PTHREAD_MUTEX_ERRORCHECK */ pthread_cond_t cv; int count1 = 0, count2 = 0; #define COUNT_LIMIT 5 void *waiter1() { int ret; while (count1 < COUNT_LIMIT) { if ((ret = pthread_mutex_lock(&mutex1)) != 0) { /* handleHandle error */ } if ((ret = pthread_cond_wait(&cv, &mutex1)) != 0) { /* handleHandle error */ } printf("count1 = %d\n", ++count1); if ((ret = pthread_mutex_unlock(&mutex1)) != 0) { /* handleHandle error */ } } return NULL; } void *waiter2() { int ret; while (count2 < COUNT_LIMIT) { if ((ret = pthread_mutex_lock(&mutex1)) != 0) { /* handleHandle error */ } if ((ret = pthread_cond_wait(&cv, &mutex1)) != 0) { /* handleHandle error */ } printf("count2 = %d\n", ++count2); if ((ret = pthread_mutex_unlock(&mutex1)) != 0) { /* handleHandle error */ } } return NULL; } |
...
The severity is medium because improperly accessing shared data could lead to data integrity violation. Likelihood is probable because in such an implementation, an error code would not be returned, and remediation cost is high because detection and correction of this problem are both manual.
Rule | Severity | Likelihood | Detectable |
|---|
Repairable | Priority | Level | ||||
|---|---|---|---|---|---|---|
POS53-C | Medium | Probable | Yes | No | P8 | L2 |
Automated Detection
Tool | Version | Checker | Description | ||||||
|---|---|---|---|---|---|---|---|---|---|
| Helix QAC |
| C1769 C++1769 | |||||||
| Parasoft C/C++test |
| CERT_C |
medium
probable
high
P4
L3
...
-POS53-a | Do not use more than one mutex for concurrent waiting operations on a condition variable | ||||||||
| Polyspace Bug Finder |
| CERT C: Rule POS53-C | Checks for multiple mutexes used with same conditional variable (rule fully covered) |
Bibliography
...