Safeguard
Application Security•

CWE-663 Non-Reentrant Functions in Concurrent Code and Signals

Calling a function that keeps internal state from a signal handler or a second thread can corrupt memory or crash the program. Use reentrant calls or add locking.

Safeguard Security Team
3 min read

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:

  1. Read every signal handler and list the functions it calls.
  2. Compare that list with your platform's documented async-signal-safe functions.
  3. 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.
  4. Stress test under load, since timing bugs hide in light traffic.

How to fix it

MITRE's mitigations:

  1. Use reentrant functions if available.
  2. Add synchronization to your non-reentrant function.
  3. 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.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.