Back to Blog
critical SEVERITY7 min read

How heap buffer overflow happens in C dupstr() and how to fix it

A critical heap buffer overflow vulnerability was discovered in the `dupstr()` function inside `tools/strliteral.c`, where `strcpy()` was called on a heap-allocated buffer without first verifying that `malloc()` had succeeded. If `malloc()` returned `NULL`, the subsequent `strcpy()` would write into address zero — corrupting memory and potentially enabling arbitrary code execution. The fix replaces the unsafe `strcpy()` call with a `NULL` check followed by a bounds-safe `memcpy()`, closing the v

O
By Orbis AppSec
Published July 5, 2026Reviewed July 5, 2026

Answer Summary

This is a heap buffer overflow vulnerability (CWE-122) in the C function `dupstr()` located in `tools/strliteral.c`. The root cause is that `strcpy(dup, str)` was called immediately after `malloc()` without checking whether `malloc()` returned `NULL`, meaning a failed allocation could cause a write to address zero or heap metadata corruption. The fix adds a `NULL` guard after `malloc()` and replaces `strcpy()` with `memcpy(dup, str, len + 1)`, which copies exactly the right number of bytes and cannot overrun the allocation. This is a standard C memory-safety pattern recommended by CERT C and OWASP.

Vulnerability at a Glance

cweCWE-122
fixAdded NULL guard after malloc() and replaced strcpy() with memcpy()
riskHeap corruption leading to potential arbitrary code execution
languageC
root causestrcpy() called on malloc() return value without NULL check
vulnerabilityHeap Buffer Overflow via unchecked strcpy() after malloc()

How heap buffer overflow happens in C dupstr() and how to fix it

Introduction

The tools/strliteral.c file is a utility used during the build process to convert raw string literals into C identifiers and escaped representations. It's the kind of file that rarely gets a second look — small, unglamorous, and seemingly low-risk. But buried inside its dupstr() function at line 19 was a critical heap buffer overflow: strcpy() was called on the return value of malloc() without ever checking whether the allocation had actually succeeded.

This is a deceptively simple mistake that has caused real-world exploits for decades. The C standard is explicit: if malloc() fails, it returns NULL. Writing through a NULL pointer is undefined behavior — and in practice, on modern systems, it can corrupt heap metadata, crash the process, or in adversarial conditions, enable an attacker to redirect control flow.


The Vulnerability Explained

Here is the vulnerable version of dupstr():

static char *dupstr(char *str) {
  size_t len = strlen(str);
  char *dup = malloc(len + 1);
  strcpy(dup, str);   // ← line 22: no NULL check before this write
  dup[len] = '\0';
  return dup;
}

The logic looks reasonable at first glance: calculate the string length, allocate exactly enough space, copy the string in, null-terminate it, return the pointer. The problem is in the ordering and the absence of a guard.

What happens when malloc() fails?

malloc() returns NULL when the system cannot satisfy the allocation request — under memory pressure, in resource-constrained environments, or when a fuzzer or attacker deliberately exhausts the heap. The code does not check for this. It passes NULL directly to strcpy():

strcpy(NULL, str);  // undefined behavior — writes to address 0x0

On most modern operating systems, address 0x0 is not mapped, so this immediately segfaults. But heap allocators are complex. Depending on the allocator implementation (glibc, jemalloc, tcmalloc), a NULL or near-NULL write can silently corrupt allocator bookkeeping structures — free-list pointers, chunk size fields, or canary values — setting up a classic heap exploitation primitive.

The redundant null-termination compounds the issue

Notice that after strcpy(dup, str), there is a second line:

dup[len] = '\0';

strcpy() already writes the null terminator. This line is redundant — and it also dereferences dup a second time without a NULL check, doubling the exposure.

Exploitation scenario

An attacker who controls the input to strliteral.c (via command-line arguments or a crafted input file processed during the build) could provide a string designed to trigger a malloc() failure — for example, by supplying an extremely long string that exhausts available heap memory before dupstr() is called. The resulting strcpy(NULL, ...) corrupts heap metadata. In a more sophisticated scenario targeting a persistent process, this could be chained into arbitrary code execution by overwriting a function pointer stored in a heap-adjacent allocation.


The Fix

The fix makes two targeted changes to dupstr():

Before:

static char *dupstr(char *str) {
  size_t len = strlen(str);
  char *dup = malloc(len + 1);
  strcpy(dup, str);
  dup[len] = '\0';
  return dup;
}

After:

static char *dupstr(char *str) {
  size_t len = strlen(str);
  char *dup = malloc(len + 1);
  if (dup == NULL) {
    return NULL;
  }
  memcpy(dup, str, len + 1);
  return dup;
}

Change 1: NULL guard after malloc()

if (dup == NULL) {
  return NULL;
}

This is the most critical change. Before touching dup for any write operation, the code now verifies that malloc() succeeded. If it did not, the function returns NULL immediately, propagating the allocation failure to the caller rather than writing through a null pointer. This is the standard CERT C pattern (MEM32-C: Detect and handle memory allocation errors).

Change 2: strcpy() replaced with memcpy()

memcpy(dup, str, len + 1);

strcpy() is replaced with memcpy(dup, str, len + 1). Since len was already computed as strlen(str), copying len + 1 bytes copies the string content plus its null terminator in a single, length-bounded operation. memcpy() does not scan for a null terminator and does not write beyond the specified count — it is strictly bounded by the third argument. The redundant dup[len] = '\0' line is also removed, since memcpy() already copies the terminator as part of the len + 1 byte range.

This change eliminates both the unbounded copy risk and the redundant write, making the function's memory behavior fully explicit and auditable.


Prevention & Best Practices

1. Always check malloc() return values

In C, every call to malloc(), calloc(), or realloc() can return NULL. Treat this as a contract: if you do not check the return value, you do not own the pointer.

// ❌ Wrong
char *buf = malloc(size);
strcpy(buf, input);

// ✅ Correct
char *buf = malloc(size);
if (buf == NULL) return NULL;
memcpy(buf, input, size);

2. Prefer memcpy() over strcpy() when length is already known

If you have already called strlen() to compute the length, use that information. memcpy() with an explicit byte count is both safer and faster than strcpy(), which must scan for the null terminator at runtime.

3. Use compiler and sanitizer tooling

  • AddressSanitizer (ASan): Compile with -fsanitize=address to catch heap buffer overflows at runtime during testing.
  • UndefinedBehaviorSanitizer (UBSan): Compile with -fsanitize=undefined to catch NULL pointer dereferences.
  • Valgrind: Run test suites under Valgrind to detect invalid memory accesses.
  • clang-analyzer / cppcheck: Static analysis tools that flag unchecked malloc() return values.

4. Adopt safer string APIs

Where possible, use strlcpy() (BSD/macOS) or strncpy() with explicit length limits. For new code, consider using snprintf() for formatted string construction, which always null-terminates within the specified buffer size.

5. Apply CERT C guidelines

CERT C Secure Coding Standard rule MEM32-C specifically addresses this pattern: Detect and handle memory allocation errors. Integrating CERT C rules into code review checklists prevents this class of bug from reaching production.

Relevant standards

  • CWE-122: Heap-based Buffer Overflow
  • CWE-476: NULL Pointer Dereference
  • OWASP: Buffer Overflow
  • CERT C: MEM32-C, STR31-C

Key Takeaways

  • strcpy() after malloc() without a NULL check is always a bug — even in small utility functions like dupstr() that appear low-risk.
  • The redundant dup[len] = '\0' after strcpy() was a code smell that signaled the author was uncertain about the copy behavior, which should prompt a closer review of the surrounding logic.
  • memcpy() with a pre-computed length is the correct replacement for strcpy() when the string length is already known — it is bounded, explicit, and does not re-scan for the null terminator.
  • Build-time tools are not exempt from security reviewtools/strliteral.c is a production file processed during the build pipeline, and a compromised build tool can be as dangerous as a compromised runtime binary.
  • A single missing NULL check can turn a correct-looking allocation into a heap corruption primitive — the fix is two lines, but the impact of the original bug was critical severity.

How Orbis AppSec Detected This

  • Source: User-controlled string input passed to dupstr() via command-line arguments or input files processed by tools/strliteral.c
  • Sink: strcpy(dup, str) at tools/strliteral.c:22, called on the unchecked return value of malloc(len + 1)
  • Missing control: No NULL check on the malloc() return value before the strcpy() write; no bounds argument passed to the copy function
  • CWE: CWE-122 — Heap-based Buffer Overflow
  • Fix: Added a NULL guard immediately after malloc() and replaced strcpy() with memcpy(dup, str, len + 1) for a bounded, explicit copy

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.


Conclusion

The dupstr() vulnerability in tools/strliteral.c is a textbook example of how a two-line oversight — missing a NULL check and using an unbounded copy function — can elevate a utility function to a critical security risk. The fix is equally concise: guard the allocation, use memcpy() with an explicit length, and remove the redundant null-termination. These are not exotic techniques; they are foundational C memory-safety practices that every C developer should have in their muscle memory.

The broader lesson is that no file in a production codebase is too small or too peripheral to warrant security review. Build tools, code generators, and utility scripts all represent attack surface. Treat them accordingly.


References

Frequently Asked Questions

What is a heap buffer overflow in C?

A heap buffer overflow occurs when a program writes more data into a heap-allocated buffer than it was allocated to hold, or writes into a NULL pointer returned by a failed malloc(), corrupting adjacent memory or triggering a crash.

How do you prevent heap buffer overflow in C?

Always check the return value of malloc() before using the pointer, and use bounds-aware copy functions like memcpy() or strlcpy() instead of strcpy(), which has no length limit.

What CWE is heap buffer overflow?

Heap buffer overflow is classified as CWE-122 (Heap-based Buffer Overflow), a subset of CWE-119 (Improper Restriction of Operations within the Bounds of a Memory Buffer).

Is malloc() size calculation enough to prevent heap buffer overflow?

No. Correctly sizing the malloc() call is necessary but not sufficient — you must also verify that malloc() did not return NULL before writing to the buffer, and use copy functions that respect the allocated size.

Can static analysis detect heap buffer overflow from unchecked malloc()?

Yes. Static analysis tools like Semgrep, Coverity, and clang-analyzer can flag patterns where the return value of malloc() is used without a NULL check, and where strcpy() is used in place of safer alternatives.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #9

Related Articles

high

How insecure string copy functions happen in C apputils.c and how to fix it

A high-severity buffer overflow vulnerability was discovered in `src/apps/common/apputils.c`, where `strncpy()` was used without guaranteed null-termination across four call sites — including the `sock_bind_to_device()` and `getdomainname()` functions. The fix replaces all unsafe `strncpy()` calls with `snprintf()`, which enforces both length bounds and automatic null-termination. Left unpatched, these flaws could allow an attacker to corrupt memory, crash the process, or potentially execute arb

critical

How integer overflow in buffer size calculation happens in C++ and how to fix it

A critical integer overflow vulnerability was discovered in OpenCV's HAL filter implementation where multiplying image dimensions without overflow protection could allocate dangerously undersized buffers. An attacker supplying crafted image dimensions (e.g., 65536×65536) could trigger heap corruption through out-of-bounds writes. The fix promotes the calculation to 64-bit arithmetic with a single cast.

critical

How buffer overflow via strcpy() happens in C zlib and how to fix it

A critical buffer overflow vulnerability was discovered in `general/libzlib/gzlib.c` where multiple `strcpy()` and `strcat()` calls operated without bounds checking. An attacker controlling file paths or error messages could overflow destination buffers, potentially achieving arbitrary code execution. The fix replaces these unsafe string operations with bounded `memcpy()` calls that respect pre-calculated buffer lengths.

critical

How buffer overflow in rcdevice.c request parser happens in C and how to fix it

A critical buffer overflow vulnerability was discovered in `src/main/io/rcdevice.c` at line 489, where the RC device request parser wrote incoming data into a fixed-size buffer without validating against the hard-coded maximum capacity `RCDEVICE_PROTOCOL_MAX_DATA_SIZE`. An attacker controlling the device's I/O data stream could overflow the buffer by sending a payload longer than `expectedDataLength`, potentially achieving arbitrary code execution. The fix adds a second bounds check against the

critical

How buffer overflow via unchecked memcpy offset happens in C++ PCL point cloud parsing and how to fix it

A critical out-of-bounds read vulnerability was discovered in `pcpatch_pcl.cpp` where the `readFloat` lambda performed a `memcpy` operation using an untrusted offset value without validating buffer boundaries. An attacker could craft malicious PCD point cloud files with large offset values to read memory outside allocated buffers, potentially leaking sensitive data or causing crashes. The fix adds a bounds check ensuring `f->offset + sizeof(float)` stays within the row buffer before any memory c

critical

How buffer overflow in stb_image.h memcpy happens in C image parsing and how to fix it

A critical buffer overflow vulnerability was discovered in stb_image.h at line 4823, where a memcpy operation copied image data without validating buffer bounds. The multiplication of width (x) and channel count (img_n) could overflow or exceed allocated memory, allowing attackers to corrupt memory through malicious PNG files. The fix adds an explicit size_t cast to prevent integer overflow during the buffer size calculation.