Back to Blog
critical SEVERITY7 min read

Stack Buffer Overflow in FTM File Parser: How strcpy() Almost Enabled Arbitrary Code Execution on ESP32

A critical stack buffer overflow vulnerability was discovered in `ftm_file.cpp`, where unchecked `strcpy()` calls allowed attacker-controlled filenames from crafted FTM files to overwrite stack memory, including the saved return address, enabling arbitrary code execution on ESP32 devices. The fix replaces both dangerous `strcpy()` calls with bounds-checked `strncpy()` plus explicit null-termination, eliminating the overflow vector entirely. This is a textbook reminder that unsafe C string functi

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

Answer Summary

This is a stack buffer overflow vulnerability (CWE-121) in C code within `ftm_file.cpp`, caused by using `strcpy()` to copy attacker-controlled filename strings into fixed-size stack buffers without any length checking. On ESP32 targets, this allowed a crafted FTM file to overwrite the saved return address and achieve arbitrary code execution. The fix replaces both `strcpy()` calls with `strncpy()` plus explicit null-termination, enforcing the buffer's maximum length and eliminating the overflow entirely.

Vulnerability at a Glance

cweCWE-121
fixReplace strcpy() with strncpy() and explicit null-termination to enforce buffer size limits
riskArbitrary code execution on ESP32 devices via crafted FTM files
languageC/C++
root causeUnchecked strcpy() copies attacker-controlled filename data into fixed-size stack buffers
vulnerabilityStack Buffer Overflow

Stack Buffer Overflow in FTM File Parser: How strcpy() Almost Enabled Arbitrary Code Execution on ESP32

Introduction

In the world of embedded systems, a single unsafe function call can be the difference between a working device and a fully compromised one. This post breaks down a critical stack buffer overflow vulnerability discovered in an FTM file parser targeting ESP32 hardware — and explains exactly how two lines of code closed a door that could have let attackers run arbitrary code on the device.

If you write C or C++ — especially for embedded targets — this one is required reading.


What Is a Stack Buffer Overflow?

A stack buffer overflow occurs when a program writes more data into a stack-allocated buffer than it was sized to hold. The excess data spills into adjacent stack memory, potentially overwriting:

  • Local variables
  • Saved frame pointers
  • Saved return addresses

That last item is the critical one. When a function returns, the CPU jumps to the address stored on the stack. If an attacker controls what gets written there, they control where the program goes next — which is the definition of arbitrary code execution.

This class of vulnerability is catalogued as CWE-121: Stack-based Buffer Overflow and has been responsible for some of the most significant exploits in computing history, from the Morris Worm (1988) to modern firmware attacks.


The Vulnerability Explained

Where It Lived

The vulnerability existed in two locations inside main/src/storage/ftm_file.cpp:

  • Line 181 — inside the open_ftm() function
  • Line 216 — inside the save_as_ftm() function

Both locations performed the same unsafe operation:

// VULNERABLE CODE
strcpy(current_file, filename);

Why This Is Dangerous

strcpy() copies bytes from the source string to the destination buffer until it hits a null terminator (\0). It performs zero bounds checking. It does not know — or care — how large the destination buffer is.

In this case:
- current_file is a fixed-size stack buffer with a compile-time size
- filename is read directly from an attacker-controlled FTM file

This means anyone who can craft a malicious FTM file and get the device to open it can supply a filename field of arbitrary length. If that filename exceeds the size of current_file, the overflow begins — writing attacker-controlled bytes into adjacent stack memory.

The Attack Scenario

Here's how a real-world exploitation chain could look:

  1. Attacker crafts a malicious .ftm file with an oversized filename field — say, 512 bytes instead of the expected 32.
  2. Victim device opens the file (e.g., loaded from an SD card, received over a network, or synced from a file share).
  3. strcpy() copies all 512 bytes into a buffer sized for far fewer, overflowing into the stack frame.
  4. The saved return address is overwritten with attacker-controlled bytes.
  5. When open_ftm() returns, the CPU jumps to the attacker's chosen address — which could point to shellcode, a ROP chain, or a known function with dangerous side effects.

On a microcontroller like the ESP32, this is especially impactful. Embedded devices often run with minimal memory protections — no ASLR, no stack canaries by default, limited DEP/NX enforcement — making exploitation significantly more reliable than on a hardened desktop OS.

Severity: CRITICAL
CWE: CWE-121 (Stack-based Buffer Overflow)
Attack Vector: Local / Physical (malicious file)
Impact: Arbitrary code execution on ESP32


The Fix

The fix is elegant in its simplicity: replace strcpy() with strncpy() and add explicit null-termination.

Before (Vulnerable)

// open_ftm() - Line 181
strcpy(current_file, filename);

// save_as_ftm() - Line 216
strcpy(current_file, filename);

After (Fixed)

// open_ftm() - Line 181
strncpy(current_file, filename, sizeof(current_file) - 1);
current_file[sizeof(current_file) - 1] = '\0';

// save_as_ftm() - Line 216
strncpy(current_file, filename, sizeof(current_file) - 1);
current_file[sizeof(current_file) - 1] = '\0';

Why This Works

Let's break down each part of the fix:

strncpy(current_file, filename, sizeof(current_file) - 1)

  • strncpy() copies at most n bytes from the source to the destination.
  • Using sizeof(current_file) - 1 as the limit ensures we never write past the end of the buffer, regardless of how long filename is.
  • The -1 reserves space for the null terminator.

current_file[sizeof(current_file) - 1] = '\0'

This line is crucial and often overlooked. strncpy() has a subtle behavior: if the source string is longer than n, it does NOT append a null terminator. Without this explicit assignment, the buffer could be left without proper termination, leading to undefined behavior when the string is later read.

By explicitly setting the last byte to '\0', we guarantee the buffer is always a valid, null-terminated C string — regardless of input length.

The Full Diff

-    strcpy(current_file, filename);
+    strncpy(current_file, filename, sizeof(current_file) - 1);
+    current_file[sizeof(current_file) - 1] = '\0';

Two lines. Two locations. Critical vulnerability eliminated.


Prevention & Best Practices

This vulnerability is a member of a well-known family. Here's how to prevent the entire class:

1. Ban strcpy() (and its dangerous relatives) in your codebase

The following C standard library functions perform no bounds checking and should be avoided in new code:

Dangerous Function Safer Alternative
strcpy() strncpy() + explicit null-term, or strlcpy()
strcat() strncat() or strlcat()
sprintf() snprintf()
gets() fgets()
scanf("%s", ...) scanf("%Ns", ...) with width limit

2. Prefer strlcpy() where available

On platforms that support it (BSD, macOS, and via libraries on Linux/ESP-IDF), strlcpy() is even safer than strncpy() because it always null-terminates and returns the length of the source string, making truncation detection easy:

if (strlcpy(current_file, filename, sizeof(current_file)) >= sizeof(current_file)) {
    // Filename was truncated — handle the error
    return -1;
}

3. Use sizeof() instead of magic numbers

Notice the fix uses sizeof(current_file) rather than a hardcoded constant like 32 or 256. This is important: if the buffer size is ever changed, the bounds check automatically stays in sync. Hardcoded sizes are a maintenance trap.

4. Enable compiler and linker protections

For embedded targets like ESP32 (using ESP-IDF / GCC), enable:

# Stack canaries — detect overflow before return
target_compile_options(myapp PRIVATE -fstack-protector-strong)

# Fortify source — adds runtime checks to string functions
target_compile_definitions(myapp PRIVATE _FORTIFY_SOURCE=2)

Stack canaries won't prevent the overflow, but they will detect it at runtime before the corrupted return address is used, turning a potential RCE into a controlled crash.

5. Validate input at the boundary

The deepest fix is to validate attacker-controlled data before it reaches any buffer operation. If the FTM file format defines a maximum filename length, enforce it when parsing:

if (strlen(filename) >= sizeof(current_file)) {
    DBG_PRINTF("Filename too long: %zu bytes\n", strlen(filename));
    return -EINVAL;
}
strncpy(current_file, filename, sizeof(current_file) - 1);
current_file[sizeof(current_file) - 1] = '\0';

Fail loudly at the parsing stage rather than silently truncating or overflowing later.

6. Use static analysis tools

Tools that can catch this class of vulnerability automatically:

  • Cppcheck — Free static analyzer with buffer overflow detection
  • Clang Static Analyzer — Built into LLVM, excellent C/C++ coverage
  • PVS-Studio — Commercial, very thorough
  • CodeQL — GitHub's semantic code analysis, free for open source
  • OrbisAI Security — AI-powered scanner that flagged this exact vulnerability

Integrate at least one of these into your CI/CD pipeline. This vulnerability would have been caught immediately by any of them.

7. Security Standards Reference

  • CWE-121: Stack-based Buffer Overflow
  • CWE-676: Use of Potentially Dangerous Function
  • OWASP: A03:2021 – Injection (covers injection of malicious data)
  • CERT C: STR31-C — Guarantee sufficient storage for strings

Conclusion

This vulnerability is a perfect case study in why unsafe C string functions are still dangerous in 2024. The code was probably written quickly, with the assumption that filenames would always be "reasonable." But in security, we never get to assume what an attacker will supply.

The key takeaways:

  • strcpy() is unsafe — always use a bounds-checked alternative
  • strncpy() requires explicit null-termination — don't forget the extra line
  • Use sizeof() for buffer limits — not magic numbers
  • Validate attacker-controlled input at the boundary — before it reaches any buffer
  • Enable compiler protections — stack canaries are a cheap safety net
  • Run static analysis in CI — catch these issues before they ship

The fix here was two lines of code. The potential impact without it was arbitrary code execution on a physical device. That ratio — minimal fix cost vs. maximum exploit impact — is exactly why security scanning and code review matter, and why even small, "boring" C utility functions deserve careful attention.

Secure the boring code. That's where the real vulnerabilities hide.


This vulnerability was automatically detected and fixed by OrbisAI Security. Automated security scanning helps catch issues like this before they reach production.

Frequently Asked Questions

What is a stack buffer overflow?

A stack buffer overflow occurs when a program writes more data into a stack-allocated buffer than it can hold, overwriting adjacent memory including saved return addresses, which can allow attackers to redirect code execution.

How do you prevent stack buffer overflow in C?

Replace unsafe functions like strcpy() and gets() with bounds-checked alternatives such as strncpy(), strlcpy(), or snprintf(), and always validate input length before copying into fixed-size buffers.

What CWE is stack buffer overflow?

Stack buffer overflow is classified as CWE-121 (Stack-based Buffer Overflow), a subset of CWE-119 (Improper Restriction of Operations within the Bounds of a Memory Buffer).

Is strncpy() enough to prevent buffer overflow in C?

strncpy() prevents writing past the buffer boundary, but it does not guarantee null-termination if the source string is longer than the limit. Always follow strncpy() with an explicit null-terminator assignment (e.g., buf[n-1] = '\0') for complete safety.

Can static analysis detect stack buffer overflow from strcpy()?

Yes. Static analysis tools like Semgrep, Coverity, CodeQL, and even compiler warnings (-Wstringop-overflow) can flag unsafe strcpy() calls with user-controlled input, making them well-suited for catching this class of vulnerability in CI pipelines.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #7

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.