In threading, 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 {{initglobal_lock}} mutex is used to ensure that the worker thread isand initializedmain safely,thread becausedo thenot workercollide threadin notifiesaccessing the main thread once it has set its cancellation policy.
{code:bgColor=#ffcccc}
/* We need no mutex on these variables. They are initiailized {{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 managedprints exclusively byout the workercurrent thread.values Finally after the worker thread
is cancelled, they are once again accessed by the main thread. */of {{a}} and {{b}}. Ideally one should be 5 and the other should be 10.
{code:bgColor=#ffcccc}
volatile int a = 5, b = 10;
/* Lock to enable threads workerto access threada toand initializeb safely */
pthread_mutex_t initglobal_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t init_lock_sig = PTHREAD_COND_INITIALIZER;
void* worker_thread(void* dummy) {
int i, c;
int result;
/* Set the cancelability flag while holding init lock. */
if ((result = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,&i)) != 0) {
/* handle error */
}
while (1) {
if ((result = pthread_mutex_lock(&initglobal_lock)) != 0) {
/* Handlehandle error */
}
c = b;
b = a;
a = c;
if ((result = pthread_mutex_setcanceltypeunlock(PTHREAD_CANCEL_ASYNCHRONOUS,&i&global_lock)) != 0) {
/* Handlehandle error */
}
if ((result = pthread_cond_signal(&init_lock_sig)) != 0) {
/* Handle error *//* now we're safe to cancel, creating cancel point */
pthread_testcancel();
}
return NULL;
}
int main(void) {
int result;
pthread_t worker;
if ((result = pthread_mutex_unlockcreate(&init_lock &worker, NULL, worker_thread, NULL)) != 0) {
/* Handlehandle error */
}
while (1) {
c = b;
b = a;
a = c;
/* now we're safe to cancel, creating cancel point/* .. 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_testcancelcancel();
}
return NULL;
}
int main(void) {
int result;
a = 5;
b = 10;
pthread_t worker;
/* Creates thread while init_lock is held. This guarantees that we
don't continue until worker thread is safely initialized. */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(&initglobal_lock)) != 0) {
/* Handlehandle error */
}
printf("a: %i | b: %i", a, b);
if ((result = pthread_createmutex_unlock( &worker, NULL, worker_thread, NULL&global_lock)) != 0) {
/* Handlehandle error */
}
if ((result = pthread_cond_wait( &init_lock_sig, &init_lock)) != 0) {
/* Handle error */
}
if ((result = pthread_mutex_unlock(&init_lock)) != 0) {
/* Handle error */
}
/* .. Do stuff...meanwhile worker thread runs for some time */
/* Cancel worker thread, and wait for it to finish before continuing */
if ((result = pthread_cancel(worker)) != 0) {
/* Handle error */
}
if ((result = pthread_join(worker, 0)) != 0) {
/* Handle error */
}
printf("a: %i | b: %i\n", a, b); /* a should always be different from b */
/* Clean up */
if ((result = pthread_mutex_destroy(&init_lock)) != 0) {
/* Handle error */
}
if ((result = pthread_cond_destroy(&init_lock_sig)) != 0) {
/* Handle error */
}
return 0;
}
{code}
{mc}
For test purposes, the 'Do stuff' section in {{main()}} can just contain {{getc(stdin);}}
{mc}
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.
h2. Compliant Solution
From [IEEE standards page|AA. References#Open Group 04]:
{quote}
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.
{quote}
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:bgColor=#ccccff}
void* worker_thread(void* dummy) {
/* ... */
if ((result = pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,&i)) != 0) {
/* handle error */
}
/* ... */
}
{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}} 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 cancelledreturn 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 {{pthread_cancel()}} call will fail.
h2. Noncompliant Code Example
In this example, the {{worker}} thread arranges to release the {{global_lock}} mutex if it gets interrupted.
{code:bgColor=#ffcccc}
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, 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);
if ((result = pthread_mutex_unlock(&global_lock)) != 0) {
/* handle error */
}
/* now we're safe to cancel, creating cancel point */
pthread_testcancel();
}
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 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.
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, then the {{pthread_cancel()}} call will fail.
Furthermore, while less likely, the program can still deadlock if the worker thread gets cancelled after the {{global_lock}} is acquired but before {{pthread_cleanup_push()}} is invoked. It can also deadlock if the worker thread gets cancelled after the call to {{pthread_cleanup_pop()}} but before the {{global_lock}} is released. In either of these cases, the worker thread is cancelled while still holding {{global_lock}}, and the program will deadlock.
h2. Compliant Solution
From [IEEE standards page|AA. References#Open Group 04]:
{quote}
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.
{quote}
Since the default condition according to the IEEE standards for POSIX is {{PTHREAD_CANCEL_DEFERRED}}, it is not necessary to invoke {{pthread_setcanceltype()}} in the compliant 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}} 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.
The other race conditions that plague the noncompliant code examples are not possible here. Because the worker thread doe snot modify its cancel-type, it may not be cancelled before being improperly initialied. 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]
|