cldflt.sys — Missing sign/bounds/overflow validation of file-range values on cloud-files control paths (CWE-20) made unconditional; fixed
KB5094127
1. Overview
| Field | Value |
|---|---|
| Unpatched binary | cldflt_unpatched.sys |
| Patched binary | cldflt_patched.sys |
| Overall similarity | 0.9917 |
| Matched functions | 1010 |
| Changed functions | 3 |
| Identical functions | 1007 |
| Unmatched (unpatched → patched) | 0 / 0 |
Verdict: Three validation checks (sign / bounds / integer-overflow) on caller-influenced file-range values were gated behind the WIL feature-staging predicate Feature_Servicing_PostRelOutlookHangIssue__private_IsEnabledDeviceUsage (at 0x1C00097F8 in the unpatched build). While that feature is not enabled, C short-circuit evaluation skips each check. The patch deletes the predicate from the binary entirely and makes all three checks unconditional (no legacy branch is retained), so this is a delivered fix, not a staged rollout. The reachable impact is limited to passing an unvalidated negative/oversized/overflowing file offset or length into internal range bookkeeping, a file-zeroing FSCTL, and the cloud-files transfer handlers; no memory-corruption primitive is demonstrable from the binaries.
2. Vulnerability Summary
All three findings share one root cause: a security-relevant validation is placed under the control of Feature_Servicing_PostRelOutlookHangIssue__private_IsEnabledDeviceUsage (0x1C00097F8), a WIL feature-staging predicate. In the unpatched build the predicate returns the feature's current state; when the feature is not enabled the guarded validation does not run. The patch removes the predicate (its slot at 0x1C00097F8 is occupied by an unrelated helper in the patched build) and runs each validation unconditionally.
Note: Feature_Servicing_PostRelOutlookHangIssue__private_IsEnabledDeviceUsage reads the cached WIL feature-enabled state (WPP_MAIN_CB.Dpc.DeferredContext, bit 0x10 selects the cached value in bit 0) and otherwise falls back to Feature_Servicing_PostRelOutlookHangIssue__private_IsEnabledFallback → wil_details_IsEnabledFallback. This is feature staging, not ETW/WPP tracing.
Finding A - HsmiOpDehydrateNotificationCallback (0x1C007A2A0) (MEDIUM, primary)
- Severity: Medium
- Class: Missing sign/bounds validation of a file-range offset (CWE-20: Improper Input Validation; CWE-670: Always-Incorrect Control Flow Implementation)
- Affected function: Pre-operation callback registered for the cloud-files dehydrate operation (via
HsmpCldNotifyPreOperation).
Root cause. The checked value is loaded at 0x1C007A2FB (mov rcx, [r15+10h]) from the dehydrate parameters block (Parameters + 0x10, decompiled as v79 = *((_QWORD *)Parameters + 2)). This is the start offset of the file range being dehydrated, taken from the HSM control message, not the create AllocationSize. The strict check if (v79 < 0 || v79 > v32) (where v32 is the stream size) is placed inside if (Feature_Servicing_PostRelOutlookHangIssue__private_IsEnabledDeviceUsage() != 0). When the feature is not enabled, execution falls through to only else if (v79 > v32) - a signed comparison that a negative offset passes (negative < positive). A negative or oversized offset then flows into:
- v80 = v79 + *((_QWORD *)Parameters + 3) (range end = offset + length), used later only in size comparisons and masking (it is not dereferenced as a pointer),
- alignment math v39 = v79 - 1 and a size argument to HsmiMarkFileRangeNoLock (0x1C0052BD8) (internal file-range bookkeeping, pool tags 0x4244/0x4256),
- the FILE_ZERO_DATA_INFORMATION offset passed to FltFsControlFile(..., 0x980C8 /* FSCTL_SET_ZERO_DATA */, &InputBuffer, 0x10, ...).
The patch makes the check unconditional: if (v82 < 0 || v82 > v32) rejects out-of-range offsets with 0xC000CF0B before any of the above.
Entry point & call chain:
1. A caller issues IRP_MJ_FILE_SYSTEM_CONTROL with an HSM control code (e.g. 0x9043C) carrying a dehydrate request for a cloud-backed file.
2. HsmFltPreFILE_SYSTEM_CONTROL (0x1C0076D40) dispatches it (Iopb->MajorFunction == 13).
3. → HsmFltProcessHSMControl (0x1C002FE70)
4. → HsmFltProcessDehydrate (0x1C0078674), which registers the callback via HsmpCldNotifyPreOperation (0x1C0038910).
5. → HsmiOpDehydrateNotificationCallback (0x1C007A2A0), where the range offset is validated.
Finding B - CldiPortProcessTransfer (0x1C0062258) (MEDIUM)
- Severity: Medium
- Class: Missing sign + integer-overflow validation (CWE-190: Integer Overflow; CWE-20)
- Affected function: Dispatcher for transfer messages (
0x101–0x106) on the Cloud Files communication port.
Root cause. The reject condition is if (Feature_Servicing_PostRelOutlookHangIssue__private_IsEnabledDeviceUsage() != 0 && (v16 < 0 || (v26 != -1 && (v26 < 0 || v26 > 0x7FFFFFFFFFFFFFFF - v16)))). This is a short-circuit AND on the reject branch: when the feature is not enabled the left operand is 0, the reject branch is skipped, and the dispatcher proceeds to the transfer handlers without validating the sizes/offsets v16 (r14) and v26 (rdi). v16 and v26 are parsed from the transfer message; v26 is read from the message body (*(_QWORD *)(v24 + v8)) after header validation. WPP_SF_qiiDiid (0x1C0009D20) / WPP_SF_qiiDqDd (0x1C0009DCC) are WPP software-trace helpers on the error branch, not memory-copy routines. The patch removes the feature operand so the sign+overflow check always runs.
Entry point & call chain:
1. A client of the cloud-files communication port sends a transfer message with code 0x101–0x106.
2. → CldiPortProcessTransfer (0x1C0062258).
3. → the matching handler (CldiPortProcessTransferData / CldiPortProcessAckData / CldiPortProcessRetrieveData / CldiPortProcessRestartHydration / CldiPortProcessTransferPlaceholders / CldiPortProcessReportProgress) with v16 / v26 as offset/length arguments.
Finding C - HsmiOpUpdatePlaceholderFile (0x1C007BD14) (LOW)
- Severity: Low
- Class: Missing sign check on a computed file offset (CWE-20)
- Affected function: Placeholder-update handler.
Root cause. In a loop over the update range list, the code computes an aligned file offset v64 and then applies if (Feature_Servicing_PostRelOutlookHangIssue__private_IsEnabledDeviceUsage() != 0 && v64 < 0). Short-circuit AND means the v64 < 0 check is skipped when the feature is not enabled, and v64 flows into HsmiMarkFileRangeNoLock (0x1C0052BD8) (pool tag 0x4244) as the range offset. The patch collapses the condition to if (v64 < 0). Because v64 is a page-aligned value derived from the update entries, a negative result is an edge case, which is why this is rated Low.
3. Pseudocode Diff
Finding A - HsmiOpDehydrateNotificationCallback
// ===== UNPATCHED (0x1C007A2A0) =====
v79 = *((_QWORD *)Parameters + 2); // dehydrate range start offset (Parameters+0x10)
v80 = v79 + *((_QWORD *)Parameters + 3); // range end = offset + length
...
if ( Feature_Servicing_PostRelOutlookHangIssue__private_IsEnabledDeviceUsage() != 0 ) { // *** FEATURE GATE ***
if ( v79 < 0 || v79 > v32 ) { // strict check: negative OR > stream size
InformationFile = 0xC000CF0B; // (skipped when feature not enabled)
goto reject;
}
}
else if ( v79 > v32 ) { // fallthrough: signed cmp only
InformationFile = 0xC000CF0B; // negative v79 is NOT caught
goto reject;
}
// proceeds with unchecked negative/oversized offset:
v39 = v79 - 1; // alignment math -> size for HsmiMarkFileRangeNoLock
HsmiMarkFileRangeNoLock(v8, 0x4256u, v78, v77, ...);
FltFsControlFile(Instance, FileObject, 0x980C8 /*FSCTL_SET_ZERO_DATA*/, &InputBuffer, 0x10, ...);
// ===== PATCHED (0x1C007A290) =====
if ( v82 < 0 || v82 > v32 ) { // *** UNCONDITIONAL ***
InformationFile = 0xC000CF0B; // reject any out-of-range offset
goto reject;
}
// proceeds only with a valid in-range offset
Finding B - CldiPortProcessTransfer
// ===== UNPATCHED (0x1C0062258) =====
if ( Feature_Servicing_PostRelOutlookHangIssue__private_IsEnabledDeviceUsage() != 0 // feature not enabled -> AND is false
&& (v16 < 0
|| v26 != -1 && (v26 < 0 || v26 > 0x7FFFFFFFFFFFFFFF - v16)) ) // reject branch skipped
{
v17 = 0xC000CF0B; // reject
return v17;
}
// proceeds to the 0x101-0x106 transfer handlers with v16 / v26
// ===== PATCHED =====
if ( v16 < 0
|| v26 != -1 && (v26 < 0 || v26 > 0x7FFFFFFFFFFFFFFF - v16) ) // always checked
{
v17 = 0xC000CF0B;
return v17;
}
Finding C - HsmiOpUpdatePlaceholderFile
// ===== UNPATCHED (0x1C007BD14) =====
if ( Feature_Servicing_PostRelOutlookHangIssue__private_IsEnabledDeviceUsage() != 0 && v64 < 0 ) { // AND -> check skipped
// negative-offset error handling
}
HsmiMarkFileRangeNoLock(a2, 0x4244u, v64, v65, ...);
// ===== PATCHED (0x1C007BCA4) =====
if ( v64 < 0 ) { // unconditional
// negative-offset error handling
}
4. Assembly Analysis
Finding A - HsmiOpDehydrateNotificationCallback
Unpatched (0x1C007A2A0), the gated check:
00000001C007A831 call Feature_Servicing_PostRelOutlookHangIssue__private_IsEnabledDeviceUsage
00000001C007A836 test eax, eax
00000001C007A838 jz loc_1C007A897 ; feature not enabled -> skip strict check
; --- strict check reached only when feature enabled ---
00000001C007A83A mov rax, [rbp+57h+var_B8] ; reload range offset (Parameters+0x10)
00000001C007A83E test rax, rax
00000001C007A841 js loc_1C007A84C ; offset < 0 -> reject
00000001C007A843 cmp rax, rbx ; offset vs stream size
00000001C007A846 jle loc_1C007A8E8 ; offset <= size -> proceed
; --- fallthrough when feature not enabled ---
00000001C007A897 cmp [rbp+57h+var_B8], rbx ; offset vs stream size only
00000001C007A89B jle loc_1C007A8E8 ; signed: negative offset proceeds
00000001C007A89D mov eax, 0C000CF0Bh ; reject only when offset > size
; --- downstream use of an unchecked offset ---
00000001C007AB2A call HsmiMarkFileRangeNoLock
00000001C007ADF0 call cs:__imp_FltFsControlFile ; FSCTL 0x980C8 (SET_ZERO_DATA)
Patched (0x1C007A290) replaces the gate with an unconditional check:
00000001C007A824 test rcx, rcx
00000001C007A827 js loc_1C007AEB2 ; offset < 0 -> error (unconditional)
00000001C007A82D cmp rcx, rax ; offset vs stream size
00000001C007A830 jg loc_1C007AEB2 ; offset > size -> error (unconditional)
; error path at 0x1C007AEB2: mov ebx, 0C000CF0Bh
Finding B - CldiPortProcessTransfer
Unpatched (0x1C0062258):
00000001C0062477 call Feature_Servicing_PostRelOutlookHangIssue__private_IsEnabledDeviceUsage
00000001C006247C test eax, eax
00000001C006247E jz loc_1C0062520 ; feature not enabled -> skip check, go to dispatch
00000001C0062484 test r14, r14
00000001C0062487 js short loc_1C00624AA
00000001C0062489 cmp rdi, 0FFFFFFFFFFFFFFFFh
00000001C006248D jz loc_1C0062520
00000001C0062493 test rdi, rdi
00000001C0062496 js short loc_1C00624AA
00000001C0062498 mov rax, 7FFFFFFFFFFFFFFFh
00000001C00624A2 sub rax, r14
00000001C00624A5 cmp rdi, rax
00000001C00624A8 jle short loc_1C0062520
00000001C00624AA mov ebx, 0C000CF0Bh ; reject
Patched (0x1C0062258) - the feature call is gone and the check runs first:
00000001C0062477 test r14, r14
00000001C006247A js short loc_1C006249D
00000001C006247C cmp rdi, 0FFFFFFFFFFFFFFFFh
00000001C0062480 jz loc_1C0062513
00000001C0062486 test rdi, rdi
00000001C0062489 js short loc_1C006249D
00000001C006248B mov rax, 7FFFFFFFFFFFFFFFh
00000001C0062495 sub rax, r14
00000001C0062498 cmp rdi, rax
00000001C006249B jle short loc_1C0062513
00000001C006249D mov ebx, 0C000CF0Bh ; reject
The WPP error-branch callees relocated (WPP_SF_qiiDiid/WPP_SF_qiiDqDd moved from 0x1C0009D20/0x1C0009DCC to 0x1C0009CCC/0x1C0009D78) purely because the two feature-predicate functions were removed from the binary; this is address churn, not a behavioral change.
Finding C - HsmiOpUpdatePlaceholderFile
Unpatched (0x1C007BD14):
00000001C007C8E7 call Feature_Servicing_PostRelOutlookHangIssue__private_IsEnabledDeviceUsage
00000001C007C8EC test eax, eax
00000001C007C8EE jz short loc_1C007C8F9 ; feature not enabled -> skip r12 check
00000001C007C8F0 test r12, r12
00000001C007C8F3 js loc_1C007C98F ; offset < 0 -> reject
00000001C007C8F9 ; ... r8 = r12 (offset); edx = 4244h; call HsmiMarkFileRangeNoLock
Patched (0x1C007BCA4):
00000001C007C877 test r12, r12 ; sign check always runs
00000001C007C87A js loc_1C007CAE8
00000001C007C880 ; ... r8 = r12; edx = 4244h; call HsmiMarkFileRangeNoLock
5. Trigger Conditions
Finding A
cldflt.sysloaded and active on a volume backing cloud-synced files. Confirm withfltmc filters.- A caller able to send the HSM dehydrate control (
IRP_MJ_FILE_SYSTEM_CONTROL, control code0x9043C) for a cloud-backed placeholder file. - The dehydrate request specifies a range whose start offset (
Parameters + 0x10), interpreted as signedint64, is negative or exceeds the stream size. - The feature
Feature_Servicing_PostRelOutlookHangIssueis not enabled, soFeature_Servicing_PostRelOutlookHangIssue__private_IsEnabledDeviceUsagereturns 0 and the strictv79 < 0 || v79 > v32check is skipped; the fallthroughv79 > v32does not catch a negative offset. - Observable effect on the unpatched build: the unvalidated offset reaches
HsmiMarkFileRangeNoLock(internal range bookkeeping) and theFSCTL_SET_ZERO_DATArequest. This is a missing-validation defect; a memory-corruption outcome is not demonstrable from the binaries and the underlying file system independently validates theSET_ZERO_DATArange.
Finding B
- A client connected to the cloud-files communication port.
- A transfer message with code
0x101–0x106whose parsed valuesv16(offset) andv26(length) sum past0x7FFFFFFFFFFFFFFF, or wherev16/v26is negative. - Feature not enabled, so the sign+overflow reject branch is skipped and the values reach the transfer handler unchecked.
Finding C
- A placeholder-update operation whose range list yields a computed aligned offset
v64that is negative. - Feature not enabled, so the
v64 < 0check is skipped andv64reachesHsmiMarkFileRangeNoLock.
No race conditions are involved; these are straight-line validation gaps.
6. Impact Assessment
The delivered change is input-validation hardening on kernel file-range values reached from the cloud-files control and communication-port paths. Concretely:
- Finding A: an unvalidated negative/oversized dehydration offset reaches internal file-range bookkeeping (
HsmiMarkFileRangeNoLock) and aFSCTL_SET_ZERO_DATArequest. The value is a file offset/length, not a kernel address, and is not dereferenced as a pointer. - Finding B: an unvalidated size/offset pair with no overflow check reaches the
0x101–0x106transfer handlers. - Finding C: an unvalidated negative computed offset reaches
HsmiMarkFileRangeNoLock.
No exploit primitive (out-of-bounds read/write of kernel memory, controlled pointer, info-leak, or pool-corruption chain) is demonstrable from these two builds. The realistic worst case is inconsistent internal range state or a failed/oddly-scoped file operation rather than a proven memory-safety violation. Severity is therefore Medium for A and B (attacker-influenced values on kernel control paths, missing sign/bounds/overflow checks) and Low for C (negative result is an edge case of a page-aligned computation).
7. Verification Notes
For anyone reproducing this against the two binaries:
- Confirm
Feature_Servicing_PostRelOutlookHangIssue__private_IsEnabledDeviceUsageexists at0x1C00097F8incldflt_unpatched.sysand is absent fromcldflt_patched.sys(its slot at0x1C00097F8holdsRtlStringCchPrintfWin the patched build). - Finding A: at
0x1C007A831(feature call) →0x1C007A838(jz 0x1C007A897), the strict check at0x1C007A83A–0x1C007A846is bypassed when the feature is not enabled; the fallthrough at0x1C007A897–0x1C007A89Bcompares against the stream size only. The checked offset is loaded at0x1C007A2FBfromParameters + 0x10. In the patched build the unconditional check is at0x1C007A824–0x1C007A830. - Finding B: at
0x1C0062477(feature call) →0x1C006247E(jz 0x1C0062520), the sign+overflow check at0x1C0062484–0x1C00624A8is bypassed when the feature is not enabled. In the patched build the check runs first at0x1C0062477. - Finding C: at
0x1C007C8E7(feature call) →0x1C007C8EE(jz 0x1C007C8F9), thetest r12,r12; jscheck at0x1C007C8F0is bypassed when the feature is not enabled. In the patched build the check runs unconditionally at0x1C007C877. - The dehydrate range offset for Finding A originates in the HSM control message parsed by
HsmFltProcessHSMControl (0x1C002FE70)and carried intoHsmFltProcessDehydrate (0x1C0078674); the offset/length for Finding B come from the transfer message parsed inCldiPortProcessTransfer.
8. Changed Functions - Full Triage
Only three functions changed; all three are the same feature-gated-validation fix.
| Function | Similarity | Change type | Note |
|---|---|---|---|
HsmiOpDehydrateNotificationCallback (0x1C007A2A0) |
0.9642 | security_relevant | Removed the Feature_Servicing_PostRelOutlookHangIssue__private_IsEnabledDeviceUsage() gate around the sign/bounds check on the dehydration range offset (Parameters + 0x10); if (offset < 0 || offset > stream_size) is now unconditional. |
CldiPortProcessTransfer (0x1C0062258) |
0.9850 | security_relevant | Removed the feature operand from the short-circuit AND on the reject branch; the sign+integer-overflow check on v16/v26 from the transfer message now always runs. WPP error-branch callees relocated because the feature-predicate functions were removed (address churn only). |
HsmiOpUpdatePlaceholderFile (0x1C007BD14) |
0.9889 | security_relevant | Removed the feature operand from the short-circuit AND guarding v64 < 0; the negative-offset check now always runs. |
The feature predicate Feature_Servicing_PostRelOutlookHangIssue__private_IsEnabledDeviceUsage (0x1C00097F8) and its fallback Feature_Servicing_PostRelOutlookHangIssue__private_IsEnabledFallback (0x1C0009830) are both removed from the patched build.
9. Unmatched Functions
None added or removed by the diff. The patch is surgical within three existing functions (plus deletion of the two feature-predicate helpers).
10. Confidence & Caveats
Confidence: High on the mechanism and direction. In all three functions the unpatched build gates a sign/bounds/overflow check behind the feature predicate and the patched build removes the predicate and runs the check unconditionally, with no legacy branch retained - a delivered fix. The pseudocode and assembly addresses cited above are taken directly from the two builds.
Caveats:
- The default enabled/disabled state of Feature_Servicing_PostRelOutlookHangIssue cannot be read from these binaries alone; the assessment assumes the guarded (strict) behavior was not universally active, which is consistent with the vendor converting it to unconditional.
- No memory-corruption or information-disclosure primitive is demonstrable from the two builds; the impact is rated on the missing validation of attacker-influenced kernel file-range values, not on any reconstructed exploit.
- The exact access required to reach the HSM dehydrate control (Finding A) and the cloud-files communication port (Finding B) is not established here and bounds real-world reachability.
DONE