1. Overview

Field Value
Unpatched binary clfs_unpatched.sys
Patched binary clfs_patched.sys
Overall similarity 0.9912
Matched functions 1261
Changed functions 2
Identical functions 1259
Unmatched (unpatched → patched) 0 / 0

Verdict. The patch removes a WIL feature-flag gate in two CLFS request handlers, CClfsRequest::ReserveAndAppendLog (0x1C0057144, IOCTL 0x8007A827) and CClfsRequest::WriteRestart (0x1C0058B6C0x1C0058B5C, IOCTL 0x8007281F). In the unpatched build the call to CClfsRequest::GetAlignedBufferSize (0x1C0028CBC), which queries the backing device's real sector size, is gated behind the feature Feature_2227659065__private_IsEnabledDeviceUsage (0x1C000D9C4). When the feature is off (the default), the handlers use a smaller, non-device-derived length instead. The patch removes the gate and calls GetAlignedBufferSize unconditionally.

This is a staged rollout of device-sector-size-aware buffer alignment (support for storage whose physical sector size is greater than 512 bytes, i.e. 4Kn / Advanced Format media), delivered through Windows' WIL feature-staging mechanism. It is a functional-correctness / compatibility change, not a memory-safety fix. No out-of-bounds read or write, and no kernel pool-corruption primitive, is demonstrable from the two builds. The direction of any size mismatch is opposite to what an overflow would require: the unpatched default path produces a smaller aligned length, and the patched path rounds the length up to the true sector size. A smaller MDL length locks and transfers fewer bytes; it does not cause the storage stack to overrun adjacent pool.

The change is real and is located exactly as described below, but its security relevance is assessed as none.


2. Change Summary

Both changed functions carry the same edit, expressed in two symmetric code paths.

Finding A — CClfsRequest::ReserveAndAppendLog (0x1C0057144, IOCTL 0x8007A827)

  • Severity: None (no security-relevant change)
  • Nature: WIL feature-gate removed; device-sector-size query made unconditional.
  • Affected function: CClfsRequest::ReserveAndAppendLog at 0x1C0057144 in both builds, reached via ClfsDispatchIoRequest → CClfsRequest::Dispatch (case 0x8007A827).

What changed. In the unpatched build, the alignment length used for the MDL that is built over the restart-area buffer is chosen by a feature check. The code calls Feature_2227659065__private_IsEnabledDeviceUsage (0x1C000D9C4), which tests bit 0x10 of Feature_2227659065__private_featureState and otherwise consults the WIL fallback (Feature_2227659065__private_IsEnabledFallback, default parameter 3). When the feature is off (the default), the code takes the branch lea r8d, [rbx+1FFh]; and r8d, 0FFFFFE00h, i.e. rounds the input size up to a 512-byte boundary. When the feature is on, it calls GetAlignedBufferSize (0x1C0028CBC) to obtain the device-sector-aligned length. That length is passed to the MDL wrapper ClfsProbeAndAllocateMdl (0x1C0011420) and, later, to MmProbeAndLockPages and MmMapLockedPagesSpecifyCache.

The patch deletes the feature check and both branches, and calls GetAlignedBufferSize unconditionally before the MDL allocation. GetAlignedBufferSize itself falls back to 512-byte alignment when the device query fails or reports a sector size ≤ 0x200.

Why this is not a security fix. The length computed here is the length of an MDL built over an already-allocated restart-area buffer ([[this+30h]+70h]), and it is the length of the resulting disk transfer. The device-aligned length produced by GetAlignedBufferSize is greater than or equal to the 512-byte-aligned length used by the unpatched default. A smaller MDL length locks fewer pages and transfers fewer bytes; it cannot cause a write past the end of the buffer or into adjacent pool. The Windows storage stack transfers exactly the number of bytes described by the MDL/IRP; it does not re-derive a larger sector length mid-transfer and overrun the caller's buffer. On large-sector (4Kn) media a sub-sector-aligned transfer would be rejected or bounce-buffered by the storage driver, which is a functional-correctness concern (truncated or failed restart-area I/O), not memory corruption. The feature name (IsEnabledDeviceUsage) and its content (query the device's real sector size) are consistent with large-sector-media support being rolled out, not with a memory-safety patch.

Entry point / reachability. The function is reached from user mode via the CLFS log APIs. The dispatch chain is:

  1. CClfsDriver::LogIoDispatch (0x1C0034550).
  2. ClfsDispatchIoRequest (0x1C00345A0).
  3. CClfsRequest::Dispatch (0x1C00347C4) — function-code switch; case 0x8007A827 at 0x1C0034B5B jumps to 0x1C0034BC2, which calls CClfsRequest::ReserveAndAppendLog at 0x1C0034BC5.
  4. CClfsRequest::ReserveAndAppendLog (0x1C0057144).

This chain is genuine, but it leads only to the alignment change described above; it is not a reachable memory-corruption primitive.

Finding B — CClfsRequest::WriteRestart (0x1C0058B6C0x1C0058B5C, IOCTL 0x8007281F)

  • Severity: None (no security-relevant change)
  • Nature: Same WIL feature-gate removal as Finding A.
  • Affected function: CClfsRequest::WriteRestart, 0x1C0058B6C (unpatched) / 0x1C0058B5C (patched).

What changed. Same pattern. At 0x1C0058CB8 the unpatched code calls the same Feature_2227659065__private_IsEnabledDeviceUsage gate. When it returns zero the code branches (jz 0x1C0058CFF) over the GetAlignedBufferSize call and reaches ClfsProbeAndAllocateMdl with the raw length [this+108h] (no device-based alignment). When the feature is on, it calls GetAlignedBufferSize(this, [this+108h]) first. The patch removes the gate and the dual-path logic, and calls GetAlignedBufferSize unconditionally (patched at 0x1C0058CAE) before ClfsProbeAndAllocateMdl. The stack frame shrinks accordingly.

The dispatch chain is identical to Finding A except for the case selector: IOCTL 0x8007281F at 0x1C0034A1C jumps to 0x1C0034AAE, which calls CClfsRequest::WriteRestart at 0x1C0034AB1.

Why this is not a security fix. Same reasoning as Finding A. The patched path rounds [this+108h] up to the device sector size, producing a length greater than or equal to the raw value used by the unpatched default. The larger, device-aligned length is required for valid whole-sector I/O on large-sector media; the smaller unpatched length truncates the transfer. Neither direction overruns the buffer.


3. Pseudocode Diff

CClfsRequest::ReserveAndAppendLog (0x1C0057144)

// ---------- UNPATCHED ----------
flag = Feature_2227659065__private_IsEnabledDeviceUsage();   // bit 0x10 of featureState, else WIL fallback
if (flag == 0) {                                             // default: feature off
    r8 = (size + 0x1FF) & 0xFFFFFE00;                        // 512-byte round-up
} else {
    r8 = GetAlignedBufferSize(this, size);                   // device-sector-aligned length
}
status = ClfsProbeAndAllocateMdl(AccessMode, buffer, r8, ...);   // MDL over existing buffer
// ... MmProbeAndLockPages / MmMapLockedPagesSpecifyCache over r8 bytes

// ---------- PATCHED ----------
r8 = GetAlignedBufferSize(this, size);                       // always device-sector-aligned
status = ClfsProbeAndAllocateMdl(AccessMode, buffer, r8, ...);

GetAlignedBufferSize (0x1C0028CBC, byte-identical in both builds) queries the device via a guarded indirect call and rounds up to the reported sector size, falling back to 512-byte alignment only when the query fails or the sector size is ≤ 0x200:

// GetAlignedBufferSize (unchanged between builds)
status = (*(*this_dev + 0xF0))(...);           // query device geometry (guarded icall)
if (status < 0 || sector <= 0x200)
    return (size + 0x1FF) & ~0x1FF;            // fallback: 512-byte alignment
return (size + sector - 1) & ~(sector - 1);   // device-sector alignment

CClfsRequest::WriteRestart (0x1C0058B6C0x1C0058B5C)

// ---------- UNPATCHED ----------
flag = Feature_2227659065__private_IsEnabledDeviceUsage();
r8 = *(this + 0x108);
if (flag != 0) {
    r8 = GetAlignedBufferSize(this, *(this + 0x108));   // device-sector-aligned
}
// else: r8 keeps the raw *(this+0x108) length
ClfsProbeAndAllocateMdl(AccessMode, buffer, r8, ...);

// ---------- PATCHED ----------
r8 = GetAlignedBufferSize(this, *(this + 0x108));       // always device-sector-aligned
ClfsProbeAndAllocateMdl(AccessMode, buffer, r8, ...);

4. Assembly Analysis

CClfsRequest::ReserveAndAppendLog (0x1C0057144) — unpatched key region

00000001C00578E9  call    Feature_2227659065__private_IsEnabledDeviceUsage
00000001C00578EE  mov     [rsp+268h+Length], eax
00000001C00578F5  mov     ecx, edi
00000001C00578F7  test    eax, eax
00000001C00578F9  setnz   cl
00000001C00578FC  test    ecx, ecx
00000001C00578FE  jz      short loc_1C005790F        ; feature off -> 512-byte round-up
00000001C0057900  mov     edx, ebx                   ; size
00000001C0057902  mov     rcx, rsi                   ; this
00000001C0057905  call    ?GetAlignedBufferSize@CClfsRequest@@AEAAKK@Z
00000001C005790A  mov     r8d, eax
00000001C005790D  jmp     short loc_1C005791D
00000001C005790F  lea     r8d, [rbx+1FFh]            ; 512-byte round-up (feature-off path)
00000001C0057916  and     r8d, 0FFFFFE00h
00000001C005791D  lea     rax, [rsp+268h+Mdl]
00000001C0057925  mov     rdx, [rsi+30h]
00000001C005792E  mov     rdx, [rdx+70h]             ; existing restart-area buffer
00000001C0057939  call    ClfsProbeAndAllocateMdl

Later in the same function the MDL is locked and mapped:

00000001C0057A36  call    ... MmProbeAndLockPages
00000001C0057A6F  call    ... MmMapLockedPagesSpecifyCache

CClfsRequest::ReserveAndAppendLog (0x1C0057144) — patched equivalent

00000001C0057908  mov     edx, ebx                   ; size
00000001C005790A  mov     rcx, r14                   ; this
00000001C005790D  call    ?GetAlignedBufferSize@CClfsRequest@@AEAAKK@Z   ; unconditional
00000001C0057912  mov     r8d, eax
00000001C0057915  mov     rdx, [r14+30h]
00000001C0057926  mov     rdx, [rdx+70h]
00000001C0057931  call    ClfsProbeAndAllocateMdl

The call Feature_2227659065__private_IsEnabledDeviceUsage, the lea r8d, [rbx+1FFh] and the and r8d, 0FFFFFE00h instructions are gone; the conditional collapses to a single unconditional GetAlignedBufferSize call.

CClfsRequest::WriteRestart (0x1C0058B6C) — unpatched key region

00000001C0058CB8  call    Feature_2227659065__private_IsEnabledDeviceUsage
00000001C0058CBD  mov     [rsp+118h+arg_10], eax
00000001C0058CC4  mov     ecx, r14d
00000001C0058CC7  test    eax, eax
00000001C0058CC9  setnz   cl
00000001C0058CCC  mov     r8d, [rsi+108h]            ; raw length
00000001C0058CD3  test    ecx, ecx
00000001C0058CD5  jz      short loc_1C0058CFF        ; feature off -> skip query, use raw length
00000001C0058CD7  mov     rbx, [rsi+30h]
00000001C0058CDB  mov     edx, r8d
00000001C0058CDE  mov     rcx, rsi
00000001C0058CE1  call    ?GetAlignedBufferSize@CClfsRequest@@AEAAKK@Z   ; feature-on path only
00000001C0058CE6  mov     r8d, eax
...
00000001C0058CFF  mov     rax, [rsi+30h]             ; feature-off path: r8d = raw [rsi+108h]
00000001C0058D10  mov     rdx, [rax+70h]
00000001C0058D17  call    ClfsProbeAndAllocateMdl

CClfsRequest::WriteRestart (0x1C0058B5C) — patched equivalent

00000001C0058CA5  mov     edx, [rsi+108h]            ; length, no feature check
00000001C0058CAB  mov     rcx, rsi
00000001C0058CAE  call    ?GetAlignedBufferSize@CClfsRequest@@AEAAKK@Z   ; unconditional
00000001C0058CB3  mov     r8d, eax
00000001C0058CC3  mov     rdx, [rbx+70h]
00000001C0058CCA  call    ClfsProbeAndAllocateMdl

ClfsProbeAndAllocateMdl is the same routine in both builds (relocated from 0x1C0011420 to 0x1C00113E0). It probes the caller-supplied buffer for write access and calls IoAllocateMdl(buffer, length, 0, 1, NULL); the length it receives is the aligned value computed above.


5. Trigger Conditions

The two code paths differ from unpatched to patched only when both of the following hold:

  1. Volume geometry. The CLFS log's backing device reports a physical sector size greater than 512 bytes (4Kn / Advanced Format media, or virtual disks configured with a >512-byte physical sector). On 512-byte-sector devices GetAlignedBufferSize returns the same 512-byte-aligned value as the unpatched default, so the two builds behave identically.
  2. Feature-off default. Bit 0x10 / the WIL fallback for Feature_2227659065 selects the non-device path. This is the pre-rollout default.

Under those conditions, the observable difference is the length passed to ClfsProbeAndAllocateMdl, MmProbeAndLockPages and MmMapLockedPagesSpecifyCache: a 512-byte-aligned length (unpatched default) versus the true-sector-aligned length (patched). This is a change in how much of the restart-area buffer is locked and transferred to or from disk. It is a functional-correctness difference for large-sector media, not a memory-safety trigger. No bugcheck or pool-corruption effect is demonstrable from the binaries.


6. Impact Assessment

No security-relevant primitive is established by this change.

  • The length in question sizes an MDL built over a pre-existing buffer and sizes the corresponding disk transfer. The device-aligned length (patched) is greater than or equal to the 512-byte-aligned length (unpatched default). A smaller MDL locks and transfers fewer bytes; it cannot write past the buffer or into adjacent pool.
  • The storage stack transfers exactly the MDL/IRP-described length; it does not independently expand the transfer to the device sector size and overrun the caller's buffer. On large-sector media a sub-sector-aligned transfer is rejected or bounce-buffered by the storage driver.
  • No out-of-bounds read, out-of-bounds write, information disclosure, or kernel pool overflow is observable from the two builds. There is no attacker-controlled length or content that turns this alignment choice into a memory-corruption primitive.

Consequently the earlier CWE-787 / CWE-125 classification and HIGH severity are not supported and are withdrawn. The change is best characterized as WIL-staged large-sector-media alignment support with, at most, defense-in-depth value.


7. Verification Notes

The following observations are directly checkable in a kernel debugger against clfs_unpatched.sys and clfs_patched.sys; they confirm the nature of the change, not a memory-safety defect.

Finding A — ReserveAndAppendLog

  • bp at 0x1C00578E9 (unpatched): the call to Feature_2227659065__private_IsEnabledDeviceUsage. EAX == 0 selects the 512-byte-round-up branch at 0x1C005790F.
  • bp at 0x1C0057939 (unpatched) / 0x1C0057931 (patched): the ClfsProbeAndAllocateMdl call. R8D is the aligned length. On a >512-byte-sector device it is 512-aligned in the unpatched default path and device-sector-aligned in the patched build.
  • dt _MDL @rcx at MmProbeAndLockPages: the ByteCount reflects whichever aligned length was chosen. This confirms the alignment difference, not a corruption.

Finding B — WriteRestart

  • bp at 0x1C0058CB8 (unpatched): the feature-gate call. EAX == 0 takes the jz 0x1C0058CFF branch that uses the raw [rsi+108h] length.
  • bp at 0x1C0058D17 (unpatched) / 0x1C0058CCA (patched): R8D is the length handed to ClfsProbeAndAllocateMdl — raw [rsi+108h] in the unpatched default, device-sector-aligned in the patched build.

Feature-state note

Feature_2227659065__private_IsEnabledDeviceUsage (0x1C000D9C4) is standard WIL feature-staging code: it reads Feature_2227659065__private_featureState, tests bit 0x10, and otherwise calls Feature_2227659065__private_IsEnabledFallback with default parameter 3. In the patched build the same slot at 0x1C000D9C4 is renumbered to Feature_3017759032__private_IsEnabledDeviceUsage — ordinary WIL feature-database churn between builds.


8. Changed Functions — Full Triage

Two functions changed in the entire diff.

Function Similarity Change type Note
CClfsRequest::ReserveAndAppendLog (0x1C0057144) 0.9514 Non-security (WIL feature-gate removal) Feature-gated GetAlignedBufferSize query made unconditional; removed call Feature_2227659065__private_IsEnabledDeviceUsage, lea r8d,[rbx+1FFh], and r8d,0FFFFFE00h. ClfsProbeAndAllocateMdl relocated 0x1C0011420 → 0x1C00113E0 (same routine).
CClfsRequest::WriteRestart (0x1C0058B6C0x1C0058B5C) 0.9559 Non-security (WIL feature-gate removal) Same pattern; feature check plus dual-path logic collapsed to a single unconditional GetAlignedBufferSize call. Stack frame shrank (0xE0 → 0xD0) as the conditional-path locals are gone.

Cosmetic / relocation changes. ClfsProbeAndAllocateMdl (0x1C0011420 → 0x1C00113E0) and a couple of neighbours are the same routines at addresses shifted by a fixed delta due to earlier .text edits. GetAlignedBufferSize (0x1C0028CBC) is byte-identical between builds, as are ClfsDispatchIoRequest, CClfsRequest::Dispatch, MmProbeAndLockPages and MmMapLockedPagesSpecifyCache.


9. Unmatched Functions

None. Both unmatched_unpatched and unmatched_patched are zero. No sanitizer, bounds check, or validation routine was added or removed. The only behavioral change is removal of the WIL feature gate so the device-sector-size query is always performed.


10. Confidence & Caveats

Confidence: High that the change is accurately located and is a WIL-staged alignment rollout rather than a security fix.

  • The structural diff is minimal and unambiguous: a WIL feature gate (Feature_2227659065__private_IsEnabledDeviceUsage) that selected between a 512-byte-aligned length and a device-sector-aligned length was removed, making the device-sector query unconditional.
  • GetAlignedBufferSize rounds the length up to the true sector size. The unpatched default therefore produces a smaller length, and a smaller MDL cannot overrun the buffer or adjacent pool. The size-mismatch direction is inconsistent with an out-of-bounds write or read.
  • The feature name and content (query the device's real sector size) match large-sector-media (4Kn) support, and the feature is renumbered between builds — typical WIL servicing churn, not a memory-safety patch.

Caveat. If a future build is found where the same buffer is allocated at one alignment and written at a larger, independently derived sector length, that specific mismatch would warrant re-examination. No such flow is present in these two builds; the alignment length here consistently sizes both the buffer's MDL and the corresponding transfer.