In threading, pthreads have the option of being set 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 in to into the thread to kill it, thus posing similarities problems similar to POS44those in CON37-C. Do not use signals to terminate threadscall signal() in a multithreaded program, which is strongly related to SIG02-C. Avoid using signals to implement normal functionality. These expand POS44-C and SIG02-C expand on the dangers of canceling a thread suddenly as this , 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 two locksone lock. The initglobal_mut mutex is used to ensure lock mutex ensures 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 heldmain 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 | ||||
|---|---|---|---|---|
| ||||
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; /* Lock to enable worker thread to initialize safely */ pthread_mutex_t init_lock = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t init_lock_sig = PTHREAD_COND_INITIALIZER; void cleanup_worker_void* worker_thread(void* dummy) { int resulti; if ((result = pthread_mutex_unlock(&global_lock)) != 0) { /* handle error */ } } void* worker_thread(void* dummy) { int i, c; int result; /* set the cancelability flag during mutex. */ /* this guarantees this block calls after pthread_cond_wait() and, more importantly, before pthread_cancel() */ if ((result = pthread_mutex_lock(&init_lock)) != 0) { /* handle error */ } if ((result = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,&i)) != 0) { /* handle error */ } pthread_cleanup_push( cleanup_worker_thread, NULL); if ((result = pthread_cond_signal(&init_lock_sig)) != 0) { /* handle error */ } if ((result = pthread_mutex_unlock(&init_lock)) != 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 */ } /* now we're safe to cancel, creating cancel point */ pthread_testcancel(); } pthread_cleanup_pop( 1); return NULL; } int main(void) { int result; a = 5; b = 10; pthread_t worker; if ((result = pthread_mutex_lock(&init_lock)) != 0) { /* handle error */ } if ((result = pthread_create( &worker, NULL, worker_thread, NULL)) != 0) { /* handle error */ } /* wait.. until canceltype is set */ 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, like data input */ getc(stdin);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 */ } /* this will always print either "a: 5 | b: 10" or "a: 10 | b: 5" */ /* clean up */ return 0; } |
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 | ||||
|---|---|---|---|---|
| ||||
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_mutex_destroy(&init_locksetcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,&i)) != 0) { /* handle error */ } while (1) { if ((result = pthread_condmutex_destroylock(&initglobal_lock_sig)) != 0) { /* handle error */ } pthread_cleanup_push( return 0release_global_lock, NULL); c = b; b = a; a = c; pthread_cleanup_pop(1); } return NULL; } |
This code is thread-safe in that it invokes no undefined behavior. However, this program can still create 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 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.
Since Because the default condition for POSIX, 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 it is not necessary to invoke pthread_setcanceltype() with PTHREAD_CANCEL_DEFERRED. All remaining code is identical to the noncompliant example. in the compliant solution:
| Code Block | ||||
|---|---|---|---|---|
| ||||
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_setcanceltypeunlock(PTHREAD_CANCEL_DEFERRED,&i&global_lock)) != 0) { /* handle error */ } /* ... */ } now we're safe to cancel, creating cancel point */ pthread_testcancel(); } return NULL; } |
Because 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 are both 5, or they are both 10 is 10 or that a is 10 and b is 5, but they will always be revealed to have the same value when the worker thread is cancelleddifferent 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 |
L1
Automated Detection
...
L3 |
Automated Detection
Tool | Version | Checker | Description | ||||||
|---|---|---|---|---|---|---|---|---|---|
| Astrée |
| bad-macro-use bad-macro-expansion | Supported | ||||||
| Axivion Bauhaus Suite |
| CertC-POS47 | |||||||
| Helix QAC |
| C5035 | |||||||
| Klocwork |
| CERT.POS.THREAD.ASYNC_CANCEL | |||||||
| Parasoft C/C++test |
| CERT_C-POS47-a | The function 'pthread_setcanceltype()' should not be called with 'PTHREAD_CANCEL_ASYNCHRONOUS' argument | ||||||
| PC-lint Plus |
| 586 | Fully supported | ||||||
| Polyspace Bug Finder |
| CERT C: Rule POS47-C | Checks for asynchronously cancellable thread (rule fully covered) | ||||||
| RuleChecker |
| bad-macro-use bad-macro-expansion | Supported |
Bibliography
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Other Languages
Related Guidelines
SEI CERT Oracle Coding Standard for Java: THI05In 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
In Java, similar reasoning resulted in the deprecation of Thread.stop().
References
...
Bibliography
...
| [MKS] |
...
...
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]