Back to Blog
high SEVERITY7 min read

How insecure string copy functions happen in C and how to fix them

A high-severity buffer overflow risk was discovered in `login/main.c` where `strcpy()` was used to copy the `HOME` environment variable into a fixed-size 512-byte buffer without any bounds checking. An attacker controlling the `HOME` environment variable could overflow `pwd_file_name`, potentially corrupting memory or hijacking execution. The fix replaces the two-step `strcpy`/`strcat` pattern with a single, bounds-safe `snprintf` call.

O
By Orbis AppSec
Published August 22, 2026Reviewed August 22, 2026

Answer Summary

This vulnerability is an insecure string copy (CWE-120, "Buffer Copy without Checking Size of Input") in C, located in `login/main.c`. The root cause is a `strcpy(pwd_file_name, getenv("HOME"))` call that copies an environment variable into a 512-byte stack buffer with no length check, enabling a stack buffer overflow. The fix replaces the unsafe `strcpy`/`strcat` pair with `snprintf(pwd_file_name, sizeof(pwd_file_name), "%s/.vnc/vnc_password", getenv("HOME"))`, which enforces the destination buffer size and guarantees null-termination in a single atomic operation.

Vulnerability at a Glance

cweCWE-120
fixReplace strcpy+strcat with snprintf using sizeof(pwd_file_name) as the size bound
riskStack buffer overflow enabling memory corruption or code injection
languageC
root causestrcpy() copies getenv("HOME") into a fixed 512-byte buffer with no length validation
vulnerabilityInsecure String Copy (strcpy/strcat without bounds checking)

How Insecure String Copy Functions Happen in C and How to Fix Them

The File That Handles Your VNC Password

The login/main.c file is responsible for one of the most sensitive operations a program can perform: authenticating a user by reading a stored VNC password from disk. To build the path to that password file, the code needed to combine the user's home directory with the relative path /.vnc/vnc_password. A seemingly simple task — but the way it was implemented introduced a high-severity memory safety vulnerability on line 11.


The Vulnerability Explained

Here is the vulnerable code that Semgrep flagged:

// BEFORE — vulnerable code (login/main.c, lines 11–12)
char pwd_file_name[512] = {0};
strcpy(pwd_file_name, getenv("HOME"));
strcat(pwd_file_name, "/.vnc/vnc_password");

What's Wrong Here?

strcpy copies bytes from its source into the destination buffer and stops only when it hits a null terminator (\0) in the source string. It does not know how large the destination buffer is. If the source string is longer than the destination, strcpy happily writes past the end of the buffer — a classic stack buffer overflow.

In this specific case, the source is getenv("HOME") — the value of the HOME environment variable. On a typical Linux system this might be /home/alice (10 bytes), well within the 512-byte pwd_file_name buffer. But environment variables are user-controlled data. There is no operating-system guarantee that HOME is short.

Consider what happens when an attacker (or a misconfigured environment) sets:

export HOME=$(python3 -c "print('A' * 600)")

Now getenv("HOME") returns a 600-character string. strcpy will write all 600 bytes into pwd_file_name, overflowing the 512-byte stack buffer by 88 bytes, overwriting the stack frame — potentially including the saved return address.

The follow-up strcat call compounds the problem: even if HOME were exactly 512 bytes, strcat would then append /.vnc/vnc_password (19 bytes) further past the end of the buffer.

Why This Matters for a Login Component

This is not an abstract theoretical risk. A login binary often runs with elevated privileges (e.g., setuid root or as a system service). A stack buffer overflow in a privileged login process is a classic privilege escalation primitive. Even if the overflow is not directly exploitable today due to stack canaries or ASLR, it constitutes an exploit primitive — a code pattern that automated exploit-development tooling can chain with other weaknesses to achieve reliable exploitation.


The Fix

The fix replaces the two-step strcpy/strcat pattern with a single, bounds-enforcing snprintf call:

// AFTER — hardened code (login/main.c, line 11)
snprintf(pwd_file_name, sizeof(pwd_file_name), "%s/.vnc/vnc_password", getenv("HOME"));

Before vs. After

Before After
Function strcpy + strcat snprintf
Bounds check None sizeof(pwd_file_name) = 512 bytes
Null termination Implicit (if no overflow) Guaranteed by snprintf
Atomic No (two calls) Yes (single call)
Overflow behavior Silent memory corruption Truncation, no overflow

Why snprintf Solves This Specific Problem

snprintf(dest, n, fmt, ...) writes at most n - 1 characters into dest and always appends a null terminator, regardless of how long the formatted result would be. By passing sizeof(pwd_file_name) — which the compiler evaluates at compile time as 512 — the call can never write beyond the boundary of the stack buffer.

If HOME is pathologically long, snprintf will truncate the result to 511 characters and null-terminate it. The program will then attempt to open a truncated path, fail gracefully with a file-not-found error, and deny login — a safe failure mode rather than a memory corruption event.

Using sizeof(pwd_file_name) instead of a hardcoded 512 is also a defensive maintenance practice: if the buffer size is ever changed, the snprintf limit updates automatically.


Prevention & Best Practices

1. Ban strcpy and strcat in New Code

Treat strcpy, strcat, gets, and sprintf as deprecated. Most modern C codebases enforce this via compiler warnings (-Wdeprecated-declarations) or static analysis rules.

// Never use these for untrusted or variable-length input:
strcpy(dst, src);          // no bounds check
strcat(dst, src);          // no bounds check
sprintf(dst, fmt, ...);    // no bounds check
gets(buf);                 // removed from C11 entirely

2. Use Bounded Alternatives

Unsafe Safe Alternative Notes
strcpy strlcpy (BSD/macOS) or snprintf strlcpy not in C standard; snprintf is portable
strcat strlcat or snprintf Same portability note
sprintf snprintf Always pass sizeof(buf)
gets fgets Already used correctly elsewhere in this file

Note: strcpy_s is part of Annex K of C11 but is optional and not widely available on Linux. snprintf is the most portable safe choice.

3. Validate Environment Variables Before Use

When your program depends on environment variables like HOME, validate them early:

const char *home = getenv("HOME");
if (home == NULL) {
    fprintf(stderr, "HOME environment variable not set\n");
    exit(EXIT_FAILURE);
}
if (strlen(home) > 480) {  // leave room for the suffix
    fprintf(stderr, "HOME path too long\n");
    exit(EXIT_FAILURE);
}

4. Enable Compiler Hardening Flags

gcc -Wall -Wextra -Wformat-security \
    -fstack-protector-strong \
    -D_FORTIFY_SOURCE=2 \
    -pie -fPIE \
    login/main.c -o login

-D_FORTIFY_SOURCE=2 causes glibc to replace strcpy with a version that checks buffer sizes at runtime when the destination size is known at compile time.

5. Run Static Analysis in CI

The Semgrep rule that caught this issue (c.lang.security.insecure-use-string-copy-fn) is freely available and can be integrated into any CI pipeline:

# .github/workflows/semgrep.yml
- name: Semgrep scan
  run: semgrep --config "p/c" login/main.c

Relevant Standards

  • CWE-120: Buffer Copy without Checking Size of Input
  • OWASP: Input Validation Cheat Sheet
  • SEI CERT C: STR31-C — Guarantee that storage for strings has sufficient space for character data and the null terminator

Key Takeaways

  • getenv() returns attacker-controlled data — the HOME environment variable in login/main.c can be set to an arbitrarily long string before the process launches, making it untrusted input that must be bounded before copying.
  • Two-step strcpy/strcat is doubly dangerous — even if strcpy didn't overflow, the subsequent strcat(pwd_file_name, "/.vnc/vnc_password") could push the buffer over its limit on its own.
  • snprintf with sizeof() is the portable C fix — it enforces the limit, guarantees null termination, and collapses two unsafe calls into one safe one.
  • Login code warrants extra scrutiny — a buffer overflow in a privileged authentication binary is a direct path to privilege escalation; the severity of memory safety bugs scales with the privilege level of the process.
  • Semgrep's insecure-use-string-copy-fn rule catches this pattern automatically — integrating it into CI would have flagged this before it ever reached production.

How Orbis AppSec Detected This

  • Source: The HOME environment variable read via getenv("HOME") at login/main.c:11 — an externally controlled value with no length constraint.
  • Sink: strcpy(pwd_file_name, getenv("HOME")) at login/main.c:11, copying into the 512-byte stack buffer pwd_file_name with no size argument.
  • Missing control: No length validation of the HOME value before the copy, and no size bound passed to strcpy.
  • CWE: CWE-120 — Buffer Copy without Checking Size of Input ('Classic Buffer Overflow').
  • Fix: Replaced strcpy(pwd_file_name, getenv("HOME")) + strcat(pwd_file_name, "/.vnc/vnc_password") with snprintf(pwd_file_name, sizeof(pwd_file_name), "%s/.vnc/vnc_password", getenv("HOME")), enforcing a hard 512-byte limit and guaranteeing null termination.

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

A single strcpy call on line 11 of login/main.c turned an environment variable into a potential stack buffer overflow in a security-critical login component. The root cause is one of the oldest mistakes in C programming: trusting that external data will fit in a fixed-size buffer without checking. The fix is equally straightforward — snprintf with an explicit size bound closes the overflow entirely in one line of code.

Memory safety bugs in C don't announce themselves at compile time. They require deliberate use of bounded string functions, compiler hardening flags, and automated static analysis to catch before they reach production. In a login binary — where the stakes of exploitation are highest — there is no acceptable reason to use strcpy on user-controlled input.


References

Frequently Asked Questions

What is an insecure string copy vulnerability in C?

It occurs when functions like strcpy() or strcat() copy data into a fixed-size buffer without verifying the source length fits, potentially overwriting adjacent memory and enabling buffer overflow attacks.

How do you prevent insecure string copies in C?

Use bounded alternatives such as snprintf(), strlcpy(), or strcpy_s() that accept an explicit size argument and prevent writes beyond the destination buffer.

What CWE is insecure string copy?

CWE-120 — "Buffer Copy without Checking Size of Input ('Classic Buffer Overflow')".

Is checking the string length before strcpy() enough to prevent this vulnerability?

It can help, but it introduces TOCTOU risk and is easy to get wrong. Using a single bounded function like snprintf() is safer and more idiomatic because the limit is enforced atomically inside the call itself.

Can static analysis detect insecure string copy vulnerabilities?

Yes. Tools like Semgrep, Coverity, and cppcheck can flag strcpy/strcat calls directly. Semgrep's rule `c.lang.security.insecure-use-string-copy-fn` detected exactly this pattern in login/main.c at line 11.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #6

Related Articles

high

How c.lang.security.use-after-free.use-after-free happens in C and how to fix it

A use-after-free vulnerability was discovered in `ggml-alloc.c` where `galloc->leaf_allocs` could be referenced after being freed during graph memory reallocation. The fix nullifies the pointer immediately after `free()` and uses explicit `sizeof(struct leaf_alloc)` to prevent undefined behavior. This defensive hardening eliminates an exploit primitive in a speech-to-text processing pipeline.

critical

How buffer overflow in Intel SGX enclave ECALLs happens in C and how to fix it

A critical buffer overflow vulnerability was discovered in Intel SGX enclave functions `ecall_encrypt_data` and `ecall_decrypt_data` in `backend/sgx/enclave/enclave.c`. The functions performed memory operations without validating that the provided buffer lengths matched the actual allocated buffer sizes, allowing an attacker controlling the untrusted application to trigger heap corruption within the secure enclave by passing oversized length parameters.

critical

How buffer overflow happens in C SGX enclave memcpy and how to fix it

A critical buffer overflow vulnerability was discovered in `backend/sgx/enclave/enclave.c` where the `ecall_store_data` function performed `memcpy` operations without proper bounds checking against the actual destination buffer size. An attacker could supply a malicious `data_len` parameter to overflow the enclave's secure storage buffer, potentially corrupting trusted execution environment memory. The fix replaces a hardcoded magic number check with a precise size comparison against the actual

medium

How integer underflow in array splice operations happens in C and how to fix it

A critical integer underflow vulnerability was discovered in tree-sitter's array.h header file, where the `_array__splice()` function calculated array sizes without proper bounds checking. The vulnerable code relied on assert() statements that are disabled in release builds, allowing arithmetic underflow when `old_count > *size + new_count`, potentially causing memory corruption through out-of-bounds memcpy operations.

high

How buffer overflow via sprintf() happens in C string formatting and how to fix it

A high-severity buffer overflow vulnerability was discovered in `bench/strbuild/strbuild.c` where `sprintf()` wrote formatted output into a 64-byte stack buffer (`line[64]`) without any bounds checking. An attacker who could influence the values in the `NAMES[]`, `c[]`, or `v[]` arrays could overflow this buffer, potentially corrupting the stack and hijacking control flow. The fix replaces `sprintf()` with `snprintf(line, sizeof(line), ...)` to enforce a strict 64-byte write limit.

high

How Regular Expression Denial of Service happens in JavaScript and how to fix it

CVE-2026-33671 is a Regular Expression Denial of Service (ReDoS) vulnerability in the picomatch glob-matching library, triggered by specially crafted extglob patterns that cause catastrophic regex backtracking. The fix upgrades picomatch to version 4.0.4 (with overrides pinning all transitive copies) in the client's dependency tree, eliminating the vulnerable regex evaluation path. Left unpatched, any code path that passes user-influenced glob patterns to picomatch could be weaponized to stall a