Back to Blog
high SEVERITY5 min read

Integer Overflow in PlayerAnimation.cpp memcpy Size Calculations

A critical integer overflow vulnerability was discovered in `animation/PlayerAnimation.cpp` where `vCount * sizeof(float) * 3` calculations could wrap around on 32-bit platforms when processing malicious animation files. An attacker could craft a model file with an oversized vertex count to trigger a heap buffer overflow via memcpy. The fix adds bounds checks against `SIZE_MAX` before all size computations used in memory copy operations.

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

Answer Summary

This is an integer overflow vulnerability (CWE-190) in C++ animation processing code (`PlayerAnimation.cpp`) where `memcpy` size calculations like `vCount * sizeof(float) * 3` can wrap around to small values when `vCount` is attacker-controlled from a malicious animation file. The fix adds bounds checks (`vCount > SIZE_MAX / (sizeof(float) * 4)`) before any memory operations to ensure the multiplication cannot overflow, preventing heap buffer overflows on 32-bit and 64-bit platforms.

Vulnerability at a Glance

cweCWE-190 (Integer Overflow or Wraparound)
fixAdded `vCount > SIZE_MAX / (sizeof(float) * 4)` bounds check before memcpy calls
riskArbitrary code execution via crafted animation file
languageC++
root causeUnchecked multiplication of attacker-controlled vCount by sizeof(float)*3 in memcpy size argument
vulnerabilityInteger Overflow leading to Heap Buffer Overflow

Integer Overflow in PlayerAnimation.cpp memcpy Size Calculations

Introduction

The animation/PlayerAnimation.cpp file handles importing and processing skeletal animation data—vertex positions, normals, tangents, and joint weights—from model files. At line 222, a critical flaw existed in how the code computed byte sizes for memcpy operations. The variable vCount, read directly from animation file data, was multiplied by sizeof(float) * 3 (or * 4 for tangents) without any overflow validation. On 32-bit platforms—or anywhere vCount is stored as a 32-bit integer—a carefully chosen value like 0x20000000 (536,870,912) would cause the multiplication to silently wrap around to a tiny value, resulting in a heap buffer overflow that could enable arbitrary code execution.

This is the kind of vulnerability that turns a harmless-looking animation file into a weapon.

The Vulnerability Explained

Let's look at the core issue. In the original code at line 212:

size_t vCount = va->size(), wCount = jData._weightList.size();
if (vCount != wCount)
{
    // warning and return
}

After this check, vCount was used directly in memory operations like:

memcpy(dest, src, vCount * sizeof(float) * 3);  // positions
memcpy(dest, src, vCount * sizeof(float) * 4);  // tangents

The problem is arithmetic: sizeof(float) is 4 bytes. So vCount * 4 * 3 = vCount * 12. On a 32-bit platform where size_t is 32 bits, SIZE_MAX is 0xFFFFFFFF (4,294,967,295). If an attacker sets vCount = 0x20000000 (536,870,912):

0x20000000 * 12 = 0x180000000

This exceeds 32 bits. The result wraps to 0x80000000 on some platforms, or even smaller values depending on the exact multiplication chain. The memcpy then copies far fewer bytes than the destination buffer expects—or worse, if the allocation was based on the wrapped value while the source contains the full data, it writes past the buffer boundary.

Attack Scenario

  1. Attacker crafts a malicious .osgb or animation file with a vertex array whose reported size is 0x55555556 (1,431,655,766).
  2. Application loads the file through the ozz animation pipeline, which calls into PlayerAnimation.cpp.
  3. va->size() returns the attacker-controlled value as vCount.
  4. The multiplication vCount * sizeof(float) * 4 overflows: 0x55555556 * 16 = 0x555555560, which truncates to 0x55555560 on 32-bit—or on 64-bit with a 32-bit intermediate cast, wraps differently.
  5. memcpy uses the incorrect size, writing beyond the heap buffer and corrupting adjacent memory structures.
  6. Attacker achieves code execution by overwriting function pointers or vtable entries in adjacent heap objects.

A similar pattern existed at line 515 for tCount * sizeof(float) in the timepoints memcpy:

if (tCount > 0) memcpy(anim.timepoints_.data(), &timePoints[0], tCount * sizeof(float));

The Fix

The fix introduces explicit bounds checks that validate the multiplication cannot overflow before it's performed.

Change 1: Line 213 — Vertex/Weight Processing Guard

Before:

size_t vCount = va->size(), wCount = jData._weightList.size();
if (vCount != wCount)

After:

size_t vCount = va->size(), wCount = jData._weightList.size();
if (vCount > SIZE_MAX / (sizeof(float) * 4)) return;  // guard: integer overflow in memcpy sizes
if (vCount != wCount)

The check vCount > SIZE_MAX / (sizeof(float) * 4) ensures that even the largest multiplication used downstream (vCount * sizeof(float) * 4 for tangent data) cannot overflow size_t. Since sizeof(float) * 4 = 16, this limits vCount to SIZE_MAX / 16—which is still over 1 billion vertices on a 64-bit system, far more than any legitimate animation file would contain. If the check fails, the function returns early, safely rejecting the malicious input.

Change 2: Line 515 — Timepoints memcpy Guard

Before:

if (tCount > 0) memcpy(anim.timepoints_.data(), &timePoints[0], tCount * sizeof(float));

After:

if (tCount > 0 && (size_t)tCount <= SIZE_MAX / sizeof(float))
    memcpy(anim.timepoints_.data(), &timePoints[0], (size_t)tCount * sizeof(float));

Here, tCount is validated against SIZE_MAX / sizeof(float) before the multiplication occurs. The explicit (size_t) cast also ensures the multiplication happens at the full platform width, preventing intermediate 32-bit truncation.

Why Both Changes Matter

The PR notes that similar patterns exist at lines 81, 223, 227, 233, and 248. The two changes address the primary entry points where attacker-controlled sizes flow into memcpy. The sizeof(float) * 4 divisor in the first check is deliberately chosen as the largest multiplier used anywhere downstream, creating a single guard that protects all subsequent size calculations for that vCount value.

Prevention & Best Practices

1. Always validate before multiplying for memory operations:

// Safe pattern
if (count > SIZE_MAX / element_size) {
    // reject input
    return error;
}
memcpy(dst, src, count * element_size);

2. Use compiler overflow builtins when available:

size_t result;
if (__builtin_mul_overflow(vCount, sizeof(float) * 3, &result)) {
    return; // overflow detected
}
memcpy(dst, src, result);

3. Treat file-derived sizes as untrusted input. Any value read from a file—vertex counts, array lengths, string sizes—must be validated against reasonable bounds before use in memory operations.

4. Consider std::span or container-based copies instead of raw memcpy, which can enforce bounds at the type level.

5. Enable AddressSanitizer in CI to catch heap overflows during testing with fuzzed inputs.

6. Reference: This vulnerability maps to CWE-190: Integer Overflow or Wraparound and is closely related to CWE-122: Heap-based Buffer Overflow.

Key Takeaways

  • vCount * sizeof(float) * 3 is dangerous when vCount comes from file data—always check vCount <= SIZE_MAX / (sizeof(float) * 3) first.
  • A single guard using the largest downstream multiplier (* 4 for tangents) protects all smaller multiplications (* 3 for positions, * 1 for weights) in the same function.
  • Animation/model file parsers are high-value attack surfaces—they process complex binary data and are often exposed to untrusted content (downloaded assets, user uploads, mod files).
  • The (size_t) cast on tCount in the second fix prevents intermediate 32-bit truncation when tCount is declared as int but used in a size_t multiplication.
  • Regression tests using adversarial values like 0x55555556 and UINT32_MAX/12 + 1 ensure the overflow detection logic is never accidentally removed.

Conclusion

This vulnerability demonstrates how a simple arithmetic operation—multiplying a vertex count by a constant—can become a critical security flaw when the input is attacker-controlled. The fix is elegant in its simplicity: a single comparison against SIZE_MAX before any multiplication occurs. For developers working with binary file formats, 3D assets, or any code that computes buffer sizes from external data, this pattern should be second nature. Validate the math before you trust the result.

Frequently Asked Questions

What is an integer overflow vulnerability?

An integer overflow occurs when an arithmetic operation produces a value that exceeds the maximum size of the integer type, causing it to wrap around to a small or negative value. In memory operations, this can lead to undersized allocations or incorrect memcpy sizes.

How do you prevent integer overflow in C++?

Check that multiplicands won't exceed SIZE_MAX before performing the multiplication, use safe math libraries, or compare against the quotient (e.g., `if (a > SIZE_MAX / b)` before computing `a * b`). Compiler built-ins like `__builtin_mul_overflow` also help.

What CWE is integer overflow?

CWE-190 (Integer Overflow or Wraparound) covers cases where arithmetic operations exceed the maximum value for the integer type, potentially leading to buffer overflows or other memory corruption.

Is using size_t enough to prevent integer overflow?

No. While size_t is unsigned and platform-width, multiplying large values can still overflow it—especially on 32-bit platforms where SIZE_MAX is only ~4GB. Explicit bounds checking before multiplication is required.

Can static analysis detect integer overflow?

Yes, many static analyzers (Coverity, PVS-Studio, clang-tidy) can flag unchecked arithmetic in memcpy/malloc size arguments, though they may produce false positives. Targeted security scanners are particularly effective at finding these patterns.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #28

Related Articles

high

How Spring Boot EndpointRequest.to() security bypass happens in Java Spring Boot and how to fix it

CVE-2025-22235 is a high-severity vulnerability in Spring Boot where `EndpointRequest.to()` creates an incorrect request matcher when an actuator endpoint is not exposed, potentially allowing unauthorized access to protected endpoints. The fix upgrades Spring Boot from 3.4.4 to 3.4.5 in the anti-corruption-layer service's `pom.xml`. This is particularly dangerous because actuator endpoints can expose sensitive operational data and administrative functions.

medium

How path traversal happens in C file extraction and how to fix it

A path traversal vulnerability in the borpak archive extraction tool allowed attackers to write files to arbitrary locations on the filesystem by crafting malicious .pak archives with `../` sequences in filenames. This medium-severity issue in `tools/borpak/source/borpak.c` could enable system compromise through overwriting critical files like `.bashrc` or cron jobs. The fix implements path validation to ensure extracted files never escape the intended extraction directory.

critical

How SQL injection happens in PostgreSQL dictionary synchronization and how to fix it

A critical SQL injection vulnerability in `zhparser--2.1.sql` allowed attackers to execute arbitrary SQL commands by crafting malicious database names. The vulnerability existed because the dictionary synchronization function constructed COPY commands using string concatenation without proper escaping. This fix implements parameterized queries to safely handle database identifiers.

critical

How command injection happens in Go ffmpeg wrappers and how to fix it

A critical command injection vulnerability was discovered in `drivers/local/util.go` where user-influenced file paths were passed directly to `ffmpeg.Input()` without any sanitization. Because many ffmpeg wrapper libraries construct shell command strings under the hood, an attacker could embed shell metacharacters in a file path to execute arbitrary OS commands with server-level privileges. The fix introduces a `sanitizeFilePath()` function that validates paths are absolute, clean, and point to

critical

How heap buffer overflow happens in C++ JPEG2000 decoding and how to fix it

A critical heap buffer overflow vulnerability was discovered in the OpenJPEG wrapper for Android (jp2forandroid). The `opj_read_from_byte_array()` function performed memcpy operations without validating that the source offset hadn't exceeded the buffer length, allowing maliciously crafted JPEG2000 images to trigger arbitrary code execution. A simple bounds check before the copy operation now prevents this exploitation path.

critical

How heap buffer overflow happens in C UART response handling and how to fix it

A critical heap buffer overflow vulnerability was discovered in the AT client response handler (`sm_at_client.c`) where incoming UART data was copied into a fixed-size buffer without verifying available capacity. A compromised modem or malicious UART data could trigger arbitrary heap corruption. The fix replaces an assertion-only guard with proper bounds clamping using `MIN()` to ensure writes never exceed the `at_cmd_resp` buffer allocation.