Stack Buffer Overflow in fontconvert.c: How strcpy() Without Bounds Checking Can Crash Your System
Introduction
If you've ever worked with the Adafruit GFX Library — one of the most widely used graphics libraries in the Arduino and embedded systems ecosystem — you may have used the fontconvert tool to convert TrueType fonts into C header files for use on small displays. It's a handy utility, but buried inside it was a textbook security vulnerability: a stack buffer overflow caused by a call to strcpy() with no bounds checking whatsoever.
This vulnerability, classified as CWE-120 (Buffer Copy without Checking Size of Input), has been assigned a HIGH severity rating. It's a reminder that even developer tooling — code that never ships to end users — can carry serious security risks, especially when it lives inside automated build systems, CI/CD pipelines, or shared development environments.
In this post, we'll break down exactly what went wrong, how it could be exploited, and how the fix closes the door on this class of vulnerability.
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 designed to hold. The stack is a region of memory that stores local variables, function parameters, and — critically — return addresses that tell the CPU where to jump after a function completes.
When you overflow a stack buffer, you can overwrite that return address. In a worst-case exploitation scenario, an attacker replaces the return address with a pointer to malicious code. When the function returns, instead of going back to the legitimate caller, execution jumps to the attacker's payload.
This is one of the oldest and most well-documented vulnerability classes in software security. It was the mechanism behind the Morris Worm of 1988 — the first major internet worm — and it remains a live threat today wherever unsafe C string functions are used carelessly.
The Vulnerability Explained
Where It Lives
The vulnerable code is located in:
libraries/Adafruit-GFX-Library-1.11.10/fontconvert/fontconvert.c
Line 106
What the Code Does
The fontconvert tool takes a font filename and a size as command-line arguments, then generates a C-compatible symbol name (like FreeSans9pt7b) to use in the output header file. To do this, it extracts the base filename, strips the extension, and appends the font size and bit depth.
Here's the vulnerable code path:
// BEFORE (vulnerable)
strcpy(fontName, ptr);
// ...
sprintf(ptr, "%dpt%db", size, (last > 127) ? 8 : 7);
Why This Is Dangerous
Let's trace the problem step by step:
ptris derived from the font filename passed as a command-line argument.fontNameis a fixed-size, stack-allocated buffer.strcpy(fontName, ptr)copies the contents ofptrintofontNamewithout checking whetherptrfits.- If the filename is longer than the allocated buffer size,
strcpyhappily keeps writing — past the end offontName, past adjacent local variables, and potentially over the saved return address on the stack.
The second issue is the sprintf call:
sprintf(ptr, "%dpt%db", size, (last > 127) ? 8 : 7);
sprintf also performs no bounds checking. While the comment in the source says "fontName was alloc'd w/extra space to allow this, we're not sprintfing into Forbidden Zone" — that's only true if the first strcpy didn't already overflow the buffer. It's a fragile assumption.
Real-World Attack Scenario
You might think: "This is a developer tool, not a web server. Who would attack it?"
Consider these scenarios:
- Automated build pipelines: If a CI/CD system automatically runs
fontconverton font files pulled from an external source (e.g., a font repository, a user upload), a maliciously crafted filename could trigger the overflow. - Shared development environments: A developer downloads a font package from an untrusted source. The package contains a font with an excessively long filename. Running
fontconverton it triggers the overflow. - Supply chain attacks: A compromised font file in a dependency could carry a crafted filename designed to exploit this exact flaw.
In any of these cases, the overflow could crash the tool (denial of service) or, with careful crafting, redirect execution to attacker-controlled code.
The Fix
What Changed
The fix replaces both unsafe calls with their bounds-checked equivalents:
// BEFORE (vulnerable)
strcpy(fontName, ptr);
// ...
sprintf(ptr, "%dpt%db", size, (last > 127) ? 8 : 7);
// AFTER (fixed)
snprintf(fontName, strlen(ptr) + 20, "%s", ptr);
// ...
snprintf(ptr, 20, "%dpt%db", size, (last > 127) ? 8 : 7);
How the Fix Works
snprintf vs strcpy/sprintf
The key difference is the second argument — the maximum number of bytes to write. snprintf will never write more than n bytes (including the null terminator), regardless of how long the source string is. If the input is too long, it truncates rather than overflows.
| Function | Bounds Checked? | Safe for Untrusted Input? |
|---|---|---|
strcpy |
❌ No | ❌ No |
sprintf |
❌ No | ❌ No |
strncpy |
✅ Yes (with caveats) | ⚠️ Partially |
snprintf |
✅ Yes | ✅ Yes |
First fix — replacing strcpy:
snprintf(fontName, strlen(ptr) + 20, "%s", ptr);
This limits the write to strlen(ptr) + 20 bytes — enough for the source string plus the size suffix that gets appended later (%dpt%db with reasonable values). The %s format specifier also prevents format string injection as a secondary benefit.
Second fix — replacing sprintf:
snprintf(ptr, 20, "%dpt%db", size, (last > 127) ? 8 : 7);
The size suffix (12pt8b, for example) will never realistically exceed 20 characters, so this bound is safe and explicit. No more relying on comments to justify correctness.
The Security Improvement
By switching to snprintf, the code now has a hard upper bound on how many bytes can be written. Even if a malicious or malformed input is provided, the write stops at the specified limit. The stack frame is protected, and the saved return address cannot be overwritten through this code path.
Prevention & Best Practices
1. Ban strcpy and sprintf from Your Codebase
These functions are fundamentally unsafe for user-controlled or externally-sourced input. Most modern C security guidelines — including SEI CERT C Coding Standard and MISRA C — recommend avoiding them entirely.
Use safer alternatives:
- strcpy → strncpy, strlcpy, or snprintf
- sprintf → snprintf
- strcat → strncat or snprintf
2. Enable Compiler Protections
Modern compilers and linkers offer mitigations that make buffer overflows harder to exploit:
# Stack canaries (detect overwrites at runtime)
gcc -fstack-protector-strong ...
# Address Space Layout Randomization (randomize memory layout)
# Enabled by default on most modern Linux systems
# Fortify source (adds bounds checking to common functions)
gcc -D_FORTIFY_SOURCE=2 -O2 ...
# No-execute stack (prevent code execution on the stack)
gcc -z noexecstack ...
3. Use Static Analysis Tools
Catch these vulnerabilities before they ship:
- cppcheck — Free static analyzer for C/C++
- Clang Static Analyzer — Built into the LLVM toolchain
- Coverity — Free for open source projects
- Flawfinder — Specifically looks for dangerous C functions like
strcpy,sprintf,gets
Running flawfinder on this file would have flagged both strcpy and sprintf immediately.
4. Validate Input Length Early
Don't wait until you're copying a string to discover it's too long. Check at the point of input:
// Check before using
if (strlen(ptr) > MAX_FONT_NAME_LEN) {
fprintf(stderr, "Error: font filename too long (max %d chars)\n", MAX_FONT_NAME_LEN);
exit(1);
}
Fail fast with a clear error message rather than silently overflowing.
5. Consider Modern C++ or Memory-Safe Languages for Tooling
For developer tools like fontconvert, there's often no strict requirement to use C. Rewriting such tools in Rust, Go, or even Python eliminates entire classes of memory safety vulnerabilities by design. Rust, in particular, makes buffer overflows a compile-time error in safe code.
6. Reference Standards
This vulnerability maps to well-known security standards:
- CWE-120: Buffer Copy without Checking Size of Input ('Classic Buffer Overflow')
- CWE-121: Stack-based Buffer Overflow
- OWASP: A03:2021 – Injection (broader category)
- SEI CERT C: STR31-C. Guarantee that storage for strings has sufficient space for character data and the null terminator
Conclusion
The strcpy-without-bounds-checking vulnerability in fontconvert.c is a perfect illustration of why no code is too small or too "internal" to deserve security scrutiny. Developer tools, build scripts, and utilities often receive less attention than production application code — but they run on developer machines, in CI/CD pipelines, and sometimes with elevated privileges.
The fix is straightforward: replace strcpy and sprintf with snprintf, specify explicit bounds, and the vulnerability disappears. Two lines changed. A stack buffer overflow eliminated.
Key takeaways:
- ✅ Never use
strcpyorsprintfwith externally-sourced input — always use bounds-checked alternatives - ✅ Enable compiler stack protection flags in your build system
- ✅ Run static analysis tools as part of your CI/CD pipeline to catch these issues automatically
- ✅ Validate input length at the point of ingestion, not at the point of use
- ✅ Developer tooling deserves the same security review as production code
Memory safety vulnerabilities like this one have been with us for over 35 years. With the right tools, habits, and code review practices, there's no reason they need to persist in new code — or in the libraries developers trust every day.
This vulnerability was identified and fixed by OrbisAI Security as part of an automated security scanning and remediation workflow.