fvevol.sys — no net change
KB5082142
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
0x1C008221CisFveWipeInfoFileCreate. - Patched address
0x1400C78B0isFveControlFileCreate.
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(0x1C00805FC→0x1400C78B0): the unpatched build creates the temp control file with an inlineZwCreateFile(preceded byFveAllocateUnicodePath/FveFileGetPath); the patched build funnels the same creation through a call toFveFileOpenExwith allocation sizeregion + 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(0x1C008221C→0x1400A82B0): does not reada2+0x28/+0x30/+0x38. It computes a sector-alignment value from*(a1+0x494)and callsFveFileOpenEx. On aSTATUS_OBJECT_NAME_NOT_FOUND(0xC0000034) result witha4==0 && v9==0, the unpatched build falls back to an inlineZwCreateFile; the patched build instead issues a secondFveFileOpenEx(create disposition). Both callFveVolumeCleanupInfoon 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/jbepair at0x1C00807D1/0x1C00807E4(unpatched) or0x1400C794F/0x1400C7962(patched) and confirm both builds take the same branch for the same field values. Feedingoffset <= sizemakes 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 0x170 → 0x160; init / error-path restructuring. |
FveQueueInit (sub_1C001B0E8) |
0.97 | cosmetic | ExAllocatePoolWithTag → ExAllocatePool2(POOL_FLAG_NON_PAGED). List-head init field offsets shifted by 2 (0x6b…0x6f → 0x6d…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_1c0033000 → data_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 (ExAllocatePoolWithTag → ExAllocatePool2 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 > sizethenregion > offset - size) is present inFveControlFileCreatein both builds, confirmed instruction-for-instruction (unpatched0x1C00807C9–0x1C00807E7, patched0x1400C7947–0x1400C7965). - Address vs. function: Unpatched
0x1C008221CisFveWipeInfoFileCreate; patched0x1400C78B0isFveControlFileCreate. These are different functions, so an offset guard visible at0x1400C78B0does not correspond to missing code at0x1C008221C— the matching unpatchedFveControlFileCreateis at0x1C00805FCand already contains it. - TP-help-info path: The
FveTpIsRangeAllocatedresult check ((result - 2) <= 1) gatingFveReleaseTpHelpInfoon thea2+0x88pointer is present in both builds. - What did change here: refactors only — inline
ZwCreateFilereplaced byFveFileOpenExinFveControlFileCreate, and a fallback-create restructured inFveWipeInfoFileCreate. 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.