Back to Blog
critical SEVERITY6 min read

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

A critical buffer overflow vulnerability was discovered in the ArrowTest() function in main/main.c, where sprintf() was writing formatted strings to a 24-byte buffer without bounds checking. By replacing sprintf() with snprintf() and specifying the buffer size, the vulnerability was eliminated, preventing attackers from corrupting heap memory through oversized width or height parameters.

O
By Orbis AppSec
Published July 12, 2026Reviewed July 12, 2026

Answer Summary

This is a buffer overflow vulnerability (CWE-120) in C code where sprintf() writes to a fixed 24-byte buffer without size limits. The vulnerability occurs in main/main.c:147, 155, and 162 where width and height parameters are formatted into coordinates. The fix replaces all three sprintf() calls with snprintf(ascii, sizeof(ascii), ...) to enforce buffer boundaries, preventing heap corruption when large values are provided.

Vulnerability at a Glance

cweCWE-120 (Buffer Copy without Checking Size of Input)
fixReplace sprintf() with snprintf() and specify buffer size limit
riskHeap corruption, potential code execution via memory layout manipulation
languageC
root causesprintf() writes to fixed-size buffer without bounds checking on format string output
vulnerabilityBuffer Overflow in sprintf()

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

The Vulnerability in Context

In the ArrowTest() function of main/main.c, a critical buffer overflow vulnerability was lurking in embedded firmware code that renders graphical arrows on an LCD display. The function accepts width and height parameters from callers and uses them to calculate screen coordinates for drawing. However, these coordinates were being formatted into a small 24-byte buffer using sprintf() without any size checking—a classic recipe for heap corruption.

The vulnerability exists because an attacker with control over width or height values (perhaps through a compromised network peer on the same LAN segment, given this is embedded firmware) could provide large values like 999999, causing the formatted output to exceed the buffer boundary and overwrite adjacent heap memory.

The Vulnerability Explained

Let's examine the vulnerable code from lines 147, 155, and 162 in main/main.c:

// Line 147 - VULNERABLE
sprintf((char *)ascii, "%d,0", width-1);

// Line 155 - VULNERABLE  
sprintf((char *)ascii, "0,%d", height-1);

// Line 162 - VULNERABLE
sprintf((char *)ascii, "%d,%d", width-1, height-1);

The problem is clear when you understand the buffer constraints:

char ascii[24];  // Fixed 24-byte buffer

The sprintf() function has no knowledge of the buffer size and will write as many bytes as needed to format the output. If width or height are large values, the formatted string can easily exceed 24 bytes.

Attack scenario: Suppose an attacker controls the width and height parameters through a network message or configuration file. They provide:
- width = 2147483647 (INT_MAX)
- height = 2147483647

The formatted string "2147483646,2147483646" is 20 characters—still within bounds. But consider:
- width = 999999999999 (12 digits)
- height = 999999999999

The formatted string "999999999998,999999999998" is 25 characters, exceeding the 24-byte buffer by 1 byte. This single byte overflow corrupts the next heap structure, potentially leading to:
- Heap metadata corruption
- Use-after-free vulnerabilities
- Arbitrary code execution if the heap layout is predictable

In embedded firmware contexts, exploiting heap corruption is often more reliable than in general-purpose systems because memory layouts are more predictable and there are fewer mitigations like ASLR.

The Fix

The fix is straightforward but critical: replace all three sprintf() calls with snprintf() and explicitly pass the buffer size:

-   sprintf((char *)ascii, "%d,0",width-1);
+   snprintf((char *)ascii, sizeof(ascii), "%d,0",width-1);

-   sprintf((char *)ascii, "0,%d",height-1);
+   snprintf((char *)ascii, sizeof(ascii), "0,%d",height-1);

-   sprintf((char *)ascii, "%d,%d",width-1, height-1);
+   snprintf((char *)ascii, sizeof(ascii), "%d,%d",width-1, height-1);

What changed:
- sprintf()snprintf(): The size-aware variant
- Added second parameter: sizeof(ascii) = 24 bytes
- Format string and arguments remain unchanged

How this solves the problem:

snprintf() guarantees that it will write at most sizeof(ascii) bytes (including the null terminator). If the formatted output would exceed this limit, snprintf() truncates the string and null-terminates it safely. The buffer is protected from overflow.

For example, with the attack scenario above:
- snprintf() attempts to format "999999999998,999999999998"
- It detects this would require 26 bytes (25 characters + 1 null terminator)
- It truncates to 23 characters: "999999999998,9999999" (fits in 24-byte buffer)
- No overflow occurs

The trade-off is that the coordinate string is truncated, but this is far preferable to heap corruption and potential code execution.

Prevention & Best Practices

1. Never use sprintf() with dynamic input

The C standard library provides safer alternatives:
- snprintf(): Size-limited, returns number of characters that would have been written
- asprintf(): Dynamically allocates buffer (GNU extension)
- strncpy(): For string copying with size limits

2. Use compiler warnings and static analysis

Enable compiler warnings that flag unsafe functions:

gcc -Wall -Wformat -Wformat-overflow -Wformat-truncation main.c

These flags catch many sprintf/strcpy issues at compile time.

3. Apply the "sizeof() pattern"

Always use sizeof() when working with fixed buffers:

char buffer[256];
// Good:
snprintf(buffer, sizeof(buffer), format, args);
// Bad:
snprintf(buffer, 256, format, args);  // Magic number, breaks if buffer size changes

4. Validate input ranges

Even with snprintf(), consider validating that width and height values are within expected ranges:

if (width < 0 || width > MAX_WIDTH || height < 0 || height > MAX_HEIGHT) {
    // Reject invalid values
    return ERROR;
}

5. Use static analysis tools

Tools like:
- Semgrep: Can detect sprintf() without size limits
- Clang Static Analyzer: Finds buffer overflow patterns
- Orbis AppSec: Automatically detects and fixes these issues

CWE Reference: This vulnerability is classified as:
- CWE-120: Buffer Copy without Checking Size of Input
- CWE-119: Improper Restriction of Operations within the Bounds of a Memory Buffer

OWASP Reference: Falls under OWASP A06:2021 – Vulnerable and Outdated Components when unsafe C functions are used in production code.

Key Takeaways

  • sprintf() is never safe with dynamic input: Always use snprintf() with explicit buffer size limits in production code
  • The ArrowTest() function now enforces buffer boundaries: All three formatting calls at lines 147, 155, and 162 are now protected by snprintf() with sizeof(ascii)
  • Embedded firmware is particularly vulnerable: With predictable memory layouts and limited mitigations, heap overflow exploitation is more reliable than in general-purpose systems
  • sizeof() is your friend: Using sizeof(buffer) instead of hardcoded numbers prevents future buffer size changes from reintroducing vulnerabilities
  • Truncation is acceptable: snprintf() truncating output is far better than heap corruption; the LCD will display partial coordinates rather than crashing

How Orbis AppSec Detected This

  • Source: The width and height function parameters to ArrowTest() in main/main.c
  • Sink: Three sprintf() calls at lines 147, 155, and 162 that write to the 24-byte ascii buffer without bounds checking
  • Missing control: No validation of parameter values and no size-limiting wrapper around the formatting function
  • CWE: CWE-120 (Buffer Copy without Checking Size of Input)
  • Fix: Replaced all sprintf() calls with snprintf(ascii, sizeof(ascii), ...) to enforce buffer boundaries

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

Buffer overflow vulnerabilities in C formatting functions represent a critical security risk, especially in embedded firmware where exploitation is more reliable due to predictable memory layouts. The transition from sprintf() to snprintf() in the ArrowTest() function demonstrates that fixing these issues is often as simple as adding two parameters—the buffer size and sizeof().

This vulnerability highlights why security scanning tools are essential for C codebases: unsafe functions can easily slip past code review and testing because they only fail under specific input conditions. By adopting snprintf() as a standard practice, validating input ranges, and using static analysis tools, developers can prevent entire classes of buffer overflow vulnerabilities before they reach production.

For teams maintaining embedded firmware and systems code, treating sprintf() as a code smell and automatically flagging it for review is a practical defense strategy that catches these issues early.


References

Frequently Asked Questions

What is a buffer overflow in C sprintf()?

A buffer overflow occurs when sprintf() writes more data to a buffer than it can hold. Since sprintf() doesn't limit output size, large input values can overflow the buffer and corrupt adjacent memory.

How do you prevent buffer overflow in C formatting functions?

Always use size-limited functions like snprintf() instead of sprintf(), and pass the buffer size as a parameter. For example: snprintf(buf, sizeof(buf), format, args) instead of sprintf(buf, format, args).

What CWE is buffer overflow in sprintf()?

CWE-120 (Buffer Copy without Checking Size of Input) and CWE-119 (Improper Restriction of Operations within the Bounds of a Memory Buffer) both apply to this vulnerability.

Is using strlen() to check buffer space enough to prevent sprintf() overflow?

No, strlen() checks the input string length, not the output of sprintf(). The formatted output can be much longer than the input, so you must use snprintf() with the buffer size.

Can static analysis detect sprintf() buffer overflow?

Yes, static analysis tools can detect sprintf() calls with fixed buffers and flag them as high-risk. Orbis AppSec's multi_agent_ai scanner detected this exact pattern and confirmed the fix.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #57

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.