Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it always unsafe when I call a non-async-safe function from a signal handler?

I am just figuring out whether I can call a non-async-safe function in a signal handler.
Quotes from Linux man page signal(7):

If a signal interrupts the execution of an unsafe function, and handler calls an unsafe function, then the behavior of the program is undefined.

and TLPI :

SUSv3 notes that all functions not listed in Table 21-1 (list of async-safe functions) are considered to be unsafe with respect to signals, but points out that a function is unsafe only when invocation of a signal handler interrupts the execution of an unsafe function, and the handler itself also calls an unsafe function.

My interpretation of above quotes is that it's safe to call a non-async-safe function from a signal handler only if the signal handler did not interrupt a non-async-safe function.

For example I install a handler for SIGINT which calls an unsafe function suppose to be crypt(3) which is non-reentrant namely unsafe.

sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sa.sa_handler = handler;
sigaction(SIGINT, &sa, NULL);

And I also call printf() in an infinite loop in main(), and I only have the main thread running.

The question is for this simple example, I don't see any bad things happen when the handler interrupted the execution of printf() and it calls an unsafe function. AFAK, printf() will acquire a console lock and has an internal buffer to perform buffered I/O, but its state is consistent in this example. And although crypt() returns a statically allocated string, but it doesn't shared with other function or threads.

Am I misunderstanding something? I want someone to clarify me that is it always unsafe to have a signal handler interrupted the execution of an unsafe function in main program and itself also calls an unsafe function or it is safe to do so in some situation (e.g the simple example above)?

like image 688
StrikeW Avatar asked Aug 24 '15 15:08

StrikeW


People also ask

What is safe to do in a signal handler?

Call only asynchronous-safe functions within signal handlers. For strictly conforming programs, only the C standard library functions abort() , _Exit() , quick_exit() , and signal() can be safely called from within a signal handler.

Why is printf not async-signal-safe?

printf is non-async-signal-safe because, as you describe, it ends up manipulating global state without synchronisation. For added fun, it's not necessarily re-entrant. In your example, the signal might be handled while the first printf is running, and the second printf could mess up the state of the first call.

Is Waitpid async-signal-safe?

@AndrewHenle: Both wait() and waitpid() are listed as async-signal safe in the POSIX description of Signal Concepts.

Is Exit async-signal-safe?

A list of async-signal-safe functions is documented in man 7 signal-safety . The close() function is safe, while free() and phtread_join() are not. The exit() function is also not safe to call from a signal handler, if you wish to exit from such context you will have to do so using _exit() instead.


2 Answers

Yes indeed, it is unsafe to call a non-async-signal-safe function from inside a signal handler in all cases (unless you dive into your implementation code -e.g. libc and perhaps your compiler generating code for it); then you could perhaps prove that calling such an such function is in fact safe; but such a proof might be impossible or take months or years of your time, even with the help of static analyzers à la Frama-C, ... and require studying all the implementation details.

Concretely, it is probable that crypt is internally calling malloc (for some internal data, etc...). And the standard malloc function has obviously some global state (e.g. the vector of linked lists of buckets related to previously free-d memory zone, to be reused by future calls to malloc).

Remember that Unix signals can occur at every machine instruction (not only at C sequence points, which have some well defined semantics) in user space (a system call is then a single SYSENTER instruction). With bad luck, a signal might occur in the few machine instructions which are updating the global state of malloc. Then future calls to malloc -e.g. indirectly from your signal handler, might break havoc (i.e. undefined behavior). Such a misfortune might be unlikely (but evaluating its probability is practically impossible), but you should code against it.

Details are heavily implementation specific, depending on your compiler and optimization flags, libc, kernel, processor architecture, etc...

You might not care about async-signal-safe functions, by betting that disaster won't happen. This might be acceptable for debugging purposes (e.g. very often, but not always, a printf inside a signal handler would practically work most of the time; and the GCC compiler is internally using its "async-unsafe" libbacktrace library in signal handlers) for code wrapped with #ifndef NDEBUG, but it won't be good for production code; if you really have to add such code in a handler, mention in a comment that you know that you are doing wrongly a call to a non-async-signal-safe function, and be prepared to be cursed by future colleagues working on the same code base.

A typical trick to handle such situations is to simply set a volatile sig_atomic_t flag in the signal handler (read POSIX signal.h documentation) and check that flag in some safe place in some loop -outside of the handler-, or to write(2) one -or a few- bytes to a pipe(7) previously setup at application initialization, and have the read end of that pipe be periodically poll(2)-ed and later read by your event loop -or some other thread-).

(I've taken malloc as an example, but you could think of other widely used non-async-signal-safe functions, or even implementation specific routines, e.g. 64 bits arithmetic on a 32 bits processor, etc...).

like image 105
Basile Starynkevitch Avatar answered Sep 23 '22 03:09

Basile Starynkevitch


It is unsafe to call any async-unsafe function in a signal handler if the signal interrupts any async-unsafe function in the main program. The async-unsafe functions don't need to have anything to do with one another -- the results are undefined.

So pretty much the only way to safely call async-unsafe functions in a signal handler is to ensure that the signal can never occur when calling an aysnc-unsafe function. One way to do that is to wrap every call to any async-unsafe function with the appropriate sigblock/sigsetmask calls to ensure the signals won't be delivered while the unsafe function runs. Another is to have the main program set/clear a sigatomic flag when it calls async-unsafe functions, and have the signal handler check that flag before trying to call async-unsafe functions.

Things can be a bit better with synchronous signals (things like SIGFPE and SIGSEGV), as there you may be able to ensure that async-unsafe functions never trigger these signals and you don't allow (or care about) being sent these signals asynchronously with kill. This requires some care however -- if you have a signal handler for SIGSEGV that catches writes to write-protected memory, you need to ensure that you never pass write-protected memory to an async-unsafe function in a way where it might trigger your handler.

like image 25
Chris Dodd Avatar answered Sep 22 '22 03:09

Chris Dodd