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

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

Heap Buffer Overflow in Audio Ring Buffer: How a Missing Bounds Check Could Crash Your App

A critical heap buffer overflow vulnerability was discovered in `audio_backend.c`, where the audio ring buffer's `memcpy` operations lacked bounds validation before writing PCM data. Without checking that incoming data sizes fell within the allocated buffer's capacity, a maliciously crafted audio file could corrupt adjacent heap memory, potentially enabling arbitrary code execution. The fix adds a concise pre-flight validation guard that rejects out-of-range write requests before any memory oper

critical

Critical Heap Buffer Overflow in SSDP Control Point: How Unbounded String Operations Put Networks at Risk

A critical heap buffer overflow vulnerability was discovered and patched in the SSDP control point implementation (`ssdp_ctrlpt.c`), where multiple unbounded `strcpy` and `strcat` operations constructed HTTP request buffers without any length validation. Network-received SSDP response fields — including service type strings and location URLs — could be crafted by an attacker to exceed buffer boundaries, potentially enabling arbitrary code execution or denial of service. The fix replaces the unsa

critical

Heap Buffer Overflow in OPDS Parser: How a Misplaced Variable Nearly Opened the Door to Remote Code Execution

A critical heap buffer overflow vulnerability was discovered in `lib/OpdsParser/OpdsParser.cpp`, where the buffer allocation size was calculated *after* a fixed chunk size was used to allocate memory, meaning the actual bytes read could exceed the allocated buffer. On embedded devices parsing untrusted OPDS catalog data from the network, this flaw could allow a remote attacker to corrupt heap memory and potentially achieve arbitrary code execution. The fix was elegantly simple: move the `toRead`

critical

Heap Buffer Overflow in BLE MIDI: How a Missing Bounds Check Opens the Door to Remote Exploitation

A critical heap buffer overflow vulnerability was discovered in the BLE MIDI packet assembly code of `blemidi.c`, where attacker-controlled packet length values could trigger writes beyond allocated heap memory. The fix adds an integer overflow guard before the `malloc` call, ensuring that maliciously crafted BLE MIDI packets can no longer corrupt heap memory. This vulnerability is particularly dangerous because it is remotely exploitable by any nearby Bluetooth device — no physical access requi

critical

Heap Overflow in TOML Parser: How Integer Overflow Leads to Memory Corruption

A critical heap buffer overflow vulnerability was discovered and patched in the centitoml TOML parser, where missing integer overflow validation on a `MALLOC(len+1)` call could allow an attacker to trigger memory corruption via a crafted TOML configuration file. The vulnerability (CWE-190) is reachable through community-distributed mod or map files that the game loads from its `config/` directory, making it a realistic attack vector for remote code execution. A targeted one-line guard now preven

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