Back to Blog
critical SEVERITY8 min read

Stack Overflow in C: How strcpy and strcat Put Games at Risk

A critical buffer overflow vulnerability was discovered and patched in a shared C header file (common.h) used across an entire suite of games, where unbounded strcpy and strcat calls could allow attackers to overwrite stack memory and hijack program execution. The fix eliminates dangerous unbounded string operations, protecting every game binary that includes this shared header. Understanding this vulnerability is essential for any developer working with C/C++ string handling.

O
By orbisai0security
May 14, 2026

Stack Overflow in C: How strcpy and strcat Put an Entire Game Suite at Risk

Introduction

There's a reason security professionals have been warning about strcpy for decades — and a recently patched vulnerability in a shared game engine header file is a perfect reminder of why that warning still matters today.

A critical-severity buffer overflow was identified and fixed in common.h, a shared header file used across an entire suite of games. The vulnerability stemmed from three unbounded string operations that blindly copied caller-supplied data into fixed-size buffers with zero length validation. Because this header is shared, a single vulnerable file put every game binary in the suite at risk simultaneously.

This is exactly the kind of vulnerability that appears in CVE databases, CTF challenges, and real-world exploits alike. Whether you're a seasoned systems programmer or just starting out with C, understanding how this class of bug works — and how to fix it — is foundational knowledge.


The Vulnerability Explained

What Is a Buffer Overflow?

A buffer overflow occurs when a program writes more data into a buffer (a fixed-size region of memory) than it was designed to hold. The excess data spills into adjacent memory, potentially overwriting critical values like saved return addresses, function pointers, or other variables.

In C, the standard library functions strcpy and strcat are notorious contributors to this class of bug because they perform no bounds checking whatsoever. They copy characters until they hit a null terminator (\0), regardless of whether the destination buffer has room.

The Vulnerable Code

The vulnerability was located at lines 41, 42, and 52 of common.h. Here's a simplified representation of what the dangerous code looked like:

// ❌ VULNERABLE CODE (before fix)

char tmp_path[256];  // Fixed-size buffer on the stack

// Line 41: Copies caller-supplied path with NO length check
strcpy(tmp_path, path);

// Line 42: Appends suffix to already potentially-overflowed buffer
strcat(tmp_path, ".XXXXXX");

// Line 52: Copies scanned name into fixed-size slot without length verification
strcpy(name_buff[location], scanned_name);

Let's break down each dangerous operation:

Problem 1: strcpy(tmp_path, path) — Line 41

tmp_path is a fixed-size buffer (e.g., 256 bytes). The path variable is caller-supplied, meaning it could be any length. If an attacker or even a legitimate user provides a path longer than 255 characters, strcpy will happily write past the end of tmp_path, corrupting whatever lives next to it in memory.

Problem 2: strcat(tmp_path, ".XXXXXX") — Line 42

Even if path just barely fits into tmp_path, appending .XXXXXX (7 more characters) could push it over the edge. This is a compound overflow — the buffer was already at risk, and this line makes it worse.

Problem 3: strcpy(name_buff[location], scanned_name) — Line 52

scanned_name comes from scanning/parsing input. If the scanned name is longer than the slot in name_buff, the overflow will corrupt adjacent entries in the buffer array or other stack/heap data nearby.

Why Is This Classified as CWE-120?

This vulnerability maps to CWE-120: Buffer Copy without Checking Size of Input — also nicknamed "Classic Buffer Overflow." It's one of the oldest and most well-documented vulnerability classes in software security, yet it continues to appear in production code.

The Multiplier Effect: A Shared Header File

What elevates this from a bad bug to a critical one is the architectural detail: common.h is a shared header included by every game in the suite. This means:

  • One vulnerable file = every game binary is vulnerable
  • Patching one file = every game binary is protected
  • An attacker only needs to find one game with this code path exposed

This is a classic example of how shared code amplifies both risk and impact.

How Could an Attacker Exploit This?

Consider a scenario where a game reads a file path from a configuration file, user input, or a network source, then passes it to a function in common.h:

Normal path:    /home/user/saves/game1    (28 chars  fits fine)
Malicious path: /home/user/saves/[300 'A' characters][shellcode address]

On a system without modern mitigations, this overflow could overwrite the saved return address on the stack. When the current function returns, instead of jumping back to legitimate code, the CPU jumps to an address controlled by the attacker — enabling arbitrary code execution.

Even with modern protections like stack canaries, ASLR, and NX bits, buffer overflows can still cause:
- Program crashes / Denial of Service
- Corruption of adjacent variables (logic bugs, privilege escalation)
- Information leaks when combined with other vulnerabilities


The Fix

The fix replaces all unbounded string operations with their length-aware, bounds-checking counterparts. Here's what the corrected code looks like:

// ✅ FIXED CODE (after patch)

#define MAX_PATH_LEN 256
#define MAX_NAME_LEN 64

char tmp_path[MAX_PATH_LEN];

// Line 41: Use strncpy — limits copy to buffer capacity
strncpy(tmp_path, path, sizeof(tmp_path) - 1);
tmp_path[sizeof(tmp_path) - 1] = '\0';  // Ensure null termination

// Line 42: Use strncat — limits append to remaining space
strncat(tmp_path, ".XXXXXX", sizeof(tmp_path) - strlen(tmp_path) - 1);

// Line 52: Validate length before copy
strncpy(name_buff[location], scanned_name, MAX_NAME_LEN - 1);
name_buff[location][MAX_NAME_LEN - 1] = '\0';  // Ensure null termination

Note: Even strncpy has quirks — it doesn't guarantee null termination if the source is longer than n. The explicit null termination on the line after each strncpy call is not optional; it's a required safety measure.

Why This Fix Works

Before After Why It's Better
strcpy(dst, src) strncpy(dst, src, sizeof(dst)-1) Limits copy to buffer size
strcat(dst, suffix) strncat(dst, suffix, space_remaining) Limits append to available space
No null termination guarantee Explicit dst[n] = '\0' Prevents unterminated string bugs

The key insight is simple: every write to a fixed-size buffer must be bounded by the size of that buffer.

An Even Better Alternative: Use Safer Functions

For new code, consider using platform-specific safer alternatives:

// POSIX systems: snprintf for building strings safely
snprintf(tmp_path, sizeof(tmp_path), "%s.XXXXXX", path);

// Windows: use StringCchCopy / StringCchCat
// C11: use strcpy_s / strcat_s (Annex K)
// C++: use std::string and avoid raw buffers entirely

snprintf is particularly elegant for path construction because it handles both the copy and the append in a single, bounds-safe operation.


Prevention & Best Practices

1. Treat strcpy and strcat as Red Flags

Many organizations and style guides ban strcpy and strcat outright in new code. Configure your compiler or linter to warn on their use:

# GCC/Clang: enable fortify source
gcc -D_FORTIFY_SOURCE=2 -O2 your_code.c

# This causes runtime checks on string functions
# and compile-time warnings for detectable overflows

2. Use Static Analysis Tools

These tools can catch buffer overflow risks automatically:

Running any of these tools would have flagged the strcpy/strcat calls in common.h immediately.

3. Enable Compiler Hardening Flags

# Add to your Makefile or CMakeLists.txt
CFLAGS += -fstack-protector-strong    # Stack canaries
CFLAGS += -D_FORTIFY_SOURCE=2         # Runtime buffer checks
CFLAGS += -Wformat -Wformat-security  # Format string warnings
LDFLAGS += -Wl,-z,relro,-z,now        # RELRO protection

These don't replace fixing the bug, but they add layers of defense that make exploitation harder.

4. Apply the Principle of Input Validation

Any time your code accepts external input (file paths, usernames, network data), validate it before using it:

// Always validate input length before processing
if (strlen(path) >= MAX_PATH_LEN) {
    fprintf(stderr, "Error: path too long\n");
    return ERROR_INVALID_INPUT;
}
// Now safe to proceed
strncpy(tmp_path, path, sizeof(tmp_path) - 1);

5. Consider Moving to Memory-Safe Languages

For new projects, languages like Rust, Go, and modern C++ with smart pointers eliminate entire classes of memory safety bugs by design. The irony in this case? The project already had Rust in its dependency tree (via src-tauri/Cargo.lock) — Rust's borrow checker and string types would have made this vulnerability impossible in idiomatic code.

6. Know the Relevant Standards


Key Takeaways

This vulnerability is a textbook example of why C string handling demands constant vigilance:

  1. strcpy and strcat are dangerous — they have no awareness of destination buffer size and should be replaced with bounds-checking alternatives in all new and maintained code.

  2. Shared code multiplies risk — a single vulnerable header file put an entire game suite at risk. Audit your shared libraries and headers with extra scrutiny.

  3. The fix is straightforward — replacing unbounded operations with strncpy/strncat/snprintf and explicitly null-terminating buffers closes the vulnerability cleanly.

  4. Defense in depth matters — compiler hardening flags, static analysis, and input validation all work together to reduce the attack surface.

  5. Automated scanning works — this vulnerability was caught by an automated multi-agent AI scanner, demonstrating the value of integrating security tooling into your CI/CD pipeline.

Buffer overflows have been in the OWASP Top 10 and CWE Top 25 for years — not because they're exotic, but because they keep appearing in production code. The best time to fix a buffer overflow is before it ships. The second best time is right now.


This post was generated as part of an automated security fix workflow by OrbisAI Security. Vulnerability ID: V-001 | Severity: Critical | CWE-120.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #54

Related Articles

high

Thread-Safe Tokenization: Fixing strtok() Reentrancy in Game Script Parsing

A high-severity vulnerability was discovered in `lvl_script_commands.c` where the use of the non-reentrant `strtok()` function during level script parsing created conditions for memory corruption and potential arbitrary code execution. The fix replaces all `strtok()` calls with the thread-safe `strtok_r()` variant, eliminating shared global state that could be exploited through maliciously crafted level files. This change is part of a broader effort to harden the game's script parsing pipeline a

critical

Heap Corruption via Unchecked memcpy: How Integer Overflow Bugs Corrupt Memory in Windows File Operations

A critical buffer overflow vulnerability was discovered in `phlib/nativefile.c`, where multiple `memcpy` calls copied filename and extended-attribute data into fixed-size structures without verifying that source lengths didn't exceed destination buffer boundaries. An attacker supplying an oversized filename or EA name could corrupt adjacent heap memory, potentially enabling arbitrary code execution. The fix replaces unchecked arithmetic with Windows' safe integer helpers (`RtlULongAdd`, `RtlULon

critical

Critical DHCP Heap Overflow: How a Missing Bounds Check Opens the Door to Memory Corruption

A critical heap buffer overflow vulnerability was discovered in a DHCP server implementation where the hardware address length field (`hlen`) from an attacker-controlled packet was trusted without validation, allowing up to 239 bytes of heap corruption. The fix adds a simple bounds check before the memory copy, ensuring the copy length never exceeds the destination buffer size. This type of vulnerability can lead to remote code execution, denial of service, or full system compromise in network-f

critical

Stack Buffer Overflow in Kernel HAL: How vsprintf Almost Became a Ring-0 Exploit

A critical stack buffer overflow vulnerability was discovered in the ARM Hardware Abstraction Layer (HAL) initialization code, where an unchecked `vsprintf()` call could allow an attacker to overwrite the stack frame and achieve arbitrary code execution at the kernel level (ring-0). The fix replaces `vsprintf()` with `vsnprintf()` — a single-character change with enormous security implications. Left unpatched, this vulnerability could have allowed malicious hardware enumeration data or boot-time

critical

Critical Buffer Overflow in RC Device Parser: How One Missing Bounds Check Opens the Door to Memory Corruption

A critical buffer overflow vulnerability was discovered in the RC device request parser (`rcdevice.c`), where incoming packet data was written to a fixed-size buffer using an attacker-controlled length field as the only guard. Because the expected data length was parsed directly from the packet without being validated against the actual allocated buffer size, a malicious packet could overflow the buffer and overwrite adjacent stack or heap memory with arbitrary bytes. The fix adds a single, esse

high

Buffer Overflow in RS-232 Serial Input: How a Missing Length Check Put Embedded Systems at Risk

A critical buffer overflow vulnerability was discovered in `serial.c`, where the `rs232_buffered_input` function could write more bytes than the destination buffer `rs232_ibuff` could hold — with no size limit to stop it. An attacker with access to the RS-232 serial port could exploit this to overwrite adjacent OS memory, including return addresses and critical data structures. The fix adds a simple but essential bounds check that clamps the returned byte count to the actual buffer size.