Back to Blog
high SEVERITY6 min read

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

A high-severity buffer overflow vulnerability was discovered in `src/GL/arbgenerator.c` where `sprintf()` was used without size bounds checking on an 11-byte buffer. The fix replaces unsafe `sprintf()` calls with `snprintf()`, enforcing strict buffer boundaries and preventing potential heap corruption or code execution attacks.

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

Answer Summary

This is a **buffer overflow vulnerability in C** (CWE-120) caused by using unbounded `sprintf()` function calls in the ARB generator component. The vulnerability exists at lines 52 and 68 of `arbgenerator.c`, where integer values are formatted into a fixed 11-byte buffer without size validation. The fix replaces `sprintf(buf, "%d", varPtr->size)` with `snprintf(buf, sizeof(buf), "%d", varPtr->size)`, enforcing that no more than 11 bytes can be written, eliminating the overflow risk.

Vulnerability at a Glance

cweCWE-120 (Buffer Copy without Checking Size of Input)
fixReplace sprintf() with snprintf() and provide sizeof(buffer) as the size limit
riskAttackers controlling array sizes could overflow the stack buffer, potentially executing arbitrary code or crashing the application
languageC
root causesprintf() writes without respecting buffer boundaries; no size parameter enforces limits
vulnerabilityBuffer overflow via unbounded sprintf()

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

Introduction

In the ARB generator component (src/GL/arbgenerator.c), a critical vulnerability was lurking in plain sight. The generateVariablePre() function uses an 11-byte stack buffer to format integer values, but at line 52 and line 68, it was calling sprintf() without any size checking. This is a textbook buffer overflow vulnerability—the kind that has plagued C developers for decades. An attacker who can control the size of array variables processed by this generator could overflow the buffer, potentially corrupting the stack and executing arbitrary code.

The Vulnerability Explained

Let's look at the exact vulnerable code from the diff:

char buf[11];  // Assume 32-bits array address, should never overflow...
sprintf(buf, "%d", varPtr->size);  // VULNERABLE: No size limit!

The problem is immediately apparent: sprintf() has no knowledge of the buffer size. It will happily write as many bytes as the format string and arguments require, completely ignoring the 11-byte limit of buf.

Consider what happens when varPtr->size is a large integer:
- An int can be up to 2,147,483,647 (2^31 - 1)
- Formatted as a string with "%d", that's 10 characters plus a null terminator = 11 bytes—exactly the buffer size
- But what if varPtr->size is negative? The format string would produce a minus sign, making it 12 bytes
- What if the size value is corrupted or attacker-controlled? It could produce a much longer string

The same issue appears again at line 68:

sprintf(buf, "%zd", i);  // VULNERABLE: No size limit!

Here, i is a size_t being formatted with "%zd". On 64-bit systems, a size_t can produce up to 20 characters—far exceeding the 11-byte buffer.

How This Could Be Exploited

Since this is a CLI tool, exploitation requires the attacker to control command-line arguments or input files. An attacker could craft a specially-formed input file that:

  1. Causes varPtr->size to be set to a negative number or extremely large value
  2. Triggers the sprintf() call in generateVariablePre()
  3. Overflows the 11-byte buf into adjacent stack memory
  4. Corrupts the return address or other critical stack data
  5. Gains control of program execution

For example, if the input file specifies an array with size -2147483648, the format string "%d" would produce the string "-2147483648" (11 characters), exactly filling the buffer. But if the parsing logic has an off-by-one error or if negative values are sign-extended differently on some systems, the overflow becomes possible.

Real-World Impact

This vulnerability affects the ARB (OpenGL ARB extension) code generation pipeline. Any tool or library that uses arbgenerator.c to process untrusted ARB shader definitions could be compromised. The impact ranges from denial of service (crash) to remote code execution, depending on how the buffer overflow is leveraged.


The Fix

The fix is straightforward but critical: replace sprintf() with snprintf() and provide the buffer size as a parameter.

Before (Vulnerable)

char buf[11];
sprintf(buf, "%d", varPtr->size);
APPEND_OUTPUT2(buf)

After (Fixed)

char buf[11];
snprintf(buf, sizeof(buf), "%d", varPtr->size);
APPEND_OUTPUT2(buf)

The key changes in the diff:

  1. Line 52: sprintf(buf, "%d", varPtr->size)snprintf(buf, sizeof(buf), "%d", varPtr->size)
  2. Line 68: sprintf(buf, "%zd", i)snprintf(buf, sizeof(buf), "%zd", i)

How This Solves the Problem

snprintf() accepts a size parameter (sizeof(buf) = 11) that enforces a hard limit on how many bytes can be written. If the formatted output would exceed 10 characters (11 minus 1 for the null terminator), snprintf() truncates the output and null-terminates it safely.

For example:
- sprintf(buf, "%d", -2147483648) → writes 12 bytes, overflows buffer ❌
- snprintf(buf, 11, "%d", -2147483648) → writes exactly 10 bytes + null terminator, safe ✓

The security improvement is concrete: no matter what value is passed, the buffer cannot be overflowed. The worst-case scenario with snprintf() is truncation, not memory corruption.


Prevention & Best Practices

1. Never Use Unbounded String Functions

The C standard library provides dangerous functions that should be avoided entirely in production code:

Dangerous Safe Alternative Why
sprintf() snprintf() Accepts size limit
strcpy() strncpy() or strlcpy() Accepts size limit
strcat() strncat() or strlcat() Accepts size limit
gets() fgets() Accepts size limit

2. Always Use sizeof() for Buffer Sizes

char buffer[256];
snprintf(buffer, sizeof(buffer), "Format: %s", data);  // ✓ Correct
snprintf(buffer, 256, "Format: %s", data);            // ⚠ Error-prone
snprintf(buffer, strlen(data), "Format: %s", data);   // ❌ Wrong

Using sizeof(buffer) ensures the size matches the actual buffer, even if the buffer is later resized.

3. Enable Compiler Warnings

Modern compilers can warn about unsafe functions:

gcc -Wall -Wextra -Wformat -Wformat-security -Wno-format-nonliteral arbgenerator.c
clang -Wall -Wextra -Wformat -Wformat-security arbgenerator.c

4. Use Static Analysis Tools

Tools like Semgrep, Clang Static Analyzer, and Coverity can automatically detect these patterns:

semgrep --config=p/security-audit src/GL/arbgenerator.c

5. Reference Security Standards

  • CWE-120: Buffer Copy without Checking Size of Input
  • CWE-119: Improper Restriction of Operations within the Bounds of a Memory Buffer
  • OWASP: Buffer Overflow Prevention Cheat Sheet

Key Takeaways

  • Never use sprintf() without snprintf(): The 11-byte buffer in arbgenerator.c could be overflowed by any input producing more than 10 characters of output.
  • sizeof(buffer) is your friend: Always pass the actual buffer size to bounded functions; it's the compiler's way of helping you stay safe.
  • Negative numbers and edge cases matter: The vulnerability in arbgenerator.c could be triggered by negative array sizes or large size_t values that produce longer formatted strings.
  • Static analysis catches what code review misses: The Semgrep rule utils.custom.buffer-overflow-strcpy flagged this exact pattern automatically, demonstrating the value of automated security scanning.
  • Defense in depth: Even if input validation exists elsewhere, using snprintf() provides a critical second layer of protection against buffer overflow.

How Orbis AppSec Detected This

Source: The varPtr->size and loop index i values, which are derived from parsed input data in the ARB generator, are user-influenced.

Sink: The sprintf(buf, "%d", varPtr->size) call at line 52 and sprintf(buf, "%zd", i) at line 68 in src/GL/arbgenerator.c, which write directly to a fixed 11-byte stack buffer without bounds checking.

Missing control: No size parameter limits how many bytes sprintf() can write; the buffer size is not enforced by the function call.

CWE: CWE-120 (Buffer Copy without Checking Size of Input)

Fix: Replace sprintf() with snprintf() and pass sizeof(buf) as the maximum size parameter, ensuring writes never exceed the buffer boundary.

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 are not relics of the past—they remain a critical security risk in modern codebases. The arbgenerator.c vulnerability demonstrates how easily these issues can slip past code review: a single missing size parameter in a format function call can compromise the entire application.

The fix is simple and effective: use snprintf() instead of sprintf(). This single change enforces a hard boundary on buffer writes, eliminating the overflow risk entirely. By adopting this practice across your codebase and leveraging static analysis tools to catch violations automatically, you can significantly reduce your attack surface and build more secure software.

Remember: in C, size matters. Always specify it.


References

Frequently Asked Questions

What is a buffer overflow via sprintf()?

It occurs when sprintf() writes more data to a buffer than it can hold, overwriting adjacent memory and potentially corrupting the stack or heap.

How do you prevent buffer overflow in C?

Use size-bounded functions like snprintf(), strncpy(), or strncat() that accept a maximum size parameter, and always pass sizeof(buffer) or the actual buffer size.

What CWE is this buffer overflow?

CWE-120 (Buffer Copy without Checking Size of Input) and CWE-674 (Uncontrolled Recursion).

Is input validation enough to prevent this overflow?

No—even with validation, sprintf() can overflow if the format string or buffer size assumptions are wrong. Use snprintf() as a defense-in-depth measure.

Can static analysis detect this vulnerability?

Yes—Semgrep's `utils.custom.buffer-overflow-strcpy` rule caught this exact pattern, flagging unsafe sprintf() calls in production code.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #38

Related Articles

critical

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

A critical heap buffer overflow vulnerability was discovered in the `dupstr()` function inside `tools/strliteral.c`, where `strcpy()` was called on a heap-allocated buffer without first verifying that `malloc()` had succeeded. If `malloc()` returned `NULL`, the subsequent `strcpy()` would write into address zero — corrupting memory and potentially enabling arbitrary code execution. The fix replaces the unsafe `strcpy()` call with a `NULL` check followed by a bounds-safe `memcpy()`, closing the v

medium

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

A stack buffer overflow in `PMenu_Do_Update()` within `src/menu/menu.c` allowed repeated unchecked `sprintf()` calls to overflow a fixed 1400-byte stack buffer when menu entries contained long text strings. The fix replaces every `sprintf(string + strlen(string), ...)` call with `snprintf(string + len, sizeof(string) - len, ...)`, tracking remaining buffer space and preventing writes beyond the buffer boundary.

critical

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

A critical buffer overflow vulnerability was discovered in `TinyGsmClientSequansMonarch.h` at line 515, where `sprintf` was writing a two-character hex string into a buffer only two bytes large — leaving no room for the null terminator. The fix replaces `sprintf` with `snprintf` and increases the buffer to three bytes, preventing a one-byte overflow that could corrupt adjacent memory in embedded firmware.

critical

How out-of-bounds array access happens in C kernel modules and how to fix it

A high-severity out-of-bounds array access vulnerability was discovered in the natflow_conntrack.c kernel module where the `ct->proto.tcp.state` value was used directly as an array index without bounds validation. An attacker capable of manipulating TCP connection state could trigger reads beyond the `tcp_conntrack_names[]` array, potentially leaking kernel memory or causing system crashes. The fix adds a simple bounds check using `ARRAY_SIZE()` before array access.

critical

How buffer overflow in strcpy() happens in C configuration parsing and how to fix it

A critical buffer overflow vulnerability in `src/rpconfig.h` allowed attackers to corrupt memory by providing configuration values exceeding buffer size limits. The `rpcSetText()` function used `strcpy()` to copy user-controlled data into a fixed 256-byte buffer without bounds checking, enabling stack corruption and potential code execution. Replacing `strcpy()` with `strncpy()` and enforcing a 255-byte limit eliminated the overflow risk.

critical

How buffer overflow happens in C gdb-server and how to fix it

A critical buffer overflow vulnerability was discovered in `src/st-util/gdb-server.c` where unbounded `memcpy()` and `strcpy()` calls could write beyond allocated buffer boundaries when processing user-supplied command-line arguments. The fix replaces all unsafe string operations with bounds-checked alternatives like `snprintf()` and `memcpy()` with explicit length validation.