Back to Blog
critical SEVERITY9 min read

Stack Buffer Overflow in C Print Module: How strcpy Almost Broke Everything

A critical stack-based buffer overflow vulnerability was discovered and patched in a C print module, where user-controlled strings were being copied into fixed-size buffers using the unsafe `strcpy` function. This classic CWE-120 vulnerability could have allowed an attacker to overwrite stack memory and potentially hijack program execution. The fix eliminates the unsafe string copy operations, closing a straightforward but dangerous exploitation path.

O
By Orbis AppSec
Published May 9, 2026Reviewed June 3, 2026

Answer Summary

Stack-based buffer overflow (CWE-120) in a C print module occurred when `strcpy` copied user-controlled strings into fixed-size stack buffers without length validation. This classic vulnerability allowed attackers to write beyond buffer boundaries, potentially overwriting return addresses and hijacking control flow. The fix replaced unsafe `strcpy` calls with bounds-checked alternatives like `strncpy` or eliminated direct string copying entirely, preventing memory corruption attacks.

Vulnerability at a Glance

cweCWE-120 (Buffer Copy without Checking Size of Input)
fixEliminated unsafe strcpy calls and implemented bounds-checked string operations
riskArbitrary code execution through stack memory corruption
languageC
root causeUsing strcpy to copy user-controlled strings into fixed-size stack buffers
vulnerabilityStack-based buffer overflow via unsafe strcpy

Stack Buffer Overflow in C Print Module: How strcpy Almost Broke Everything

Severity: Critical | CWE: CWE-120 | File: source/Modules/print/print.c


Introduction

Some vulnerabilities are exotic, requiring elaborate chains of logic flaws to exploit. Others are elegant in their simplicity — and that simplicity is precisely what makes them so dangerous. The vulnerability we're examining today falls firmly into the second category: a classic stack-based buffer overflow caused by the unchecked use of strcpy in a C print module.

If you've written C code for any length of time, you've almost certainly encountered strcpy. It's everywhere in legacy codebases, tutorials, and system-level software. It's also one of the most reliably dangerous functions in the C standard library. This post breaks down exactly why, how this specific vulnerability could have been exploited, and what every systems programmer should do instead.


The Vulnerability Explained

What Is a Stack Buffer Overflow?

When a C program declares a local variable like char buffer[256], the operating system allocates exactly 256 bytes on the call stack for that variable. The stack is a tightly packed region of memory that also stores critical bookkeeping data — including the return address that tells the CPU where to jump back to when the current function finishes.

A buffer overflow occurs when you write more data into that buffer than it can hold. The excess bytes don't disappear — they keep writing, overflowing into adjacent memory. On the stack, that means potentially overwriting the saved return address.

An attacker who can control what gets written — and by how much — can point that return address anywhere they want. Including into shellcode they've injected. This is the essence of stack smashing, and it has been exploited in the wild since at least the 1980s.

What Was Happening in This Code

In source/Modules/print/print.c, the module was performing two dangerous operations:

  1. Line 262: A value sourced from a gadget UI element (user-controlled input) was being copied directly into a fixed-size stack buffer.
  2. Lines 861 & 865: Filenames read from the filesystem were being copied into fixed-size buffers.

In all cases, the copy was performed using strcpy:

// VULNERABLE CODE (illustrative example of the pattern)
char destinationBuffer[256];

// User-controlled value from UI gadget — no length check!
strcpy(destinationBuffer, userControlledGadgetValue);

// Filename from filesystem — also no length check!
char filenameBuffer[128];
strcpy(filenameBuffer, filenameFromDisk);

The strcpy function has one job: copy bytes from source to destination until it hits a null terminator (\0). It does not check whether the destination buffer is large enough. It does not stop early. It does not warn you. It just writes, byte by byte, until it's done — overwriting whatever memory comes next.

Why Both Input Sources Matter

It's worth pausing on the two input sources here, because they represent different threat models:

UI Gadget Value (Line 262):
This is direct user input. An attacker interacting with the application can craft a string of arbitrary length and feed it directly into the vulnerable strcpy call. This is the most straightforward exploitation path — the attacker has near-complete control over the overflow content.

Filesystem Filename (Lines 861, 865):
This is subtler but equally dangerous. If an attacker can influence what files exist on the filesystem — through a file upload feature, a symlink attack, or by compromising another part of the system — they can create files with extremely long names (many filesystems support filenames up to 255 bytes or more). When the print module reads and copies that filename, the overflow is triggered indirectly.

A Concrete Attack Scenario

Imagine the following sequence of events:

  1. A user opens the print dialog in an application using this module.
  2. The print dialog reads a filename from a recently-used files list stored on disk.
  3. An attacker has previously placed a file with a crafted, oversized name in a location the application monitors.
  4. When strcpy copies this filename into the 128-byte filenameBuffer, it writes 200+ bytes, overflowing the buffer.
  5. The excess bytes overwrite the stack frame, including the saved return address.
  6. When the function returns, execution jumps to an attacker-controlled address.
  7. Game over.

On modern systems, mitigations like stack canaries, ASLR (Address Space Layout Randomization), and NX bits raise the bar for exploitation — but they are not insurmountable, especially in environments where these protections are absent or weakly configured.


The Fix

The fix addresses the root cause: replacing unsafe, unchecked string copy operations with length-aware alternatives.

The Correct Approach: strncpy, strlcpy, or snprintf

The C ecosystem offers several safer alternatives to strcpy:

Option 1: strncpy

// Safer — but requires careful use
char destinationBuffer[256];
strncpy(destinationBuffer, userControlledInput, sizeof(destinationBuffer) - 1);
destinationBuffer[sizeof(destinationBuffer) - 1] = '\0'; // Always null-terminate!

⚠️ strncpy does not guarantee null termination if the source is longer than n. You must manually add the null terminator, as shown above.

Option 2: strlcpy (preferred on BSD/macOS)

// Cleaner — always null-terminates, returns length of source
char destinationBuffer[256];
strlcpy(destinationBuffer, userControlledInput, sizeof(destinationBuffer));

strlcpy always null-terminates and returns the length of the source string, making truncation detection straightforward. Not available in all standard C libraries (notably absent from glibc by default).

Option 3: snprintf (portable and flexible)

// Most portable option
char destinationBuffer[256];
snprintf(destinationBuffer, sizeof(destinationBuffer), "%s", userControlledInput);

snprintf is widely available, always null-terminates, and is naturally length-bounded. Many security-conscious codebases prefer it for this reason.

Before and After

// ❌ BEFORE — Vulnerable
char filenameBuffer[128];
strcpy(filenameBuffer, filenameFromDisk);  // No bounds check — overflow possible

// ✅ AFTER — Safe
char filenameBuffer[128];
snprintf(filenameBuffer, sizeof(filenameBuffer), "%s", filenameFromDisk);
// OR
strlcpy(filenameBuffer, filenameFromDisk, sizeof(filenameBuffer));

Why sizeof(buffer) and Not a Magic Number?

Notice the use of sizeof(destinationBuffer) rather than hardcoding 128 or 256. This is intentional and important. If someone later refactors the code and changes the buffer size, a hardcoded length limit might not be updated to match — creating a new off-by-one or overflow opportunity. Using sizeof ties the limit directly to the actual allocation, making the code more resilient to future changes.


Prevention & Best Practices

This vulnerability is a textbook example of a class of bugs that has existed for decades. The good news: it's entirely preventable with the right habits and tooling.

1. Ban strcpy (and Friends) From Your Codebase

Consider adding a linter rule or compiler warning to flag uses of strcpy, strcat, sprintf, and gets. GCC and Clang both support -Wdeprecated-declarations and -D_FORTIFY_SOURCE=2, which can catch some of these at compile time.

# Add to your Makefile or CMakeLists.txt
CFLAGS += -D_FORTIFY_SOURCE=2 -Wall -Wextra

2. Enable Compiler Hardening Flags

Modern compilers can insert stack canaries — sentinel values placed between local variables and the return address. If a buffer overflow overwrites the canary, the program detects the corruption and aborts before the corrupted return address is used.

# GCC stack protection
CFLAGS += -fstack-protector-strong

3. Use Static Analysis Tools

Several excellent tools can catch strcpy and similar patterns automatically:

Tool Type Notes
Coverity Commercial SAST Industry standard for C/C++
CodeQL Free/Open (GitHub) Excellent CWE-120 coverage
Flawfinder Open Source Lightweight, C-focused
Clang Static Analyzer Free Built into LLVM toolchain
cppcheck Open Source Fast, low false-positive rate

Running these tools as part of your CI/CD pipeline ensures that new instances of unsafe functions are caught before they reach production.

4. Consider Using Safer Languages for New Code

Where performance requirements allow, consider writing new modules in languages with built-in memory safety. Rust, in particular, makes buffer overflows essentially impossible at the language level — the borrow checker and bounds-checked slices prevent the entire class of vulnerability. The presence of Rust dependencies in this very project's Cargo.lock suggests the team is already moving in this direction.

5. Validate and Sanitize All Inputs — At Every Layer

Even with safe string functions, you should validate input lengths before processing them:

// Validate before copying
if (strlen(userInput) >= sizeof(destinationBuffer)) {
    // Handle error: input too long
    log_error("Input exceeds maximum allowed length");
    return ERROR_INPUT_TOO_LONG;
}
snprintf(destinationBuffer, sizeof(destinationBuffer), "%s", userInput);

Defense in depth means you shouldn't rely on a single mitigation. Validate inputs, use safe functions, and enable compiler protections.

6. Know Your Standards

This vulnerability is well-documented in established security frameworks:

  • CWE-120: Buffer Copy without Checking Size of Input ('Classic Buffer Overflow')
  • CWE-121: Stack-based Buffer Overflow (a child of CWE-120)
  • OWASP Top 10: A03:2021 – Injection (buffer overflows fall under memory injection)
  • CERT C Coding Standard: Rule STR31-C — Guarantee that storage for strings has sufficient space for character data and the null terminator
  • SANS/CWE Top 25: CWE-120 consistently appears in the most dangerous software weaknesses list

Conclusion

The vulnerability patched in this PR is a reminder that some of the oldest bugs in software engineering are still being written today. strcpy was flagged as dangerous in security literature as far back as the 1990s, yet it continues to appear in production code — often in security-sensitive paths like print modules that handle filenames and user input.

The fix here is straightforward: replace unchecked string copies with length-bounded alternatives. But the broader lesson is about habit and tooling. No developer intentionally writes buffer overflows. They happen when dangerous functions are used without thinking, when code is written under time pressure, or when legacy patterns are copy-pasted without scrutiny.

Key takeaways:

  • 🚫 Never use strcpy, strcat, sprintf, or gets in new code
  • ✅ Use snprintf, strlcpy, or strncpy (with explicit null termination) instead
  • 🔍 Run static analysis tools like CodeQL or Flawfinder on every C/C++ codebase
  • 🛡️ Enable compiler hardening flags (-fstack-protector-strong, -D_FORTIFY_SOURCE=2)
  • 📏 Always validate input lengths before processing
  • 🦀 Consider Rust for new systems-level code where memory safety is critical

Security isn't a feature you add at the end — it's a discipline you build into every line of code. Patches like this one are important, but the goal is to write code that doesn't need them in the first place.


This vulnerability was identified and fixed as part of an automated security scanning and remediation workflow. Continuous security scanning is one of the most effective ways to catch issues like this before they reach production.


References:
- CWE-120: Buffer Copy without Checking Size of Input
- CERT C Coding Standard: STR31-C
- OWASP Buffer Overflow
- Smashing The Stack For Fun And Profit — Aleph One (Phrack, 1996)

Frequently Asked Questions

What is stack-based buffer overflow?

Stack-based buffer overflow occurs when data is written beyond the boundaries of a fixed-size buffer allocated on the stack, potentially overwriting critical memory like return addresses, frame pointers, and local variables. In C, this commonly happens with unsafe functions like strcpy, gets, or sprintf that don't perform bounds checking.

How do you prevent stack-based buffer overflow in C?

Prevent buffer overflows by using safe string functions (strncpy, snprintf, strlcpy), validating input lengths before copying, using compiler protections (stack canaries, ASLR), and employing static analysis tools. Modern C code should avoid strcpy, gets, and sprintf entirely in favor of their bounds-checked alternatives.

What CWE is stack-based buffer overflow?

Stack-based buffer overflow is classified as CWE-120 (Buffer Copy without Checking Size of Input) and is related to CWE-121 (Stack-based Buffer Overflow) and the broader CWE-119 (Improper Restriction of Operations within the Bounds of a Memory Buffer). It's one of the most dangerous vulnerability classes, historically responsible for major exploits.

Is using strncpy enough to prevent buffer overflow?

While strncpy is safer than strcpy, it's not foolproof. It may not null-terminate strings if the source exceeds the destination size, and it still requires correct size calculation. Better approaches include using strlcpy (where available), snprintf for formatted strings, or modern safe string libraries. Always validate that destination buffers are properly null-terminated.

Can static analysis detect stack-based buffer overflow?

Yes, modern static analysis tools like Semgrep, CodeQL, Coverity, and compiler warnings (gcc -Wall -Wextra) can detect many buffer overflow patterns, especially obvious cases like strcpy usage. However, complex overflows involving pointer arithmetic or indirect data flow may require dynamic analysis, fuzzing, or manual code review to identify.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #87

Related Articles

critical

How buffer overflow happens in C tar header parsing and how to fix it

A critical buffer overflow vulnerability was discovered in `microtar/microtar.c` where the `raw_to_header()` and `header_to_raw()` functions used unbounded `strcpy()` and `sprintf()` calls to copy tar header fields. Malicious tar files with non-null-terminated name fields could overflow destination buffers, potentially leading to code execution. The fix replaces all unsafe string operations with bounded alternatives: `memcpy()` with explicit null-termination and `snprintf()` instead of `sprintf(

critical

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

A critical buffer overflow vulnerability was discovered in `src/firmware/src/net/ieee80211.c` at line 1584, where the `ieee80211_input()` function processed raw 802.11 data frames without verifying that the incoming frame was large enough to contain a valid `ieee80211_frame` header. An attacker within wireless range could craft undersized or malformed frames to trigger memory corruption, potentially leading to remote code execution on the firmware. The fix adds a single, targeted bounds check th

high

How buffer overflow from unsafe string copy functions happens in C network interface code and how to fix it

A high-severity buffer overflow vulnerability was discovered in `generic/eth-impl.c`, where unsafe `strncpy()` and `sprintf()` calls could write beyond buffer boundaries when handling network interface names and device filenames. The fix replaced these dangerous functions with bounded `snprintf()` calls that guarantee null-termination and prevent memory corruption.

critical

How buffer overflow in FuzzIxml.c sprintf() happens in C and how to fix it

A critical buffer overflow vulnerability was discovered in `fuzzer/FuzzIxml.c` where `sprintf()` wrote a PID-formatted filename into a fixed 256-byte stack buffer without any bounds checking. The fix replaces `sprintf()` with `snprintf()`, explicitly passing the buffer size to prevent any overflow. While exploitation in this specific fuzzer context requires local access, the pattern is a textbook example of CWE-120 that developers should recognize and eliminate everywhere it appears.

critical

How buffer overflow happens in C HTML parsing and how to fix it

A critical buffer overflow vulnerability in `include/html_parse.h` allowed attackers to overflow buffers by providing malicious HTML input exceeding buffer capacity. The fix adds proper bounds checking before memcpy() operations to prevent memory corruption and potential code execution.

critical

How buffer overflow in memcpy() happens in Node.js N-API bindings and how to fix it

A critical buffer overflow vulnerability was discovered in the GetBufferAsVector() function in examples_nodejs/src/zupt_napi.cpp, where memcpy() copied data from JavaScript Uint8Array buffers without proper bounds validation. This vulnerability could allow attackers to trigger memory corruption by providing maliciously crafted input arrays to the native Node.js module, potentially leading to crashes or arbitrary code execution.