Back to Blog
critical SEVERITY8 min read

Stack Buffer Overflow via Unbounded sprintf() in HardInfo2 CPU Utility

A critical stack buffer overflow vulnerability was discovered and patched in HardInfo2's cpu_util.c, where six unbounded sprintf() calls wrote locale-translated CPU topology labels into fixed-size stack buffers without length constraints. An attacker supplying a crafted translation file could overflow the stack buffer, overwrite saved return addresses, and potentially achieve arbitrary code execution. The fix replaces these dangerous calls with length-bounded alternatives, eliminating the overfl

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

Answer Summary

This is a critical stack buffer overflow vulnerability (CWE-121) in HardInfo2's C codebase, specifically in `cpu_util.c`. Six unbounded `sprintf()` calls wrote locale-translated CPU topology labels into fixed-size stack buffers, allowing an attacker with control over a translation file to overflow the stack, overwrite saved return addresses, and potentially execute arbitrary code. The fix replaces every vulnerable `sprintf()` call with `snprintf()`, passing the destination buffer's size as a bound to prevent any write beyond the allocated stack space.

Vulnerability at a Glance

cweCWE-121
fixReplace all six sprintf() calls with length-bounded snprintf() calls
riskArbitrary code execution via crafted translation file
languageC
root causeSix sprintf() calls writing unbounded locale-translated strings into fixed-size stack buffers
vulnerabilityStack Buffer Overflow via Unbounded sprintf()

Critical Stack Buffer Overflow Fixed in HardInfo2: When Translation Files Become Weapons

Introduction

Most developers are familiar with the classic buffer overflow — it's one of the oldest vulnerabilities in the book. Yet despite decades of awareness, unbounded string formatting functions continue to appear in production C code, quietly waiting for the right conditions to become exploitable. This week, a critical severity stack buffer overflow was patched in HardInfo2's cpu_util.c, serving as a timely reminder that even utility code handling "harmless" display labels can harbor serious security flaws.

This vulnerability is particularly interesting because the attack vector isn't network input or user-supplied data in the traditional sense — it's a crafted locale translation file. If you've never considered your gettext .po files as a security boundary, this post is for you.


The Vulnerability Explained

What Is a Stack Buffer Overflow?

Before diving in, a quick refresher: a stack buffer overflow occurs when a program writes more data into a fixed-size buffer allocated on the stack than the buffer can hold. The excess data spills over into adjacent stack memory, potentially overwriting critical control data like saved return addresses or frame pointers. When the function returns, instead of jumping back to legitimate code, the CPU can be redirected to attacker-controlled instructions.

This class of vulnerability is tracked under CWE-121: Stack-based Buffer Overflow and has been the root cause of countless real-world exploits.

The Vulnerable Code

In hardinfo2/cpu_util.c, around lines 266–278, there were six calls to sprintf() that looked something like this:

char sock_str[32];
char core_str[32];

// Dangerous: no length limit specified
sprintf(sock_str, "%s %d", _("Socket"), socket_id);
sprintf(sock_str, "%s %d", _("Core"),   core_id);
sprintf(core_str, "%s %d", _("Book"),   book_id);
sprintf(core_str, "%s %d", _("Drawer"), drawer_id);
// ... and two more similar calls

Two things make this dangerous:

  1. Fixed-size destination buffers: sock_str and core_str are allocated with a fixed size (e.g., 32 bytes) on the stack.
  2. The _() macro: This is the standard gettext internationalization macro. At runtime, _("Socket") doesn't return the literal string "Socket" — it returns whatever translation is loaded from the active locale file. The length of that string is determined entirely at runtime.

Why the _() Macro Is the Critical Factor

Here's where it gets subtle. If these calls used hardcoded string literals, the maximum output length would be predictable and a developer might (correctly) size the buffer accordingly. But because _() is involved, the actual string length is:

  • Determined at runtime by the loaded .mo/.po translation file
  • Potentially unbounded — nothing in the gettext API limits translation string length
  • Controllable by an attacker who can supply a crafted translation file

Consider this: if a malicious or corrupted translation file maps "Socket" to a 200-character string, the sprintf() call will happily write all 200+ characters plus the formatted integer into a 32-byte buffer, smashing the stack.

The Attack Scenario

Normal execution:
  _("Socket")  "Socket" (6 bytes)
  sprintf result: "Socket 3" (8 bytes)  fits in 32-byte buffer

Malicious translation file:
  _("Socket")  "AAAAAAAAAA...AAA" (200 bytes)
  sprintf result: 200+ bytes written into 32-byte buffer  OVERFLOW
  Overwrites: local variables, saved frame pointer, saved return address
  Effect: Arbitrary code execution when function returns

Real-world impact includes:
- Privilege escalation if HardInfo2 runs with elevated privileges (e.g., to access /proc or hardware sensors)
- Code execution on systems where an attacker can influence locale settings or translation files
- Denial of service via reliable application crash

This is especially relevant in multi-user environments, containerized deployments with shared locale directories, or any scenario where locale files are sourced from untrusted locations.


The Fix

What Changed

The fix replaces all six unbounded sprintf() calls with snprintf(), the length-aware counterpart that guarantees it will never write more than N bytes to the destination buffer (including the null terminator).

Before (vulnerable):

char sock_str[64];
char core_str[64];

// No length bound — will overflow if _() returns a long string
sprintf(sock_str, "%s %d", _("Socket"), socket_id);
sprintf(core_str, "%s %d", _("Core"),   core_id);
sprintf(sock_str, "%s %d", _("Book"),   book_id);
sprintf(core_str, "%s %d", _("Drawer"), drawer_id);

After (safe):

char sock_str[64];
char core_str[64];

// Length-bounded — will never overflow the destination buffer
snprintf(sock_str, sizeof(sock_str), "%s %d", _("Socket"), socket_id);
snprintf(core_str, sizeof(core_str), "%s %d", _("Core"),   core_id);
snprintf(sock_str, sizeof(sock_str), "%s %d", _("Book"),   book_id);
snprintf(core_str, sizeof(core_str), "%s %d", _("Drawer"), drawer_id);

Why snprintf() Solves the Problem

snprintf(buf, size, fmt, ...) takes an explicit size argument. No matter how long the formatted output would be, it writes at most size - 1 characters and always null-terminates the buffer. The function also returns the number of characters that would have been written, allowing callers to detect truncation if needed.

Key properties of the fix:
- No stack overflow possible: The write is hard-capped at sizeof(buf)
- Using sizeof(buf) instead of a magic number: This ties the limit directly to the actual buffer size, so if the buffer is ever resized, the limit automatically adjusts
- Consistent pattern: All six calls are fixed with the same idiom, eliminating the vulnerability class entirely from this code section

A Note on Truncation

One might ask: "But what if the translation string is legitimately long and gets truncated?" This is a valid concern for functionality, but it's the correct security trade-off. A truncated label in a UI is a minor display issue. A stack buffer overflow is a critical security vulnerability. When in doubt, truncate safely rather than overflow dangerously.

If full translation strings must be supported, the proper solution is dynamic allocation:

// Even safer: dynamically sized buffer
int needed = snprintf(NULL, 0, "%s %d", _("Socket"), socket_id) + 1;
char *sock_str = malloc(needed);
if (sock_str) {
    snprintf(sock_str, needed, "%s %d", _("Socket"), socket_id);
    // ... use sock_str ...
    free(sock_str);
}

Prevention & Best Practices

1. Treat sprintf() as Deprecated in Security-Sensitive Code

In modern C development, sprintf() and strcpy() should be considered legacy functions that require explicit justification to use. Most static analysis tools will flag them by default. Prefer:

Dangerous Safe Alternative
sprintf() snprintf()
strcpy() strncpy(), strlcpy()
strcat() strncat(), strlcat()
gets() fgets()

2. Treat Locale/Translation Data as Untrusted Input

This is the less obvious lesson from this vulnerability. Translation strings sourced via gettext are external data — they come from files on disk that could be modified or replaced. Apply the same skepticism you'd apply to network input:

  • Never assume a translated string fits in a fixed buffer
  • Always use length-bounded formatting when incorporating translated strings
  • Consider validating translation file integrity (e.g., checksums) in security-sensitive applications

3. Use sizeof(buffer) Not Magic Numbers

// Bad: magic number can drift out of sync with actual buffer size
char buf[64];
snprintf(buf, 32, ...);  // ❌ Wrong limit, still dangerous

// Good: limit is always in sync with buffer declaration
char buf[64];
snprintf(buf, sizeof(buf), ...);  // ✅ Always correct

4. Enable Compiler Protections

Modern compilers and linkers offer several mitigations that can limit the exploitability of buffer overflows:

# GCC/Clang: Stack canaries, ASLR-friendly PIE, FORTIFY_SOURCE
gcc -fstack-protector-strong \
    -D_FORTIFY_SOURCE=2 \
    -fpie -Wformat -Wformat-security \
    -o myprogram myprogram.c
  • -fstack-protector-strong: Inserts stack canary values that detect overflows before function return
  • -D_FORTIFY_SOURCE=2: Enables compile-time and runtime checks for unsafe string functions
  • -Wformat-security: Warns about format string vulnerabilities

Note: These are mitigations, not fixes. The correct fix is always to eliminate the overflow in the code itself.

5. Static Analysis Integration

Several tools can catch unbounded sprintf() calls automatically:

  • Clang Static Analyzer: Detects buffer overflows and unsafe API usage
  • Coverity: Free for open source, excellent C/C++ analysis
  • CodeQL: GitHub's semantic code analysis engine (free for open source)
  • Flawfinder: Lightweight tool specifically targeting dangerous C/C++ functions
  • cppcheck: Open-source static analysis for C/C++

Adding one of these to your CI/CD pipeline would have caught this vulnerability before it ever reached production.

6. Relevant Security Standards


Conclusion

This vulnerability is a textbook example of how context transforms risk. A sprintf() call writing a short, hardcoded label string seems harmless. The same call writing a runtime-translated string is a critical security vulnerability. The difference is entirely in understanding where your data comes from and whether its length is bounded.

The key takeaways from this patch:

  1. sprintf() is dangerous whenever the output length isn't provably bounded at compile time. Use snprintf() with sizeof(buffer).
  2. Translation files are external data. Strings returned by _() / gettext() can be arbitrarily long and should be treated with the same caution as user input.
  3. Compiler warnings and static analysis catch these issues before they become CVEs. Integrate them into your build pipeline.
  4. Defense in depth matters: Stack canaries and FORTIFY_SOURCE don't replace correct code, but they raise the bar for exploitation.

The fix here is small — swapping sprintf for snprintf and adding a size argument — but the security impact is significant. Six potential stack smashing vectors, eliminated. This is the essence of secure coding: small, disciplined habits that compound into robust, trustworthy software.

Stay safe, and always bound your buffers. 🔒


This vulnerability was identified and patched by OrbisAI Security. If you maintain C/C++ projects, consider running a security scan to catch similar issues 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 was sized to hold, overwriting adjacent memory including saved return addresses, which can allow an attacker to redirect execution flow.

How do you prevent stack buffer overflow in C?

Replace unbounded string functions like sprintf(), strcpy(), and gets() with their length-bounded counterparts: snprintf(), strncpy(), and fgets(). Always pass the destination buffer's size as a bound, and validate input lengths before writing.

What CWE is stack buffer overflow?

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

Is stack canary protection enough to prevent stack buffer overflow exploitation?

Stack canaries detect overflows at function return time and can prevent exploitation in many cases, but they are not a substitute for fixing the root cause. Canaries can sometimes be bypassed via information leaks or partial overwrites, so fixing the vulnerable sprintf() call with snprintf() is always the correct approach.

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

Yes. Static analysis tools like Semgrep, Coverity, cppcheck, and clang's static analyzer can flag unbounded sprintf() calls writing into fixed-size buffers. Orbis AppSec automatically detected this exact pattern in HardInfo2 and opened a pull request with the fix.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #220

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.