Calling the signal()
function in a multithreaded program is undefined behavior. (See undefined behavior 135.)
Noncompliant Code Example
This noncompliant code example invokes the signal()
function from a multithreaded program:
#include <signal.h> #include <stddef.h> #include <threads.h> volatile sig_atomic_t flag = 0; void handler(int signum) { flag = 1; } /* Runs until user sends SIGUSR1 */ int func(void *data) { while (!flag) { /* ... */ } return 0; } int main(void) { signal(SIGUSR1, handler); /* Undefined behavior */ thrd_t tid; if (thrd_success != thrd_create(&tid, func, NULL)) { /* Handle error */ } /* ... */ return 0; }
NOTE: The SIGUSR1
signal value is not defined in the C Standard; consequently, this is not a C-compliant code example.
Compliant Solution
This compliant solution uses an object of type atomic_bool
to indicate when the child thread should terminate its loop:
#include <stdatomic.h> #include <stdbool.h> #include <stddef.h> #include <threads.h> atomic_bool flag = ATOMIC_VAR_INIT(false); int func(void *data) { while (!flag) { /* ... */ } return 0; } int main(void) { thrd_t tid; if (thrd_success != thrd_create(&tid, func, NULL)) { /* Handle error */ } /* ... */ /* Set flag when done */ flag = true; return 0; }
Exceptions
CON37-C-EX1: Implementations such as POSIX that provide defined behavior when multithreaded programs use custom signal handlers are exempt from this rule [IEEE Std 1003.1-2013].
Risk Assessment
Mixing signals and threads causes undefined behavior.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
CON37-C | Low | Probable | Low | P6 | L2 |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Automated Detection
Tool | Version | Checker | Description |
---|---|---|---|
Astrée | 24.04 | stdlib-use-signal | Fully checked |
CodeSonar | 8.1p0 | BADFUNC.SIGNAL | Use of signal |
Coverity | 2017.07 | MISRA C 2012 Rule 21.5 | Over-constraining |
Cppcheck Premium | 24.9.0 | premium-cert-con37-c | Fully implemented |
Helix QAC | 2024.3 | C5021 C++5022 | |
Klocwork | 2024.3 | MISRA.STDLIB.SIGNAL | |
LDRA tool suite | 9.7.1 | 44 S | Enhanced enforcement |
Parasoft C/C++test | 2023.1 | CERT_C-CON37-a | The signal handling facilities of <signal.h> shall not be used |
PC-lint Plus | 1.4 | 586 | Fully supported |
Polyspace Bug Finder | R2024a | CERT C: Rule CON37-C | Checks for signal call in multithreaded program (rule fully covered) |
RuleChecker | 24.04 | stdlib-use-signal | Fully checked |
Bibliography
[IEEE Std 1003.1-2013] | XSH 2.9.1, "Thread Safety" |
13 Comments
Geoff Clare
This appears to be advocating asynchronous cancellation for general use, whereas it can only be used safely in very limited circumstances. POSIX only requires three functions (
pthread_cancel()
,pthread_setcancelstate()
, andpthread_setcanceltype()
) to be async-cancel safe. A conforming application cannot call any other standard functions while the calling thread has asynchronous cancellation enabled.Robert Seacord
This is rather vague:
If the cancellation type is set to asynchronous, the thread is terminated immediately, however in most cases it is not safe to do so, and should be generally avoided.
Geoff provided some good information, why don't you incorporate it?
Frank Martinez
Is the issue in the first example the call to "pthread_kill()", the use of "SIGKILL", or both?
David Svoboda
I'd say that sending KILL is the primary problem. I'm not sure what the effect is of sending signals other than SIGKILL to individual threads...presumably POSIX defines these cases.
Geoff Clare
The point of this rule is that an uncaught signal terminates the whole process, not just the selected thread. Using SIGKILL in the example confuses things because of its uncatchablility, and distracts from the main point. So I have changed the example to use SIGTERM. I also noticed the code comment after the
pthread_kill()
call implied that the process terminates some time after pthread_kill() returns, whereas in factpthread_kill()
will not return, and I have changed the comment.Geoff Clare
"Do not use signals in multithreaded programs. This is undefined behavior in C11 (Section 7.14.1.1, paragraph 7)."
False. 7.14.1.1 para 7 says use of the signal() function is undefined behavior, not use of signals.
I think moving this from POSIX to CON was a mistake, since the whole point of the rule is all tied up with behaviour relating to signals that is required by POSIX but not by the C Standard. A non-POSIX implementation where raise(SIGTERM) terminates the calling thread and does not terminate the process would actually be allowed by the C Standard.
I would recommend moving this back and undoing all the recent changes, then adding a rule to CON which says don't use the signal() function in multithreaded programs.
David Svoboda
I took this advice; this is now a distinct rule from POS44-C. Do not use signals to terminate threads
Robert Seacord
But it would be nice to have a specific reference to the POSIX exception, so people can see where this exception is given instead of just believing us.
Robert Seacord (Manager)
comment submitted by email:
I just read CON37-C, which instructs not to use signal() in a multithreaded
environment, since C11 does not specify how signals are supposed to be dispatched among threads.
Say I have an application with a pool of C11 threads reading from a single POSIX message queue. This queue is filled with commands coming from various other applications (a web server, ad-hoc TCP connections to automated command senders, etc.).
If the queue is blocking, threads will block on
mq_receive()
and cannot be told to exit cleanly: only one thread will receive theTERMINATE
command from the message queue. The others will still be blocked onmq_receive()
, becausemq_close() + mq_unlink()
do not let other threads blocked onmq_receive()
to return.If the queue is non-blocking, I would use a conditional variable to wait for a message, and send a single signal when a message arrives. One waiting thread will be woken up and call
mq_receive().
The question is, how to be notified when a message arrives? mqueue.h has mq_notify(), which can deliver a notification either by calling a signal handler or by creating a new thread. This sounds promising: a "main" thread could launch all receiving threads, and then call
mq_notify()
which will launch a signalling thread when a message arrives.However,
mq_notify()
requires the use of a structure defined insignal.h
. Is this a sign thatmq_notify()
should not be used with C11 threads? Or is this just a coincidence to be ignored?More generally, is the "threadpool" pattern appropriate here? Should I rather spawn one thread per message, or does the overhead associated with thread creation prevent scalability?
David Keaton
Very good points. Obviously a lot of thought went into that comment.
It turns out there is a simpler solution. POSIX advises that software developers abandon signal() in favor of sigaction() because the latter is specially designed to work well with threads.
Geoff Clare
You are right that POSIX applications should use sigaction() instead of signal(), but the reason you give is wrong. From 1988 to 2000, POSIX did not require implementations to provide signal() at all. If conforming POSIX applications wanted to handle signals they had to use sigaction(). Threads were added to POSIX in 1995; signal() was not added until 2001 (via the merge with the Single UNIX Specification). So threads had nothing to do with the preference for sigaction() over signal(). Furthermore, in multithreaded programs sigwait(), which was added as part of the threads amendment, is preferred over sigaction().
David Svoboda
This rule cites the C11 standard which should make clear that usage of signal() with multiple threads is Undefined Behavior...the platform is free to do anything in such cases.
Other standards, like POSIX, impose greater constraints. If your program is only going to run on POSIX systems, then your program might be well-defined (by the POSIX standard, not by C11).
We have one rule: POS44-C. Do not use signals to terminate threads to address signals & pthreads. (perhaps we need more)
Kévin Le Gouguec
(Hi, this is the author of the email comment who just figured out how to sign up)
The motivation behind my comment was to find a way to manage a threadpool using only C11 threading primitives. This is probably an exercise in futility since I'm using POSIX message queues anyway, but that explains why POS44-C did not provide a satisfying answer (it solves the problem by using
pthread_cancel()
, which has no equivalent in C11).I eventually went with
mq_notify()
'sSIGEV_THREAD
option.mqueue.h
actually providesstruct sigevent
(by includingbits/siginfo.h
), sosignal.h
is not needed, contrary to what I originally thought.Since in terms of features, I see
pthread.h
as a superset ofthreads.h
, the bottomline is that POS44-C has another solution which does not involvepthread_cancel()
(though it relies on threads watching the termination predicate and returning of their own accord, so this might not count as "terminating").