Safeguard
Application Security•

CWE-479 Non-Reentrant Functions in Signal Handlers

Calling malloc or syslog inside a signal handler can corrupt memory when the signal lands mid call. Here is why it happens and the flag pattern that avoids it.

Safeguard Security Team
3 min read

CWE-479, Signal Handler Use of a Non-reentrant Function, describes a signal handler that calls a function which cannot safely be interrupted and called again before the first call finishes. It applies to C and C++ programs, mostly daemons and servers that handle signals such as SIGCHLD or SIGTERM.

How it happens

A signal can arrive at almost any moment, including in the middle of a library call. If the handler then calls the same library, two calls overlap on shared state.

MITRE uses syslog() as an example. It allocates a small amount of memory as scratch space. If it is suspended by a signal and the handler calls syslog() too, the memory used by both calls ends up in an undefined and possibly exploitable state.

malloc() and free() are the more serious case. They keep metadata in global structures to track which memory is in use. Overlapping calls can corrupt that metadata.

// Vulnerable: free() and syslog() are not safe in a handler
void on_signal(int sig) {
    syslog(LOG_INFO, "got signal %d", sig);
    free(session_table);
    exit(0);
}

If the main program was inside malloc() or free() when the signal arrived, the allocator's bookkeeping is now being touched twice.

Two observed cases in the record. CVE-2005-0893: a signal handler calls a function that ultimately uses malloc(). CVE-2004-2259: a SIGCHLD signal to an FTP server can cause a crash under heavy load while it runs non-reentrant functions such as malloc and free.

What an attacker gains

MITRE lists two consequences. First, possible execution of arbitrary code through a write-what-where condition, affecting integrity, confidentiality and availability. Second, memory and application data modification, because signal race conditions often result in data corruption.

How to find it

Static analysis can find some instances by flagging calls to non-reentrant functions reachable from a registered handler. Review each signal() or sigaction() registration and read the handler and everything it calls.

Because the bug depends on timing, tests rarely catch it. That makes review more valuable than testing here.

How to fix it

MITRE gives four steps.

  1. Use languages or libraries that offer reentrant functionality, or that make this weakness easier to avoid.
  2. Design handlers to only set flags rather than do complex work.
  3. Make sure no non-reentrant function appears in a handler.
  4. Use sanity checks to shrink the timing window. This is a partial measure only, since some attacks can still land in a narrower window, even by accident.

The flag pattern looks like this.

static volatile sig_atomic_t got_signal = 0;

void on_signal(int sig) {
    got_signal = 1;          /* only set a flag */
}

int main(void) {
    /* register handler with sigaction() ... */
    for (;;) {
        if (got_signal) {
            got_signal = 0;
            syslog(LOG_INFO, "shutting down");   /* safe: not in handler */
            cleanup();
            break;
        }
        do_work();
    }
}

The handler touches one flag. The main loop does the real work at a point the program controls.

Quick check

Why is the fix to do less in the handler instead of adding locks?

A lock taken in the interrupted code can never be released while the handler waits for it, so the program can deadlock. Setting a flag avoids the problem entirely.

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.