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
Published May 13, 2026Reviewed 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

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.

Frequently Asked Questions

What is preprocessor macro buffer overflow?

It's when `#define` macros silently replace safe, bounds-checking string functions (like `strcpy_s`) with unsafe versions (like `strcpy`) that don't validate buffer sizes, creating buffer overflow vulnerabilities throughout the codebase without any visible code changes.

How do you prevent preprocessor macro buffer overflow in C++?

Never use macros to replace safe string functions with unsafe ones. Always use bounds-checking functions like `strcpy_s`, `strcat_s`, and `sprintf_s`. Enable compiler warnings for deprecated functions (`/sdl` in MSVC, `-Wdeprecated-declarations` in GCC/Clang) and audit all macro definitions in your codebase.

What CWE is preprocessor macro buffer overflow?

CWE-120 (Buffer Copy without Checking Size of Input) and CWE-676 (Use of Potentially Dangerous Function). The preprocessor macros cause the code to use dangerous functions like `strcpy` and `strcat` that don't check buffer boundaries.

Is using strcpy_s enough to prevent preprocessor macro buffer overflow?

No, not if macros redefine `strcpy_s` to call `strcpy`. You must audit all `#define` directives in your codebase to ensure safe functions aren't being silently replaced. The function call looks safe in the source code, but the preprocessor changes it before compilation.

Can static analysis detect preprocessor macro buffer overflow?

Yes, but it requires macro-aware analysis. Tools must expand macros and check the actual function being called, not just the source code text. Semgrep with `--pro` flag, Clang Static Analyzer, and specialized C/C++ security scanners can detect when macros substitute safe functions with unsafe ones.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #230

Related Articles

high

How insecure string copy functions happen in C calculations.c and how to fix it

A high-severity buffer overflow vulnerability was discovered in `src/calculations.c` at line 37, where a two-step `strncpy` + manual null-termination pattern left the door open for subtle memory safety bugs when copying string data into the `entry->type` field. The fix replaces both lines with a single `snprintf` call that handles bounds and null-termination atomically, eliminating the risk entirely. This is a common C pitfall that affects production CLI tools and can be exploited when attacker-

high

How integer truncation heap overflow happens in C++ UEFI ACPI parsing and how to fix it

A high-severity integer truncation vulnerability was discovered in `Mobility.Uefi.Acpi.cpp` where heap allocation sizes were stored in a 16-bit integer (`MO_UINT16`), causing silent truncation when the computed size exceeded 65535 bytes. This led to undersized heap allocations followed by out-of-bounds writes, exploitable by an attacker who can influence ACPI SRAT table contents in virtualized environments. The fix promotes the size variable to `MO_UINTN` (platform-native width) to prevent trunc

critical

How integer overflow in buffer size calculation happens in C and how to fix it

A critical integer overflow vulnerability was discovered in the `nsh_setvar()` function in `nshlib/nsh_vars.c`, where the buffer size calculation `newsize = pstate->varsz + varlen` could wrap around, causing a heap buffer overflow. The fix adds overflow checking before the addition, preventing attackers with shell access from corrupting memory by setting variables with crafted names and values.

high

How buffer overflow via sprintf() happens in C networking code and how to fix it

A high-severity buffer overflow vulnerability was discovered in `profile.c` where `sprintf()` was used to format server addresses without any bounds checking. An attacker who could influence the `SERVER_BASE_PORT` value or trigger integer overflow in the port calculation could write beyond the `server_address` buffer. The fix replaces `sprintf()` with `snprintf()` using explicit buffer size limits at both call sites (lines 99 and 220).

critical

How integer overflow happens in C reliable.c and how to fix it

A critical integer overflow vulnerability was discovered in `reliable.c` at line 1299, where the `packet_buffer_size` calculation used signed `int` arithmetic that could wrap to a negative or undersized value when large `fragment_size` values were involved. By casting each operand to `size_t` before multiplication, the fix eliminates the overflow risk entirely and ensures the allocated buffer is always large enough to hold the reassembled packet data.

high

How insecure string copy functions happen in C (cyw43.c) and how to fix it

Three unsafe string copy calls in `src/cyw43.c` — including a bare `strcpy()` and two `strncpy()` calls — created buffer overflow risks in a CYW43 Wi-Fi driver emulation layer. The fix replaces all three with `snprintf()`, which enforces buffer size limits and guarantees null-termination in a single, consistent operation. Left unaddressed, these vulnerabilities could allow an attacker controlling input like a TAP interface name or SSID to corrupt adjacent memory and potentially execute arbitrary