Safeguard
Application Security•

CWE-590 Free of Memory Not on the Heap: Causes and Fixes

Calling free() on a stack, global, or interior pointer corrupts the allocator and can give attackers a write primitive. See how it happens and how to stop it.

Safeguard Security Team
3 min read

CWE-590 is what you get when a C program calls free() on a pointer that never came from malloc(), calloc(), or realloc(). The pointer might refer to a stack variable, a global, or the middle of a heap block. The allocator does not know the difference until it is too late.

It affects memory-unsafe code, in practice C, and the parsers and format handlers that juggle buffers of changing size.

How it happens

Buffers get reused. A function starts with a small local array, and when input grows it switches to heap memory. Cleanup code later frees "the buffer" without remembering which kind it holds.

/* Vulnerable pattern */
char local[64];
char *buf = local;
if (needed > sizeof local) {
    buf = malloc(needed);
}
/* ... use buf ... */
free(buf);   /* frees the stack array when no malloc happened */

MITRE's observed example follows this story: CVE-2023-22291 is described as a word processor parser that attempts to free a stack pointer when intending to expand memory to hold a larger buffer.

What an attacker gains

When free() is called on an invalid pointer, the program's memory management data structures may become corrupted. The crash is the mild outcome. MITRE notes that an attacker may be able to cause free() to operate on controllable memory locations to modify critical program variables or execute code, a "write, what where" primitive. That means arbitrary code execution with the privileges of the vulnerable program.

How to find it

The record lists three methods.

  • Fuzzing, which feeds large numbers of diverse inputs and watches for crashes and memory corruption.
  • Static analysis (SAST), which can find some instances by modelling data flow in source or compiled code.
  • Dynamic analysis with compile-time instrumentation such as AddressSanitizer or valgrind, which reports an invalid free the moment it happens. Crafted inputs are still needed to reach the code.

How to fix it

  1. Only free pointers that you called malloc on previously. MITRE calls this the recommended solution.
  2. Track which pointers point at the beginning of valid chunks, and free them only once.
  3. Before freeing, make sure the pointer was allocated on the heap and that the memory belongs to you.
  4. Where possible use a vetted library or a language with memory management abstractions. MITRE notes that glibc on Linux provides some protection against freeing invalid pointers.
/* Safer: remember who owns the memory */
char local[64];
char *buf = local;
int heap = 0;
if (needed > sizeof local) {
    buf = malloc(needed);
    if (buf == NULL) return -1;
    heap = 1;
}
/* ... use buf ... */
if (heap) free(buf);

Try it yourself

Run any C file you maintain through a static scanner, then build the same code with AddressSanitizer and run its tests. The two views catch different things.

Click through it at your own pace, or open it full screen.

Quick check

Why is freeing a stack pointer worse than a plain crash? It can corrupt allocator data structures, and an attacker who controls the memory may turn that into a write primitive and code execution.

What is the simplest rule that prevents this? Only free pointers you previously obtained from malloc, calloc, or realloc, and free each one once.

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.