Back to Blog
critical SEVERITY8 min read

Critical Buffer Overflow Fixed: When "Safe" Functions Aren't Safe

A critical vulnerability in DeepSkyStackerKernel's StackWalker.cpp was silently replacing bounds-checking string functions with their unsafe counterparts via preprocessor macros, exposing the entire codebase to buffer overflow attacks. This fix removes the dangerous macro definitions that discarded buffer size arguments, restoring the intended memory safety protections across all call sites. Understanding how this subtle macro trick works is essential for any C/C++ developer working with string

O
By orbisai0security
May 13, 2026
#buffer-overflow#c-cpp#security#cwe-120#memory-safety#preprocessor-macros#secure-coding

Critical Buffer Overflow Fixed: When "Safe" Functions Aren't Safe

Introduction

Imagine you install a deadbolt on your front door, feel secure, and go to sleep — only to discover that someone secretly replaced the lock mechanism with a hollow prop that looks identical from the outside. That's essentially what was happening in DeepSkyStackerKernel/StackWalker.cpp.

A critical vulnerability (V-001, CWE-120) was discovered where preprocessor macros were silently redefining the safe CRT string functions strcpy_s and strcat_s to their unsafe counterparts strcpy and strcat. Developers writing code believed they were using memory-safe functions. They were not.

This kind of vulnerability is particularly dangerous because it's invisible at the call site. Every engineer reading the code sees strcpy_s and reasonably assumes bounds checking is in place. The deception happens at the preprocessor level, long before a human eye or code reviewer would typically look.

If you write C or C++ code — especially in security-sensitive applications — this post is essential reading.


The Vulnerability Explained

What Are strcpy_s and strcat_s?

Microsoft introduced the "safe" CRT functions (strcpy_s, strcat_s, sprintf_s, etc.) as part of the Secure CRT library to address the classic C string handling problem: unbounded memory writes.

The classic strcpy function copies a string from a source to a destination buffer with absolutely no regard for how large the destination buffer is:

// UNSAFE: No bounds checking whatsoever
char dest[16];
strcpy(dest, some_attacker_controlled_string); // 💥 Potential overflow

The safe variant strcpy_s requires the caller to specify the destination buffer size:

// SAFE: Bounds are checked; fails gracefully if exceeded
char dest[16];
strcpy_s(dest, sizeof(dest), some_attacker_controlled_string); // ✅ Protected

If the source string is too long, strcpy_s triggers a constraint handler (typically aborting the program) rather than silently overwriting memory. This is a critical safety guarantee.

The Malicious (or Negligent) Macro

The vulnerability in StackWalker.cpp involved preprocessor macro definitions that looked something like this:

// THE VULNERABLE CODE (conceptual representation)
// Defined somewhere in StackWalker.cpp around line 229

#define strcpy_s(dest, size, src)  strcpy(dest, src)
#define strcat_s(dest, size, src)  strcat(dest, src)

Notice what these macros do:
- They accept the three-argument signature of the safe functions (including the size parameter)
- They silently discard the size argument entirely
- They delegate to the unsafe two-argument versions

This means every single call to strcpy_s or strcat_s in any file that includes or is compiled after this macro definition is silently converted to an unsafe, unbounded string operation. The buffer size argument is thrown away like it never existed.

How Does This Happen in Practice?

The C preprocessor runs before compilation and performs textual substitution. When the compiler sees:

strcpy_s(filename_buffer, MAX_PATH, user_supplied_path);

After macro expansion, it actually compiles:

strcpy(filename_buffer, user_supplied_path);

The MAX_PATH argument vanishes. The compiler never sees it. No warning is generated. The resulting binary contains an unsafe string copy.

What Is a Buffer Overflow, and Why Does It Matter?

A buffer overflow occurs when a program writes data beyond the allocated boundary of a buffer. In C/C++, this results in undefined behavior — but in practice, it means overwriting adjacent memory.

Depending on what's adjacent in memory, an attacker who can control the overflowing string can:

  • Overwrite return addresses on the stack → redirect execution to attacker-controlled code
  • Overwrite function pointers → hijack control flow
  • Overwrite adjacent variables → manipulate program logic
  • Cause crashes → denial of service

This class of vulnerability (CWE-120: Buffer Copy Without Checking Size of Input) has been responsible for some of the most devastating exploits in computing history, from the Morris Worm (1988) to modern RCE vulnerabilities.

Real-World Attack Scenario

DeepSkyStacker processes astronomical image files and metadata. Consider this attack path:

  1. An attacker crafts a malicious image file with an excessively long filename, EXIF metadata field, or embedded symbol name
  2. The application reads this data into a string variable
  3. Code that appears to safely call strcpy_s(buffer, sizeof(buffer), metadata_field) is actually calling strcpy(buffer, metadata_field) due to the macro
  4. The oversized string overflows the buffer, overwriting the stack frame
  5. With careful crafting, the attacker overwrites the return address to point to shellcode or a ROP chain
  6. When the function returns, execution jumps to attacker-controlled code

Even without full code execution, a reliable crash can be weaponized as a denial-of-service attack against the application.


The Fix

What Changed

The fix removes the dangerous macro definitions from StackWalker.cpp. By eliminating these #define statements, the actual strcpy_s and strcat_s implementations from the secure CRT library are restored throughout the codebase.

Before (Vulnerable):

// These macros silently neutered all safe string function calls
#define strcpy_s(dest, size, src)  strcpy(dest, src)
#define strcat_s(dest, size, src)  strcat(dest, src)

After (Fixed):

// Macros removed — strcpy_s and strcat_s now call the real secure CRT implementations
// No redefinition. The safe functions work as intended.

Why This Fix Works

By removing the macro definitions:

  1. strcpy_s calls now reach the real implementation in the secure CRT library, which performs bounds checking
  2. The size argument is no longer discarded — it's used to validate the copy operation
  3. Constraint handling is restored — if a string would overflow, the runtime handles it safely rather than silently corrupting memory
  4. Every call site in the codebase is protected — since the macros affected all downstream code, removing them restores safety everywhere simultaneously

This is a high-leverage fix: one change in one file restores memory safety across potentially hundreds of call sites.

The Broader Security Principle

This vulnerability illustrates an important lesson: security guarantees can be undermined at layers below where developers are looking. A code reviewer examining a function that calls strcpy_s would reasonably conclude it's safe. The vulnerability existed at the preprocessor layer, which most developers never inspect during routine review.


Prevention & Best Practices

1. Audit Preprocessor Macro Definitions

Never redefine standard library security functions with unsafe equivalents. Establish a policy that macros overriding CRT functions are forbidden and enforce it in code review.

Use grep or static analysis to detect such patterns:

# Search for dangerous macro redefinitions
grep -rn "#define strcpy" .
grep -rn "#define strcat" .
grep -rn "#define sprintf" .
grep -rn "#define gets" .

2. Enable Compiler Warnings and Treat Them as Errors

Modern compilers can catch some of these issues. Use aggressive warning levels:

# CMake example
target_compile_options(your_target PRIVATE
    -Wall
    -Wextra
    -Werror
    /W4      # MSVC
    /WX      # MSVC: warnings as errors
)

3. Use Static Analysis Tools

Tools like these can detect unsafe string operations and suspicious macro definitions:

  • Clang-Tidy with bugprone-* and cert-* checks
  • Coverity — specifically detects CWE-120 patterns
  • PVS-Studio — strong C/C++ static analyzer
  • CodeQL — GitHub's semantic analysis platform
  • Semgrep — customizable pattern matching for security rules

Example Semgrep rule to detect this pattern:

rules:
  - id: unsafe-strcpy-macro-redef
    pattern: |
      #define strcpy_s(...)  strcpy(...)
    message: "Dangerous redefinition of safe CRT function"
    severity: ERROR
    languages: [c, cpp]

4. Prefer Modern C++ String Handling

Where possible, use std::string instead of raw character arrays:

// Prefer this:
std::string filename = user_input;  // No buffer overflow possible

// Over this:
char filename[MAX_PATH];
strcpy_s(filename, sizeof(filename), user_input.c_str());  // Still risky

std::string dynamically manages its own memory and cannot overflow in the traditional sense (it may throw std::bad_alloc under extreme conditions, but won't corrupt adjacent memory).

5. Use AddressSanitizer During Development and Testing

AddressSanitizer (ASan) is a compiler instrumentation tool that detects buffer overflows at runtime:

# Compile with AddressSanitizer
clang++ -fsanitize=address -g -o your_app your_app.cpp

# Or with GCC
g++ -fsanitize=address -g -o your_app your_app.cpp

Running your test suite with ASan enabled will catch buffer overflows that static analysis might miss.

6. Understand the Relevant Standards

7. Establish a Banned Functions Policy

Microsoft publishes a list of banned C/C++ functions that should never appear in production code. Enforce this with a pre-commit hook or CI check:

# Functions banned by Microsoft SDL
strcpy, strcat, sprintf, gets, scanf (with %s), ...

Any occurrence of these functions should require explicit justification and security review.


Conclusion

This vulnerability is a masterclass in how subtle and dangerous preprocessor abuse can be. A two-line macro definition silently stripped memory safety protections from an entire codebase, turning every "safe" string operation into a potential buffer overflow — all while looking perfectly fine to anyone reading the code.

The key takeaways for developers:

  1. Preprocessor macros can undermine security guarantees invisibly — audit them carefully, especially in legacy codebases
  2. Never redefine standard security functions — this is a red flag in code review, whether intentional or accidental
  3. Defense in depth matters — use static analysis, runtime sanitizers, and code review together, because no single technique catches everything
  4. The safe CRT functions exist for a reasonstrcpy_s, strcat_s, and their siblings are not optional inconveniences; they are essential protections
  5. High-leverage fixes are worth celebrating — removing these two macro definitions simultaneously restored safety across hundreds of call sites

Buffer overflows have been a known vulnerability class for over four decades, yet they remain consistently in the OWASP Top 10 and CWE Top 25. The reason isn't that developers don't know about them — it's that the C/C++ language makes it remarkably easy to introduce them, sometimes through mechanisms as subtle as a #define in a utility file.

Write safe code. Review your macros. Trust but verify your safety functions.


This vulnerability was identified and fixed by OrbisAI Security. Automated security scanning combined with LLM-assisted code review confirmed both the vulnerability and the fix.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #230

Related Articles

critical

Stack Buffer Overflow in MapScale: How Five Unsafe sprintf Calls Created a Critical Vulnerability

A critical stack-based buffer overflow vulnerability was discovered and patched in `src/mapscale.c`, where five unbounded `sprintf` calls wrote formatted output into fixed-size stack buffers without any bounds checking. An attacker controlling unit text strings could overflow the stack buffer, potentially overwriting the function return address and achieving arbitrary code execution. The fix replaces dangerous `sprintf` calls with their bounds-checked counterparts, eliminating the overflow risk

critical

Heap Buffer Overflows in YAML Parser: How Unchecked memcpy Calls Create Critical Attack Vectors

A critical heap buffer overflow vulnerability was discovered and patched in the YAML parser embedded within an Android VPN application, where five unvalidated `memcpy` calls could allow an attacker to corrupt heap memory by supplying a crafted YAML configuration file. This class of vulnerability is particularly dangerous because it can lead to arbitrary code execution or application crashes in security-sensitive contexts. The fix adds proper bounds validation before each copy operation, eliminat

critical

Heap Buffer Overflow in Lexer: How a Missing Bounds Check Becomes Critical

A critical heap buffer overflow vulnerability was discovered and patched in a C lexer implementation, where accumulated line data could silently overwrite adjacent heap memory due to a missing bounds check before a memcpy operation. This class of vulnerability can lead to arbitrary code execution, data corruption, or application crashes, making it one of the most dangerous bugs a C developer can encounter. The fix reinforces why defensive buffer management is non-negotiable in systems-level code

Critical Buffer Overflow Fixed: When "Safe" Functions Aren't Safe | Fenny Security Blog