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:
- Attacker crafts a malicious
.ftmfile with an oversizedfilenamefield — say, 512 bytes instead of the expected 32. - Victim device opens the file (e.g., loaded from an SD card, received over a network, or synced from a file share).
strcpy()copies all 512 bytes into a buffer sized for far fewer, overflowing into the stack frame.- The saved return address is overwritten with attacker-controlled bytes.
- 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 mostnbytes from the source to the destination.- Using
sizeof(current_file) - 1as the limit ensures we never write past the end of the buffer, regardless of how longfilenameis. - The
-1reserves 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.
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.