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 orbisai0security
May 28, 2026

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.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #187

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