Back to Blog
medium SEVERITY6 min read

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.

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

Answer Summary

This is an integer overflow vulnerability (CWE-190) in C that occurs when arithmetic operations in bounds checks wrap around, bypassing security validations. In the W_Read() function, adding offset + buffer_len could overflow to a small value, allowing out-of-bounds memory reads. The fix separates the check into two steps: first validate that offset < wad->length, then safely compute buffer_len > wad->length - offset to avoid overflow.

Vulnerability at a Glance

cweCWE-190
fixSplit into two separate checks to avoid overflow in arithmetic operations
riskOut-of-bounds memory read leading to information disclosure or crash
languageC
root causeArithmetic overflow in bounds check allows bypass when offset + buffer_len wraps around
vulnerabilityInteger Overflow in Bounds Check

Introduction

In the DOOM source port's WAD file handling code, we discovered a critical integer overflow vulnerability in DOOM/w_file.c at line 93. The W_Read function is responsible for safely reading data from memory-mapped WAD files, but a subtle flaw in its bounds checking logic allowed attackers to bypass validation entirely using carefully crafted offset values.

The vulnerable code attempted to prevent out-of-bounds reads with what appeared to be a straightforward check: if (offset + buffer_len > wad->length). However, this arithmetic operation is susceptible to integer overflow when offset approaches UINT_MAX, causing the sum to wrap around to a small value and bypass the protection.

This vulnerability matters for any developer working with binary file formats, network protocols, or memory-mapped I/O where untrusted input controls offset and length values.

The Vulnerability Explained

The W_Read function reads data from a memory-mapped WAD file into a buffer. Before performing the read, it needs to ensure the requested range [offset, offset + buffer_len) falls within the valid file bounds. Here's the vulnerable code:

size_t W_Read(wad_file_t *wad, unsigned int offset,
              void *buffer, size_t buffer_len)
{
    if (offset + buffer_len > wad->length)
        buffer_len = wad->length - offset;

    memcpy(buffer, wad->mapped + offset, buffer_len);
    return buffer_len;
}

The problem lies in the expression offset + buffer_len. When offset is a value like 0xFFFFFFFF (UINT_MAX on 32-bit) and buffer_len is 2, the addition wraps around:

0xFFFFFFFF + 2 = 0x00000001 (due to overflow)

If wad->length is, say, 100, the check becomes 1 > 100, which is false. The bounds check passes, and the code proceeds to execute memcpy(buffer, wad->mapped + 0xFFFFFFFF, 2), attempting to read from an address far beyond the mapped file.

Attack Scenario

An attacker crafts a malicious WAD file with manipulated lump directory entries. WAD files contain a directory that specifies offsets and sizes for each data "lump." By setting a lump's offset to a value near UINT_MAX, the attacker can:

  1. Bypass bounds validation through integer overflow
  2. Read arbitrary memory beyond the WAD file's mapped region
  3. Cause a crash if the address is unmapped, or leak sensitive data if it's accessible

This is particularly dangerous because WAD files are commonly shared and loaded from untrusted sources (custom game mods, downloaded content, etc.).

Secondary Issue: Integer Underflow

There's also a subtler problem. If offset > wad->length and the check somehow passes, the "correction" buffer_len = wad->length - offset would underflow, producing a massive buffer_len value and causing memcpy to read far more data than intended.

The Fix

The fix restructures the bounds checking to avoid arithmetic that can overflow. Instead of computing offset + buffer_len, it uses two separate checks:

size_t W_Read(wad_file_t *wad, unsigned int offset,
              void *buffer, size_t buffer_len)
{
    if (offset >= wad->length)
        return 0;

    if (buffer_len > wad->length - offset)
        buffer_len = wad->length - offset;

    memcpy(buffer, wad->mapped + offset, buffer_len);
    return buffer_len;
}

Before and After Comparison

Aspect Before (Vulnerable) After (Fixed)
Offset validation None if (offset >= wad->length) return 0;
Length calculation offset + buffer_len > wad->length (can overflow) buffer_len > wad->length - offset (safe after offset check)
Underflow protection None Guaranteed by checking offset first

Why This Works

  1. First check: if (offset >= wad->length) ensures that offset is strictly within bounds. If the offset is at or beyond the file length, we return 0 immediately—there's nothing to read.

  2. Second check: Only after confirming offset < wad->length do we compute wad->length - offset. This subtraction is now guaranteed not to underflow because we know wad->length > offset.

  3. Safe comparison: buffer_len > wad->length - offset compares without addition, eliminating the overflow risk entirely.

The fix also includes a comprehensive regression test (tests/test_invariant_w_file.c) that validates the security invariant: "W_Read must never read beyond wad->length regardless of offset/buffer_len values." The test covers:

  • Integer overflow bypass attempts (UINT32_MAX offset)
  • Boundary conditions (offset equals length)
  • Underflow scenarios (offset exceeds length)
  • Near-overflow boundary cases

Prevention & Best Practices

Safe Integer Arithmetic Patterns

When performing bounds checks in C, follow these principles:

  1. Validate inputs independently before combining them
    ```c
    // Bad: can overflow
    if (a + b > limit)

// Good: check separately
if (a > limit || b > limit - a)
```

  1. Use subtraction instead of addition when possible
    c // Instead of: offset + len > max // Use: len > max - offset (after validating offset < max)

  2. Consider using safe integer libraries
    - GCC's __builtin_add_overflow()
    - Microsoft's safe_int library
    - Custom checked arithmetic macros

Compiler Protections

Enable compiler flags that help detect integer issues:
- -ftrapv: Trap on signed overflow (GCC/Clang)
- -fsanitize=integer: Runtime integer overflow detection
- -Wconversion: Warn on implicit conversions that may lose data

Code Review Checklist

When reviewing bounds-checking code, ask:
- Can any arithmetic operation overflow?
- Are all operands validated before use?
- What happens at boundary values (0, MAX, MAX-1)?
- Is the order of operations safe?

Key Takeaways

  • Never trust arithmetic in bounds checks: The expression offset + buffer_len > limit is a classic integer overflow pattern that appears safe but fails at boundary values.
  • Validate offset before computing remaining space: Always check offset < length before computing length - offset to prevent underflow.
  • WAD file parsers must treat directory entries as untrusted: Offset and size values from file headers can be attacker-controlled.
  • Regression tests should include overflow boundary cases: Test with UINT_MAX, UINT_MAX - small_value, and values that would cause wraparound.
  • The W_Read function now implements defense-in-depth: Two independent checks ensure neither overflow nor underflow can bypass bounds validation.

How Orbis AppSec Detected This

  • Source: WAD file lump directory entries containing offset and size values parsed from untrusted input files
  • Sink: memcpy(buffer, wad->mapped + offset, buffer_len) in DOOM/w_file.c:97
  • Missing control: No validation that offset + buffer_len arithmetic wouldn't overflow before comparison
  • CWE: CWE-190 (Integer Overflow or Wraparound)
  • Fix: Split the bounds check into two separate validations—first ensuring offset is within bounds, then safely computing the maximum readable length

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 bounds checking are insidious because the code often looks correct at first glance. The pattern if (a + b > limit) is intuitive but dangerous when a and b can be controlled by an attacker. The fix demonstrated here—separating validation into independent checks and using subtraction after validation—is a robust pattern applicable to any code handling untrusted offsets and lengths.

When working with binary file formats, memory-mapped I/O, or any code where arithmetic values come from external sources, always consider what happens at the boundaries of your data types. A few extra lines of defensive code can prevent critical memory safety vulnerabilities.

References

Frequently Asked Questions

What is integer overflow in bounds checking?

Integer overflow in bounds checking occurs when arithmetic used to validate memory access wraps around due to exceeding the maximum value of the data type, causing the check to pass when it should fail.

How do you prevent integer overflow in C?

Prevent integer overflow by checking operands before arithmetic operations, using separate comparisons that avoid addition, or using safe integer arithmetic libraries that detect overflow conditions.

What CWE is integer overflow?

Integer overflow is classified as CWE-190: Integer Overflow or Wraparound, which describes numeric errors where computation produces a value outside the representable range.

Is checking offset + buffer_len enough to prevent buffer overflows?

No, checking offset + buffer_len can itself overflow. You must first verify offset is within bounds, then check buffer_len against the remaining space using subtraction rather than addition.

Can static analysis detect integer overflow vulnerabilities?

Yes, static analysis tools can detect many integer overflow patterns, especially in bounds checks. Tools like Semgrep, Coverity, and compiler warnings (-ftrapv, -fsanitize=integer) help identify these issues.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #7

Related Articles

critical

How command injection happens in Node.js subprocess and how to fix it

A critical command injection vulnerability in `tools/dev/src/index.ts` allowed attackers to execute arbitrary shell commands through unsanitized subprocess arguments. The fix was simple but essential: explicitly setting `shell: false` in the `spawn()` call to prevent shell metacharacter interpretation. This vulnerability demonstrates why subprocess handling requires explicit security controls in Node.js.

critical

How GitHub token exposure happens in TypeScript CLI utilities and how to fix it

A critical credential exposure vulnerability was discovered in `cli/src/utils/github.ts`, where three GitHub API fetch calls were made without any safe token-loading mechanism, risking accidental hardcoding or token leakage in logs and CI/CD pipelines. The fix introduces a centralized `getAuthHeaders()` function that reads the token exclusively from the `GITHUB_TOKEN` environment variable and safely injects it into all outbound API requests. This ensures credentials never touch source code, buil

high

How improper handling of case sensitivity happens in Go MCP SDK and how to fix it

A high-severity vulnerability (CVE-2026-27896) in the Model Context Protocol Go SDK v1.3.0 allowed attackers to bypass security controls through improper handling of case sensitivity. The fix upgrades the dependency from v1.3.0 to v1.3.1, which correctly normalizes case comparisons. This vulnerability was particularly concerning for CLI tools where attackers could manipulate input to evade validation logic.

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 via sprintf happens in C++ fuzzer code and how to fix it

A critical buffer overflow vulnerability was discovered in `prog/fuzzing/recog_basic_fuzzer.cc` where `sprintf` writes to a fixed 256-byte buffer without bounds checking. An attacker providing crafted fuzzer input could exploit this to corrupt memory. The fix replaces `sprintf` with `snprintf`, enforcing the buffer size limit and preventing overflow.

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