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 Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published May 13, 2026•Reviewed June 3, 2026

Answer Summary

This is a preprocessor macro buffer overflow vulnerability (CWE-120) in C++ where `#define` directives in DeepSkyStackerKernel's StackWalker.cpp silently replaced safe string functions (`strcpy_s`, `strcat_s`, `sprintf_s`) with their unsafe counterparts (`strcpy`, `strcat`, `sprintf`), discarding buffer size arguments. The fix removes these dangerous macro definitions, restoring the intended memory safety protections and allowing the compiler to enforce bounds checking at all call sites throughout the codebase.

Vulnerability at a Glance

cweCWE-120 (Buffer Copy without Checking Size of Input)
fixRemove all unsafe macro definitions to restore bounds-checking string functions
riskSilent replacement of safe string functions with unsafe versions across entire codebase
languageC++
root causeMacro definitions that substituted `strcpy_s`→`strcpy`, `strcat_s`→`strcat`, `sprintf_s`→`sprintf`
vulnerabilityPreprocessor macro substitution disabling buffer overflow protection

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.


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 reason — strcpy_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.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #230

Related Articles

critical

Aardvark.Cef.Process.Core Shared Memory Handler: Unvalidated memcpy

The shared memory handler in Aardvark.Cef.Process.Core failed to validate that the `length` parameter passed from JavaScript did not exceed the destination buffer size before calling `memcpy`. This allowed a compromised Chromium renderer process to corrupt heap memory by supplying an oversized `handle->length` value to the `openMapping` function. The fix adds explicit bounds checking using `GetArrayBufferByteLength()` before the copy operation.

high

XShmGetImage Heap Corruption: Unvalidated Image Height in streamproxy

The `XShmGetImage` function in the X11 shared memory image path copies pixel data row-by-row using `memcpy` without validating that the source image height matches the destination buffer height. An attacker or compromised server could provide an oversized source image, causing writes beyond the allocated heap buffer and triggering heap corruption or code execution.

critical

SGX Enclave ecall_store_data memcpy Buffer Overflow in 256-Byte

The Intel SGX enclave's trusted bridge functions `ecall_store_data` and `ecall_retrieve_data` used `memcpy()` to move data into and out of a fixed 256-byte `secure_storage` buffer without validating that `data_len` fit within destination boundaries. An attacker providing oversized `data_len` values could corrupt enclave memory, breaking SGX's confidentiality guarantees.

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

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.