1. Overview

Field Value
Binary fvevol.sys — Windows BitLocker Full Volume Encryption (FVE) disk filter driver
Unpatched ID fvevol_unpatched.sys
Patched ID fvevol_patched.sys
Overall similarity 0.7398 (multi-version diff, not a single hotfix)
Matched functions 1379
Changed functions 1140
Identical 239
Unmatched (either direction) 0 / 0

Verdict: The metadata file-creation path shows no security-relevant change between the two builds. The offset/size bounds validation that governs the BitLocker control-file allocation is a property of FveControlFileCreate and is present, byte-for-byte in logic, in both the unpatched and patched builds. FveWipeInfoFileCreate (the function at unpatched address 0x1C008221C) does not perform that offset arithmetic at all; its only inter-build difference is a refactor of a file-creation fallback. Neither function introduces or removes an integer-underflow or bounds guard.

Because overall similarity is ~0.74, this is a version-to-version rebuild: most of the 1140 "changed" functions are refactor / cosmetic churn (pool-API migration, image-base relocations, WPP trace-GUID renames).


2. Function Summary

Finding 1 — Metadata offset/size bounds validation is present in both builds (no net change)

Severity Informational (no exploitable delta)
Class CWE-191 integer-underflow guard — already present in both builds
Function A (address 0x1C008221C) FveWipeInfoFileCreate @ 0x1C008221C (unpatched); pairs with FveWipeInfoFileCreate @ 0x1400A82B0 (patched)
Function B (address 0x1400C78B0) FveControlFileCreate @ 0x1400C78B0 (patched); pairs with FveControlFileCreate @ 0x1C00805FC (unpatched)
Entry point BitLocker metadata/control file operations reached via the FVE volume-activation path

What the addresses actually resolve to.

  • Unpatched address 0x1C008221C is FveWipeInfoFileCreate.
  • Patched address 0x1400C78B0 is FveControlFileCreate.

These are two distinct functions. FveControlFileCreate in the unpatched build lives at 0x1C00805FC; FveWipeInfoFileCreate in the patched build lives at 0x1400A82B0.

The offset/size arithmetic and its guard live in FveControlFileCreate, and are identical in both builds.

FveControlFileCreate reads three fields from the metadata structure (a2):

  • *(a2 + 0x28) — offset field,
  • *(a2 + 0x30) — region size field,
  • *(a2 + 0x38) — size field,

and gates the allocation on the following, present in both builds:

if (offset > size) {                 // *(a2+0x28) > *(a2+0x38)
    if (region > offset - size) {    // *(a2+0x30) > (*(a2+0x28) - *(a2+0x38))
        // allocation size = size + region - offset  (provably in range)
        ... create file ...
    }
}

The subtraction offset - size is only reached after offset > size is confirmed, so it cannot underflow; and region > offset - size bounds the final size size + region - offset. This guard is a longstanding property of the driver, not a new addition.

The metadata-pointer path is likewise unchanged: both builds validate *(a2 + 0x88) (field a2+136) via FveTpIsRangeAllocated and constrain the result ((result - 2) <= 1) before calling FveReleaseTpHelpInfo.

What actually differs between the matched pairs.

  • FveControlFileCreate (0x1C00805FC0x1400C78B0): the unpatched build creates the temp control file with an inline ZwCreateFile (preceded by FveAllocateUnicodePath / FveFileGetPath); the patched build funnels the same creation through a call to FveFileOpenEx with allocation size region + size - offset. This is a refactor of how the file is opened; the bounds guard preceding it is unchanged. Remaining diffs are WPP trace-GUID name churn (WPP_b027462a…WPP_c5463791…).
  • FveWipeInfoFileCreate (0x1C008221C0x1400A82B0): does not read a2+0x28/+0x30/+0x38. It computes a sector-alignment value from *(a1+0x494) and calls FveFileOpenEx. On a STATUS_OBJECT_NAME_NOT_FOUND (0xC0000034) result with a4==0 && v9==0, the unpatched build falls back to an inline ZwCreateFile; the patched build instead issues a second FveFileOpenEx (create disposition). Both call FveVolumeCleanupInfo on the locally initialized info buffer. No offset/size arithmetic and no bounds delta are involved.

Net security impact: none. There is no attacker-controlled underflow that is guarded in one build and unguarded in the other.


3. Pseudocode

FveControlFileCreate — bounds guard, identical in both builds

// UNPATCHED  FveControlFileCreate @ 0x1C00805FC
if ( *(_QWORD *)(a2 + 128) == 0 && a8 == 0 ) {
    v27 = *(_QWORD *)(a2 + 40);      // offset  (a2+0x28)
    v28 = *(_QWORD *)(a2 + 56);      // size    (a2+0x38)
    if ( v27 > v28 ) {               // guard #1: offset > size
        v29 = *(_QWORD *)(a2 + 48);  // region  (a2+0x30)
        if ( v29 > v27 - v28 ) {     // guard #2: region > offset - size
            v30.QuadPart = v28 + v29 - v27;         // in-range size
            ...
            ZwCreateFile(&FileHandle, 0xC0000000, &ObjectAttributes,
                         &IoStatusBlock, &v37, 6u, 0, 3u, 0x886Au, nullptr, 0);
        }
    }
}
// PATCHED  FveControlFileCreate @ 0x1400C78B0
if ( *(_QWORD *)(a2 + 128) == 0 && a8 == 0 ) {
    v17 = *(_QWORD *)(a2 + 40);      // offset  (a2+0x28)
    v18 = *(_QWORD *)(a2 + 56);      // size    (a2+0x38)
    if ( v17 > v18 ) {               // same guard #1
        v19 = *(_QWORD *)(a2 + 48);  // region  (a2+0x30)
        if ( v19 > v17 - v18 ) {     // same guard #2
            v20 = FveFileOpenEx((PCUNICODE_STRING)a2, a3, 0, L"TEMP",
                                0, 0, 3u, 0x886Au, 0,
                                v19 + v18 - v17,   // same in-range size
                                nullptr, v35);
        }
    }
}

The two builds differ only in that the patched side calls FveFileOpenEx where the unpatched side inlines ZwCreateFile. The if (offset > size) { if (region > offset - size) ... } guard is present in both.

FveWipeInfoFileCreate — the function at 0x1C008221C

// UNPATCHED  FveWipeInfoFileCreate @ 0x1C008221C  (patched twin @ 0x1400A82B0)
// No a2+0x28/+0x30/+0x38 reads. Alignment from *(a1+0x494); creates via FveFileOpenEx.
if ( a4 != 0 ) {
    v10 = *(_DWORD *)(a1 + 1172);            // 0x494
    v11 = ((v10-1)&v10) ? (0x200 % v10 ? v10 - 0x200%v10 + 512 : 512)
                        : (~(v10-1) & (v10+511));   // sector alignment
} else v11 = 0;
inited = FveFileOpenEx(a2, &WIPE_INFORMATION_FILE_TYPE_GUID, 0, 0,
                       a4 ? 5 : 1, 0x886Au, v9, v11, 0, a5);
// unpatched: on 0xC0000034 fallback -> inline ZwCreateFile
// patched  : on 0xC0000034 fallback -> second FveFileOpenEx(create)

4. Assembly Analysis

FveControlFileCreate bounds guard — unpatched (0x1C00805FC)

0x1C00807C9  mov     rcx, [r14+28h]     ; offset field  a2+0x28
0x1C00807CD  mov     rdx, [r14+38h]     ; size   field  a2+0x38
0x1C00807D1  cmp     rcx, rdx
0x1C00807D4  jbe     loc_1C0080681      ; bail if offset <= size
0x1C00807DA  mov     rdi, [r14+30h]     ; region field  a2+0x30
0x1C00807E1  sub     rax, rdx           ; rax = offset - size
0x1C00807E4  cmp     rdi, rax
0x1C00807E7  jbe     loc_1C00809B2      ; bail if region <= (offset - size)
0x1C00807ED  sub     rdi, rcx           ; compute in-range size

FveControlFileCreate bounds guard — patched (0x1400C78B0)

0x1400C7947  mov     rdx, [rdi+28h]     ; offset field  a2+0x28
0x1400C794B  mov     rcx, [rdi+38h]     ; size   field  a2+0x38
0x1400C794F  cmp     rdx, rcx
0x1400C7952  jbe     loc_1400C7A2B      ; bail if offset <= size
0x1400C7958  mov     r8,  [rdi+30h]     ; region field  a2+0x30
0x1400C795F  sub     rax, rcx           ; rax = offset - size
0x1400C7962  cmp     r8,  rax
0x1400C7965  jbe     loc_1400C7A2B      ; bail if region <= (offset - size)
0x1400C796B  sub     rcx, rdx           ; compute in-range size

The compare/branch sequence is instruction-for-instruction equivalent across builds. There is no comparison present in one build and absent in the other.


5. Trigger Conditions

A malformed BitLocker FVE metadata block cannot drive an offset underflow through FveControlFileCreate: in both builds the fields at a2+0x28, a2+0x38, and a2+0x30 are validated (offset > size and region > offset - size) before the subtraction and before the file-creation call. If either check fails, the function skips the creation branch and proceeds. There is therefore no differential input that crashes the unpatched build while the patched build survives on this path.

FveWipeInfoFileCreate reads none of those fields; its allocation size derives from a sector-alignment computation on *(a1+0x494), which is driver/volume geometry rather than an unbounded on-disk length.


6. Exploit Primitive & Development Notes

No exploit primitive arises from this path. The size handed to the control-file allocation, region + size - offset, is bounded on both sides by the two comparisons that execute in both builds, so it is neither near-zero-by-underflow nor a wraparound value under attacker control. The metadata-pointer branch (a2+0x88) is validated identically in both builds via FveTpIsRangeAllocated before FveReleaseTpHelpInfo.

For completeness, fvevol.sys loads at a fixed base per boot and is subject to KASLR, SMEP/SMAP, kernel CFG, HVCI, and PatchGuard like any signed kernel driver; none of these are relevant here because no memory-safety delta exists in this path.


7. Debugger Playbook

Assumptions: kernel debugger attached; symbols for fvevol.sys.

Breakpoints

bp fvevol!FveControlFileCreate      ; unpatched 0x1C00805FC / patched 0x1400C78B0
bp fvevol!FveWipeInfoFileCreate     ; unpatched 0x1C008221C / patched 0x1400A82B0
bp fvevol!FveFileOpenEx             ; unpatched 0x1C0089BD4 / patched 0x1400C7418

What to inspect at FveControlFileCreate

  • rdx = a2 (metadata structure pointer).
  • The three fields the guard reads: text dq @rdx+0x28 L1 ; offset dq @rdx+0x30 L1 ; region size dq @rdx+0x38 L1 ; size dq @rdx+0x80 L1 ; must be 0 to enter the create branch
  • Step through the cmp/jbe pair at 0x1C00807D1/0x1C00807E4 (unpatched) or 0x1400C794F/0x1400C7962 (patched) and confirm both builds take the same branch for the same field values. Feeding offset <= size makes both builds skip the creation branch identically.

Key offsets

Address What
0x1C00807C9 Unpatched FveControlFileCreate: reads a2+0x28/a2+0x38, guard #1.
0x1C00807DA Unpatched: reads a2+0x30, guard #2 against offset - size.
0x1400C7947 Patched FveControlFileCreate: reads a2+0x28/a2+0x38, guard #1.
0x1400C7958 Patched: reads a2+0x30, guard #2.
0x1C008221C FveWipeInfoFileCreate entry (unpatched). Alignment from *(a1+0x494).

Struct / offset notes

Offset in a2 Role Guarded in both builds
+0x28 offset yes — offset > size before use
+0x30 region size yes — region > offset - size
+0x38 size yes — base of the guarded subtraction
+0x80 gate flag must be 0 to enter the create branch
+0x88 TP-help metadata pointer validated via FveTpIsRangeAllocated in both builds
+0x494 (in a1) sector-alignment hint read by FveWipeInfoFileCreate

8. Changed Functions — Full Triage

The diff reports 1140 changed functions. The most-divergent are itemized below. None in this set makes a security-relevant change.

Function Sim. Change type One-line note
FveWipeInfoFileCreate (sub_1C008221C) 0.40 refactor File-creation fallback on STATUS_OBJECT_NAME_NOT_FOUND changed from inline ZwCreateFile to a second FveFileOpenEx call. No offset/size bounds delta; reads none of a2+0x28/+0x30/+0x38.
FveEowConvStep (sub_1C00433B0) 0.64 behavioral Large metadata/EOW convert-step function; version-level restructuring. The region/offset underflow clamp is identical in both builds. Not exploitable.
FveConvStopCommon (sub_1C004577C) 0.90 behavioral Stack frame shrank 0x1700x160; init / error-path restructuring.
FveQueueInit (sub_1C001B0E8) 0.97 cosmetic ExAllocatePoolWithTagExAllocatePool2(POOL_FLAG_NON_PAGED). List-head init field offsets shifted by 2 (0x6b…0x6f0x6d…0x71); new init at +0x384.
FveEowConvStepIssueWorker (sub_1C00432B0) 0.63 cosmetic Calls FveEowConvStep in both builds; only differences are a WPP trace-GUID rename and a decompiler prototype-recovery artifact (the FveEowConvStep call rendered with three arguments unpatched vs one patched). No behavior change.
FveValidateIoCryptoConfig (sub_1C008A610) 0.91 cosmetic Largest function (1187 inst). Address/log-string churn (data_1c0033000data_14003b000); STATUS_SUCCESS literal replaced with 0.
FveEowFinalSweepIsFreeSpaceRange (sub_1C004530C) 0.42 behavioral Free-space-range validation; the address-range overflow and bitmap-index bounds checks are unchanged in substance. Restructuring is driven by WIL feature-staging for Feature_BitLockerEphemeralVolumeSupport plus struct-offset churn.

Cosmetic / register-allocation cluster. The bulk of the 1140 changed functions are dominated by: - pool allocator migration (ExAllocatePoolWithTagExAllocatePool2 with explicit POOL_FLAG_*), - field-offset shifts from struct-layout drift (e.g. the +2 byte shift in FveQueueInit), - address-constant relocations from the rebuilt image base, - WPP trace-GUID renames and STATUS_SUCCESS vs 0 literal substitutions.

None constitute behavior changes by themselves. Treat them as noise when triaging future diffs of the same driver.

Behavioral (non-security) flags worth noting. - FveEowConvStepIssueWorker (sub_1C00432B0) calls FveEowConvStep in both builds; its only inter-build differences are a WPP trace-GUID rename and a decompiler prototype-recovery artifact. No operational-semantic change. - FveEowConvStep (sub_1C00433B0) and FveEowFinalSweepIsFreeSpaceRange (sub_1C004530C) are heavily restructured parsing/validation paths, but the load-bearing validation is unchanged: FveEowConvStep's region/offset underflow clamp (if (used > next - cur) { if (delta > 0xFFFFFFFF) KeBugCheckEx(0x120, 8, 0xC0000095) }) is identical in both builds, and FveEowFinalSweepIsFreeSpaceRange's restructuring is dominated by WIL feature-staging for Feature_BitLockerEphemeralVolumeSupport plus struct-offset churn. No concrete flaw.


9. Unmatched Functions

removed: []
added:   []

No functions were added or removed across the diff boundary. This is consistent with a version-bump rebuild. There is no newly-added sanitizer function and no removed check on the metadata file-creation path.


10. Confidence & Caveats

Confidence: High that the metadata file-creation path carries no security-relevant change.

  • Ground truth from the binaries: The offset/size guard (offset > size then region > offset - size) is present in FveControlFileCreate in both builds, confirmed instruction-for-instruction (unpatched 0x1C00807C90x1C00807E7, patched 0x1400C79470x1400C7965).
  • Address vs. function: Unpatched 0x1C008221C is FveWipeInfoFileCreate; patched 0x1400C78B0 is FveControlFileCreate. These are different functions, so an offset guard visible at 0x1400C78B0 does not correspond to missing code at 0x1C008221C — the matching unpatched FveControlFileCreate is at 0x1C00805FC and already contains it.
  • TP-help-info path: The FveTpIsRangeAllocated result check ((result - 2) <= 1) gating FveReleaseTpHelpInfo on the a2+0x88 pointer is present in both builds.
  • What did change here: refactors only — inline ZwCreateFile replaced by FveFileOpenEx in FveControlFileCreate, and a fallback-create restructured in FveWipeInfoFileCreate. Both are behavior-preserving with respect to bounds validation.

EOW convert/sweep functions examined. The heavily restructured FveEowConvStep and FveEowFinalSweepIsFreeSpaceRange were diffed directly: their bounds and underflow validation is unchanged across builds (FveEowConvStep's underflow clamp is byte-for-byte identical), and the restructuring is version churn plus WIL feature-staging (Feature_BitLockerEphemeralVolumeSupport), not a security fix.