CWE-663 is about calling a non-reentrant function where something else might call it, or change its state, at the same time. The competing code might be another thread or a signal handler. The record lists C as a platform, and the problem is most familiar in C and C++ programs with signal handlers, daemons and multithreaded servers.
How it happens
A reentrant function can be interrupted partway through and called again safely. A non-reentrant one cannot, usually because it keeps state in a static buffer or global variable, or because it uses a lock it already holds. If a signal arrives while the function is running and the handler calls it again, the second call walks over the first call's state.
Memory allocation is a well known case. If the main program is in the middle of malloc or free and a signal handler calls them too, the allocator's internal structures can be left inconsistent.
/* Vulnerable: free() is not safe to call from a signal handler */
void on_child_exit(int sig) {
free(global_buf); /* main code may be inside malloc/free right now */
global_buf = NULL;
}
The record lists two examples:
- CVE-2001-1349: unsafe calls to library functions from a signal handler.
- CVE-2004-2259: a SIGCHLD signal to an FTP server can cause a crash under heavy load while executing non-reentrant functions like malloc and free.
What an attacker gains
MITRE lists integrity and confidentiality impact: modifying memory, reading memory, modifying or reading application data, and altering execution logic. The FTP server example above is a crash under load, so denial of service is a realistic outcome in practice.
How to find it
The record lists automated static analysis (SAST) as a way to find some instances. Many tools carry a list of functions that are unsafe in signal handlers. Beyond that:
- Read every signal handler and list the functions it calls.
- Compare that list with your platform's documented async-signal-safe functions.
- For threaded code, look for functions with static buffers, such as older string tokenizers and time formatting calls, and check whether a reentrant variant exists.
- Stress test under load, since timing bugs hide in light traffic.
How to fix it
MITRE's mitigations:
- Use reentrant functions if available.
- Add synchronization to your non-reentrant function.
- In Java, use the ReentrantLock class.
For signal handlers, a common safe pattern is to do almost nothing in the handler: set a flag and let the main loop do the real work.
#include <signal.h>
volatile sig_atomic_t child_exited = 0;
void on_child_exit(int sig) { child_exited = 1; } /* only set a flag */
/* in the main loop */
if (child_exited) { child_exited = 0; free(global_buf); global_buf = NULL; }
Quick check
What makes a function non-reentrant?
Typically it relies on shared state, such as a static buffer or global variable, so a second call that overlaps the first can corrupt it.
What is the safest way to handle a signal?
Keep the handler minimal. Set a flag and do the real work outside the handler.