cdfs.sys — integer overflow leading to kernel pool buffer overflow (CWE-190, CWE-122) in CdAddAllocationFromDirent fixed
KB5073723
Overview
- Unpatched Binary:
cdfs_unpatched.sys - Patched Binary:
cdfs_patched.sys - Overall Similarity Score: 0.9924
- Diff Statistics:
- Matched Functions: 210
- Changed Functions: 3
- Identical Functions: 207
- Unmatched (Unpatched): 0
- Unmatched (Patched): 0
- Verdict: The patch resolves an integer overflow vulnerability in the Windows CD-ROM File System driver (
cdfs.sys) that allows a crafted ISO image to trigger a kernel pool buffer overflow. The demonstrable, reachable impact is kernel memory corruption resulting in a system crash (BSOD / Denial of Service). Reliable privilege escalation is not demonstrated: the overflow is an unbounded, multi-gigabyte linear copy that cannot be groomed and requires the driver to first succeed at a multi-gigabyte pool allocation.
2. Vulnerability Summary
- Severity: High
- Vulnerability Class: Integer Overflow leading to Kernel Pool Buffer Overflow (CWE-190 / CWE-122; the out-of-bounds copy is also CWE-787)
- Affected Function:
CdAddAllocationFromDirent
Root Cause:
The CdAddAllocationFromDirent function is responsible for growing an array of allocation extents (file layout data) when parsing an ISO9660 disk image. The array capacity at FCB+0x108 is initialized to 1 and doubles on each growth, so it only ever takes power-of-two values (1, 2, 4, ..., 2^n). When the array reaches capacity, the function allocates a new buffer for double the current entry count and copies the old data into it.
The unpatched code calculates the size of this new allocation using 32-bit arithmetic (capacity * 0x50). The 32-bit product overflows once the capacity exceeds 0xFFFFFFFF / 0x50 (about 53.7 million). Because capacity is a power of two, the first value that actually reaches this condition is 0x4000000 (2^26 = 67,108,864). At that growth the 32-bit calculation 0x4000000 * 0x50 = 0x140000000 truncates to 0x40000000 (1 GiB), so ExAllocatePoolWithTag allocates a 1 GiB buffer.
However, the memmove routine that follows calculates the copy length using 64-bit arithmetic (40LL * capacity), which does not overflow. It attempts to copy 0x4000000 * 0x28 = 0xA0000000 bytes (about 2.5 GiB) into the 1 GiB buffer, overrunning it by roughly 1.5 GiB. The patch fixes this by upgrading the allocation size calculation to 64-bit and explicitly checking if the final size exceeds a 32-bit boundary, raising an exception if it does.
Attacker-Reachable Entry Point & Data Flow:
1. An attacker provides a crafted ISO9660 image containing a file whose parsing adds on the order of 67 million directory entry extents (enough to drive the capacity counter to 2^26).
2. A user mounts the image and opens or reads the crafted file (via Explorer or CreateFile/ReadFile), triggering an IRP_MJ_CREATE or IRP_MJ_READ.
3. The IRP is dispatched to CdFsdDispatch / CdFspDispatch.
4. The request flows through CdCommonCreate or CdCommonRead.
5. It enters CdInitializeFcbFromFileContext or CdPrepareBuffers.
6. CdLookupAllocation is invoked to parse file extents.
7. CdAddAllocationFromDirent is called to store extent data, triggering the integer overflow and subsequent kernel pool overflow.
3. Pseudocode Diff
// UNPATCHED CdAddAllocationFromDirent
// Note the 32-bit arithmetic (eax) for allocation size
uint32_t alloc_size_32 = (capacity * 5) << 4; // *** 32-BIT OVERFLOW *** (capacity * 0x50)
void* new_buf = ExAllocatePoolWithTag(PagedPool, alloc_size_32, 'CdmA');
memset(new_buf, 0, alloc_size_32);
// Note the 64-bit arithmetic (r8) for memmove size
uint64_t copy_size_64 = (capacity * 5) << 3; // *** 64-BIT, NO OVERFLOW *** (capacity * 0x28)
memmove(new_buf, old_array, copy_size_64); // *** HEAP OVERFLOW ***
// PATCHED CdAddAllocationFromDirent
// Uses 64-bit register (r10) for allocation size calculation
uint64_t alloc_size_64 = (capacity * 5) << 4;
// Explicit bounds check added before calling ExAllocatePoolWithTag
if (alloc_size_64 > 0xFFFFFFFF) {
// Raises STATUS_UNEXPECTED_IO_ERROR (0xC00000E9) and aborts safely
CdRaiseStatusEx(IRP_context, STATUS_UNEXPECTED_IO_ERROR, ...);
}
// Safe allocation and copy
void* new_buf = ExAllocatePoolWithTag(PagedPool, alloc_size_64, 'CdmA');
memset(new_buf, 0, alloc_size_64);
memmove(new_buf, old_array, alloc_size_64);
4. Assembly Analysis
Unpatched Vulnerable Sequence:
; --- GROWTH PATH: calculate size and allocate doubled array ---
0x1c000a49d lea eax, [rax+rax*4] ; *** BUG: 32-bit multiply (capacity * 5) ***
0x1c000a4a6 shl eax, 0x4 ; *** BUG: 32-bit shift (capacity * 0x50). Truncates allocation size! ***
0x1c000a4ae mov edx, eax ; edx = truncated size
0x1c000a4b2 call qword [rel 0x1c0008040] ; ExAllocatePoolWithTag(PagedPool, truncated_size, 'CdmA')
...
; --- memmove old array into new (undersized) buffer ---
0x1c000a4ce mov ecx, dword [rdi+0x108] ; ecx = capacity
0x1c000a4db lea r8, [rcx+rcx*4] ; r8 = capacity * 5 (64-bit)
0x1c000a4e2 shl r8, 0x3 ; r8 = capacity * 0x28 (64-bit, FULL SIZE)
0x1c000a4e6 call 0x1c0002e40 ; call memmove -> TRIGGERS MASSIVE POOL OVERFLOW
Patched Safe Sequence:
; --- GROWTH PATH: calculate size with 64-bit math and check bounds ---
0x1c000a4a1 lea r10, [rax+rax*4] ; FIX: 64-bit multiply
0x1c000a4aa shl r10, 0x4 ; FIX: 64-bit shift. r10 = correct capacity * 0x50
0x1c000a4b0 cmp r10, rdx ; rdx holds 0xFFFFFFFF. Compare size against max 32-bit int
0x1c000a4b3 cmovbe eax, r10d ; If size <= 0xFFFFFFFF, use actual size
0x1c000a4b7 ja 0x1c000a5fa ; If size > 0xFFFFFFFF, jump to error path (ABORT)
...
; --- Error Path ---
0x1c000a5fa mov r9d, 0x20000
0x1c000a60b mov edx, 0xc00000e9 ; STATUS_UNEXPECTED_IO_ERROR
0x1c000a610 call 0x1c00010e0 ; CdRaiseStatusEx -> safely aborts without overflowing
5. Trigger Conditions
- Image Crafting: Create a malicious ISO9660 disk image. The image must contain a file constructed with an extraordinarily large number of directory entry extents (on the order of 67 million). This can be achieved via many interleaved extents.
- Mounting: Mount the disk image using the native Windows ISO mounter or a third-party utility. No administrator privileges are required for standard users to mount an ISO in Windows 10/11.
- Access: Open a file on the mounted volume or read its contents (e.g.,
CreateFileW("\\.\D:\target_file", ...)). - Parsing & Overflow: The CDFS driver parses the file layout. The array capacity counter at
FCB+0x108starts at 1 and doubles as extents are processed (1, 2, 4... 2^n). - Ignition: When the capacity counter reaches
0x4000000(2^26 = 67,108,864, the first power of two past the ~53.7 million overflow threshold), the 32-bit allocation calculation (capacity * 0x50 = 0x140000000) truncates to0x40000000(1 GiB). Reaching this growth also requires the previous growth at capacity0x2000000to have succeeded, i.e. a ~2.5 GiB paged-pool allocation. - Crash: The subsequent 64-bit
memmoveattempts to write0xA0000000(~2.5 GiB) into the 1 GiB buffer, overrunning it by ~1.5 GiB, resulting in kernel memory corruption and a BSOD (e.g.,BAD_POOL_CALLER).
6. Exploit Primitive & Development Notes
- Primitive: Kernel pool buffer overflow. The overflow source is the old allocation-extent array, whose entries hold parsed directory extent fields (extent logical block number, data length, interleave parameters), so the overrun content is influenced by the crafted ISO. However, the overrun is a single ~1.5 GiB linear
memmove, not a bounded or targetable write. - Practical impact: The demonstrable, reachable outcome is kernel memory corruption leading to a system crash (BSOD). The copy length (~2.5 GiB) is far larger than any pool allocation and cannot be sized down to a controlled overrun: the memory layout is a power-of-two doubling series, so the smallest overrun that reaches the vulnerable path is already ~1.5 GiB. A copy of that magnitude runs off the end of the pool region into unmapped memory before any adjacent object could be usefully corrupted, so it does not provide a controllable write primitive.
- Preconditions that further constrain exploitation:
- Reaching capacity 2^26 requires the driver to first succeed at the previous growth (a ~2.5 GiB paged-pool allocation) and to parse on the order of 67 million directory extents, implying a multi-gigabyte crafted image and a target with abundant paged pool.
- These constraints, combined with the uncontrollable overrun size, mean reliable local privilege escalation is not demonstrated; the honest, reachable impact is Denial of Service.
7. Debugger PoC Playbook
Breakpoints:
bp cdfs!CdAddAllocationFromDirent
bp 0x1c000a49d ".printf \"VULN SIZE CALC: capacity=%x\\n\", @rax; .if (@rax < 0x4000000) { gc } .else { .echo CRITICAL CAPACITY REACHED }"
bp 0x1c000a4b2 ".printf \"ExAllocatePoolWithTag size (truncated)=%x\\n\", @edx"
bp 0x1c000a4e6 ".printf \"MEMMOVE size (full 64-bit)=%x\\n\", @r8"
What to Inspect:
* At Function Entry: rdx is the FCB pointer. Inspect dt _FCB or specifically [rdx+0x108] (capacity). r8d contains the index currently being added.
* At 0x1c000a49d (Size Calc): eax holds the raw capacity. Watch it double across multiple calls (1, 2, 4, ...). When it hits 0x4000000, the bug is primed. Note how the subsequent shift instructions leave edx (allocation size = 0x40000000) drastically smaller than the true 64-bit size (0x140000000).
* At 0x1c000a4b2 (Allocation): ecx, edx, r8d contain the PoolType, Size, and Tag. Inspect rax after the call (gu) to see the allocated pool pointer.
* At 0x1c000a4e6 (Overflow site): rcx is the undersized destination buffer. rdx is the valid source array. r8 is the massive copy length. Use !pool @rcx to see the buffer layout before stepping.
Trigger Setup:
1. Boot a Windows VM with a WinDbg kernel debugger attached.
2. Replace cdfs.sys with the unpatched binary or ensure you are testing on an unpatched system.
3. Prepare a crafted .iso file containing a file whose parsing adds on the order of 67 million directory extents.
4. Mount the .iso on the victim OS (e.g., Mount-DiskImage in PowerShell).
5. Issue Invoke-Item X:\path\to\crafted\file (where X is the mounted drive) to trigger IRP_MJ_CREATE.
Expected Observation:
The debugger will break at the custom breakpoints as the capacity doubles. Once capacity hits 0x4000000, continuing execution will trigger the memmove. It will write past the allocated 1 GiB pool buffer. The system will BSOD with BAD_POOL_CALLER (0xC2) or PAGE_FAULT_IN_NONPAGED_AREA (0x50) from within nt!memmove called by cdfs!CdAddAllocationFromDirent.
8. Changed Functions — Full Triage
CdAddAllocationFromDirent(Similarity: 0.8648)- Change Type: Security Relevant
- Notes: Contains the core vulnerability. Stack frame increased to accommodate 64-bit math and overflow checking. Added an explicit branch to raise
STATUS_UNEXPECTED_IO_ERRORinstead of allowing pool allocation truncation. CdInitializeFcbFromFileContext(Similarity: 0.9922)- Change Type: Behavioral
- Notes: Cosmetic register allocation changes (r12 ↔ r13). Functionally updated to pass
arg1(the IRP context) properly toCdAddAllocationFromDirentto support the new error path. Call target offset updated. CdLookupAllocation(Similarity: 0.9928)- Change Type: Behavioral
- Notes: Cosmetic register allocation changes (r14 ↔ rsi). Like the FCB init function, its call to
CdAddAllocationFromDirentwas updated to correctly pass the IRP context. Source line number constants for error reporting shifted by 1.
9. Unmatched Functions
- Added: None.
- Removed: None.
- Implication: The mitigation was strictly self-contained within the existing logic flow, requiring no new helper functions or security wrappers.
10. Confidence & Caveats
- Confidence Level: High. The assembly diff explicitly demonstrates the mismatch between the 32-bit allocation size calculation and the 64-bit
memmovelength calculation. - Assumptions: The capacity at
FCB+0x108is initialized to 1 and doubles as extents are parsed (mov esi, 1; mov [rdi+108h], esiat the FCB-init site, andadd eax, eaxin the growth path). The first overflowing growth occurs at capacity0x4000000(2^26), so the count is driven by index reaching that value (on the order of 67 million extents added). - Manual Verification Required: Researchers must manually craft or programmatically generate a specifically malformed ISO9660 image. Creating a file with tens of millions of extents in a physical or virtual image format requires custom tooling to bypass standard ISO authoring limitations. Standard tools like
mkisofsmay refuse to generate such a malformed structure. Note also that triggering the overflow requires the earlier growth at capacity0x2000000to succeed, which itself demands a ~2.5 GiB paged-pool allocation on the target.