Versions Compared

Key

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

...

In

...

threading, pthreads can optionally be set to cancel immediately or defer until a specific cancellation point. Canceling asynchronously (immediately) is dangerous, however, because most threads are in fact not safe to cancel immediately.

The IEEE standards page states that

only functions that are cancel-safe may be called from a thread that is asynchronously cancelable.

Canceling asynchronously would follow the same route as passing a signal into the thread to kill it, posing problems similar to those in CON37-C. Do not call signal() in a multithreaded program, which is strongly related to SIG02-C. Avoid using signals to implement normal functionality. POS44-C and SIG02-C expand on the dangers of canceling a thread suddenly, which can create a data race condition.

Noncompliant Code Example

In this noncompliant code example, the worker thread is doing something as simple as swapping a and b repeatedly.

This code uses one lock. The global_lock mutex ensures that the worker thread and main thread do not collide in accessing the a and b variables.

The worker thread repeatedly exchanges the values of a and b until it is canceled by the main thread. The main thread then prints out the current values of a and b. Ideally, one should be 5, and the other should be 10.

Code Block
bgColor#ffcccc
langc
 pthreads have the option of being set to cancel immediately or defer until a specific cancellation point. Canceling asynchronously (immediately) is dangerous, however, because most threads are in fact not safe to cancel immediately.

The [IEEE standards page|AA. References#Open Group 04] states that:
{quote}only functions that are cancel-safe may be called from a thread that is asynchronously cancelable.{quote}

Canceling asynchronously would follow the same route as passing a signal in to the thread to kill it, thus posing similarities to [POS44-C. Do not use signals to terminate threads|POS44-C. Do not use signals to terminate threads], which is strongly related to [SIG02-C. Avoid using signals to implement normal functionality|SIG02-C. Avoid using signals to implement normal functionality]. These expand on the dangers of canceling a thread suddenly as this can create a [data race condition|BB. Definitions#data race].


h2. Noncompliant Code Example

In this noncompliant code example the {{worker}} thread is doing something as simple as swapping {{a}} and {{b}} repeatedly.

This code uses one lock. The {{global_lock}} mutex is used to ensure that the worker thread and main thread do not collide in accessing the {{a}} and {{b}} variables.

The worker thread repeatedly exchanges the values of {{a}} and {{b}}, until it is cancelled by the main thread. The main thread then prints out the current values of {{a}} and {{b}}. Ideally one should be 5 and the other should be 10.

{code:bgColor=#ffcccc}
volatile int a = 5,;
volatile int b = 10;

/* Lock to enable threads to access a and b safely */
pthread_mutex_t global_lock = PTHREAD_MUTEX_INITIALIZER;

void* worker_thread(void* dummy) {
  int i,;
  int c;
  int result;

  if ((result = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,&i)) != 0) {
    /* handle error */
  }

  while (1) {
    if ((result = pthread_mutex_lock(&global_lock)) != 0) {
      /* handle error */
    }
    c = b;
    b = a;
    a = c;
    if ((result = pthread_mutex_unlock(&global_lock)) != 0) {
      /* handle error */
    }
  }
  return NULL;
}


int main(void) {
  int result;
  pthread_t worker;

  if ((result = pthread_create( &worker, NULL, worker_thread, NULL)) != 0) {
    /* handle error */
  }

  /* .. Do stuff...meanwhile worker thread runs for some time */

  /* since we don't know when the character is read in, the program could continue at any time */
  if ((result = pthread_cancel(worker)) != 0) {
    /* handle error */
  }
  /* pthread_join waits for the thread to finish up before continuing */
  if ((result = pthread_join(worker, 0)) != 0) {
    /* handle error */
  }

  if ((result = pthread_mutex_lock(&global_lock)) != 0) {
    /* handle error */
  }
  printf("a: %i | b: %i", a, b);
  if ((result = pthread_mutex_unlock(&global_lock)) != 0) {
    /* handle error */
  }

  return 0;
}
{code}

{mc}
For test purposes, the 'Do stuff' section in {{main()}} can just contain {{getc(stdin);}}
{mc}

However, this program is subject to a race condition, because an asynchronous cancel can happen at *any* time.  If the worker thread is cancelled while the {{global_lock}} mutex is held, then it is never actually released. In this case the main thread will wait forever trying to acquire the {{global_lock}}, and the program will deadlock

It is also possible that the main thread cancels the worker thread before it has invoked {{pthread_setcanceltype()}}. If this happens, then the cancellation will be delayed until the worker thread calls {{pthread_setcanceltype()}}.

h2. Noncompliant Code Example

In this example, the {{worker}} thread arranges to release the {{global_lock}} mutex if it gets interrupted.

{code:bgColor=#ffcccc}

However, this program is subject to a race condition because an asynchronous cancel can happen at any time. If the worker thread is canceled while the global_lock mutex is held, it is never actually released. In this case, the main thread will wait forever trying to acquire the global_lock, and the program will deadlock.

It is also possible that the main thread cancels the worker thread before it has invoked pthread_setcanceltype(). If this happens, the cancellation will be delayed until the worker thread calls pthread_setcanceltype().

Noncompliant Code Example

In this example, the worker thread arranges to release the global_lock mutex if it gets interrupted:

Code Block
bgColor#ffcccc
langc
void release_global_lock(void* dummy) {
  int result;
  if ((result = pthread_mutex_unlock(&global_lock)) != 0) {
    /* handle error */
  }
}

void* worker_thread(void* dummy) {
  int i,;
  int c;
  int result;

  if ((result = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,&i)) != 0) {
    /* handle error */
  }

  while (1) {
    if ((result = pthread_mutex_lock(&global_lock)) != 0) {
      /* handle error */
    }
    pthread_cleanup_push( release_global_lock, NULL);
    c = b;
    b = a;
    a = c;
    pthread_cleanup_pop(1);
  }
  return NULL;
}
{code}

The

...

global

...

variables

...

are

...

still

...

subject

...

to

...

a

...

race

...

condition

...

because

...

an

...

asynchronous

...

cancel

...

can

...

happen

...

at

...

any

...

time.

...

For

...

instance,

...

the

...

worker

...

thread

...

could

...

be canceled just 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.

The program is still subject to the race condition where the main thread cancels the worker thread before it has invoked pthread_setcanceltype(). If this happens, the cancelation will be delayed until the worker thread calls pthread_setcanceltype().

Furthermore, though less likely, the program can still deadlock if the worker thread gets canceled after the global_lock is acquired but before pthread_cleanup_push()

...

is

...

invoked.

...

In

...

this

...

case,

...

the

...

worker

...

thread

...

is

...

canceled while

...

holding

...

global_lock

...

,

...

and

...

the

...

program

...

will

...

deadlock.

...

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.

...

Because the default condition for POSIX, according to the IEEE standards, is PTHREAD_CANCEL_DEFERRED

...

,

...

it

...

is

...

not

...

necessary

...

to

...

invoke

...

pthread_setcanceltype()

...

in

...

the

...

compliant solution:

Code Block
bgColor#ccccff
langc
 solution.

{code:bgColor=#ccccff}
void* worker_thread(void* dummy) {
  int c;
  int result;

  while (1) {
    if ((result = pthread_mutex_lock(&global_lock)) != 0) {
      /* handle error */
    }
    c = b;
    b = a;
    a = c;
    if ((result = pthread_mutex_unlock(&global_lock)) != 0) {
      /* handle error */
    }

    /* now we're safe to cancel, creating cancel point */
    pthread_testcancel();
  }
  return NULL;
}
{code}

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}} is 5 and {{b}} is 10, or that {{a}} is 10 and {{b}} is 5, but they will always be revealed to have different values when the {{worker}} thread is cancelled.

The other race conditions that plague the noncompliant code examples are not possible here. Because the worker thread does not modify its cancel-type, it may not be cancelled before being improperly initialized.  And since it cannot be cancelled while the {{global_lock}} mutex is held, there is no possibility of deadlock, and the worker thread does not need to register any cleanup handlers.


h2. 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 | {color:#ff0000}{*}P12{*}{color} | {color:#ff0000}{*}L1{*}{color} |

h3. Automated  Detection

TODO

h3. Related  Vulnerabilities

Search for vulnerabilities resulting from the violation of this rule  on the [CERT website|https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+POS47-C].

h3. Other Languages

In Java, similar reasoning resulted in the deprecation of {{Thread.stop()}} and appears in the Java Secure Coding Standard as [CON24-J. Do not use Thread.stop() to terminate threads|java:CON24-J. Do not use Thread.stop() to terminate threads] .

h2. References

\[[MKS|AA. References#MKS]\] [{{pthread_cancel()}} Man Page|http://www.mkssoftware.com/docs/man3/pthread_cancel.3.asp]
\[[Open Group 04|AA. References#Open Group 04]\] [Threads Overview|http://www.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_09.html]

Because 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 is 5 and b is 10 or that a is 10 and b is 5, but they will always be revealed to have different values when the worker thread is canceled.

The other race conditions that plague the noncompliant code examples are not possible here. Because the worker thread does not modify its cancel type, it cannot be canceled before being improperly initialized. And because it cannot be canceled while the global_lock mutex is held, there is no possibility of deadlock, and the worker thread does not need to register any cleanup handlers.

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

Detectable

Repairable

Priority

Level

POS47-C

Medium

Probable

No

No

P4

L3

Automated Detection

Tool

Version

Checker

Description

Astrée
Include Page
Astrée_V
Astrée_V

bad-macro-use

bad-macro-expansion

Supported
Axivion Bauhaus Suite

Include Page
Axivion Bauhaus Suite_V
Axivion Bauhaus Suite_V

CertC-POS47
Helix QAC

Include Page
Helix QAC_V
Helix QAC_V

C5035
Klocwork
Include Page
Klocwork_V
Klocwork_V
CERT.POS.THREAD.ASYNC_CANCEL
Parasoft C/C++test

Include Page
Parasoft_V
Parasoft_V

CERT_C-POS47-a

The function 'pthread_setcanceltype()' should not be called with 'PTHREAD_CANCEL_ASYNCHRONOUS' argument

PC-lint Plus

Include Page
PC-lint Plus_V
PC-lint Plus_V

586

Fully supported

Polyspace Bug Finder

Include Page
Polyspace Bug Finder_V
Polyspace Bug Finder_V

CERT C: Rule POS47-CChecks for asynchronously cancellable thread (rule fully covered)
RuleChecker

Include Page
RuleChecker_V
RuleChecker_V

bad-macro-use

bad-macro-expansion

Supported

Bibliography


Related Vulnerabilities

Search for vulnerabilities resulting from the violation of this rule on the CERT website.

Related Guidelines

SEI CERT Oracle Coding Standard for Java: THI05-J. Do not use Thread.stop() to terminate threads
In Java, similar reasoning resulted in the deprecation of Thread.stop().

Bibliography


...

Image Added Image Added Image Added