Back to Blog
high SEVERITY6 min read

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

O
By Orbis AppSec
Published July 10, 2026Reviewed July 10, 2026

Answer Summary

This is an integer truncation leading to heap buffer overflow (CWE-680/CWE-122) in C++ UEFI firmware code. The `MO_UINT16` type was used to store a size calculation (`sizeof(MO_UEFI_ACPI_SIMPLE_MEMORY_RANGE_ITEM) * Count`) that can exceed 65535, causing silent truncation and undersized heap allocation. The fix changes the type from `MO_UINT16` to `MO_UINTN` (native pointer-width integer), ensuring the full multiplication result is preserved for the heap allocation call.

Vulnerability at a Glance

cweCWE-680 (Integer Overflow to Buffer Overflow)
fixChanged size variable type from MO_UINT16 to MO_UINTN (platform-native width)
riskHeap corruption enabling arbitrary code execution in firmware context
languageC++ (UEFI firmware)
root causeSize calculation stored in MO_UINT16 truncates when sizeof(struct) * Count > 65535
vulnerabilityInteger truncation leading to heap buffer overflow

How Integer Truncation Heap Overflow Happens in C++ UEFI ACPI Parsing and How to Fix It

Introduction

The Mobility.Uefi.Acpi.cpp file handles parsing of ACPI SRAT (System Resource Affinity Table) data to query memory ranges in a UEFI firmware environment. A critical flaw in the MoUefiAcpiQueryMemoryRanges, MoUefiAcpiQueryMergedMemoryRanges, and MoUefiAcpiQueryMemoryHoles functions created a heap buffer overflow vulnerability that could be exploited by an attacker who controls ACPI table contents—a realistic scenario in virtualized environments where the hypervisor provides firmware tables.

The root cause? A single type choice: MO_UINT16 was used to store the result of a size multiplication that can easily exceed 65535. This two-byte integer silently truncates the result, causing MoPlatformHeapAllocate to allocate a buffer far smaller than needed, while the subsequent loop writes data far beyond the allocation boundary.

The Vulnerability Explained

Let's look at the vulnerable code at line 262 (and similar patterns at lines 370 and 433):

MO_UINT16 Size = static_cast<MO_UINT16>(
    sizeof(MO_UEFI_ACPI_SIMPLE_MEMORY_RANGE_ITEM) * Count);

PMO_UEFI_ACPI_SIMPLE_MEMORY_RANGE_ITEM Ranges = nullptr;
if (MO_RESULT_SUCCESS_OK != ::MoPlatformHeapAllocate(
    reinterpret_cast<PMO_POINTER>(&Ranges),
    Size))

Here's the math that makes this dangerous:

  • MO_UEFI_ACPI_SIMPLE_MEMORY_RANGE_ITEM is at least 16 bytes (likely containing base address, length, and flags for a memory range descriptor)
  • Count is a MO_UINT16, so it can be up to 65535
  • The multiplication 16 * Count can produce values up to 1,048,560 (0xFFFF0)
  • But MO_UINT16 can only hold values up to 65535 (0xFFFF)

When Count exceeds 4095 (for a 16-byte struct), the multiplication overflows the 16-bit Size variable. For example:

Count True Size (bytes) Truncated MO_UINT16 Size Buffer Shortfall
4096 65,536 0 65,536 bytes
5000 80,000 14,464 65,536 bytes
65535 1,048,560 1,048,560 & 0xFFFF = 65,520 983,040 bytes

After the undersized allocation, a loop iterates Count times, writing sizeof(MO_UEFI_ACPI_SIMPLE_MEMORY_RANGE_ITEM) bytes per iteration into the Ranges buffer. This writes potentially hundreds of kilobytes beyond the allocated heap buffer.

Attack Scenario

In a virtualized environment (e.g., running under a malicious or compromised hypervisor), an attacker can craft an ACPI SRAT table with a large number of Memory Affinity Structures. When the guest OS firmware parses this table:

  1. The SRAT parser counts memory affinity entries → Count = 5000
  2. Size = (MO_UINT16)(16 * 5000)Size = 14464 (truncated from 80000)
  3. MoPlatformHeapAllocate allocates only 14,464 bytes
  4. The filling loop writes 80,000 bytes into a 14,464-byte buffer
  5. 65,536 bytes overflow into adjacent heap memory

This heap corruption can be leveraged for arbitrary code execution at the firmware level—a particularly devastating outcome since firmware code runs at the highest privilege level, below the operating system.

The Fix

The fix is elegant in its simplicity: change the type of Size from MO_UINT16 to MO_UINTN (the platform-native unsigned integer type, equivalent to size_t). This was applied in three locations:

Before (vulnerable):

MO_UINT16 Size = static_cast<MO_UINT16>(
    sizeof(MO_UEFI_ACPI_SIMPLE_MEMORY_RANGE_ITEM) * Count);

After (fixed):

MO_UINTN Size =
    sizeof(MO_UEFI_ACPI_SIMPLE_MEMORY_RANGE_ITEM) * Count;

The changes were made at three call sites:

  1. Line 231 (MoUefiAcpiQueryMemoryRanges): Allocates buffer for raw memory ranges from SRAT
  2. Line 373 (MoUefiAcpiQueryMergedMemoryRanges): Allocates buffer for merged/consolidated ranges
  3. Line 436 (MoUefiAcpiQueryMemoryHoles): Allocates buffer for memory hole descriptors

By using MO_UINTN (which is 32-bit on 32-bit platforms and 64-bit on 64-bit platforms), the multiplication result is preserved without truncation. On a 64-bit UEFI platform, MO_UINTN can hold values up to 2^64-1, making overflow from this multiplication impossible. Even on 32-bit platforms, the maximum result (16 × 65535 = 1,048,560) fits comfortably within a 32-bit unsigned integer.

Notice also that the explicit static_cast<MO_UINT16> was removed. That cast was actually masking a compiler warning about narrowing conversion—it told the compiler "yes, I know this might truncate, do it anyway." Removing the cast and using the correct type eliminates both the vulnerability and the need for the cast.

Prevention & Best Practices

1. Use Appropriate Types for Size Calculations

Always use size_t, MO_UINTN, or equivalent platform-native types for memory size calculations. Never store allocation sizes in types narrower than the platform's addressing width.

// BAD: Narrow type for size
MO_UINT16 size = element_size * count;

// GOOD: Platform-native type
MO_UINTN size = element_size * count;

// BETTER: With explicit overflow check
MO_UINTN size;
if (count > MO_UINTN_MAX / element_size) {
    return MO_RESULT_ERROR_ARITHMETIC_OVERFLOW;
}
size = element_size * count;

2. Enable Compiler Warnings

Use -Wconversion and -Wnarrowing (GCC/Clang) or /W4 (MSVC) to catch implicit narrowing conversions. Treat these warnings as errors in security-critical code.

3. Audit static_cast to Narrower Types

Any static_cast that narrows an integer (e.g., static_cast<uint16_t>(...)) in a size calculation context should be treated as a red flag during code review.

4. Validate Counts Before Multiplication

For firmware code processing hardware tables, validate that counts are within reasonable bounds before performing size calculations:

if (Count > MAX_EXPECTED_MEMORY_RANGES) {
    return MO_RESULT_ERROR_INVALID_PARAMETER;
}

5. Use Safe Integer Arithmetic Libraries

Consider using safe integer libraries (like SafeInt for C++ or checked arithmetic intrinsics) that detect overflow at runtime.

Key Takeaways

  • Never use MO_UINT16 (or uint16_t) to store the result of a size multiplication — even if the count itself fits in 16 bits, the product of count × element_size likely does not.
  • static_cast to a narrower type is not validation — it's silent truncation. The explicit cast at line 262 gave a false sense of intentionality while hiding a critical bug.
  • ACPI table parsing is an attack surface in virtualized environments — hypervisors control the firmware tables presented to guests, making count/size fields attacker-controlled.
  • The same bug pattern appeared three times in the same file (MoUefiAcpiQueryMemoryRanges, MoUefiAcpiQueryMergedMemoryRanges, MoUefiAcpiQueryMemoryHoles), demonstrating how copy-paste propagates vulnerabilities.
  • Firmware-level heap overflows are uniquely dangerous — they execute below the OS, potentially compromising the entire trust chain from boot through runtime.

How Orbis AppSec Detected This

  • Source: The Count value derived from parsing ACPI SRAT Memory Affinity Structures in the firmware table (attacker-controlled in virtualized environments)
  • Sink: MoPlatformHeapAllocate(reinterpret_cast<PMO_POINTER>(&Ranges), Size) in Mobility.Uefi.Acpi.cpp:262 (and lines 373, 436)
  • Missing control: No use of an appropriately-sized integer type for the size calculation; the MO_UINT16 type silently truncates the multiplication result before it reaches the allocator
  • CWE: CWE-680 (Integer Overflow to Buffer Overflow)
  • Fix: Changed the Size variable type from MO_UINT16 to MO_UINTN in all three affected allocation sites, preventing truncation of the size calculation

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

This vulnerability demonstrates a deceptively simple but devastating pattern: using an integer type that's too small to hold a computed size. The MO_UINT16 type seemed reasonable at first glance—after all, Count itself is a MO_UINT16—but the multiplication by a struct size pushes the result well beyond 16-bit range. In firmware code that processes hardware tables, this creates a directly exploitable heap overflow.

The fix—changing three variable declarations from MO_UINT16 to MO_UINTN—is minimal in code change but maximal in security impact. It's a reminder that type selection is a security decision, especially when that type determines how much memory gets allocated.

References

Frequently Asked Questions

What is integer truncation leading to heap overflow?

It occurs when a size calculation is stored in a variable too small to hold the result, causing the value to wrap around. When this truncated value is used for memory allocation, the allocated buffer is smaller than expected, and subsequent writes overflow it.

How do you prevent integer truncation heap overflows in C++?

Use appropriately-sized integer types (size_t or platform-native types like MO_UINTN) for all size calculations, especially those involving multiplication. Add explicit overflow checks before allocation, and avoid casting size results to narrower types.

What CWE is integer truncation to buffer overflow?

CWE-680 (Integer Overflow to Buffer Overflow) is the primary classification, with CWE-122 (Heap-based Buffer Overflow) as the resulting weakness and CWE-190 (Integer Overflow or Wraparound) as the root cause.

Is using size_t enough to prevent integer truncation overflows?

Using size_t or equivalent platform-native types eliminates truncation for most practical cases, but on 32-bit platforms with very large counts, multiplication can still overflow. Best practice combines proper types with explicit overflow checks (e.g., checking if count > SIZE_MAX / element_size).

Can static analysis detect integer truncation vulnerabilities?

Yes, many static analyzers flag narrowing conversions and truncation in size calculations. Tools like Coverity, PVS-Studio, and compiler warnings (-Wconversion, -Wnarrowing) can detect these patterns, though they may produce false positives requiring triage.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1

Related Articles

critical

How arbitrary code execution via injected protobuf definition type fields happens in Node.js and how to fix it

A critical arbitrary code execution vulnerability (CVE-2026-41242) was discovered in protobufjs versions prior to 7.5.5 and 8.0.1, allowing attackers to inject malicious code through crafted protobuf definition type fields. The fix upgrades the dependency from the vulnerable version 6.11.4 to 7.5.5, which properly sanitizes type field inputs during protobuf parsing. This vulnerability is especially dangerous because protobufjs is widely used in Node.js applications for serialization, meaning a s

high

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

A high-severity command injection vulnerability was discovered in `bootstrap.js` at line 34, where the `exec()` function was used to run shell commands constructed from a `folder` variable. By replacing `exec()` with `execFile()` and passing arguments as an array, the fix eliminates the shell interpolation entirely, closing the door on command injection attacks that could have affected all downstream consumers of this Node.js library.

high

How unsafe pickle deserialization happens in NumPy's np.load() and how to fix it

A high-severity arbitrary code execution vulnerability was discovered in `tools/ardy-engine/retarget.py` where `np.load()` was called with `allow_pickle=True`, enabling attackers to embed malicious pickle payloads in `.npz` files. The fix was a single-character change—switching `allow_pickle=True` to `allow_pickle=False`—that eliminates the deserialization attack vector while preserving the file's legitimate array data loading functionality.

high

How SSRF and Credential Leakage via Absolute URLs happens in axios and how to fix it

A high-severity vulnerability (CVE-2025-27152) in axios versions prior to 1.8.2 allowed Server-Side Request Forgery (SSRF) attacks and credential leakage when making HTTP requests with absolute URLs. This vulnerability was fixed by upgrading from axios 1.7.4 to 1.8.2 in both package.json and package-lock.json, eliminating the attack vector that could have exposed authentication tokens and enabled unauthorized server-side requests.

high

How pickle-based arbitrary code execution happens in PyTorch and how to fix it

A high-severity arbitrary code execution vulnerability was discovered in `scripts/export_joyvasa_audio.py` where `torch.load()` was called with `weights_only=False`, allowing any pickle-serialized Python object — including malicious code — to execute during checkpoint loading. The fix switches to `weights_only=True` and explicitly allowlists only the two non-standard classes the checkpoint actually requires: `argparse.Namespace` and `pathlib.PosixPath`. This closes a real code execution path tha

critical

How WebSocket protocol length header abuse happens in Node.js and how to fix it

A critical vulnerability (CVE-2026-54466) in websocket-driver versions prior to 0.7.5 allows attackers to corrupt WebSocket messages by manipulating protocol length headers. This can lead to data integrity issues, denial of service, or potentially arbitrary code execution in affected Node.js applications. The fix involves upgrading the websocket-driver dependency to version 0.7.5.