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 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 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.