Dangers of malloc and printf in C Signal Handlers
In C, asynchronous signal handlers interrupt the normal flow of
program execution at arbitrary points, meaning they can run while any
function in the main execution context is partially complete. Invoking
non-reentrant functions like malloc() or
printf() inside an asynchronous signal handler causes
severe risks, notably deadlocks, memory corruption, and undefined
behavior. Because standard heap allocation and formatted I/O functions
rely on shared global states and internal locks, they violate POSIX
async-signal-safety requirements and must be avoided inside signal
handling routines.
Non-Reentrancy and Async-Signal-Safety
A function is reentrant if it can be interrupted in the middle of its execution and safely called again (reentered) before the previous invocation completes. A subset of reentrant functions is classified by POSIX as "async-signal-safe." These functions are guaranteed to be safe when invoked from an asynchronous signal handler.
Functions like malloc() and printf() are
not async-signal-safe. When a hardware or software signal is delivered,
the operating system pauses the main thread at its current CPU
instruction and transfers control to the registered signal handler. If
the main thread was interrupted while executing an internal section of
malloc() or printf(), the state of those
functions is half-modified.
Why Allocating
Heap Memory (malloc) is Dangerous
Dynamic memory allocation functions—including malloc(),
calloc(), realloc(), and
free()—manage shared metadata structures that track free
and used blocks of memory across the heap.
- Deadlocks: To prevent race conditions in
multi-threaded programs, modern C standard libraries wrap heap
operations with internal mutexes. If the main program calls
malloc()and acquires the allocator lock, and a signal arrives before that lock is released, the signal handler runs on the same thread. If the handler callsmalloc(), it attempts to acquire the exact same lock. Because the lock holder (the main program thread) is suspended waiting for the signal handler to return, the signal handler waits indefinitely for the lock to be released, resulting in an immediate deadlock. - Heap Corruption: In single-threaded builds or
non-locking allocators, calling
malloc()while an allocation is in flight causes the handler to manipulate half-updated pointers in the heap's free lists or chunk headers. This corrupts heap structures, leading to segmentation faults (SIGSEGV) or silent memory corruption that manifests much later.
Why Calling printf
is Dangerous
printf() provides formatted output using the standard
I/O library (stdio), which relies on buffered streams
(FILE *).
- Deadlocks on Stream Locks: Standard I/O streams
maintain thread safety via recursive or non-recursive stream locks (such
as
flockfile()). If a signal interrupts code that is writing tostdoutwhile the stream's lock is held, callingprintf()from within the handler will block on that same lock, permanently freezing the application. - Buffer Inconsistency: Standard I/O maintains an
internal memory buffer to reduce the frequency of system calls. If
printf()is interrupted mid-write, the buffer's write pointers and data indexes are inconsistent. Callingprintf()from the signal handler can overwrite uncommitted output, double-flush buffers, or attempt invalid memory access when calculating offsets, causing garbage output or process crashes.
Safe Alternatives
To safely handle signals in C:
- Flag Setting: The recommended approach is to modify
a flag of type
volatile sig_atomic_t(oratomic_flagin C11) inside the handler and defer all processing, including memory allocation and logging, to the main loop. - Low-Level System Calls: If output is strictly
necessary within a signal handler, use direct, unbuffered,
async-signal-safe system calls such as
write()using file descriptorSTDERR_FILENO(2) instead ofprintf(). - Self-Pipe Trick: Write a single byte over a
non-blocking pipe or
eventfdfrom within the signal handler to wake up aselect(),poll(), orepoll()loop running in the main thread.