Back to Blog
critical SEVERITY8 min read

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

A critical integer overflow vulnerability was discovered in `src/api.c`'s `find_config_path()` function, where string lengths were added together without overflow checks before allocating a buffer. An attacker controlling environment variables like `APPDATA`, `HOME`, or `XDG_DATA_HOME` could supply extremely long values to trigger an integer overflow, resulting in an undersized buffer allocation and a subsequent heap buffer overflow. The fix adds explicit overflow guards using `SIZE_MAX` compari

O
By Orbis AppSec
Published June 30, 2026Reviewed June 30, 2026

Answer Summary

This is an integer overflow vulnerability (CWE-120) in C's `find_config_path()` function in `src/api.c`, where `strlen(base) + strlen(DBC_PATH_SUFFIX) + 1` could overflow a `size_t` variable, causing `malloc()` to allocate a buffer far smaller than needed. The fix adds `SIZE_MAX` boundary checks before every size calculation, so if the sum would overflow, the function safely returns `nullptr` instead of allocating an undersized buffer. Additionally, `malloc()` was replaced with `calloc()` for the wide-character buffer to ensure zero-initialization.

Vulnerability at a Glance

cweCWE-120
fixAdded SIZE_MAX overflow guards before each buffer size calculation in find_config_path()
riskHeap buffer overflow via attacker-controlled environment variables
languageC
root causestrlen() results added without checking for size_t overflow before malloc()
vulnerabilityInteger Overflow in Buffer Size Calculation

How Integer Overflow in Buffer Size Calculations Happens in C and How to Fix It

Introduction

The find_config_path() function in src/api.c is responsible for one of the most routine tasks in systems programming: constructing a filesystem path from environment variables and a known suffix. It reads APPDATA on Windows, HOME on macOS, or XDG_DATA_HOME on Linux, appends a constant path suffix (DBC_PATH_SUFFIX), and returns the result. Simple, right?

Not quite. Hidden inside three separate buffer size calculations was a classic integer overflow waiting to be triggered. The vulnerable pattern looked like this:

size_t n = strlen(base) + strlen(DBC_PATH_SUFFIX) + 1;
char *path = (char *)malloc(n);

If an attacker can control the value of base — and they can, because base is derived from environment variables — they can supply a string long enough to make strlen(base) + strlen(DBC_PATH_SUFFIX) + 1 wrap around SIZE_MAX back to a small number. malloc() happily allocates that small buffer, returns a non-NULL pointer, and the subsequent strcpy or snprintf blows right past the end of it.

This is the kind of bug that passes code review because the logic looks correct. You're adding two lengths and a null terminator — what could go wrong? In C, quite a lot.


The Vulnerability Explained

What Actually Goes Wrong

On a 64-bit system, size_t can hold values up to SIZE_MAX (typically 18446744073709551615). If strlen(base) returns a value close to SIZE_MAX, adding even a small number to it causes the result to wrap around to near zero. This is integer overflow.

Here's the specific vulnerable code from src/api.c before the fix, appearing in three separate locations within find_config_path():

Location 1 (Windows path, ~line 53):

// VULNERABLE: no overflow check
size_t n = strlen(base) + strlen(DBC_PATH_SUFFIX) + 1;
char *path = (char *)malloc(n);

Location 2 (HOME-based path, ~line 74):

// VULNERABLE: no overflow check
size_t n = strlen(home) + strlen(DBC_PATH_SUFFIX) + 1;
char *path = (char *)malloc(n);

Location 3 (XDG path with prefix + infix, ~line 94):

// VULNERABLE: three-term addition with no overflow check
size_t n = strlen(prefix) + strlen(infix) + strlen(DBC_PATH_SUFFIX) + 1;
char *path = (char *)malloc(n);

Notice that Location 3 is the most dangerous: it adds three strlen() results together. Each addition is an independent opportunity for overflow.

The Attack Scenario

An attacker with the ability to set environment variables (a realistic capability in many deployment contexts — CI/CD pipelines, containerized environments, or any scenario where a less-privileged process can influence the environment of a more-privileged one) sets HOME to a string of length SIZE_MAX - 5. When find_config_path() runs:

  1. strlen(home) returns SIZE_MAX - 5
  2. strlen(DBC_PATH_SUFFIX) returns, say, 10
  3. The addition: (SIZE_MAX - 5) + 10 + 1 = SIZE_MAX + 6 → wraps to 5
  4. malloc(5) succeeds and returns a valid 5-byte buffer
  5. The subsequent path construction writes tens of bytes into a 5-byte buffer
  6. Heap corruption

On a real system, HOME values this long are unusual but not impossible to construct programmatically. In contexts where this library is consumed by a Node.js application (as noted in the PR's threat model), the attack surface for environment variable manipulation is broader.

Why the NULL Check Doesn't Save You

A common misconception is that checking if (!path) { return nullptr; } after malloc() is a sufficient safety net. It isn't. When the overflow produces a small non-zero value like 5, malloc(5) succeeds. The pointer is valid. The NULL check passes. The overflow happens silently.


The Fix

The fix introduces explicit overflow guards before every buffer size calculation, using SIZE_MAX as the upper bound. It also switches from malloc() to calloc() for the wide-character buffer to ensure zero-initialization.

Before and After: The Three Fixes

Fix 1 — Windows path (lines 53–58):

// BEFORE
size_t n = strlen(base) + strlen(DBC_PATH_SUFFIX) + 1;

// AFTER
size_t base_len = strlen(base);
if (base_len > SIZE_MAX - sizeof(DBC_PATH_SUFFIX)) {
    free(base);
    return nullptr;
}
size_t n = base_len + sizeof(DBC_PATH_SUFFIX);

Fix 2 — HOME-based path (lines 74–79):

// BEFORE
size_t n = strlen(home) + strlen(DBC_PATH_SUFFIX) + 1;

// AFTER
size_t home_len = strlen(home);
if (home_len > SIZE_MAX - sizeof(DBC_PATH_SUFFIX)) {
    return nullptr; // LCOV_EXCL_LINE
}
size_t n = home_len + sizeof(DBC_PATH_SUFFIX);

Fix 3 — XDG three-term path (lines 104–110):

// BEFORE
size_t n = strlen(prefix) + strlen(infix) + strlen(DBC_PATH_SUFFIX) + 1;

// AFTER
size_t prefix_len = strlen(prefix);
size_t infix_len = strlen(infix);
if (prefix_len > SIZE_MAX - infix_len - sizeof(DBC_PATH_SUFFIX)) {
    return nullptr; // LCOV_EXCL_LINE
}
size_t n = prefix_len + infix_len + sizeof(DBC_PATH_SUFFIX);

Fix 4 — Wide-character buffer (line 41):

// BEFORE
wchar_t *wbase = (wchar_t *)malloc((size_t)needed * sizeof(wchar_t));

// AFTER
wchar_t *wbase = (wchar_t *)calloc((size_t)needed, sizeof(wchar_t));

Why These Changes Work

The SIZE_MAX guard pattern is the idiomatic C way to check for size_t overflow before addition. By checking if (base_len > SIZE_MAX - sizeof(DBC_PATH_SUFFIX)), you're asking: "would adding sizeof(DBC_PATH_SUFFIX) to base_len exceed SIZE_MAX?" If yes, return safely. If no, the addition is safe to perform.

sizeof() instead of strlen() for the suffix is a subtle but important improvement. DBC_PATH_SUFFIX is a compile-time string literal. sizeof(DBC_PATH_SUFFIX) includes the null terminator and is evaluated at compile time, eliminating the runtime strlen() call and the need for the explicit + 1. This reduces the number of terms in the addition and makes the intent clearer.

calloc() instead of malloc() for the wide-character buffer ensures the allocated memory is zero-initialized. This prevents potential reads of uninitialized memory if the subsequent GetEnvironmentVariableW call doesn't fully populate the buffer.

The #include <stdint.h> added at the top of the file ensures that SIZE_MAX is available (it's defined in <stdint.h> and <limits.h>).


Prevention & Best Practices

1. Always Guard size_t Arithmetic Before malloc()

The canonical pattern for two-term addition:

if (a > SIZE_MAX - b) {
    // overflow would occur
    return NULL;
}
size_t n = a + b;

For three terms:

if (a > SIZE_MAX - b - c) {
    return NULL;
}
size_t n = a + b + c;

2. Prefer sizeof() for Compile-Time String Literals

When appending a known constant suffix, use sizeof(SUFFIX) instead of strlen(SUFFIX) + 1. It's evaluated at compile time, includes the null terminator automatically, and reduces runtime arithmetic.

3. Use Safe String Functions

Functions like snprintf() with a known buffer size, or POSIX asprintf() which allocates the correct buffer automatically, eliminate manual size calculation entirely:

// asprintf handles allocation for you
char *path = NULL;
if (asprintf(&path, "%s%s", base, DBC_PATH_SUFFIX) == -1) {
    return NULL;
}

4. Enable Compiler Warnings and Sanitizers

  • Compile with -Wall -Wextra -Wformat to catch common issues
  • Use AddressSanitizer (-fsanitize=address) during testing to catch heap overflows at runtime
  • Use UndefinedBehaviorSanitizer (-fsanitize=undefined) to catch integer overflows at runtime

5. Reference Standards


Key Takeaways

  • Never add strlen() results directly before malloc() without a SIZE_MAX guard — this pattern appeared three times in find_config_path() and each instance was independently exploitable.
  • Environment variables are attacker-controlled inputHOME, APPDATA, and XDG_DATA_HOME in find_config_path() are all user-controllable, making this a realistic attack vector rather than a theoretical one.
  • A successful malloc() return does not mean your size calculation was correct — overflow to a small non-zero value will produce a valid but undersized pointer that bypasses NULL checks.
  • sizeof() is safer than strlen() for compile-time constants — switching from strlen(DBC_PATH_SUFFIX) + 1 to sizeof(DBC_PATH_SUFFIX) eliminates a runtime calculation and makes overflow arithmetic simpler to reason about.
  • calloc() over malloc() for buffers that must be initialized — the switch for the wchar_t buffer in the Windows code path removes a potential uninitialized-read risk at negligible cost.

How Orbis AppSec Detected This

  • Source: Attacker-controlled environment variables (APPDATA, HOME, XDG_DATA_HOME) read into the base, home, and prefix variables inside find_config_path() in src/api.c
  • Sink: malloc(n) at lines ~56, ~77, and ~107, where n is computed from unchecked strlen() additions
  • Missing control: No overflow check on size_t arithmetic before the allocation; the sum of strlen() values could silently wrap around SIZE_MAX
  • CWE: CWE-120 — Buffer Copy without Checking Size of Input ("Classic Buffer Overflow")
  • Fix: Added SIZE_MAX subtraction guards before each size calculation so that inputs causing overflow cause the function to return nullptr safely instead of allocating an undersized buffer

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

Integer overflow in buffer size calculations is one of those vulnerabilities that hides in plain sight. The arithmetic in find_config_path() looked reasonable at a glance — add a base path length, add a suffix length, add one for the null terminator. But without explicit overflow guards, that innocuous addition becomes a heap corruption primitive for any attacker who can influence environment variables.

The fix is surgical and clear: check that base_len > SIZE_MAX - sizeof(DBC_PATH_SUFFIX) before computing n, and return nullptr if the check fails. This pattern costs almost nothing in performance and completely eliminates the overflow risk. Combined with the switch from malloc() to calloc() for the wide-character buffer, the repaired find_config_path() function now handles adversarial inputs safely.

If you're writing C code that constructs paths or strings from external inputs, make SIZE_MAX guard checks part of your muscle memory. They're cheap, readable, and the difference between a secure path-construction function and a heap overflow.


References

Frequently Asked Questions

What is an integer overflow in buffer size calculation?

It occurs when arithmetic on integer values (like adding string lengths) wraps around the maximum value of the integer type, producing a result smaller than expected. When that result is passed to malloc(), the allocated buffer is too small, and subsequent writes overflow into adjacent memory.

How do you prevent integer overflow in C buffer size calculations?

Before adding lengths, check that the sum won't exceed SIZE_MAX. For example: `if (base_len > SIZE_MAX - sizeof(DBC_PATH_SUFFIX)) { return NULL; }`. Use `sizeof()` for compile-time-known strings instead of `strlen()` to reduce runtime risk.

What CWE is integer overflow in buffer size calculation?

CWE-120 (Buffer Copy without Checking Size of Input, also known as "Classic Buffer Overflow"). Related identifiers include CWE-190 (Integer Overflow or Wraparound) and CWE-131 (Incorrect Calculation of Buffer Size).

Is checking for NULL after malloc() enough to prevent integer overflow?

No. If an integer overflow produces a small but non-zero value, malloc() will succeed and return a valid (but undersized) pointer. The NULL check will pass, and the subsequent string copy will overflow the buffer silently.

Can static analysis detect integer overflow in C buffer size calculations?

Yes. Tools like Semgrep, Coverity, and CodeQL can detect patterns where strlen() results are added without overflow guards before being passed to malloc(). Orbis AppSec detected this specific instance using its multi-agent AI scanner.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #156

Related Articles

critical

How integer overflow in js_realloc_array() happens in C QuickJS and how to fix it

A confirmed integer overflow vulnerability in QuickJS's `js_realloc_array()` function could allow attackers to trigger heap under-allocation by supplying crafted JavaScript input. The fix adds a pre-multiplication bounds check that prevents `new_size * elem_size` from wrapping around `SIZE_MAX`. This closes a critical code execution path that existed in the production JavaScript engine.

critical

How buffer overflow in memcpy happens in C x_util.c and how to fix it

A critical buffer overflow vulnerability was discovered in `hardinfo2/x_util.c` where `memcpy` operations copied data into dynamically allocated arrays without validating that the destination buffer was large enough. The vulnerable pattern used raw `malloc`/`realloc` without checking the return value before immediately using the pointer as a `memcpy` destination, meaning a failed allocation could lead to a NULL pointer dereference or out-of-bounds write. The fix replaces the unsafe manual alloca

medium

How integer overflow in bounds checking happens in C and how to fix it

A critical integer overflow vulnerability was discovered in the W_Read function of DOOM/w_file.c that allowed attackers to bypass bounds checking by crafting WAD files with malicious offset values near UINT_MAX. The fix implements a two-step validation approach that first checks if the offset exceeds the file length, then safely calculates the remaining bytes without risk of overflow.

critical

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

A critical buffer overflow vulnerability was discovered in the `daemonize()` function of `tpl.c`, where command-line arguments are concatenated into a fixed-size 8192-byte buffer using `strcat()` without any bounds checking. An attacker who controls command-line arguments can overflow this buffer to corrupt adjacent memory and potentially achieve arbitrary code execution. The fix adds a buffer-length check before each concatenation to ensure writes never exceed the declared buffer size.

high

How buffer overflow via insecure strcpy/strncpy happens in C textbox widgets and how to fix it

A high-severity buffer overflow vulnerability was discovered in the Aroma UI framework's textbox widget where `strncpy()` was used to copy user-provided text without guaranteed null-termination safety. The fix replaces the dangerous `strncpy()` pattern with `snprintf()`, which automatically handles buffer boundaries and null-termination in a single, safer operation.

critical

How buffer overflow in memcpy happens in C bios_disk.h and how to fix it

A critical buffer overflow vulnerability was discovered in `include/bios_disk.h` at line 474, where a `memcpy` operation copies 512 bytes from a source buffer without properly validating that the calculated offset from the `sectnum` parameter stays within bounds. An attacker controlling the `sectnum` parameter could trigger an out-of-bounds read, potentially leaking sensitive memory contents or causing a crash. The fix adds a proper bounds check before the memcpy call to ensure the source offset