Back to Blog
critical SEVERITY8 min read

Critical Integer Overflow in C: How a Simple Multiplication Almost Caused Heap Corruption

A critical integer overflow vulnerability was discovered and patched in `src/asb_governor.c`, where an unchecked multiplication during buffer reallocation could produce a dangerously undersized heap allocation on 32-bit systems. An attacker with a crafted session file could trigger heap corruption, potentially leading to arbitrary code execution. The fix adds proper overflow detection before any memory allocation, closing a classic but devastating class of memory safety bug.

O
By orbisai0security
May 23, 2026

Critical Integer Overflow in C: How a Simple Multiplication Almost Caused Heap Corruption

Introduction

At first glance, the line of code that caused this vulnerability looks completely harmless:

new_size = new_cap * sizeof(char *);

One multiplication. No pointers dereferenced, no user input directly involved, no obvious footgun. Yet on a 32-bit Android system, this single expression could be weaponized by an attacker to corrupt the heap, potentially enabling arbitrary code execution — all by feeding the application a carefully crafted session file.

This is the story of CWE-190: Integer Overflow or Wraparound, one of the oldest and most persistently dangerous vulnerability classes in systems programming. It was patched in src/asb_governor.c, and understanding why it matters is essential for any developer writing C, C++, or any language that exposes raw memory operations.


The Vulnerability Explained

Background: Dynamic Arrays in C

The vulnerable code implements a classic dynamic array (also called a growable buffer). The pattern is familiar to any C programmer:

  1. Start with a small initial capacity (e.g., 4 slots).
  2. When the array fills up, double the capacity.
  3. Call realloc to resize the backing memory.
  4. Continue filling the array.

This pattern is efficient and widely used. The problem arises at step 3, specifically in how the new allocation size is computed.

The Vulnerable Code

The vulnerable logic, located at src/asb_governor.c:478, looked something like this:

// VULNERABLE CODE — Do not use
size_t new_cap = old_cap * 2;
char **new_buf = realloc(buf, new_cap * sizeof(char *));
if (!new_buf) {
    // handle allocation failure
}
buf = new_buf;
cap = new_cap;

The critical flaw: new_cap * sizeof(char *) is computed without any overflow check.

Why This Is Dangerous on 32-bit Systems

On a 32-bit Android system, size_t is a 32-bit unsigned integer, meaning it can hold values from 0 to 4,294,967,295 (2³² − 1). sizeof(char *) is 4 bytes on a 32-bit platform.

Now consider what happens when new_cap reaches a large value through repeated doublings. If new_cap is, say, 0x40000001 (1,073,741,825):

new_cap * sizeof(char *)
= 0x40000001 * 4
= 0x100000004

But 0x100000004 doesn't fit in a 32-bit size_t. It wraps around to 0x00000004 — just 4 bytes.

realloc is then called asking for a 4-byte buffer. It happily obliges. The code then proceeds to write hundreds of millions of pointers into that 4-byte allocation. This is heap corruption.

The Attack Scenario

The vulnerability is triggered by reading a crafted session file. Here's how an attacker could exploit it:

  1. Craft a malicious session file containing an extremely large number of lines — enough to push new_cap past the overflow boundary through repeated doublings.
  2. Deliver the file to the application (e.g., by replacing a session file on a rooted device, via a man-in-the-middle attack on session sync, or through any other file-delivery mechanism the application exposes).
  3. The application reads the file, doubling its buffer capacity with each reallocation.
  4. At the overflow boundary, realloc receives a wrapped-around size and returns a tiny allocation.
  5. Subsequent writes go far beyond the allocated buffer, corrupting adjacent heap metadata and data.
  6. Heap corruption can be leveraged for a variety of follow-on attacks, including arbitrary code execution, privilege escalation, or denial of service.

The number of lines required to trigger this on a 32-bit system is large but not impractical — especially if the attacker can automate file generation and the application processes session files automatically in the background.

Real-World Impact

  • Heap corruption is one of the most powerful primitives an attacker can have. Modern heap exploitation techniques (House of Force, tcache poisoning, etc.) can turn heap corruption into arbitrary write, and from there into code execution.
  • On Android, this could mean a malicious app or a compromised sync service gains the privileges of the vulnerable process.
  • The vulnerability is silent — no crash, no error log, just quietly corrupted memory that may not manifest until much later, making it extremely hard to diagnose without a dedicated security review.

The Fix

What Changed

The fix adds an explicit integer overflow check before the realloc call. The corrected logic looks like this:

// SAFE CODE — After the fix
size_t new_cap = old_cap * 2;

// Check for integer overflow before computing allocation size
if (new_cap == 0 || new_cap > SIZE_MAX / sizeof(char *)) {
    // Overflow would occur — abort safely
    handle_error("Integer overflow in buffer capacity calculation");
    return ERROR_OVERFLOW;
}

size_t alloc_size = new_cap * sizeof(char *);

char **new_buf = realloc(buf, alloc_size);
if (!new_buf) {
    handle_error("Memory allocation failed");
    return ERROR_NOMEM;
}

buf = new_buf;
cap = new_cap;

How the Fix Works

The key insight is the guard condition:

if (new_cap > SIZE_MAX / sizeof(char *))

This checks whether new_cap * sizeof(char *) would exceed SIZE_MAX before performing the multiplication. This is the standard safe pattern for detecting integer overflow in C:

Instead of checking after the multiplication (too late — the damage is done), divide the maximum safe value by one operand and compare it to the other.

If new_cap is greater than SIZE_MAX / sizeof(char *), then the multiplication would overflow, and the code returns an error instead of proceeding with a dangerously undersized allocation.

Additionally, the fix checks new_cap == 0, which guards against the edge case where old_cap itself was already at the maximum and doubling wrapped to zero.

Before vs. After at a Glance

Aspect Before (Vulnerable) After (Fixed)
Overflow check ❌ None ✅ Pre-multiplication guard
Allocation size Could wrap to tiny value Always proportional to new_cap
Error handling Silent heap corruption Explicit error return
32-bit safety ❌ Unsafe ✅ Safe

Prevention & Best Practices

1. Always Validate Before Multiplying for Allocation Sizes

The golden rule for allocation size arithmetic in C:

// UNSAFE
size_t alloc = count * element_size;
void *buf = malloc(alloc);

// SAFE
if (count > SIZE_MAX / element_size) {
    // overflow would occur
    return ERROR;
}
size_t alloc = count * element_size;
void *buf = malloc(alloc);

This pattern should be second nature whenever you compute an allocation size from a variable count.

2. Use Safe Integer Libraries

For C/C++ projects, consider using established safe integer libraries:

  • SafeInt (Microsoft) — C++ template library for safe integer operations.
  • CERT's intsafe.h (Windows) — Windows-specific safe integer functions.
  • __builtin_mul_overflow (GCC/Clang) — Compiler built-in for detecting overflow:
size_t alloc_size;
if (__builtin_mul_overflow(new_cap, sizeof(char *), &alloc_size)) {
    // overflow detected
    return ERROR;
}
void *new_buf = realloc(buf, alloc_size);

3. Impose Reasonable Upper Bounds

Even with overflow checks, it's good practice to cap the maximum capacity at a sensible limit:

#define MAX_SESSION_LINES 10000000UL  // 10 million lines is already extreme

if (new_cap > MAX_SESSION_LINES) {
    return ERROR_TOO_LARGE;
}

This provides defense-in-depth: even if the overflow check were somehow bypassed, the size limit prevents runaway memory consumption.

4. Enable Compiler Sanitizers During Development

The UndefinedBehaviorSanitizer (UBSan) can catch integer overflows at runtime during testing:

# Compile with UBSan
clang -fsanitize=undefined,integer src/asb_governor.c -o asb_governor_test

# Or with GCC
gcc -fsanitize=undefined src/asb_governor.c -o asb_governor_test

UBSan will print a detailed error and abort the program the moment an integer overflow occurs, making these bugs trivial to find in testing.

5. Use Static Analysis Tools

Several static analysis tools can detect this class of vulnerability before code even runs:

  • Coverity — Detects integer overflow in allocation contexts.
  • CodeQL — GitHub's semantic code analysis engine with queries for CWE-190.
  • Clang Static Analyzer — Free, built into LLVM.
  • Flawfinder — Lightweight C/C++ security scanner.

6. Write Regression Tests for Boundary Conditions

The regression test suite included with this fix is an excellent model. It tests:

  • Power-of-2 boundaries (1, 2, 4, 8, 16, ..., 131072) — exactly where capacity doublings occur.
  • Near-overflow values (2^31 - 1, 2^32 - 1, 2^63 - 1).
  • Adversarial content (path traversal strings, format strings, SQL injection payloads, null bytes).
  • Empty and degenerate inputs (empty string, newlines only, single line).

This kind of parametric boundary testing is exactly what's needed to catch integer overflow bugs before they reach production.

7. Know the Relevant Standards

This vulnerability maps to well-documented security standards:


Conclusion

The vulnerability fixed in this PR is a textbook example of why integer arithmetic in C demands explicit, defensive validation — especially when the result feeds into a memory allocation. The code was doing everything else right: using realloc correctly, doubling capacity efficiently, handling allocation failures. But one missing overflow check was enough to turn a routine buffer resize into a potential code execution primitive on 32-bit systems.

The key takeaways for developers:

  1. Never trust multiplication for allocation sizes without an overflow pre-check — use the if (n > SIZE_MAX / size) pattern or compiler built-ins.
  2. 32-bit platforms are still real — embedded systems, older Android devices, and IoT hardware are 32-bit, and overflow boundaries are much easier to reach there.
  3. Heap corruption is serious — it's not "just a crash." In the hands of a skilled attacker, heap corruption is often a path to arbitrary code execution.
  4. Sanitizers and static analysis are your friends — UBSan, CodeQL, and Coverity can catch these bugs automatically during development.
  5. Regression tests at boundaries matter — the test suite accompanying this fix is a model worth emulating: test power-of-2 boundaries, near-overflow values, and adversarial inputs systematically.

Security bugs like this one are a reminder that in systems programming, the most dangerous vulnerabilities often hide in the most ordinary-looking code. A disciplined habit of overflow-checking every allocation computation is a small investment with an enormous security payoff.


This vulnerability was identified and patched as part of an automated security review. The fix was verified by build, scanner re-scan, and LLM code review. The accompanying regression test suite guards against future regressions of this vulnerability class.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #6

Related Articles

medium

Integer Overflow in Shared Memory Bounds Check: How a Missing Cast Opened the Door to Arbitrary Memory Writes

A subtle but dangerous integer overflow vulnerability was discovered in `lib/rpmi_shmem.c`, where bounds checks on shared memory operations could be silently bypassed due to 32-bit arithmetic overflow. By carefully crafting `offset` and `len` values, an OS-level or hypervisor-level caller could direct firmware writes to arbitrary memory addresses — including interrupt vector tables and security-critical configuration structures. The fix was elegantly simple: casting operands to 64-bit before add

critical

Heap Overflow in TOML Parser: How Integer Overflow Leads to Memory Corruption

A critical heap buffer overflow vulnerability was discovered and patched in the centitoml TOML parser, where missing integer overflow validation on a `MALLOC(len+1)` call could allow an attacker to trigger memory corruption via a crafted TOML configuration file. The vulnerability (CWE-190) is reachable through community-distributed mod or map files that the game loads from its `config/` directory, making it a realistic attack vector for remote code execution. A targeted one-line guard now preven

critical

Heap Corruption via Unchecked memcpy: How Integer Overflow Bugs Corrupt Memory in Windows File Operations

A critical buffer overflow vulnerability was discovered in `phlib/nativefile.c`, where multiple `memcpy` calls copied filename and extended-attribute data into fixed-size structures without verifying that source lengths didn't exceed destination buffer boundaries. An attacker supplying an oversized filename or EA name could corrupt adjacent heap memory, potentially enabling arbitrary code execution. The fix replaces unchecked arithmetic with Windows' safe integer helpers (`RtlULongAdd`, `RtlULon

critical

Integer Overflow to Heap Buffer Overflow: Fixing a Critical memcpy Bounds Check in libretro-db

A critical heap buffer overflow vulnerability was discovered in `libretro-db/rmsgpack_dom.c`, where a missing integer width cast allowed an attacker-controlled string length value of `UINT32_MAX` to wrap around to zero, completely collapsing the bounds check before a `memcpy` call. The fix is a single targeted cast to `uint64_t` that closes the overflow window and ensures the bounds check behaves correctly regardless of the input value. This class of vulnerability is a textbook example of how in

critical

Critical Integer Sign Bug in runtime_malloc(): How a Missing Check Enables Heap Corruption

A critical vulnerability in `runtime/zenith_runtime.c` allowed the `runtime_malloc()` function to accept negative size values, which when cast to an unsigned type could either trigger a massive failed allocation or produce a dangerously undersized buffer ripe for overflow. The fix adds a simple but essential guard clause that rejects non-positive sizes before they ever reach `malloc()`. Left unpatched, this class of bug can lead to heap metadata corruption, process crashes, or even arbitrary cod

medium

Integer Overflow in Packet Reassembly: How One Missing Check Enables Heap Corruption

A critical heap buffer overflow vulnerability was discovered in the network packet reassembly function of `net_channel_ex.c`, where an attacker-controlled `bodylen` field could be used to corrupt heap memory without any bounds validation. The fix introduces a simple yet effective integer overflow check before accumulating packet body lengths, preventing malformed packets from triggering memory corruption. This type of vulnerability is a stark reminder that even low-level arithmetic operations in