Back to Blog
critical SEVERITY9 min read

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

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

Answer Summary

This is a critical heap buffer overflow vulnerability (CWE-122) in C code within `phlib/nativefile.c`, caused by multiple `memcpy` calls that copied filename and extended-attribute (EA) data into fixed-size structures without verifying source lengths against destination buffer boundaries. The vulnerability also involved integer overflow in size calculations used for heap allocations. The fix replaces unchecked arithmetic with Windows' safe integer helpers `RtlULongAdd` and `RtlULongMult`, which return failure codes if overflow would occur, preventing both the integer overflow and the subsequent out-of-bounds memory write.

Vulnerability at a Glance

cweCWE-122
fixReplace unchecked addition/multiplication with RtlULongAdd/RtlULongMult; validate all lengths before memcpy
riskHeap memory corruption enabling arbitrary code execution or privilege escalation
languageC
root causememcpy calls copied variable-length filename/EA data into fixed-size structures without bounds checking; size arithmetic could overflow before allocation
vulnerabilityHeap Buffer Overflow via Unchecked memcpy and Integer Overflow

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

Introduction

Buffer overflows are among the oldest and most dangerous vulnerability classes in systems programming — and they keep showing up in production code. This post breaks down a critical-severity heap buffer overflow discovered in phlib/nativefile.c, a native file-operation helper used in a Windows application built on the Process Hacker / SystemInformer codebase.

The root cause is deceptively simple: arithmetic used to calculate allocation sizes was not protected against integer overflow, and the lengths of caller-supplied strings were never validated before being copied into those allocations. The result? A carefully crafted filename or extended-attribute (EA) name could silently corrupt heap memory adjacent to the allocated buffer.

If you write C or C++ code that touches the Windows Native API — or any low-level file I/O — this vulnerability pattern is one you need to recognize on sight.


The Vulnerability Explained

What Is a Heap Buffer Overflow?

A heap buffer overflow occurs when code writes data beyond the end of a heap-allocated buffer. Unlike stack overflows (which famously overwrite return addresses), heap overflows corrupt adjacent heap metadata or other live allocations. This can lead to:

  • Arbitrary code execution if an attacker can control what gets written and where
  • Privilege escalation in kernel-adjacent or privileged processes
  • Denial of service through process crashes or corrupted state
  • Information disclosure if heap layout leaks sensitive data from adjacent allocations

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


The Vulnerable Code: Two Separate Sites

The vulnerability manifested in two places inside phlib/nativefile.c.

Site 1 — PhMoveFile: Rename Information Buffer

// VULNERABLE — before the fix
renameInfoLength = sizeof(FILE_RENAME_INFORMATION) + fileNameLength + sizeof(UNICODE_NULL);
renameInfo = PhAllocateStack(renameInfoLength);

FILE_RENAME_INFORMATION is a Windows Native API structure used when renaming files via NtSetInformationFile. It has a flexible trailing array for the new filename. The code calculates the required buffer size by adding three values together using plain C arithmetic:

  • sizeof(FILE_RENAME_INFORMATION) — fixed structure size
  • fileNameLength — caller-supplied, derived from a UNICODE_STRING
  • sizeof(UNICODE_NULL) — a two-byte null terminator

The problem: If fileNameLength is close to ULONG_MAX (0xFFFFFFFF), the addition wraps around to a small number. The allocation succeeds — allocating a tiny buffer — but the subsequent memcpy writes the full, large filename into it, blowing past the buffer boundary and corrupting the heap.

Site 2 — PhSetFileExtendedAttributes: EA Information Buffer

// VULNERABLE — before the fix
infoLength = sizeof(FILE_FULL_EA_INFORMATION) + (ULONG)Name->Length + sizeof(ANSI_NULL);
if (Value) infoLength += (ULONG)Value->Length + sizeof(ANSI_NULL);

FILE_FULL_EA_INFORMATION holds extended attributes for NTFS files. The same pattern repeats: unchecked addition of caller-supplied Name->Length and Value->Length values. A SIZE_T-to-ULONG cast ((ULONG)Name->Length) silently truncates on 64-bit systems if Name->Length exceeds 32 bits, and the additions that follow can still overflow.

There's a secondary issue here too: the EA name length is stored in a UCHAR field (EaNameLength, max 255) and the value length in a USHORT field (EaValueLength, max 65535). If the caller provides lengths that don't fit in those narrower types, the truncated values written into the structure will be inconsistent with the actual data copied — a recipe for downstream memory corruption.


Attack Scenario

Consider a privileged file manager or security tool that calls PhMoveFile with a destination path derived from user input (a rename dialog, a command-line argument, or a network-sourced path). An attacker who can influence the destination filename can supply a string whose byte length, when added to sizeof(FILE_RENAME_INFORMATION), wraps around to a small value. The allocator hands back a tiny buffer; the memcpy copies gigabytes (or just a few extra bytes) past it.

In practice, exploitability depends on:
- Whether the attacker controls input to these functions
- The heap layout at the time of the overflow
- Whether the process runs with elevated privileges (common for file utilities)

Even without a full exploit chain, a reliable crash (denial of service) is trivially achievable once this primitive is in hand.


The Fix

The fix replaces every unchecked arithmetic expression with Windows safe integer helper functions that return an error status on overflow, and adds narrowing conversion checks before assigning lengths to smaller-typed struct fields.

Fix 1 — PhMoveFile: Safe Size Calculation

// FIXED — after the patch
status = RtlULongAdd(sizeof(FILE_RENAME_INFORMATION), fileNameLength, &renameInfoLength);
if (!NT_SUCCESS(status))
    goto CleanupExit;

status = RtlULongAdd(renameInfoLength, sizeof(UNICODE_NULL), &renameInfoLength);
if (!NT_SUCCESS(status))
    goto CleanupExit;

renameInfo = PhAllocateStack(renameInfoLength);
if (!renameInfo) return STATUS_NO_MEMORY;

RtlULongAdd (from <intsafe.h>) performs the addition and returns STATUS_INTEGER_OVERFLOW if the result would exceed ULONG_MAX. The code now bails out cleanly before any allocation occurs, rather than allocating a dangerously undersized buffer.

Fix 2 — PhSetFileExtendedAttributes: Safe Size Calculation + Narrowing Checks

// FIXED — after the patch

// Step 1: Validate Name->Length fits in ULONG, then in UCHAR
status = RtlSIZETToULong(Name->Length, &nameLength);
if (!NT_SUCCESS(status))
    return status;
status = RtlULongToUChar(nameLength, &eaNameLength);  // must fit in UCHAR (max 255)
if (!NT_SUCCESS(status))
    return status;

// Step 2: Build infoLength safely
status = RtlULongAdd(sizeof(FILE_FULL_EA_INFORMATION), nameLength, &infoLength);
if (!NT_SUCCESS(status))
    return status;
status = RtlULongAdd(infoLength, sizeof(ANSI_NULL), &infoLength);
if (!NT_SUCCESS(status))
    return status;

// Step 3: Validate Value->Length fits in ULONG, then in USHORT
if (Value)
{
    status = RtlSIZETToULong(Value->Length, &valueLength);
    if (!NT_SUCCESS(status))
        return status;
    status = RtlULongToUShort(valueLength, &eaValueLength);  // must fit in USHORT (max 65535)
    if (!NT_SUCCESS(status))
        return status;

    status = RtlULongAdd(infoLength, valueLength, &infoLength);
    // ... continues
}

This fix addresses three distinct hazards in one location:
1. Integer overflow in the size arithmetic (via RtlULongAdd)
2. Truncating cast from SIZE_T to ULONG (via RtlSIZETToULong)
3. Narrowing mismatch between logical length and struct field width (via RtlULongToUChar / RtlULongToUShort)


Why RtlULongAdd and Friends?

Microsoft ships a comprehensive safe-integer library in <intsafe.h> specifically to address this class of bug. Each function:
- Performs the operation
- Checks for overflow/underflow
- Returns S_OK / STATUS_INTEGER_OVERFLOW accordingly
- Writes the result only if safe

Function Operation Overflow condition
RtlULongAdd a + b → ULONG Result > ULONG_MAX
RtlSIZETToULong SIZE_T → ULONG Value > ULONG_MAX
RtlULongToUChar ULONG → UCHAR Value > 255
RtlULongToUShort ULONG → USHORT Value > 65535

Using these functions is the Windows-idiomatic equivalent of Rust's checked_add() or C++'s std::numeric_limits checks.


Prevention & Best Practices

1. Never Use Unchecked Arithmetic for Allocation Sizes

Any expression of the form sizeof(X) + userSuppliedLength in C is a potential integer overflow. Treat every such expression as suspect until proven safe.

Unsafe pattern:

size_t bufLen = sizeof(HEADER) + userLen;  // wraps if userLen is huge
void *buf = malloc(bufLen);
memcpy(buf, userdata, userLen);            // overflow

Safe pattern (POSIX/Linux):

if (userLen > SIZE_MAX - sizeof(HEADER)) { return -EINVAL; }
size_t bufLen = sizeof(HEADER) + userLen;
void *buf = malloc(bufLen);
if (!buf) return -ENOMEM;
memcpy(buf, userdata, userLen);

Safe pattern (Windows Native API):

ULONG bufLen;
if (!NT_SUCCESS(RtlULongAdd(sizeof(HEADER), userLen, &bufLen)))
    return STATUS_INTEGER_OVERFLOW;
void *buf = ExAllocatePool(PagedPool, bufLen);

2. Validate Narrowing Conversions Explicitly

When you assign a wider integer to a narrower field (e.g., ULONGUCHAR), always check that the value fits. Silent truncation is a logic bug that can become a security bug.

// BAD
info->EaNameLength = (UCHAR)nameLen;  // silently truncates 300 → 44

// GOOD
if (nameLen > UCHAR_MAX) return STATUS_INVALID_PARAMETER;
info->EaNameLength = (UCHAR)nameLen;

3. Use Compiler and Sanitizer Tooling

Tool What it catches
AddressSanitizer (ASan) Heap/stack buffer overflows at runtime
UndefinedBehaviorSanitizer (UBSan) Integer overflow (with -fsanitize=integer)
/RTC1 (MSVC) Runtime checks for stack corruption
/analyze (MSVC SAL) Static analysis of buffer size annotations
CodeQL Dataflow analysis for tainted size expressions
Coverity / Polyspace Commercial static analysis

For Windows kernel and native-mode code specifically, Driver Verifier and Application Verifier with heap checking enabled will catch overflows at the point of occurrence rather than at a later, confusing crash site.

4. Annotate Buffer Parameters with SAL

Microsoft's Source Annotation Language (SAL) lets you document — and statically verify — buffer size constraints:

// Tell /analyze that DestBuffer has DestSize bytes available
void CopyData(
    _Out_writes_bytes_(DestSize) PVOID DestBuffer,
    _In_ ULONG DestSize,
    _In_reads_bytes_(SrcSize) PCVOID SrcBuffer,
    _In_ ULONG SrcSize
);

The MSVC static analyzer will flag calls where SrcSize > DestSize.

5. Apply the Principle of Least Trust to Lengths

Any length value that originates from outside the immediate function — a caller argument, a network packet, a file on disk, a user-mode buffer in kernel code — must be treated as untrusted until validated. This is especially true in:

  • File system drivers and filter drivers
  • IPC message handlers
  • Parsers for binary file formats
  • Any code that bridges user-mode and kernel-mode

6. Relevant Standards and References


Conclusion

This vulnerability is a textbook example of why integer arithmetic in allocation size calculations is a security-critical operation, not a mundane implementation detail. The original code looked completely reasonable at a glance — three values added together to size a buffer — but the absence of overflow checks turned it into a heap corruption primitive.

The fix is equally instructive: it doesn't require a dramatic architectural change. Replacing three lines of arithmetic with a handful of RtlULongAdd calls and narrowing-conversion checks is all it takes to close the vulnerability. The Windows safe-integer API exists precisely for this purpose, and using it consistently is a low-friction habit that pays significant security dividends.

Key takeaways for developers:

  1. Every addition or multiplication that feeds into an allocation size is a potential integer overflow — treat it that way.
  2. Every narrowing cast (wider int → narrower int) is a potential truncation bug — validate before casting, don't cast and hope.
  3. Use platform-provided safe integer libraries (<intsafe.h> on Windows, __builtin_add_overflow on GCC/Clang, checked_add in Rust) rather than rolling your own checks.
  4. Run AddressSanitizer and UBSan in your CI pipeline — they catch these bugs cheaply before they reach production.
  5. Automated security scanning (as used to detect this issue) is a valuable layer of defense, but it works best when paired with developer education about why these patterns are dangerous.

Secure coding is a discipline, not a checklist. Understanding the mechanism of vulnerabilities like this one is what allows you to spot novel instances of the same pattern in code that no scanner has seen before.


Fixed by the OrbisAI Security automated remediation pipeline. Learn more at orbisappsec.com.

Frequently Asked Questions

What is a heap buffer overflow?

A heap buffer overflow occurs when a program writes more data into a heap-allocated buffer than it was sized to hold, corrupting adjacent memory. In this case, memcpy copied attacker-controlled filename or EA data past the end of a fixed-size structure.

How do you prevent heap buffer overflow in C?

Always validate that the source length does not exceed the destination buffer size before calling memcpy or similar functions. Use safe integer arithmetic helpers (like RtlULongAdd on Windows or checked_add in other environments) to prevent size calculations from wrapping around.

What CWE is heap buffer overflow?

Heap buffer overflow is classified as CWE-122 (Heap-based Buffer Overflow), a subtype of CWE-119 (Improper Restriction of Operations within the Bounds of a Memory Buffer).

Is bounds checking on the final memcpy enough to prevent this vulnerability?

No. If the size passed to malloc/HeapAlloc was itself computed with overflowing arithmetic, the allocation may be too small even before memcpy runs. Both the size calculation and the copy operation must be validated independently.

Can static analysis detect this type of vulnerability?

Yes. Static analysis tools like Semgrep, CodeQL, and PVS-Studio can flag memcpy calls where the length argument is derived from user-controlled input without prior validation, as well as integer arithmetic that lacks overflow checks.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #2910

Related Articles

high

How insecure string copy functions happen in C apputils.c and how to fix it

A high-severity buffer overflow vulnerability was discovered in `src/apps/common/apputils.c`, where `strncpy()` was used without guaranteed null-termination across four call sites — including the `sock_bind_to_device()` and `getdomainname()` functions. The fix replaces all unsafe `strncpy()` calls with `snprintf()`, which enforces both length bounds and automatic null-termination. Left unpatched, these flaws could allow an attacker to corrupt memory, crash the process, or potentially execute arb

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.

critical

How buffer overflow via strcpy() happens in C zlib and how to fix it

A critical buffer overflow vulnerability was discovered in `general/libzlib/gzlib.c` where multiple `strcpy()` and `strcat()` calls operated without bounds checking. An attacker controlling file paths or error messages could overflow destination buffers, potentially achieving arbitrary code execution. The fix replaces these unsafe string operations with bounded `memcpy()` calls that respect pre-calculated buffer lengths.

critical

How buffer overflow in rcdevice.c request parser happens in C and how to fix it

A critical buffer overflow vulnerability was discovered in `src/main/io/rcdevice.c` at line 489, where the RC device request parser wrote incoming data into a fixed-size buffer without validating against the hard-coded maximum capacity `RCDEVICE_PROTOCOL_MAX_DATA_SIZE`. An attacker controlling the device's I/O data stream could overflow the buffer by sending a payload longer than `expectedDataLength`, potentially achieving arbitrary code execution. The fix adds a second bounds check against the

critical

How buffer overflow via unchecked memcpy offset happens in C++ PCL point cloud parsing and how to fix it

A critical out-of-bounds read vulnerability was discovered in `pcpatch_pcl.cpp` where the `readFloat` lambda performed a `memcpy` operation using an untrusted offset value without validating buffer boundaries. An attacker could craft malicious PCD point cloud files with large offset values to read memory outside allocated buffers, potentially leaking sensitive data or causing crashes. The fix adds a bounds check ensuring `f->offset + sizeof(float)` stays within the row buffer before any memory c

critical

How buffer overflow in stb_image.h memcpy happens in C image parsing and how to fix it

A critical buffer overflow vulnerability was discovered in stb_image.h at line 4823, where a memcpy operation copied image data without validating buffer bounds. The multiplication of width (x) and channel count (img_n) could overflow or exceed allocated memory, allowing attackers to corrupt memory through malicious PNG files. The fix adds an explicit size_t cast to prevent integer overflow during the buffer size calculation.