Safeguard
Application Security•

CWE-122 Heap-based Buffer Overflow: How Chains of Small Bugs Cause It

Heap overflows often start with an integer or length bug upstream. See how the chain works, what attackers gain, and how to test and fix for CWE-122.

Safeguard Security Team
5 min read

CWE-122 is the heap-based buffer overflow. MITRE describes it as a buffer overflow where the buffer that can be overwritten is allocated in the heap portion of memory, generally with a routine such as malloc(). It is a variant of the classic overflow in CWE-120.

It matters to anyone who writes or depends on C and C++ code that allocates memory dynamically. That covers browsers, JavaScript engines, image and font libraries, databases and most network software. The platform list on MITRE's page is memory-unsafe languages, C and C++.

How it happens

On the stack, a buffer has a size known at compile time. On the heap, the size is usually computed at run time, often from input. That is where trouble starts, because many heap overflows are the last link in a chain of smaller mistakes.

Look at the observed examples on MITRE's page. Each one is a chain.

  • CVE-2007-4268: an integer signedness error passes a signed comparison and leads to a heap overflow.
  • CVE-2009-2523: the product does not handle an input string that is not NULL terminated, leading to a buffer over-read or a heap-based overflow.
  • CVE-2021-43537: in a web browser, an unsigned 64-bit integer is forcibly cast to a 32-bit integer, and potentially leading to an integer overflow.
  • CVE-2025-46687: a JavaScript engine skips a length check, an integer overflow causes a smaller buffer than expected to be allocated, and a heap overflow follows.

The pattern is a size calculation that goes wrong, followed by a copy that trusts the original size. In code it looks like this.

// Vulnerable: count * size can wrap around
uint32_t count = read_u32(pkt);
uint32_t size  = 16;
uint8_t *buf = malloc(count * size);   // may be tiny after wraparound
for (uint32_t i = 0; i < count; i++) {
    read_record(pkt, buf + i * size);  // writes far past the allocation
}

If count is large enough, the multiplication wraps, malloc returns a small block, and the loop writes the full amount anyway. The related weaknesses are CWE-131 for the wrong buffer size and CWE-128 for wrap-around.

What an attacker gains

MITRE lists these consequences.

  • Crashes. Overflows generally lead to crashes, and they can also cause infinite loops and heavy CPU or memory use.
  • Code execution. Overflows can often be used to execute arbitrary code, outside the program's implicit security policy. Besides important user data, heap memory can hold function pointers that may be overwritten to redirect execution.
  • Defeating other protections. When the result is arbitrary code execution, it can usually subvert any other security service.

Heap corruption can also be less direct. An attacker may only damage nearby data at first, then use that damage to gain a more powerful ability later.

How to find it

  • Fuzzing. MITRE calls fuzzing a powerful technique for producing large numbers of diverse inputs. Structured fuzzers that understand a file or protocol format reach deeper into parsers where heap overflows hide.
  • Runtime memory checks. Build with AddressSanitizer for C and C++ so heap overflows are caught at the moment they happen, with a report showing where the buffer was allocated.
  • Review of size arithmetic. Search for malloc, calloc, realloc and new[] calls where the size is computed from external values. Ask whether the multiplication or addition can wrap, and whether the copy loop uses the same value.

How to fix it

The record carries no mitigation list. These steps follow from the chains above.

  1. Check arithmetic before allocating. Compare operands against the maximum before multiplying, or use a compiler built-in such as __builtin_mul_overflow (GCC and Clang) or a checked multiplication helper.
  2. Use one variable for the allocation size and the copy bound, so they cannot drift apart.
  3. Be careful with casts between signed and unsigned types and between 64-bit and 32-bit integers.
  4. Do not assume input strings are NULL terminated. Carry the length with the data.
  5. Cap sizes taken from untrusted headers at a sane maximum.
// Fixed: overflow checked, one size drives everything
uint32_t count = read_u32(pkt);
size_t total;
if (count > MAX_RECORDS || __builtin_mul_overflow((size_t)count, 16, &total)) {
    return -1;
}
uint8_t *buf = malloc(total);
if (!buf) return -1;
for (uint32_t i = 0; i < count; i++) {
    read_record(pkt, buf + (size_t)i * 16);
}

Heap bugs in the images you ship

Many heap overflows reach you through a font, image or compression library inside a container or a package. The image may contain a vulnerable copy that nobody on your team wrote. Container scanning inventories those components so advisories can be matched to them.

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

Quick check

Why are heap overflows often linked to integer bugs?

Heap buffer sizes are usually computed at run time. If the calculation wraps or is truncated, the allocation is too small while the copy still uses the original, larger count.

Is a heap overflow always remote code execution?

No. MITRE lists crashes as the general outcome, with code execution possible when the attacker can control the corrupted memory. Treat it as serious either way.

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.