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

2

8

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)?

Janijania answered 24/8, 2015 at 15:14 Comment(5)
Unsafe doesn't mean you'll always get burned.Np
As long as the standard says it's unsafe, it's unsafe. We don't know if it always will be so. "Unsafe" mean "bad things may happen", not "...will happen".Pulverulent
What kind of application are you coding? Is it a critical embedded software or a useless game demo?Entity
@BasileStarynkevitch Actually I am writing a utility function likes the backtrace command in gdb that will print the call stack, and it will be called from a SIGSEGV handler. It's a OS course project.Janijania
This related question might be of interest #14422451 .Whereof
E
8

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...).

Entity answered 24/8, 2015 at 15:25 Comment(12)
"No, ..." should read "Yes, ..." shouldn't it? At least if the reference is defaulting to be the 1st, the title's question. :-SElsie
@alk: I corrected to "Yes indeed". Sorry, English is not my native language.Entity
@BasileStarynkevitch: Wouldn't it be safe for the signal handler to call a non-async-safe function in an application in which every non-async-safe function were protected by masking the appropriate signal (perhaps using wrapper functions)? Just playing devil's advocate here.Reindeer
@rici: Because many non-async-safe are system functions. You won't want to patch malloc for example.Entity
@BasileStarynkevitch: I didn't say I'd want to do it :) Just that it could be possible. (And wrapping doesn't mean patching, at least in my usage. I have often wrapped malloc, for one reason or another -- usually error detection -- and I think xmalloc is a pretty common idiom.) Certainly, it would require wrapping a lot of functions...Reindeer
My point is that wrapping malloc is not enough. You'll need to ensure that the system malloc is not interfering, so you'll need change it.Entity
In your backtrace debugging case, just accept to use wrongly async signal unsafe functions, and document that you know that it is in principle wrong.Entity
@BasileStarynkevitch: Any library function which calls malloc must be on the list of non-async-safe functions, since it obviously cannot be async-safe. So if I wrap every non-async-safe function, I must have covered every use of malloc. (Granted, that means I need to extend the wrap every external library function as well, unless I have some way to know that such a function is async-signal-safe. But perhaps my app only uses posix functions.)Reindeer
But then you have rewritten the entire libcEntity
@BasileStarynkevitch: I haven't rewritten anything. All I've done is add simple wrapper functions to my code. I could even wrap call sites with a preprocessor macro, now that variadic macros are available.Reindeer
I don't know if it's entirely correct to say that "signals can occur at every machine instruction". Signals can occur at any machine instruction running in userspace, but a signal cannot interrupt a system call in kernel space, unless the kernel explicitly allows it. Imagine if a userspace signal handler could corrupt the state of the kernel... yikes. But in general I agree that this answer is correct - it is unsafe to call a non-async-safe function from a signal handler without some heavy, heavy safeguards.Verst
From user space, a system call is a single machine instruction (e.g. SYSENTER)Entity
P
5

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.

Pino answered 24/8, 2015 at 18:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.