Back to Blog
high SEVERITY7 min read

How insecure-use-string-copy-fn happens in C and how to fix it

A high-severity vulnerability was identified in `plugin/bin/install.c` where `strcpy()` and `strncpy()` were used to handle path strings without proper bounds checking or guaranteed null-termination. The fix replaces `strcpy()` with direct character assignment and `strncpy()` with `snprintf()`, eliminating both buffer overflow and missing null-terminator risks in the plugin installation workflow.

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

Answer Summary

This is an insecure string copy vulnerability (CWE-120) in C, found in `plugin/bin/install.c` where `strcpy()` and `strncpy()` were used to manipulate file paths. `strcpy()` performs no bounds checking, risking buffer overflow, while `strncpy()` may not null-terminate the destination. The fix replaces `strcpy(path, ".")` with direct character assignment (`path[0] = '.'; path[1] = '\0';`) and replaces `strncpy(root_path, cwd, PATH_MAX)` with `snprintf(root_path, PATH_MAX, "%s", cwd)`, which both bounds-checks and null-terminates.

Vulnerability at a Glance

cweCWE-120 (Buffer Copy without Checking Size of Input)
fixReplace strcpy() with direct char assignment and strncpy() with snprintf() for bounded, null-terminated copies
riskBuffer overflow leading to potential code execution or program crash during plugin installation
languageC
root causestrcpy() used without bounds checking and strncpy() used without guaranteed null-termination in path handling
vulnerabilityInsecure use of string copy functions (strcpy/strncpy)

Introduction

The file plugin/bin/install.c handles the plugin installation workflow—resolving the current working directory, finding the project root, and orchestrating a multi-step install process. But two string copy calls on lines 64 and 75 introduced subtle yet high-severity risks: an unbounded strcpy() in the get_root_dir() function and a strncpy() in main() that could silently drop the null terminator.

These aren't theoretical concerns. The root_path and path buffers are used immediately afterward in file existence checks, directory traversal, and printf() calls. A missing null terminator or an overflowed buffer in this context could corrupt adjacent stack variables, crash the installer, or—in a worst-case scenario—allow an attacker who controls the working directory path to inject code.

This post dissects exactly what went wrong, how the fix works at the code level, and why the chosen replacement functions are superior to the alternatives.

The Vulnerability Explained

Two Dangerous Patterns in One File

Pattern 1: Unbounded strcpy() at line 64

if (strlen(path) == 0) strcpy(path, ".");

Inside get_root_dir(), if no root directory marker is found while traversing upward, the function falls back to setting path to "." (the current directory). The strcpy() call here doesn't check the size of path. While the source string "." is only 2 bytes (including the null terminator), the real danger is the pattern: this teaches developers that strcpy() is acceptable in this codebase, and future modifications might introduce longer source strings without adding bounds checks.

More critically, static analysis tools like Semgrep flag this pattern because strcpy() is universally considered unsafe in security-sensitive code. Automated exploit-development tools scan for exactly these primitives as potential chain links.

Pattern 2: strncpy() without null-termination guarantee at line 75

strncpy(root_path, cwd, PATH_MAX);

In main(), the current working directory (cwd, obtained from getcwd()) is copied into root_path. While strncpy() limits the copy to PATH_MAX bytes, it has a critical flaw: if cwd is exactly PATH_MAX bytes long (including or excluding the null terminator, depending on getcwd()'s behavior), strncpy() will not append a null terminator to root_path.

This means every subsequent operation on root_path—the get_root_dir() call, the printf() on the next line, the snprintf() calls that build file paths—would read past the buffer boundary, causing undefined behavior.

Attack Scenario

Consider an attacker who can influence the working directory path (e.g., via a symlink chain or a crafted directory name on a shared system). If they create a directory path that is exactly PATH_MAX - 1 characters long:

  1. getcwd() fills cwd with PATH_MAX bytes (path + null terminator).
  2. strncpy(root_path, cwd, PATH_MAX) copies all PATH_MAX bytes but may not null-terminate if cwd uses the full buffer.
  3. get_root_dir(root_path) calls strlen(root_path), which scans past the buffer looking for a null byte.
  4. This out-of-bounds read could leak stack data, crash the program, or be chained with other vulnerabilities for code execution.

Even without an active attacker, edge-case paths on deeply nested filesystems could trigger this organically.

The Fix

The PR applies two targeted, minimal changes that preserve the original behavior while eliminating both vulnerability classes.

Change 1: Replace strcpy() with direct character assignment (line 64)

Before:

if (strlen(path) == 0) strcpy(path, ".");

After:

if (strlen(path) == 0) { path[0] = '.'; path[1] = '\0'; }

This is elegant in its simplicity. Since the source string is a single-character literal ".", there's no need for a string copy function at all. Direct assignment:
- Eliminates any function call overhead
- Makes the buffer write explicit and auditable (exactly 2 bytes)
- Removes the strcpy() from the codebase entirely, so static analyzers no longer flag it
- Guarantees null-termination by explicitly writing '\0'

Change 2: Replace strncpy() with snprintf() (line 75)

Before:

strncpy(root_path, cwd, PATH_MAX);

After:

snprintf(root_path, PATH_MAX, "%s", cwd);

snprintf() is the gold standard for bounded string operations in C because it provides both guarantees that strncpy() lacks:

  1. Bounds checking: It will never write more than PATH_MAX bytes (including the null terminator).
  2. Guaranteed null-termination: Even if truncation occurs, the output is always a valid C string.

The format string "%s" simply copies the source string, making this a drop-in replacement for strncpy() with strictly better safety properties. If cwd exceeds PATH_MAX - 1 characters, snprintf() truncates and null-terminates, whereas strncpy() would have left a non-terminated buffer.

Why Not strcpy_s()?

The Semgrep rule suggests strcpy_s() as an alternative, but the PR wisely avoids it. strcpy_s() is part of C11's optional Annex K (bounds-checking interfaces), which is not implemented by glibc, musl, or most Linux C libraries. Using snprintf() ensures portability across all POSIX-compliant systems without requiring special compiler flags or library support.

Prevention & Best Practices

1. Ban strcpy() and strncpy() from your codebase

Add a linting rule or pre-commit hook that flags any use of strcpy() or strncpy(). Both functions have safer alternatives:

Unsafe Function Safe Replacement Why
strcpy() snprintf() or strlcpy() Bounded copy with null-termination
strncpy() snprintf() or strlcpy() Guaranteed null-termination
sprintf() snprintf() Bounded formatting

2. Prefer snprintf() for string copies in C

// Instead of this:
strncpy(dest, src, sizeof(dest));

// Do this:
snprintf(dest, sizeof(dest), "%s", src);

3. Use sizeof() instead of magic numbers

When possible, use sizeof(dest) rather than a named constant like PATH_MAX to ensure the size always matches the actual buffer declaration.

4. Run static analysis in CI/CD

Tools like Semgrep can catch these patterns before they reach production. The rule c.lang.security.insecure-use-string-copy-fn.insecure-use-string-copy-fn specifically targets this class of vulnerability.

5. Consider compiler warnings

Enable -Wall -Wextra and -Wstringop-truncation (GCC 8+) to get compile-time warnings about strncpy() calls that may not null-terminate.

Key Takeaways

  • strcpy(path, ".") in get_root_dir() was an unnecessary function call—direct character assignment (path[0] = '.'; path[1] = '\0';) is safer, faster, and clearer for constant-length strings.
  • strncpy(root_path, cwd, PATH_MAX) in main() could produce a non-null-terminated root_path when the working directory path approaches PATH_MAX length, causing undefined behavior in all downstream string operations.
  • snprintf() is the portable, safe replacement for both strcpy() and strncpy() in C—it bounds-checks and null-terminates, unlike strncpy() which only does the former.
  • Even "safe-looking" code like copying a 2-byte string with strcpy() creates exploit primitives that automated attack tools can chain with other weaknesses.
  • The fix in plugin/bin/install.c is behavior-preserving—valid inputs produce identical output, but edge cases and malicious inputs are now handled safely.

How Orbis AppSec Detected This

  • Source: The current working directory string returned by getcwd(cwd, PATH_MAX) in main() at line 71 of plugin/bin/install.c, which reflects the filesystem path and could be influenced by an attacker controlling the directory structure.
  • Sink: strcpy(path, ".") at line 64 and strncpy(root_path, cwd, PATH_MAX) at line 75 in plugin/bin/install.c—both unsafe string copy functions operating on path buffers.
  • Missing control: No bounds checking on the strcpy() call and no null-termination guarantee on the strncpy() call, leaving both buffers vulnerable to overflow or unterminated string conditions.
  • CWE: CWE-120 (Buffer Copy without Checking Size of Input)
  • Fix: Replaced strcpy() with direct character assignment for the constant string case and replaced strncpy() with snprintf() for bounded, null-terminated path copying.

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 from unsafe string copy functions remain one of the most common and dangerous vulnerability classes in C. This case in plugin/bin/install.c demonstrates two distinct flavors of the problem—strcpy() without bounds checking and strncpy() without null-termination—both operating on file path buffers that flow through the entire plugin installation pipeline.

The fix is minimal but effective: direct character assignment for trivial constant strings and snprintf() for general-purpose bounded copies. These patterns should be your default in any C codebase. Run static analysis tools like Semgrep in your CI pipeline to catch these issues before they ship, and treat every strcpy()/strncpy() occurrence as a code smell that warrants immediate remediation.

References

Frequently Asked Questions

What is insecure-use-string-copy-fn?

It is a vulnerability pattern where C functions like strcpy() or strncpy() are used unsafely—strcpy() copies without checking destination buffer size, and strncpy() may not null-terminate the result, both leading to potential buffer overflows or undefined behavior.

How do you prevent insecure string copy vulnerabilities in C?

Use bounded, null-terminating alternatives like snprintf(), strlcpy() (on BSD/Linux), or strcpy_s() (C11 Annex K). For trivial cases, direct character assignment avoids function overhead entirely.

What CWE is insecure-use-string-copy-fn?

CWE-120 (Buffer Copy without Checking Size of Input), which covers cases where data is copied into a buffer without verifying the destination has sufficient space.

Is strncpy() enough to prevent buffer overflows in C?

No. While strncpy() limits the number of bytes copied, it does not guarantee null-termination when the source string is longer than or equal to the specified count, which can cause subsequent string operations to read past the buffer.

Can static analysis detect insecure string copy usage?

Yes. Tools like Semgrep, Coverity, and Clang's static analyzer have rules that flag strcpy() and strncpy() usage, recommending safer alternatives like snprintf() or strlcpy().

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1253

Related Articles

critical

How Buffer Overflow via strcpy() Happens in C++ XML Parsers and How to Fix It

A critical buffer overflow vulnerability was discovered in `buildroot-external/package/libxmlparser/xmlParser.cpp`, where the `toXMLString` function used `_tcscpy()` to write XML escape sequences into a destination buffer without any bounds checking. An attacker supplying a crafted XML document could overflow the buffer and potentially execute arbitrary code. The fix replaces all five unsafe `_tcscpy()` calls with `memcpy()` calls that copy only the exact number of bytes required for each escape

high

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

A high-severity buffer overflow vulnerability was discovered in `tools/claude-vscode-wrapper.c`, where an unbounded `strcpy()` call copied a file path into a fixed-size `MAX_PATH` buffer without any size validation. The fix replaces `strcpy()` with `snprintf()` and swaps `malloc()` for `calloc()`, ensuring both string operations and memory allocation are bounds-safe and zero-initialized.

critical

How Heap Buffer Overflows Happen in C++ ZIP Extraction and How to Fix Them

A critical heap buffer overflow vulnerability was discovered in `TKLiveSync/unzip.cpp`, where ZIP archive entry names were copied into a `PATH_MAX`-sized heap buffer using `strcpy()` without any length validation. Since the ZIP specification allows entry names up to 65,535 bytes — far exceeding typical `PATH_MAX` values of 1,024 to 4,096 bytes — a crafted archive could overflow the buffer and corrupt heap memory. The fix replaces the unsafe `strcpy`/`dirname` pattern with `std::string` operation

medium

How Integer Overflow happens in C++ image processing and how to fix it

A signed integer overflow in OpenCV's `bilateralFilter.cpp` allowed the buffer size calculation `cal_width * cal_height * cn` to wrap around to a small or negative value, causing `padding.resize()` to allocate far less memory than needed. Subsequent `memcpy` operations would then write beyond the allocated buffer, creating a heap corruption primitive. The fix is a single targeted cast to `size_t` that promotes the multiplication to unsigned 64-bit arithmetic before any overflow can occur.

critical

How Stack Buffer Overflows Happen in C with sprintf() and How to Fix Them

A critical stack buffer overflow was discovered in `libuv/Learn-libuv/docs/code/tty-gravity/main.c` where `sprintf()` wrote ANSI escape sequences and user-controlled variables into a fixed 500-byte buffer without any bounds checking. An attacker controlling the `pos`, `width`, or `message` variables could overflow the stack, overwrite return addresses, and potentially achieve arbitrary code execution. The fix replaces `sprintf()` with `snprintf()` and adds explicit length validation to ensure wr

high

How Path Traversal happens in Node.js tmp package and how to fix it

The tmp package version 0.0.33 contained a high-severity path traversal vulnerability (CVE-2026-44705) that allowed attackers to escape temporary directories through unsanitized prefix and postfix parameters. This reddit-app project was upgraded from tmp 0.0.33 to 0.2.7, which implements proper input sanitization to prevent directory traversal attacks and removes the deprecated os-tmpdir dependency.