Back to Blog
critical SEVERITY9 min read

Critical Buffer Overflow in VMS Mail: How strcpy() Became a Security Nightmare

A critical buffer overflow vulnerability was discovered and patched in `sys/vms/vmsmail.c`, where eight unchecked calls to `strcpy()` and `strcat()` allowed externally-sourced mail message content to overflow fixed-size buffers. An attacker capable of sending a crafted VMS mail message could overwrite stack return addresses, potentially achieving arbitrary code execution. The fix replaces all dangerous string operations with bounds-checked `snprintf()` calls, eliminating the overflow risk entire

O
By Orbis AppSec
Published May 28, 2026Reviewed June 3, 2026

Answer Summary

This is a CWE-120 buffer overflow vulnerability in C's VMS mail handling code (sys/vms/vmsmail.c). Eight unchecked strcpy() and strcat() calls allowed externally-sourced mail content to overflow fixed-size stack buffers, enabling attackers to overwrite return addresses and achieve arbitrary code execution. The fix replaces all unsafe string operations with bounds-checked snprintf() calls that respect buffer boundaries.

Vulnerability at a Glance

cweCWE-120 (Buffer Copy without Checking Size of Input)
fixReplace all unsafe string operations with bounds-checked snprintf()
riskArbitrary code execution through crafted VMS mail messages
languageC
root causeEight unchecked strcpy() and strcat() calls copying externally-sourced data
vulnerabilityStack-based buffer overflow via unsafe string operations

Critical Buffer Overflow in VMS Mail: How strcpy() Became a Security Nightmare

Introduction

Few vulnerability classes have haunted systems programmers longer than the humble buffer overflow. First documented in the 1988 Morris Worm — which exploited a gets() overflow in fingerd to propagate across the early internet — buffer overflows remain one of the most dangerous and exploited vulnerability types in systems programming today. Decades later, they still appear in production codebases, and this week's fix in sys/vms/vmsmail.c is a textbook example of why.

This post breaks down a critical-severity buffer overflow discovered in the VMS mail handling code, explains exactly how an attacker could exploit it, and walks through the clean, effective fix that was applied. Whether you're a seasoned C developer or someone who primarily works in memory-safe languages, understanding this class of vulnerability is essential knowledge for anyone who touches systems code.


The Vulnerability Explained

What Went Wrong?

The vulnerable code lives in vmsmail.c, a C source file responsible for parsing broadcast notifications from the VMS operating system — things like incoming mail alerts and phone/talk requests. When a mail notification arrives, the code extracts details like the sender name, folder, and message body, then assembles human-readable strings for display.

The problem? It used strcpy() and strcat() to build those strings into fixed-size buffers (txt_buf and cmd_buf) without any bounds checking whatsoever.

Here's a simplified view of the pattern that appeared eight times throughout the file:

// VULNERABLE: No bounds checking on txt_buf or cmd_buf
txt = p ? strcat(strcpy(txt_buf, "Mail for you"), p) : (char *) 0;

And another example:

// VULNERABLE: p+4 could be arbitrarily long
cmd = strcat(strcpy(cmd_buf, "MSG +"), p + 4);

And another:

// VULNERABLE: buf is externally controlled
txt = strcat(strcpy(txt_buf, "Mail for you: "), buf);

Why Is This Dangerous?

Let's be precise about what strcpy() and strcat() actually do:

  • strcpy(dest, src) — Copies bytes from src to dest until it hits a null terminator (\0). It does not check whether dest is large enough.
  • strcat(dest, src) — Appends src to the end of dest. Again, no size checking.

When the source data comes from an external, attacker-controlled input — like the body of a VMS mail message, a sender name, or a node identifier — and the destination is a fixed-size buffer on the stack or heap, you have a classic stack-based buffer overflow.

What Can an Attacker Do?

Consider a stack layout that looks something like this when the vulnerable function executes:

High addresses
┌─────────────────────┐
   Return Address       Attacker wants to overwrite this
├─────────────────────┤
   Saved Registers      Or these
├─────────────────────┤
   txt_buf[256]         Fixed-size destination buffer
├─────────────────────┤
   cmd_buf[256]      
├─────────────────────┤
   Local Variables   
└─────────────────────┘
Low addresses

If an attacker sends a mail message with a sender name or body that is longer than txt_buf can hold, the strcpy() or strcat() call will happily keep writing bytes past the end of the buffer, overwriting whatever lives above it in memory — including the function's return address.

By carefully crafting the overflow payload, an attacker can:

  1. Overwrite the return address with a pointer to attacker-controlled shellcode
  2. Redirect execution to arbitrary memory locations (e.g., a ROP chain)
  3. Corrupt heap metadata if the buffers are heap-allocated, enabling heap exploitation
  4. Crash the process (denial of service), even if full code execution isn't achieved

Real-World Attack Scenario

Imagine a VMS system running this code where users receive mail notifications. An attacker with the ability to send a VMS mail message — which on a network-connected VMS system could be any authenticated user, or potentially an unauthenticated remote sender depending on configuration — crafts a message like this:

From: ATTACKER@EVILNODE
Subject: [2048 bytes of carefully crafted payload]

When the VMS mail notification is processed, the code attempts to copy the sender name and message content into txt_buf. The payload overflows the buffer, overwrites the return address with the address of the attacker's shellcode embedded in the overflow data, and when the function returns — the attacker's code runs with the privileges of the mail-handling process.

This is not theoretical. Buffer overflows via mail parsing have been exploited in the wild for decades, from Sendmail vulnerabilities in the 1980s to modern email client exploits.


The Fix

What Changed?

The fix is elegant in its simplicity: every dangerous strcpy()/strcat() combination was replaced with snprintf(), which accepts an explicit maximum length argument and guarantees it will never write more bytes than the buffer can hold.

Let's look at each fix in detail.

Fix 1: Mail Notification Text

Before (vulnerable):

txt = p ? strcat(strcpy(txt_buf, "Mail for you"), p) : (char *) 0;

After (safe):

txt = p ? (snprintf(txt_buf, sizeof txt_buf, "Mail for you%s", p), txt_buf) : (char *) 0;

The key improvement: sizeof txt_buf tells snprintf() exactly how many bytes are available. No matter how long p is, snprintf() will write at most sizeof txt_buf - 1 characters and always null-terminate the result.

Fix 2: Folder-Specific Command Buffer

Before (vulnerable):

if (txt && (p = strstri(p, " in ")) != 0) /* specific folder */
    cmd = strcat(strcpy(cmd_buf, "MSG +"), p + 4);

After (safe):

if (txt && (p = strstri(p, " in ")) != 0) { /* specific folder */
    snprintf(cmd_buf, sizeof cmd_buf, "MSG +%s", p + 4);
    cmd = cmd_buf;
}

Notice the fix also adds proper braces around the if body — a nice defensive coding improvement that prevents future "dangling else" or accidental scope bugs.

Fix 3: Fallback Mail Notification

Before (vulnerable):

if (!txt)
    txt = strcat(strcpy(txt_buf, "Mail for you: "), buf);

After (safe):

if (!txt) {
    snprintf(txt_buf, sizeof txt_buf, "Mail for you: %s", buf);
    txt = txt_buf;
}

Again, buf is externally sourced data — potentially attacker-controlled. The snprintf() call ensures it cannot overflow txt_buf regardless of buf's length.

Fix 4: Phone/Talk Request Notification

Before (vulnerable):

txt = strcat(strcpy(txt_buf, "Do you hear ringing?  "), buf);

After (safe):

snprintf(txt_buf, sizeof txt_buf, "Do you hear ringing?  %s", buf);

Why snprintf() Is the Right Tool Here

snprintf() provides three critical safety guarantees that strcpy()/strcat() lack:

Property strcpy/strcat snprintf
Respects buffer size ❌ Never ✅ Always
Null-terminates result ✅ (if no overflow) ✅ Always
Returns useful info ❌ Just the pointer ✅ Number of chars that would have been written
Handles format strings ❌ No ✅ Yes

The return value of snprintf() is also worth noting: it returns the number of characters that would have been written if the buffer were large enough. This means you can detect truncation if needed:

int written = snprintf(txt_buf, sizeof txt_buf, "Mail for you%s", p);
if (written >= (int)sizeof txt_buf) {
    // Truncation occurred — handle if necessary
    log_warning("Mail notification text was truncated");
}

The fix as applied doesn't add truncation detection, but the critical security property — no buffer overflow is possible — is fully achieved.


Prevention & Best Practices

1. Ban strcpy() and strcat() From Your Codebase

These functions have no place in modern C code that handles external input. Consider adding a compiler warning or linter rule:

// Many codebases add this to catch unsafe functions at compile time:
#pragma GCC poison strcpy strcat gets sprintf

This causes a compile-time error if any of these functions are used, forcing developers to use safe alternatives.

2. Use Safe String Alternatives

Unsafe Function Safe Replacement Notes
strcpy(d, s) snprintf(d, sizeof d, "%s", s) Or strlcpy() on BSD/macOS
strcat(d, s) snprintf(d, sizeof d, "%s%s", d, s) Or strlcat() on BSD/macOS
gets(s) fgets(s, sizeof s, stdin) gets() is removed in C11
sprintf(d, ...) snprintf(d, sizeof d, ...) Always specify the size

3. Use sizeof on Stack Buffers, Not Magic Numbers

// BAD: Magic number can get out of sync with actual buffer size
char buf[256];
snprintf(buf, 256, "%s", input);  // If buf size changes, this breaks

// GOOD: sizeof always reflects the actual buffer size
char buf[256];
snprintf(buf, sizeof buf, "%s", input);  // Always correct

4. Consider Static Analysis Tools

Several excellent tools can catch these issues automatically:

  • Clang Static Analyzer — Detects buffer overflows, use-after-free, and more
  • Coverity — Industry-standard static analysis (free for open source)
  • AddressSanitizer (ASan) — Runtime detection of buffer overflows during testing
  • Valgrind — Memory error detection at runtime
  • CodeQL — Semantic code analysis, excellent for finding unsafe string operations

Add these to your CI/CD pipeline to catch vulnerabilities before they reach production.

5. Adopt Memory-Safe Languages Where Possible

For new code, consider languages that eliminate this class of vulnerability by design:

  • Rust — Ownership model prevents buffer overflows at compile time
  • Go — Managed memory, no raw pointer arithmetic
  • Modern C++ with span/string_view — Bounds-checked string handling

For legacy C code that must be maintained, rigorous use of safe APIs combined with static analysis is the pragmatic path forward.

6. Security Standards & References

This vulnerability is well-documented in industry security standards:


Conclusion

The buffer overflow fixed in vmsmail.c is a stark reminder that some of the oldest and most well-understood vulnerability classes are still being found — and still being exploited — in production systems today. Eight separate calls to strcpy() and strcat() with externally-controlled data represented eight separate opportunities for an attacker to crash a process, corrupt memory, or achieve arbitrary code execution.

The fix is clean, minimal, and correct: replace unbounded string operations with snprintf() calls that respect the destination buffer's actual size. No architectural changes required. No complex refactoring. Just disciplined use of the right tool for the job.

Key takeaways:

  • Never use strcpy() or strcat() with external data — they have no concept of buffer boundaries
  • snprintf() is your friend — it's bounds-safe, always null-terminates, and tells you if truncation occurred
  • Use sizeof buf not magic numbers — it stays correct even when buffer sizes change
  • Add static analysis to your CI pipeline — tools like ASan and Clang Static Analyzer catch these issues automatically
  • Treat all external input as hostile — mail content, sender names, node identifiers — any of it could be attacker-controlled

The C language gives developers enormous power and flexibility. With that power comes the responsibility to use safe APIs, validate inputs, and respect buffer boundaries. The developers who internalize these habits write code that stands up to adversarial conditions — and that's what secure software is all about.


This vulnerability was identified and fixed as part of an automated security scanning and remediation process. Automated tooling can catch these issues at scale — but understanding why they're dangerous is what turns a security alert into lasting secure coding knowledge.

Frequently Asked Questions

What is buffer overflow in C string operations?

Buffer overflow occurs when data written to a buffer exceeds its allocated size, overwriting adjacent memory. In C, unsafe functions like strcpy() and strcat() don't check buffer boundaries, allowing attackers to corrupt stack data including return addresses, potentially achieving code execution.

How do you prevent buffer overflow in C?

Use bounds-checked functions like snprintf(), strlcpy(), or strlcat() that accept buffer size parameters. Always validate input length before copying, allocate sufficient buffer space, and enable compiler protections like stack canaries and ASLR.

What CWE is buffer overflow?

Buffer overflow vulnerabilities are classified under CWE-120 (Buffer Copy without Checking Size of Input), with stack-based variants under CWE-121 and heap-based under CWE-122. They're part of the broader CWE-119 (Improper Restriction of Operations within Memory Bounds).

Is input validation enough to prevent buffer overflow?

No. While input validation helps, it's insufficient alone. You must also use bounds-checked functions, as even validated input can cause overflows if buffer sizes are miscalculated. Defense-in-depth requires both validation and safe APIs.

Can static analysis detect buffer overflow?

Yes. Static analysis tools can identify unsafe function calls like strcpy() and strcat(), flag missing bounds checks, and track data flow from untrusted sources to dangerous sinks. Modern tools like Semgrep, CodeQL, and Coverity excel at detecting these patterns.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #187

Related Articles

critical

How buffer overflow happens in C tar header parsing and how to fix it

A critical buffer overflow vulnerability was discovered in `microtar/microtar.c` where the `raw_to_header()` and `header_to_raw()` functions used unbounded `strcpy()` and `sprintf()` calls to copy tar header fields. Malicious tar files with non-null-terminated name fields could overflow destination buffers, potentially leading to code execution. The fix replaces all unsafe string operations with bounded alternatives: `memcpy()` with explicit null-termination and `snprintf()` instead of `sprintf(

critical

How buffer overflow happens in C ieee80211_input() and how to fix it

A critical buffer overflow vulnerability was discovered in `src/firmware/src/net/ieee80211.c` at line 1584, where the `ieee80211_input()` function processed raw 802.11 data frames without verifying that the incoming frame was large enough to contain a valid `ieee80211_frame` header. An attacker within wireless range could craft undersized or malformed frames to trigger memory corruption, potentially leading to remote code execution on the firmware. The fix adds a single, targeted bounds check th

high

How buffer overflow from unsafe string copy functions happens in C network interface code and how to fix it

A high-severity buffer overflow vulnerability was discovered in `generic/eth-impl.c`, where unsafe `strncpy()` and `sprintf()` calls could write beyond buffer boundaries when handling network interface names and device filenames. The fix replaced these dangerous functions with bounded `snprintf()` calls that guarantee null-termination and prevent memory corruption.

critical

How buffer overflow in FuzzIxml.c sprintf() happens in C and how to fix it

A critical buffer overflow vulnerability was discovered in `fuzzer/FuzzIxml.c` where `sprintf()` wrote a PID-formatted filename into a fixed 256-byte stack buffer without any bounds checking. The fix replaces `sprintf()` with `snprintf()`, explicitly passing the buffer size to prevent any overflow. While exploitation in this specific fuzzer context requires local access, the pattern is a textbook example of CWE-120 that developers should recognize and eliminate everywhere it appears.

critical

How buffer overflow happens in C HTML parsing and how to fix it

A critical buffer overflow vulnerability in `include/html_parse.h` allowed attackers to overflow buffers by providing malicious HTML input exceeding buffer capacity. The fix adds proper bounds checking before memcpy() operations to prevent memory corruption and potential code execution.

critical

How buffer overflow in memcpy() happens in Node.js N-API bindings and how to fix it

A critical buffer overflow vulnerability was discovered in the GetBufferAsVector() function in examples_nodejs/src/zupt_napi.cpp, where memcpy() copied data from JavaScript Uint8Array buffers without proper bounds validation. This vulnerability could allow attackers to trigger memory corruption by providing maliciously crafted input arrays to the native Node.js module, potentially leading to crashes or arbitrary code execution.