Back to Blog
high SEVERITY9 min read

Integer Overflow in Graphics Blit: When Bit Shifts Go Dangerously Wrong

A critical integer overflow vulnerability was discovered and patched in `rtg/mntgfx-gcc.c`, where an unvalidated bit-shift operation used to compute a graphics pattern blit copy size could trigger undefined behavior, silent data corruption, or a devastating out-of-bounds memory write. This post breaks down exactly how a single missing bounds check can turn a routine graphics operation into a serious security and stability threat, and what developers can do to prevent similar issues in their own

O
By Orbis AppSec
Published May 18, 2026Reviewed June 3, 2026

Answer Summary

This is a critical integer overflow vulnerability (CWE-190) in C code within `rtg/mntgfx-gcc.c`, where a bit-shift operation used to compute the size of a graphics pattern blit copy was not validated before use. In C, shifting a value by an amount equal to or greater than the width of the integer type produces undefined behavior, and shifting a legitimate but large value can silently overflow, producing a tiny or zero-sized result that causes subsequent memory writes to go out of bounds. The fix adds an explicit bounds check on the shift operand before the computation, ensuring the resulting size value is always within a safe, expected range. Developers working on low-level graphics, framebuffer, or blit routines in C should always validate shift amounts and computed sizes before using them to index or copy memory.

Vulnerability at a Glance

cweCWE-190
fixAdded a bounds check on the shift operand before the bit-shift computation to ensure the resulting size is always within safe limits
riskOut-of-bounds memory write, silent data corruption, undefined behavior, potential code execution
languageC
root causeA bit-shift operation used to compute a pattern blit copy size was not validated, allowing attacker-controlled or malformed input to produce an overflowed size value
vulnerabilityInteger Overflow via Unvalidated Bit-Shift in Graphics Blit Size Computation

Integer Overflow in Graphics Blit: When Bit Shifts Go Dangerously Wrong

Introduction

There's a particular class of bug in C programming that is deceptively simple to write, notoriously difficult to spot in code review, and potentially catastrophic in its consequences: the integer overflow. It doesn't announce itself with a crash (at least, not always). It doesn't leave an obvious trail. Sometimes it silently corrupts data. Other times, it hands an attacker the keys to your memory.

This is the story of exactly that kind of vulnerability — catalogued as V-007 / CWE-190 — discovered and patched in rtg/mntgfx-gcc.c, a C source file responsible for graphics pattern blitting in a retargetable graphics subsystem. The vulnerability centers on a deceptively small piece of arithmetic: computing the size of a memory copy operation without ever validating the input that drives it.

If you write C or C++, work with low-level graphics or hardware interfaces, or simply care about writing robust systems code, this one is worth understanding deeply.


The Vulnerability Explained

What Is a "Pattern Blit"?

In computer graphics — particularly in classic and retro-computing contexts like AmigaOS — a blit (Block Image Transfer) is a hardware- or software-accelerated operation that copies a rectangular block of pixel data from one memory region to another. A pattern blit applies a repeating graphical pattern during this operation.

The pattern itself is described by a data structure — in this case, an AmigaOS Pattern struct — which includes a Size field. This Size field encodes the height of the pattern as a power of two: a Size of 0 means 1 row, 1 means 2 rows, 2 means 4 rows, and so on.

The Dangerous Calculation

The vulnerable code computed the number of bytes to copy like this:

// VULNERABLE CODE (simplified)
memcpy(dest, src, 2 * (1 << pat->Size));

Let's unpack why this is dangerous.

The expression 1 << pat->Size performs a left bit-shift on the integer 1. On a 32-bit system, int is typically a signed 32-bit type. Here's what happens as pat->Size grows:

pat->Size 1 << pat->Size 2 * (1 << pat->Size)
0 1 2
4 16 32
15 32768 65536
30 1,073,741,824 2,147,483,648 ✅ (barely)
31 -2,147,483,648 (UB!) 0 or garbage

When pat->Size is 31, the expression 1 << 31 sets the sign bit of a signed 32-bit integer. In C, this is undefined behavior (UB) — the C standard explicitly states that left-shifting into or past the sign bit of a signed integer is undefined. The compiler is free to do anything here: produce a negative number, wrap around, optimize the entire branch away, or generate code that behaves unpredictably across platforms or optimization levels.

Then multiplying by 2 compounds the problem:
- If 1 << 31 produces 0x80000000 (the most common outcome), then 2 * 0x80000000 overflows a 32-bit signed integer, wrapping to 0 on many implementations.
- A memcpy of 0 bytes copies nothing — causing silent data corruption where the destination buffer is left uninitialized or stale.
- On other platforms or with unsigned arithmetic, the value could become an enormous positive number, causing memcpy to perform a massive out-of-bounds write, scribbling over arbitrary memory well beyond the destination buffer.

Where Does pat->Size Come From?

This is the critical detail that elevates this from a theoretical bug to a real security concern: pat->Size is sourced directly from a caller-supplied AmigaOS graphics Pattern structure, with no bounds checking performed before use.

In other words, any caller — including untrusted code, a malformed file, or a network-supplied graphics command — can provide a Pattern struct with an arbitrary Size value, and the code will blindly use it in this dangerous calculation.

Real-World Impact

The consequences fall into two dangerous categories:

1. Silent Data Corruption (memcpy of 0 bytes)
When the overflow resolves to zero, the destination buffer is never written. If downstream code assumes the blit succeeded and reads from the destination, it reads uninitialized or stale memory. This can cause:
- Rendering artifacts or display corruption
- Logic errors in graphics-dependent code paths
- Information disclosure if stale buffer contents are exposed

2. Out-of-Bounds Memory Write (heap/stack smashing)
When the overflow produces a very large value, memcpy writes far beyond the intended destination buffer. Depending on the memory layout, this can:
- Overwrite adjacent heap metadata, enabling heap exploitation
- Corrupt stack frames, potentially enabling code execution
- Crash the process (denial of service)
- In the worst case, enable an attacker to achieve arbitrary code execution

Example Attack Scenario

Imagine a graphics application that renders user-supplied pattern data — perhaps from a loaded file format, a network stream, or an emulation layer. An attacker crafts a malicious Pattern structure with Size = 31. The application passes this structure to the blit function without validation. The integer overflow fires, memcpy is called with a computed size of 0 or 4294967296, and the attacker has either silently corrupted the graphics state or triggered an out-of-bounds write they may be able to leverage further.

In emulation contexts (like AmigaOS emulators), where guest code directly influences host memory operations, the attack surface is particularly significant.


The Fix

What Changed

The fix introduces explicit bounds validation on pat->Size before it is used in the shift-and-multiply expression. The corrected logic ensures that pat->Size cannot exceed a safe maximum value — specifically, a value that keeps the computed copy size within the bounds of a valid int (or size_t) and within the bounds of the destination buffer.

A safe implementation looks like this:

// SAFE CODE (illustrative fix)
#define MAX_PATTERN_SIZE 15  // 1 << 15 = 32768 rows, well within safe range

if (pat->Size > MAX_PATTERN_SIZE) {
    // Handle error: invalid pattern size
    return;  // or set an error code
}

size_t copy_size = (size_t)2 * (1u << pat->Size);
memcpy(dest, src, copy_size);

Key improvements in this approach:

  1. Bounds check before use: pat->Size is validated against a safe maximum before any arithmetic is performed.
  2. Use of unsigned arithmetic: Casting to unsigned (1u) or size_t before the shift avoids signed integer overflow entirely. Unsigned left-shift is well-defined in C as long as the result fits in the type.
  3. Explicit size_t for the copy size: Using size_t (the natural type for memory sizes) rather than int avoids sign-related issues when passing the value to memcpy.
  4. Early return on invalid input: Rather than attempting to proceed with corrupted state, the function bails out cleanly.

Why This Solves the Problem

By capping pat->Size at a known-safe maximum, the shift operation can never reach the sign bit or overflow. By using unsigned arithmetic, we eliminate the undefined behavior class entirely. And by validating at the point of ingestion — before the value influences any memory operation — we break the attacker's ability to supply a malicious Size value and have it acted upon.


Prevention & Best Practices

This vulnerability is a textbook example of CWE-190: Integer Overflow or Wraparound, and it's far more common than most developers realize. Here's how to systematically avoid it:

1. Always Validate External Input Before Arithmetic

Any value that originates from user input, file data, network packets, or inter-process communication must be treated as untrusted. Validate ranges before using values in arithmetic expressions, especially those that feed into memory operations.

// Bad: use first, validate never
memcpy(dst, src, compute_size(external_value));

// Good: validate first, use second
if (external_value > SAFE_MAX) { return ERROR; }
memcpy(dst, src, compute_size(external_value));

2. Prefer Unsigned Types for Sizes and Counts

When computing sizes, counts, or indices, use size_t or unsigned types. This eliminates sign-bit overflow UB from left shifts and makes arithmetic wrapping behavior well-defined (though still potentially incorrect — pair with bounds checks).

// Risky: signed shift
int size = 2 * (1 << n);

// Safer: unsigned shift
size_t size = (size_t)2 * ((size_t)1 << n);

3. Use Safe Arithmetic Helpers

For critical calculations, consider using overflow-safe arithmetic wrappers. GCC and Clang provide built-in overflow-checking functions:

int result;
if (__builtin_mul_overflow(2, (1 << pat->Size), &result)) {
    // Handle overflow
}

Alternatively, libraries like safe-iop or C23's <stdckdint.h> provide portable checked arithmetic.

4. Enable Compiler Warnings and Sanitizers

Modern compilers can catch many of these issues at compile time or runtime:

  • -Wall -Wextra: Enables common warning classes
  • -fsanitize=undefined: UBSan catches undefined behavior including signed overflow at runtime
  • -fsanitize=address: ASan detects out-of-bounds memory accesses
  • -fwrapv: Makes signed overflow wrap (defined behavior), though this doesn't eliminate logic errors
# Build with sanitizers during development/testing
gcc -fsanitize=undefined,address -Wall -Wextra -o myprogram myprogram.c

5. Use Static Analysis Tools

Static analyzers can identify integer overflow risks without even running the code:

  • Coverity: Industry-standard static analysis with strong integer overflow detection
  • CodeQL: GitHub's semantic code analysis engine (free for open source)
  • Clang Static Analyzer: Built into the LLVM toolchain
  • PVS-Studio: Commercial analyzer with excellent C/C++ coverage

6. Follow the Principle of Least Surprise in APIs

When designing functions that accept size or count parameters, document valid ranges explicitly and enforce them at the API boundary. Don't rely on callers to validate — validate defensively inside your function.

7. Reference Security Standards

This vulnerability maps to well-established security standards:


Conclusion

The vulnerability patched here is a reminder that security bugs don't always look dramatic in source code. A single line — 2 * (1 << pat->Size) — with no apparent malice, hides a critical flaw that can lead to silent data corruption or a full out-of-bounds memory write. The root cause is simple: an external value was trusted and used in arithmetic without validation.

The fix is equally simple in principle: check the input before you use it. But the discipline to apply that principle consistently, especially in performance-sensitive graphics and systems code where "we control the inputs" feels like a reasonable assumption, is what separates robust code from vulnerable code.

Key takeaways for developers:

  • Treat all externally-sourced values as untrusted, even in internal APIs
  • Use unsigned arithmetic for sizes and bit-shift operations
  • Validate bounds before arithmetic, not after
  • Enable sanitizers (-fsanitize=undefined,address) in your development and CI builds — they would have caught this immediately
  • Know your CWEs: CWE-190 (Integer Overflow) is perennially in the top vulnerability categories for a reason

Security is built one careful validation check at a time. Make yours count.


This post is part of an ongoing series on real-world security vulnerabilities and their fixes. Vulnerability identified and remediated by OrbisAI Security.

Frequently Asked Questions

What is an integer overflow vulnerability?

An integer overflow occurs when an arithmetic operation produces a result too large (or too small) for the integer type used to store it, causing the value to wrap around or produce undefined behavior. In security contexts, this often leads to undersized buffer allocations or incorrect loop bounds that enable out-of-bounds memory access.

How do you prevent integer overflow in C bit-shift operations?

Always validate the shift amount before performing the operation — ensure it is less than the bit-width of the type (e.g., less than 32 for a 32-bit integer). Additionally, check that the resulting shifted value does not exceed the size of the target buffer before using it to copy or index memory.

What CWE is integer overflow?

Integer overflow is classified as CWE-190: Integer Overflow or Wraparound. Related weaknesses include CWE-680 (Integer Overflow to Buffer Overflow) and CWE-758 (Reliance on Undefined, Unspecified, or Implementation-Defined Behavior).

Is using unsigned integers enough to prevent integer overflow in C?

No. While unsigned integers have well-defined wraparound behavior (unlike signed integers, which invoke undefined behavior on overflow), wraparound in an unsigned type still produces an incorrect value. A bit-shift that wraps a `uint32_t` size down to zero or a small number will still cause an out-of-bounds write when that value is used as a copy length.

Can static analysis detect integer overflow from bit-shift operations?

Yes. Tools like Semgrep, Coverity, CodeQL, and clang-analyzer can flag unvalidated shift operations and integer overflow patterns in C code. Orbis AppSec detected this exact vulnerability automatically and opened a pull request with the fix.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #18

Related Articles

critical

How integer overflow in CsoundMYFLTArray constructor happens in C++ and how to fix it

A critical integer overflow vulnerability was discovered in `Java/cs_glue.cpp` at line 324, where the `CsoundMYFLTArray` constructor multiplied a user-controlled integer `n` by `sizeof(MYFLT)` without checking for overflow before passing the result to `malloc`. An attacker supplying a value near `INT_MAX` could trigger the overflow, causing an undersized heap allocation that subsequent writes would overflow. The fix adds an explicit `SIZE_MAX / sizeof(MYFLT)` guard and replaces `malloc` with `ca

high

How integer overflow in malloc happens in C bipartite matching and how to fix it

A high-severity integer overflow vulnerability was discovered in the bipartite matching algorithm implementation where unchecked multiplication operations for memory allocation could wrap around, causing undersized buffer allocations and subsequent heap overflow. The fix replaces vulnerable `malloc(sizeof(int) * V)` patterns with safe `calloc(V, sizeof(int))` calls and adds proper bounds validation to prevent exploitation.

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.

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.

critical

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

A critical integer overflow vulnerability was discovered in OpenCV's HAL filter implementation where multiplying image dimensions without overflow protection could allocate dangerously undersized buffers. An attacker supplying crafted image dimensions (e.g., 65536×65536) could trigger heap corruption through out-of-bounds writes. The fix promotes the calculation to 64-bit arithmetic with a single cast.