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

high

How insecure string copy functions happen in C calculations.c and how to fix it

A high-severity buffer overflow vulnerability was discovered in `src/calculations.c` at line 37, where a two-step `strncpy` + manual null-termination pattern left the door open for subtle memory safety bugs when copying string data into the `entry->type` field. The fix replaces both lines with a single `snprintf` call that handles bounds and null-termination atomically, eliminating the risk entirely. This is a common C pitfall that affects production CLI tools and can be exploited when attacker-

high

How integer truncation heap overflow happens in C++ UEFI ACPI parsing and how to fix it

A high-severity integer truncation vulnerability was discovered in `Mobility.Uefi.Acpi.cpp` where heap allocation sizes were stored in a 16-bit integer (`MO_UINT16`), causing silent truncation when the computed size exceeded 65535 bytes. This led to undersized heap allocations followed by out-of-bounds writes, exploitable by an attacker who can influence ACPI SRAT table contents in virtualized environments. The fix promotes the size variable to `MO_UINTN` (platform-native width) to prevent trunc

critical

How integer overflow in buffer size calculation happens in C and how to fix it

A critical integer overflow vulnerability was discovered in the `nsh_setvar()` function in `nshlib/nsh_vars.c`, where the buffer size calculation `newsize = pstate->varsz + varlen` could wrap around, causing a heap buffer overflow. The fix adds overflow checking before the addition, preventing attackers with shell access from corrupting memory by setting variables with crafted names and values.

high

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

A high-severity buffer overflow vulnerability was discovered in `profile.c` where `sprintf()` was used to format server addresses without any bounds checking. An attacker who could influence the `SERVER_BASE_PORT` value or trigger integer overflow in the port calculation could write beyond the `server_address` buffer. The fix replaces `sprintf()` with `snprintf()` using explicit buffer size limits at both call sites (lines 99 and 220).

critical

How integer overflow happens in C reliable.c and how to fix it

A critical integer overflow vulnerability was discovered in `reliable.c` at line 1299, where the `packet_buffer_size` calculation used signed `int` arithmetic that could wrap to a negative or undersized value when large `fragment_size` values were involved. By casting each operand to `size_t` before multiplication, the fix eliminates the overflow risk entirely and ensures the allocated buffer is always large enough to hold the reassembled packet data.

high

How insecure string copy functions happen in C (cyw43.c) and how to fix it

Three unsafe string copy calls in `src/cyw43.c` — including a bare `strcpy()` and two `strncpy()` calls — created buffer overflow risks in a CYW43 Wi-Fi driver emulation layer. The fix replaces all three with `snprintf()`, which enforces buffer size limits and guarantees null-termination in a single, consistent operation. Left unaddressed, these vulnerabilities could allow an attacker controlling input like a TAP interface name or SSID to corrupt adjacent memory and potentially execute arbitrary