Back to Blog
high SEVERITY8 min read

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

A high-severity buffer overflow vulnerability was discovered in `src/calculations.c` at line 37, where a two-step `strncpy` + manual null-termination pattern left the door open for subtle memory safety bugs when copying string data into the `entry->type` field. The fix replaces both lines with a single `snprintf` call that handles bounds and null-termination atomically, eliminating the risk entirely. This is a common C pitfall that affects production CLI tools and can be exploited when attacker-

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

Answer Summary

This vulnerability is an insecure string copy (CWE-676 / CWE-120) in C, located in `src/calculations.c` at line 37. The `strncpy()` function does not guarantee null-termination when the source string fills the destination buffer, creating a potential buffer overread or overflow. The fix replaces the two-line `strncpy` + manual null-termination pattern with a single `snprintf(entry->type, sizeof(entry->type), "%s", type)` call, which is bounds-safe and always null-terminates the output string.

Vulnerability at a Glance

cweCWE-120 (Buffer Copy without Checking Size of Input), CWE-676 (Use of Potentially Dangerous Function)
fixReplace strncpy + manual null-termination with snprintf(dest, size, "%s", src)
riskBuffer overflow or overread leading to crashes, memory corruption, or code injection
languageC
root causestrncpy() does not null-terminate when source length equals or exceeds destination buffer size
vulnerabilityInsecure string copy function (strncpy without guaranteed null-termination)

How Insecure String Copy Functions Happen in C calculations.c and How to Fix It

Introduction

The src/calculations.c file is the computational heart of this CLI tool — it handles loading output entries, tracking conductivity values, and assembling structured data for downstream processing. Inside the add_loading_output() function, a single strncpy call at line 37 was responsible for copying a string label into the entry->type field of a context structure. On the surface, the code looked responsible: it used strncpy (not the famously dangerous strcpy) and even included a manual null-terminator assignment on the following line. But this two-step pattern contains a subtle, high-severity flaw that Semgrep caught and that a code reviewer could easily miss.

This post breaks down exactly why that pattern is dangerous, how an attacker with control over CLI arguments or input files could exploit it, and how a single-line snprintf replacement closes the vulnerability permanently.


The Vulnerability Explained

The Vulnerable Code

Here is the code as it existed before the fix, in src/calculations.c around line 37:

strncpy(entry->type, type, sizeof(entry->type) - 1);
entry->type[sizeof(entry->type) - 1] = '\0';

At first glance, this looks like a "safe" use of strncpy. The developer:
1. Used strncpy instead of strcpy (good instinct).
2. Passed sizeof(entry->type) - 1 as the size limit to avoid writing past the buffer.
3. Manually null-terminated the last byte of the buffer.

So what's wrong?

Why strncpy Is Still Dangerous Here

strncpy has two well-known failure modes:

  1. No null-termination on exact or overflow fit: If the source string type is exactly sizeof(entry->type) - 1 characters or longer, strncpy fills the buffer completely but writes no null terminator. The manual entry->type[sizeof(entry->type) - 1] = '\0' on the next line patches this — but only if both lines always execute together, in the correct order, with no intervening logic.

  2. Fragility of the two-step pattern: The correctness of this approach depends on two separate statements being kept in sync. If a future developer adds an early return, a conditional branch, or refactors the function, the null-termination line can be separated from the strncpy call, silently reintroducing the vulnerability.

  3. strncpy pads with zeros on short strings: When the source is shorter than the count, strncpy zero-fills the remainder of the destination — a behavior that is almost never desired and can mask bugs in downstream code that relies on the buffer's content.

The Real-World Attack Scenario

This is a local CLI tool where the threat model explicitly notes that attackers can control command-line arguments or input files. The type parameter passed to add_loading_output() originates from parsed input data. Consider this scenario:

  1. An attacker crafts a malicious input file where a loading type label is exactly the size of entry->type (e.g., 16 characters if the buffer is 16 bytes).
  2. Due to a refactoring error that removed the manual null-termination line (easy to miss in a code review), entry->type is no longer null-terminated.
  3. A subsequent strlen(entry->type), printf("%s", entry->type), or string comparison walks past the end of the buffer, reading adjacent memory.
  4. Depending on what follows entry->type in the context_t struct, this could leak sensitive values, corrupt a length field, or — in the worst case — enable a write-what-where condition.

Even with the null-termination line present, the pattern is a maintenance trap: the next developer to touch this function may not realize both lines are a coupled safety mechanism.


The Fix

What Changed

The fix replaces the fragile two-line strncpy pattern with a single, atomic snprintf call:

Before:

strncpy(entry->type, type, sizeof(entry->type) - 1);
entry->type[sizeof(entry->type) - 1] = '\0';

After:

snprintf(entry->type, sizeof(entry->type), "%s", type);

Why This Fix Works

snprintf provides three critical guarantees that make it strictly superior here:

Property strncpy (old) snprintf (new)
Respects buffer size ✅ (with -1 trick) ✅ (natively)
Always null-terminates ❌ (requires extra line) ✅ (always)
Single atomic operation ❌ (two lines) ✅ (one line)
Pads with zeros ✅ (unwanted behavior) ❌ (no padding)

By passing sizeof(entry->type) (not sizeof(entry->type) - 1) as the size argument, snprintf writes at most sizeof(entry->type) - 1 characters of the format result and always places a null terminator in the final byte. This is guaranteed by the C standard for snprintf regardless of whether the source overflows the buffer.

The "%s" format specifier is intentional: it treats type as a plain string with no format interpretation, avoiding any secondary format-string injection risk that a naive snprintf(entry->type, sizeof(entry->type), type) would introduce.

Note on strcpy_s and strlcpy

The Semgrep rule suggests strcpy_s (C11 Annex K), but this is an optional extension not universally available. The PR title also mentions strlcpy, which is available on BSD/macOS but not in glibc by default. snprintf is the most portable safe alternative across all standard C environments, making it the right choice for a cross-platform CLI tool.


Prevention & Best Practices

1. Ban strcpy and strncpy at the Project Level

Add a Semgrep rule or compiler warning configuration that fails the build on any use of strcpy or strncpy. This makes the policy machine-enforced rather than relying on code review.

# .semgrep.yml
rules:
  - id: no-strcpy-strncpy
    patterns:
      - pattern: strcpy(...)
      - pattern: strncpy(...)
    message: "Use snprintf() or strlcpy() instead of strcpy/strncpy"
    severity: ERROR
    languages: [c, cpp]

2. Prefer snprintf for All String Copies Into Fixed Buffers

The pattern snprintf(dest, sizeof(dest), "%s", src) is the most portable safe string copy in C. Use it consistently.

3. Use strlcpy Where Available

On platforms with strlcpy (BSD, macOS, or via libbsd on Linux):

strlcpy(entry->type, type, sizeof(entry->type));

strlcpy always null-terminates and returns the length of the source string (not the destination), making truncation detection straightforward.

4. Validate Input Length Before Copying

For the add_loading_output() function specifically, consider asserting or logging when the type string is truncated:

if (strlen(type) >= sizeof(entry->type)) {
    // Log warning: type label truncated
}
snprintf(entry->type, sizeof(entry->type), "%s", type);

5. Relevant Standards

  • CWE-120: Buffer Copy without Checking Size of Input ("Classic Buffer Overflow")
  • CWE-676: Use of Potentially Dangerous Function
  • OWASP: Buffer Overflow
  • SEI CERT C: STR07-C — Use the bounds-checking interfaces for string manipulation

Key Takeaways

  • The two-step strncpy + manual null-termination pattern in add_loading_output() is a maintenance trap: both lines must always execute together, and any future refactoring that separates them silently reintroduces the vulnerability.
  • strncpy does not guarantee null-termination when the source string length equals or exceeds sizeof(dest) - 1 — the exact condition that matters most for security.
  • snprintf(entry->type, sizeof(entry->type), "%s", type) is one line, always safe, always null-terminated, and requires no follow-up statement to be correct.
  • The "%s" format specifier in the fix is not cosmetic — it prevents the type parameter from being interpreted as a format string, closing a secondary injection vector.
  • Static analysis (Semgrep) caught this in production code that had already gone through human review, demonstrating why automated scanning is an essential layer of defense for C codebases.

How Orbis AppSec Detected This

  • Source: The type parameter of add_loading_output() in src/calculations.c, which originates from parsed CLI arguments or input files controlled by the user.
  • Sink: strncpy(entry->type, type, sizeof(entry->type) - 1) at src/calculations.c:37, where the user-influenced string is copied into a fixed-size struct field without atomic null-termination guarantees.
  • Missing control: The two-line pattern relied on a manually written null-terminator that could be separated from the copy in future refactors; no single atomic operation ensured both bounds-safety and null-termination simultaneously.
  • CWE: CWE-120 — Buffer Copy without Checking Size of Input ("Classic Buffer Overflow"), and CWE-676 — Use of Potentially Dangerous Function.
  • Fix: Replaced the two-line strncpy + null-termination pattern with snprintf(entry->type, sizeof(entry->type), "%s", type), which atomically enforces bounds and guarantees null-termination per the C standard.

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 vulnerability in src/calculations.c is a textbook example of why "almost safe" C string handling is still dangerous. The original strncpy usage was clearly written by someone thinking about safety — they chose strncpy over strcpy and added a null-terminator. But the two-step pattern is inherently fragile: it requires two lines of code to remain coupled forever, in a codebase that will continue to evolve. The snprintf replacement collapses that logic into a single, self-contained, standard-guaranteed operation.

For C developers, the lesson is clear: treat strncpy as a deprecated function for string copying purposes. Reach for snprintf(dest, sizeof(dest), "%s", src) as your default safe string copy idiom — it's portable, atomic, and leaves no room for the null-termination mistake that has caused countless real-world vulnerabilities.


References

Frequently Asked Questions

What is an insecure string copy vulnerability in C?

It occurs when functions like strcpy() or strncpy() are used without proper bounds checking. strcpy() has no size limit at all, and strncpy() won't null-terminate the destination if the source fills the buffer, both of which can lead to buffer overflows or overreads.

How do you prevent insecure string copy vulnerabilities in C?

Use bounds-safe alternatives like snprintf(), strlcpy() (BSD/POSIX), or strcpy_s() (C11 Annex K). Always ensure the destination buffer is null-terminated after any copy operation, and prefer functions that handle this atomically.

What CWE is the insecure string copy vulnerability?

It maps primarily to CWE-120 (Buffer Copy without Checking Size of Input / 'Classic Buffer Overflow') and CWE-676 (Use of Potentially Dangerous Function). In some contexts it also relates to CWE-131 (Incorrect Calculation of Buffer Size).

Is manually adding a null terminator after strncpy() enough to prevent this vulnerability?

It can be sufficient if done correctly every single time, but it is fragile — a developer can forget the extra line, the two-step pattern creates a race-like logic gap, and it adds unnecessary complexity. A single snprintf() call is safer, cleaner, and less error-prone.

Can static analysis detect insecure string copy vulnerabilities?

Yes. Tools like Semgrep, Coverity, and Clang's static analyzer reliably flag strcpy() and strncpy() usage. Semgrep's rule c.lang.security.insecure-use-string-copy-fn detected this exact issue in calculations.c automatically.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #7

Related Articles

high

How integer truncation heap overflow happens in C++ UEFI ACPI parsing and how to fix it

A high-severity integer truncation vulnerability was discovered in `Mobility.Uefi.Acpi.cpp` where heap allocation sizes were stored in a 16-bit integer (`MO_UINT16`), causing silent truncation when the computed size exceeded 65535 bytes. This led to undersized heap allocations followed by out-of-bounds writes, exploitable by an attacker who can influence ACPI SRAT table contents in virtualized environments. The fix promotes the size variable to `MO_UINTN` (platform-native width) to prevent trunc

critical

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

A critical integer overflow vulnerability was discovered in the `nsh_setvar()` function in `nshlib/nsh_vars.c`, where the buffer size calculation `newsize = pstate->varsz + varlen` could wrap around, causing a heap buffer overflow. The fix adds overflow checking before the addition, preventing attackers with shell access from corrupting memory by setting variables with crafted names and values.

high

How buffer overflow via sprintf() happens in C networking code and how to fix it

A high-severity buffer overflow vulnerability was discovered in `profile.c` where `sprintf()` was used to format server addresses without any bounds checking. An attacker who could influence the `SERVER_BASE_PORT` value or trigger integer overflow in the port calculation could write beyond the `server_address` buffer. The fix replaces `sprintf()` with `snprintf()` using explicit buffer size limits at both call sites (lines 99 and 220).

critical

How integer overflow happens in C reliable.c and how to fix it

A critical integer overflow vulnerability was discovered in `reliable.c` at line 1299, where the `packet_buffer_size` calculation used signed `int` arithmetic that could wrap to a negative or undersized value when large `fragment_size` values were involved. By casting each operand to `size_t` before multiplication, the fix eliminates the overflow risk entirely and ensures the allocated buffer is always large enough to hold the reassembled packet data.

high

How insecure string copy functions happen in C (cyw43.c) and how to fix it

Three unsafe string copy calls in `src/cyw43.c` — including a bare `strcpy()` and two `strncpy()` calls — created buffer overflow risks in a CYW43 Wi-Fi driver emulation layer. The fix replaces all three with `snprintf()`, which enforces buffer size limits and guarantees null-termination in a single, consistent operation. Left unaddressed, these vulnerabilities could allow an attacker controlling input like a TAP interface name or SSID to corrupt adjacent memory and potentially execute arbitrary

high

How SSH channel exhaustion happens in Go crypto and how to fix it

CVE-2026-39827 is a high-severity resource exhaustion vulnerability in `golang.org/x/crypto` where an authenticated SSH client can repeatedly open channels to consume server resources without bound. The vulnerability was present in the `cloud/gcp/functions/acmedns` module at version `v0.49.0` and was resolved by upgrading to `v0.52.0`. Left unpatched, this flaw could allow an attacker with valid SSH credentials to degrade or deny service to other users of the affected GCP Cloud Function.