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 heap buffer overflow happens in C dupstr() and how to fix it

A critical heap buffer overflow vulnerability was discovered in the `dupstr()` function inside `tools/strliteral.c`, where `strcpy()` was called on a heap-allocated buffer without first verifying that `malloc()` had succeeded. If `malloc()` returned `NULL`, the subsequent `strcpy()` would write into address zero — corrupting memory and potentially enabling arbitrary code execution. The fix replaces the unsafe `strcpy()` call with a `NULL` check followed by a bounds-safe `memcpy()`, closing the v

medium

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

A stack buffer overflow in `PMenu_Do_Update()` within `src/menu/menu.c` allowed repeated unchecked `sprintf()` calls to overflow a fixed 1400-byte stack buffer when menu entries contained long text strings. The fix replaces every `sprintf(string + strlen(string), ...)` call with `snprintf(string + len, sizeof(string) - len, ...)`, tracking remaining buffer space and preventing writes beyond the buffer boundary.

critical

How buffer overflow happens in C TinyGSM sprintf and how to fix it

A critical buffer overflow vulnerability was discovered in `TinyGsmClientSequansMonarch.h` at line 515, where `sprintf` was writing a two-character hex string into a buffer only two bytes large — leaving no room for the null terminator. The fix replaces `sprintf` with `snprintf` and increases the buffer to three bytes, preventing a one-byte overflow that could corrupt adjacent memory in embedded firmware.

critical

How out-of-bounds array access happens in C kernel modules and how to fix it

A high-severity out-of-bounds array access vulnerability was discovered in the natflow_conntrack.c kernel module where the `ct->proto.tcp.state` value was used directly as an array index without bounds validation. An attacker capable of manipulating TCP connection state could trigger reads beyond the `tcp_conntrack_names[]` array, potentially leaking kernel memory or causing system crashes. The fix adds a simple bounds check using `ARRAY_SIZE()` before array access.

critical

How buffer overflow in strcpy() happens in C configuration parsing and how to fix it

A critical buffer overflow vulnerability in `src/rpconfig.h` allowed attackers to corrupt memory by providing configuration values exceeding buffer size limits. The `rpcSetText()` function used `strcpy()` to copy user-controlled data into a fixed 256-byte buffer without bounds checking, enabling stack corruption and potential code execution. Replacing `strcpy()` with `strncpy()` and enforcing a 255-byte limit eliminated the overflow risk.

critical

How buffer overflow happens in C gdb-server and how to fix it

A critical buffer overflow vulnerability was discovered in `src/st-util/gdb-server.c` where unbounded `memcpy()` and `strcpy()` calls could write beyond allocated buffer boundaries when processing user-supplied command-line arguments. The fix replaces all unsafe string operations with bounds-checked alternatives like `snprintf()` and `memcpy()` with explicit length validation.