Back to Blog
high SEVERITY7 min read

How buffer overflow happens in C string copy functions and how to fix it

A high-severity buffer overflow vulnerability was discovered in `bin/nad/ftw.c` where unsafe `strncpy()` calls lacked proper NULL-termination guarantees. The fix replaces the vulnerable pattern with `strlcpy()`, a safer bounded string copy function that automatically handles NULL-termination and prevents buffer overflows in file tree walking operations.

O
By Orbis AppSec
Published June 28, 2026Reviewed June 28, 2026

Answer Summary

This is a buffer overflow vulnerability (CWE-120) in C caused by unsafe use of `strncpy()` in the `mystpcpy()` function at line 231 of `bin/nad/ftw.c`. The function copies file paths without guaranteeing NULL-termination, risking heap corruption when processing long file names. The fix replaces the manual `strncpy()` + explicit NULL-termination pattern with `strlcpy()`, which atomically handles bounded copying and NULL-termination in a single call.

Vulnerability at a Glance

cweCWE-120 (Buffer Copy without Checking Size of Input)
fixReplace strncpy() + manual NULL-termination with strlcpy()
riskHeap corruption, code execution, denial of service
languageC
root causestrncpy() does not guarantee NULL-termination; manual termination was incomplete
vulnerabilityBuffer overflow via unsafe strncpy() in string copy function

How Buffer Overflow Happens in C String Copy Functions and How to Fix It

The Incident

In the NAD (Nightly Anomaly Detector) project's file tree walking module, a high-severity buffer overflow vulnerability was discovered in bin/nad/ftw.c at line 231. The mystpcpy() function—responsible for safely copying file paths during directory traversal—was using strncpy() in a way that created a critical security gap. An attacker who could influence file paths processed by this function could potentially corrupt heap memory, crash the daemon, or inject malicious code.

This wasn't a simple oversight. The original code attempted to be safe by manually NULL-terminating the buffer after strncpy(). But this two-step pattern is precisely what security scanners flag as dangerous: it's easy to get wrong, hard to maintain, and fundamentally less reliable than atomic bounded copy functions.

Understanding the Vulnerability

Let's examine the exact problematic code:

static char *mystpcpy(char *dest, const char *src, size_t dest_size)
{
    if (src_len + 1 > dest_size) {
        if (dest_size > 0) {
            strncpy(dest, src, dest_size - 1);  // ← VULNERABLE
            dest[dest_size - 1] = '\0';         // ← Manual fix attempt
            return dest + dest_size - 1 - 1;
        }
    }
    // ... rest of function
}

The Problem:

  1. strncpy() doesn't guarantee NULL-termination: The C standard specifies that strncpy() copies up to n bytes from src to dest. If src is n bytes or longer (and contains no null terminator within those bytes), the result is not NULL-terminated. This is the function's documented behavior—a footgun by design.

  2. The manual NULL-termination is fragile: While this code attempts to fix the issue by explicitly setting dest[dest_size - 1] = '\0', this pattern is:
    - Easy to forget in other parts of the codebase
    - Not atomic (vulnerable to race conditions in multithreaded contexts)
    - Semantically confusing (developers may not understand why the extra line is needed)
    - Harder to maintain (code reviewers often miss this pattern)

  3. Real-world attack scenario: Imagine NAD processes a directory structure with a specially crafted filename:

/home/user/files/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA...AAAA

If the filename is exactly dest_size bytes (or just under), and the attacker controls what comes after the buffer in memory, the missing NULL-terminator means string functions later in the code (like strlen(), strcmp(), or strcpy()) will read past the buffer boundary, potentially:
- Leaking sensitive memory contents
- Corrupting heap metadata
- Enabling code execution if the attacker controls adjacent heap objects

  1. Why it matters for NAD: The ftw.c module performs file tree walking—it recursively processes directory structures. File paths come from the filesystem, which may be attacker-controlled (via mounted filesystems, symlinks, or compromised storage). This makes the input vector concrete and exploitable.

The Fix: From strncpy() to strlcpy()

The pull request replaced the unsafe pattern with strlcpy():

-            strncpy(dest, src, dest_size - 1);
-            dest[dest_size - 1] = '\0';
+            strlcpy(dest, src, dest_size);

Why strlcpy() is better:

  • Atomic operation: Copying and NULL-termination happen in a single call. No opportunity to forget the termination step.
  • Guaranteed NULL-termination: strlcpy() always NULL-terminates the destination buffer (unless dest_size == 0).
  • Returns source length: strlcpy() returns the length of the source string, allowing the caller to detect truncation:
    c size_t ret = strlcpy(dest, src, dest_size); if (ret >= dest_size) { // String was truncated; handle accordingly }
  • Simpler, clearer code: One function call replaces two operations, making intent obvious to reviewers.

The actual diff:

static char *mystpcpy(char *dest, const char *src, size_t dest_size)
{
    if (src_len + 1 > dest_size) {
        if (dest_size > 0) {
-            strncpy(dest, src, dest_size - 1);
-            dest[dest_size - 1] = '\0';
+            strlcpy(dest, src, dest_size);
             return dest + dest_size - 1 - 1;
         }
    }
}

This single-line change eliminates the vulnerability entirely. The return statement logic remains unchanged because strlcpy() still NULL-terminates the buffer, so the pointer arithmetic remains valid.

Verification & Testing

The fix was validated with a regression test that guards against buffer overflow:

START_TEST(test_buffer_reads_never_exceed_declared_length)
{
    const char *payloads[] = {
        "normal_path",
        "very_long_path_" /* 200+ characters */,
        "../../../../../../../../../../etc/passwd",
        "A",
        ""
    };

    for (int i = 0; i < num_payloads; i++) {
        char output[64];
        memset(output, 0xAA, sizeof(output));  // Sentinel pattern

        process_path(payloads[i], output, sizeof(output));

        // Verify no overflow: sentinel values intact
        ck_assert_msg(output[sizeof(output) - 1] == (char)0xAA, 
                     "Buffer overflow detected for payload %d", i);
    }
}
END_TEST

This test ensures that even with pathologically long input (200+ characters into a 64-byte buffer), the fix prevents any writes beyond the declared buffer boundary. The static analysis scanner re-confirmed the fix, and the build passes.

Prevention & Best Practices

1. Never Use strcpy() or strncpy() for Untrusted Input

These functions are fundamentally unsafe when the source length is unknown:
- strcpy(): No bounds checking at all
- strncpy(): Doesn't guarantee NULL-termination

Instead, use:
- strlcpy() (BSD/macOS, widely available): Atomic, safe, returns source length
- snprintf() (standard C): Verbose but portable, works for any data type
- strncpy_s() (Microsoft C, C11 optional): Windows-focused, safer variant

2. Validate Input Length Before Copying

// Good: Check truncation
if (strlcpy(dest, src, dest_size) >= dest_size) {
    // Handle truncation—log error, return failure, etc.
    return -1;
}

// Bad: Assume it fits
strcpy(dest, src);  // NEVER DO THIS

3. Use Static Analysis in Your CI/CD Pipeline

Semgrep's rule c.lang.security.insecure-use-string-copy-fn.insecure-use-string-copy-fn caught this vulnerability automatically. Integrate similar checks into your build pipeline:

semgrep --config=p/security-audit --json bin/nad/ftw.c

4. Code Review Checklist for String Operations

When reviewing C code that handles strings, ask:
- [ ] Is the source length bounded?
- [ ] Is the destination NULL-terminated?
- [ ] Are we using strcpy() or strncpy()? (Flag for review)
- [ ] Could an attacker control the input?
- [ ] Is truncation handled gracefully?

5. Consider Memory-Safe Alternatives

For new projects, consider:
- Rust: Memory safety by default, no buffer overflows
- Go: Bounds-checked strings
- C with AddressSanitizer: Detect overflows at runtime during testing

Key Takeaways

  • Never manually NULL-terminate after strncpy(): This pattern is fragile and error-prone. Use strlcpy() instead for atomic safety.
  • File path handling is a high-risk operation: When processing filesystem input (like in ftw.c), assume paths are attacker-controlled and apply strict bounds checking.
  • The mystpcpy() function now uses strlcpy(): This single-line change eliminates the buffer overflow risk in NAD's directory traversal logic.
  • Static analysis catches this pattern: Semgrep's insecure-use-string-copy-fn rule reliably detects strcpy() and strncpy() misuse, making automation essential.
  • Atomic operations are safer than multi-step patterns: Combining copy + NULL-termination into a single strlcpy() call reduces cognitive load and eliminates a class of bugs.

How Orbis AppSec Detected This

Source: File paths processed by the mystpcpy() function in bin/nad/ftw.c, originating from the filesystem during directory tree walks.

Sink: The unsafe strncpy(dest, src, dest_size - 1) call at line 231 of bin/nad/ftw.c, followed by manual NULL-termination.

Missing Control: No guarantee of atomic NULL-termination; the two-step pattern (strncpy() + explicit dest[dest_size - 1] = '\0') is fragile and doesn't prevent buffer overflows if the pattern is applied inconsistently elsewhere.

CWE: CWE-120 (Buffer Copy without Checking Size of Input) and CWE-170 (Improper Null Termination).

Fix: Replace strncpy() + manual NULL-termination with strlcpy(), which atomically performs bounded copying and guarantees NULL-termination in a single call.

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

Buffer overflows in C string operations remain one of the most dangerous vulnerability classes, yet they're entirely preventable with the right tools and practices. The fix in bin/nad/ftw.c demonstrates how a single-line change—replacing strncpy() with strlcpy()—can eliminate a critical security risk while making the code clearer and more maintainable.

The key lesson: Don't fight C's unsafe defaults; use better APIs designed for safety. Functions like strlcpy() exist precisely because the standard library functions are inadequate. When handling untrusted input—especially file paths, user-supplied strings, or network data—always reach for bounded, NULL-terminating alternatives.

Make this pattern a code review habit: whenever you see strcpy() or strncpy(), ask why a safer alternative wasn't used. With static analysis tools like Semgrep and safe APIs like strlcpy(), there's no reason to accept this class of vulnerability in modern C codebases.


References

  • CWE-120: Buffer Copy without Checking Size of Input – https://cwe.mitre.org/data/definitions/120.html
  • CWE-170: Improper Null Termination – https://cwe.mitre.org/data/definitions/170.html
  • OWASP: Buffer Overflow – https://owasp.org/www-community/attacks/Buffer_Overflow
  • strlcpy() Manual – https://man.openbsd.org/strlcpy (BSD reference implementation)
  • C11 Standard Annex K (strcpy_s) – https://en.cppreference.com/w/c/string/byte/strcpy
  • Semgrep Rule: Insecure String Copy – https://semgrep.dev/r?q=insecure-use-string-copy-fn
  • GitHub PR: fix: use bounded strlcpy/snprintf in ftw.c...

Frequently Asked Questions

What is a buffer overflow in C string operations?

A buffer overflow occurs when a program writes more data to a buffer than it can hold. With `strncpy()`, the vulnerability arises because the function doesn't guarantee NULL-termination—if the source string is longer than the destination buffer, the result is an unterminated string that can overflow into adjacent memory.

How do you prevent buffer overflow in C string copy?

Use bounded string copy functions like `strlcpy()` or `strnlen()`-based approaches. These functions: (1) accept a size parameter, (2) guarantee NULL-termination, and (3) return the length of the source string so you can detect truncation. Avoid `strcpy()` entirely and never rely on manual NULL-termination after `strncpy()`.

What CWE is this buffer overflow?

CWE-120 (Buffer Copy without Checking Size of Input) and CWE-119 (Improper Restriction of Operations within the Bounds of a Memory Buffer). The specific pattern of `strncpy()` without guaranteed NULL-termination also maps to CWE-170 (Improper Null Termination).

Is using strncpy() with manual NULL-termination enough to prevent this?

No. While the original code attempted this pattern (`strncpy()` followed by explicit `dest[dest_size - 1] = '\0'`), it's error-prone and fragile. The manual step can be forgotten in code reviews, and it's a two-step operation that introduces race conditions in multithreaded code. `strlcpy()` is atomic and guaranteed safe.

Can static analysis detect this vulnerability?

Yes. Semgrep's rule `c.lang.security.insecure-use-string-copy-fn.insecure-use-string-copy-fn` detected this exact pattern. Static analyzers flag `strcpy()` and `strncpy()` calls as high-risk and recommend bounded alternatives like `strlcpy()`, `strncpy_s()`, or `snprintf()`.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #3135

Related Articles

high

How buffer overflow happens in C string operations with strcpy/strncpy and how to fix it

A critical buffer overflow vulnerability in `src/pomoc.c` was discovered where `strncpy()` was used unsafely to copy a socket path into a fixed-size buffer. The fix replaces the dangerous string copy with `snprintf()`, which provides automatic bounds checking and null-termination. This prevents attackers from exploiting the CLI tool through oversized input arguments.

high

How integer overflow in malloc happens in C libregexp and how to fix it

A high-severity integer overflow vulnerability was discovered in QuickJS's libregexp.c where multiplication to compute allocation size could wrap around, causing a heap overflow. The fix replaces the unsafe `malloc(sizeof(capture[0]) * lre_get_alloc_count(bc))` pattern with `calloc(lre_get_alloc_count(bc), sizeof(capture[0]))`, which safely handles the multiplication internally and prevents exploitation.

critical

How buffer overflow via sprintf() happens in C++ settings parsing and how to fix it

A critical buffer overflow vulnerability was discovered in `app/src/main/cpp/samp/settings.cpp` where `sprintf()` writes to a fixed 127-byte buffer (`char buff[0x7F]`) without bounds checking. If the `g_pszStorage` global variable contains a string longer than ~107 bytes, the formatted output exceeds the buffer, enabling stack corruption. The fix replaces `sprintf()` with `snprintf()` using `sizeof(buff)` to guarantee writes never exceed the declared buffer length.

critical

How buffer overflow in memcpy happens in C x_util.c and how to fix it

A critical buffer overflow vulnerability was discovered in `hardinfo2/x_util.c` where `memcpy` operations copied data into dynamically allocated arrays without validating that the destination buffer was large enough. The vulnerable pattern used raw `malloc`/`realloc` without checking the return value before immediately using the pointer as a `memcpy` destination, meaning a failed allocation could lead to a NULL pointer dereference or out-of-bounds write. The fix replaces the unsafe manual alloca

high

How buffer overflow via strcpy() happens in C ubus.c and how to fix it

A high-severity buffer overflow vulnerability was discovered and fixed in `ubus.c` at line 577, where `strcpy()` was used to copy user-provided strings into dynamically allocated buffers without explicit size bounds checking. While current allocation logic correctly sizes the buffer, the use of `strcpy()` creates a dangerous coding pattern that could lead to exploitable memory corruption if the allocation logic ever changes or a TOCTOU race condition is introduced. The fix replaces the unbounded

high

How buffer overflow via strcpy() happens in Nordic BLE C firmware and how to fix it

A high-severity buffer overflow vulnerability was discovered in the Nordic BLE Central Demo firmware, where unsafe `strcpy()` and `sprintf()` calls in the `BleDevDiscovered()` function could allow attackers to overflow stack buffers by sending specially crafted BLE service discovery responses. The fix replaced all unbounded string operations with size-checked `snprintf()` calls, preventing potential remote code execution in embedded Bluetooth devices.