Versions Compared

Key

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

...

In this noncompliant code example the worker thread is doing something as simple as swapping a and b repeatedly. However, this thread is can potentially create a data race condition. Because an asynchronous cancel can happen at ANY time, it could cancel right before the last line (a = c) and thereby lose the old value of b.

This code uses two locks. The init_mut mutex is used to ensure that the worker thread is initialized safely, and it notifies the main thread once it has set its cancellation policy. And the global_mut mutex is used by both threads to guarantee exclusive access to the global variables a and b. Furthermore, the worker thread uses the cleanup_worker function to clear the global_mut mutex, in case it gets cancelled while the lock is held.

Code Block
bgColor#ffcccc
volatile int a, b;
pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t mut_sig = PTHREAD_COND_INITIALIZER;

void main(void) {
  int j;
  a = 5;
  b = 10;

  pthread_mutex_lock(&mut);
  pthread_create(&thread_identifier,NULL,(void*)thread, NULL);

  /* wait until canceltype is set */
  pthread_cond_wait(&mut_sig, &mut);
  pthread_mutex_unlock(&mut);

  /* do stuff, like data input */
  j = getc(STDIN);
  /* since we don't know when the character is read in, the program could continue at any time */
  pthread_cancel(thread_identifier);

  printf("a: %i | b: %i", a, b);
  /* this could potentially print "a: 10 | b: 10" or "a: 5 | b: 5", both demonstrating data corruption */

  /* clean up */
  pthread_mutex_destroy(&mut);
  pthread_cond_destroy(&mut_sig
/* Lock to enable threads to access a and b safely */
pthread_mutex_t global_mut = PTHREAD_MUTEX_INITIALIZER;

/* Lock to enable worker thread to initialize safely */
pthread_mutex_t init_mut = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t init_mut_sig = PTHREAD_COND_INITIALIZER;

void cleanup_worker(void* dummy) {
  pthread_mutex_unlock(&global_mut);
}

void thread(void) {
  int i, c;

  /* set the cancelability flag during mutex. */
  /* this guarantees this block calls after pthread_cond_wait() and, more importantly, before pthread_cancel() */
  pthread_mutex_lock(&init_mut);
  pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,&i);
  pthread_cleanup_push( cleanup_worker, NULL);
  pthread_cond_signal(&init_mut_sig);
  pthread_mutex_unlock(&init_mut);

  while (1) {
    pthread_mutex_lock(&global_mut);
    c = b;
    b = a;
    a = c;
  }
}

Compliant Solution

From IEEE standards page:

The cancelability state and type of any newly created threads, including the thread in which main() was first invoked, shall be PTHREAD_CANCEL_ENABLE and PTHREAD_CANCEL_DEFERRED respectively.

Since the default condition according to the IEEE standards for POSIX is PTHREAD_CANCEL_DEFERRED, one would simply not set cancel type for the compliant solution.

However, since not all compilers are necessarily guaranteed to follow standards, one could also explicitly call pthread_setcanceltype with PTHREAD_CANCEL_DEFERRED.

Code Block
bgColor#ccccff

volatile int a, b;
pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t mut_sig = PTHREAD_COND_INITIALIZER;

void  pthread_mutex_unlock(&global_mut);
    /* now we're safe to cancel, creating cancel point */
    pthread_testcancel();
  }
  pthread_cleanup_pop( 1);
}

int main(void) {
  int j;
  a = 5;
  b = 10;
  pthread_t worker;

  pthread_mutex_lock(&init_mut);
  pthread_create(&thread_identifierworker,NULL,(void*)thread, NULL);

  /* wait until canceltype is set */
  pthread_cond_wait(&init_mut_sig, &init_mut);
  pthread_mutex_unlock(&init_mut);

  /* do stuff, like data input */
  j = getc(STDINstdin);
  /* since we don't know when the character is read in, the program could continue at any time */
  pthread_cancel(thread_identifierworker);

  /* pthread_join waits for the thread to finish up before continuing */
  pthread_join(thread_identifierworker, 0);

  pthread_mutex_lock(&global_mut);
  printf("a: %i | b: %i", a, b);
  pthread_mutex_unlock(&global_mut);
  /* this will always print either "a: 5 | b: 10" or "a: 10 | b: 5" */

  /* clean up */
  pthread_mutex_destroy(&init_mut);
  pthread_cond_destroy(&init_mut_sig);
}
void thread(void) {
  int i, creturn 0;

  /* set the cancelability flag during mutex. */
  /* this guarantees this block calls after pthread_cond_wait() and, more importantly, before pthread_cancel() */
  pthread_mutex_lock(&mut);
  pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,&i);
  pthread_cond_signal(&mut_sig);
  pthread_mutex_unlock(&mut);

  while (1) {
    c = b;
    b = a;
    a = c;
    /* now we're safe to cancel, creating cancel point */
    pthread_testcancel();
  }
}
}

This code is thread-safe in that it invokes no undefined behavior. However, this program can still create a race condition, because an asynchronous cancel can happen at any time. For instance, the worker thread could be cancelled right before the last line (a = c) and thereby lose the old value of b. Consequently the main thread might print that a and b have the same value.

Compliant Solution

From IEEE standards page:

The cancelability state and type of any newly created threads, including the thread in which main() was first invoked, shall be PTHREAD_CANCEL_ENABLE and PTHREAD_CANCEL_DEFERRED respectively.

Since the default condition according to the IEEE standards for POSIX is PTHREAD_CANCEL_DEFERRED, one would simply not set cancel type for the compliant solution.

However, since not all compilers are necessarily guaranteed to follow standards, you should also explicitly call pthread_setcanceltype() with PTHREAD_CANCEL_DEFERRED. All remaining code is identical to the noncompliant example.

Code Block
bgColor#ccccff

void thread(void) {
  /* ... */
  pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,&i);
  /* ... */
}

Since this code limits cancellation of the worker thread to the end of the while loop, the worker thread can preserve the data invariant that a == b. Consequently, the program might print that a and b are both 5, or they are both 10, but they will always be revealed to have the same value when the worker thread is cancelled.

Risk Assessment

Incorrectly using threads that asynchronously cancel may result in silent corruption, resource leaks and, in the worst case, unpredictable interactions.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

POS47-C

medium

probable

low

P12

L1

Automated Detection

...