Back to Blog
critical SEVERITY6 min read

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

A critical integer overflow vulnerability was discovered in the `__cstl_join` function in `opencstl/filesystem.h` that could allow attackers to trigger a heap buffer overflow by supplying crafted file path strings. The fix adds an explicit overflow check before the size calculation, returning NULL when the combined path lengths would wrap around the `size_type64` maximum value.

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

Answer Summary

This is a CWE-190 integer overflow vulnerability in C's `opencstl/filesystem.h` file, specifically in the `__cstl_join` path concatenation function. When two path strings have combined lengths that overflow a `size_type64`, the subsequent `malloc` allocates a too-small buffer, and `memcpy` writes beyond it. The fix adds a bounds check (`if (l1 > (size_type64)-1 - sep - l2 - 1) { return NULL; }`) before computing the total allocation size, preventing the overflow from occurring.

Vulnerability at a Glance

cweCWE-190
fixAdded overflow check returning NULL when combined path lengths would exceed size_type64 maximum
riskHeap buffer overflow enabling arbitrary code execution or denial of service
languageC
root causeNo validation that l1 + separator + l2 + 1 fits in size_type64 before malloc
vulnerabilityInteger overflow leading to heap buffer overflow

How Integer Overflow in path_join() Happens in C and How to Fix It

Introduction

The opencstl/filesystem.h file provides path manipulation utilities for a C library, but a critical flaw in the __cstl_join function at line 103 created a dangerous security risk. When computing the total buffer size needed to join two file paths, the function performed l1 + (need_sep ? 1 : 0) + l2 without verifying that this addition wouldn't overflow the size_type64 type. An attacker supplying adversarially crafted path strings could cause this computation to wrap around to a small value, resulting in a tiny malloc allocation followed by memcpy operations that write far beyond the buffer's bounds.

This vulnerability is particularly dangerous because opencstl is a library — any application importing this code inherits the exploitable condition. Since path joining is a fundamental filesystem operation often fed by user input (file uploads, configuration paths, plugin directories), the attack surface is broad.

The Vulnerability Explained

Let's look at the vulnerable code in opencstl/filesystem.h:

bool need_sep = !__cstl_is_sep(path1[l1 - 1]);
size_type64 total = l1 + (need_sep ? 1 : 0) + l2;
char *ret = (char *) malloc(total + 1);
memcpy(ret, path1, l1);
size_type64 pos = l1;

Here's what happens step by step:

  1. l1 is the strlen() of path1, and l2 is the strlen() of path2
  2. The code computes total = l1 + separator + l2 — but if l1 and l2 are both extremely large values (close to SIZE_MAX / 2), their sum wraps around to a small number
  3. malloc(total + 1) allocates a tiny buffer (perhaps just a few bytes)
  4. memcpy(ret, path1, l1) then copies potentially gigabytes of data into that tiny buffer

Concrete attack scenario: An attacker who can influence file paths passed to path_join — for example, through a configuration file, a plugin system, or a network protocol that specifies paths — crafts two strings where strlen(path1) + strlen(path2) + 2 overflows size_type64. On a 64-bit system, this requires paths totaling near 2^64 bytes (impractical for direct strings but possible via memory-mapped inputs or on 32-bit systems where size_type64 might be 32 bits). On a 32-bit system or if size_type64 is uint32_t, paths of ~2GB each would trigger the overflow, resulting in a heap buffer overflow that enables arbitrary code execution.

The subsequent memcpy operations at lines 105-108 copy l1 + l2 bytes total into a buffer that was allocated for the wrapped-around total + 1 bytes — a classic heap overflow that can corrupt heap metadata, overwrite adjacent allocations, and ultimately give an attacker control of program execution.

The Fix

The fix introduces an explicit overflow check before computing the total size:

Before (vulnerable):

bool need_sep = !__cstl_is_sep(path1[l1 - 1]);
size_type64 total = l1 + (need_sep ? 1 : 0) + l2;
char *ret = (char *) malloc(total + 1);

After (fixed):

bool need_sep = !__cstl_is_sep(path1[l1 - 1]);
size_type64 sep = need_sep ? 1 : 0;
if (l1 > (size_type64)-1 - sep - l2 - 1) { return NULL; }
size_type64 total = l1 + sep + l2;
char *ret = (char *) malloc(total + 1);

Let's break down the overflow check: (size_type64)-1 produces the maximum value for the type (all bits set to 1). By checking whether l1 > MAX - sep - l2 - 1, the code verifies that l1 + sep + l2 + 1 (the actual allocation size including the null terminator) won't exceed the representable range. If it would overflow, the function safely returns NULL instead of proceeding with a corrupted allocation.

The separation of sep into its own variable also improves readability and makes the overflow check cleaner — a secondary benefit of this refactoring.

A regression test was also added in tests/test_invariant_filesystem.h that exercises the function with adversarial path lengths, boundary cases (empty strings), and normal operation to ensure the fix holds under all conditions.

Prevention & Best Practices

1. Always check arithmetic before allocation

Any time you compute a buffer size from multiple inputs, verify the computation won't overflow:

// Safe pattern for computing allocation sizes
if (a > SIZE_MAX - b) {
    return NULL; // or handle error
}
size_t total = a + b;

2. Use compiler overflow built-ins when available

GCC and Clang provide __builtin_add_overflow():

size_t total;
if (__builtin_add_overflow(l1, l2, &total)) {
    return NULL;
}

3. Consider safe integer arithmetic libraries

For C projects with extensive arithmetic, libraries like SafeInt or compiler-specific checked arithmetic functions reduce the risk of missing an overflow check.

4. Fuzz test path manipulation functions

Use AFL, libFuzzer, or similar tools to generate adversarial inputs for functions that combine user-controlled lengths. Path manipulation functions are prime targets.

5. Static analysis integration

Run static analysis tools (Coverity, CodeQL, or AI-powered scanners) in CI/CD pipelines to catch integer overflow patterns automatically.

Key Takeaways

  • The __cstl_join function in filesystem.h trusted that l1 + sep + l2 would not overflow — a dangerous assumption for any library function that accepts external input
  • The fix uses (size_type64)-1 - sep - l2 - 1 as a ceiling check — this idiom correctly validates that the full allocation (including null terminator) fits within the type's range
  • Returning NULL on overflow is the correct fail-safe — callers must handle NULL returns, but this is far safer than silent heap corruption
  • Library code has amplified risk — since opencstl is imported by other applications, this single overflow check protects every downstream consumer
  • The + 1 for the null terminator must be included in the overflow check — the original code computed total then allocated total + 1, meaning even total == SIZE_MAX (without wrapping in the addition itself) would overflow at malloc(total + 1)

How Orbis AppSec Detected This

  • Source: User-controlled path strings passed as path1 and path2 parameters to __cstl_join() in opencstl/filesystem.h
  • Sink: malloc(total + 1) at line 103 followed by memcpy(ret, path1, l1) at line 105, where total is computed from unchecked string lengths
  • Missing control: No validation that l1 + sep + l2 + 1 fits within size_type64 before allocation
  • CWE: CWE-190 (Integer Overflow or Wraparound)
  • Fix: Added an explicit overflow check (if (l1 > (size_type64)-1 - sep - l2 - 1) { return NULL; }) before computing the allocation size

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 vulnerabilities in C remain one of the most dangerous classes of memory safety bugs, especially in library code that processes variable-length inputs. The __cstl_join function's path concatenation logic seemed straightforward — compute a length, allocate, copy — but the missing overflow check meant that adversarial inputs could corrupt the heap. The fix is minimal (three lines) but transforms an exploitable condition into a safe failure mode. When writing C code that computes allocation sizes from external inputs, always validate arithmetic before calling malloc.

References

Frequently Asked Questions

What is an integer overflow vulnerability?

An integer overflow occurs when an arithmetic operation produces a value that exceeds the maximum representable value of its data type, causing it to wrap around to a small number. In memory allocation contexts, this leads to undersized buffers.

How do you prevent integer overflow in C?

Check whether operands would cause overflow before performing the arithmetic. For addition, verify that `a > MAX - b` before computing `a + b`. Use compiler built-ins like `__builtin_add_overflow()` or explicit comparisons against the type's maximum value.

What CWE is integer overflow?

CWE-190: Integer Overflow or Wraparound. It describes computations that produce values exceeding the maximum for their type, often leading to buffer overflows (CWE-122) when used in memory allocation.

Is using size_t enough to prevent integer overflow?

No. While `size_t` is unsigned and large enough for most allocations, it can still overflow when multiple values are summed. Explicit overflow checks are required regardless of the integer type used.

Can static analysis detect integer overflow?

Yes. Static analysis tools and AI-powered scanners can identify patterns where unchecked arithmetic feeds into memory allocation functions like malloc, flagging potential overflow conditions before they reach production.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #9

Related Articles

high

How Denial of Service via Infinite Loop Happens in JavaScript Dependencies and How to Fix It

CVE-2026-67213 is a high-severity denial of service vulnerability in nanoid before version 5.1.6 that triggers an infinite loop during random ID generation when processing specially crafted input. We upgraded nanoid across the entire dependency tree to patch this flaw and prevent attackers from freezing application threads. This fix ensures that ID generation remains resilient even when handling adversarial input patterns.

high

How Sensitive Data Exposure happens in Zotero plugins and how to fix it

A high-severity data exposure vulnerability in `Zotero.ts` automatically transmitted complete document metadata—including private notes, attachment paths, and tags—to external LLM services without user consent. The fix replaces broad `item.toJSON()` serialization with explicit field selection, sending only essential bibliographic data.

high

How missing dependency update cooldowns happen in GitHub Dependabot configurations and how to fix it

A semgrep scan flagged `.github/dependabot.yml` for lacking a cooldown period, meaning Dependabot would immediately propose updates to brand-new package versions across npm, Bundler, and Docker ecosystems. The fix adds a `cooldown: default-days: 7` block to every `package-ecosystem` entry, forcing a one-week waiting period before newly published releases are considered — reducing exposure to malicious or unstable package drops.

high

How dependabot-missing-cooldown happens in GitHub Actions/Node.js and how to fix it

The repository's `.github/dependabot.yml` had no cooldown period configured, meaning Dependabot could immediately propose updates to newly published package versions with zero time for the community to flag malware or instability. The fix adds a `cooldown` block with `default-days: 7` to both the `npm` and `github-actions` ecosystems, forcing a 7-day waiting period before new releases are surfaced as update PRs.

high

How Path Traversal Happens in TensorFlow's Data Service and How to Fix It

TensorFlow's data service dispatcher validated dataset IDs against forward-slash traversal attacks but overlooked backslash characters on non-Windows platforms, allowing attackers to escape the root directory. A targeted fix adds explicit backslash validation across all platforms, closing a high-severity path traversal vulnerability in the snapshot management system.

critical

How Unbounded WebSocket Message Handling Causes Resource Exhaustion in Node.js and How to Fix It

The WebSocketCrossServerAdapter class in a popular Node.js WebSocket library lacked any rate limiting on inbound messages, allowing attackers to flood Redis nodes and WebSocket servers with high-volume traffic. The fix introduces a configurable `rateLimit` option that caps messages per connection per second, preventing resource exhaustion while preserving legitimate functionality.