1. Overview

  • Unpatched Binary: ntfs_unpatched.sys
  • Patched Binary: ntfs_patched.sys
  • Overall Similarity Score: 0.9922
  • Diff Statistics: 4047 matched functions, 10 changed functions, 4037 identical functions, and 0 unmatched functions in either direction.
  • Verdict: The patch removes a WIL staging feature gate (g_Feature_2859161914_59426036, evaluated by EvaluateCurrentState) from a set of NTFS Log File Service (LFS) restart-table routines. In the unpatched build, several restart-table consistency checks (zero entry-size/count detection, entry-count overflow detection, and a checked-addition on the entry count) only run when that feature evaluates true; when it evaluates false the checks are skipped. The patched build deletes the gate so the checks always run. The change is directionally a hardening: the patched build is strictly stricter. The reachable impact is modest — the affected structures are LFS restart tables (parsed from the $LogFile restart area during volume mount/recovery), and the one integer-overflow site is fed by internally bounded counts, so no attacker-controlled kernel heap-overflow primitive is demonstrable from these binaries.

2. Vulnerability Summary

All affected routines operate on NTFS restart tables — the fixed-size, pool-allocated bookkeeping tables (open-attribute table, dirty-page table, transaction table) that the Log File Service uses during transaction logging and crash recovery. The on-disk source for the validated table is the volume's $LogFile restart area, which NtfsCheckRestartTable parses during mount/recovery.

The shared mechanism is a single WIL feature descriptor, g_Feature_2859161914_59426036_FeatureDescriptorDetails, queried through EvaluateCurrentState — the WIL feature-state evaluator (0x1C0030128 unpatched), not a resource-lock/ERESOURCE query. In the unpatched build the routines call EvaluateCurrentState and only execute their consistency checks when it returns non-zero (feature enabled); when it returns zero (feature disabled) the checks are branched over. The patched build removes every one of these calls from the restart-table routines (EvaluateCurrentState call count drops from 13 to 4), making the checks unconditional. The four remaining calls in the patched build are in an unrelated feature family (NtfsSetAllocationInfo, NtfsSetEndOfFileInfo, NtfsSetPositionInfo) that is still gated in both builds.

Finding 1: Gated integer-overflow check on restart-table growth

  • Severity: Low
  • Vulnerability Class: Integer Overflow (CWE-190) — defensive check made unconditional
  • Affected Function: NtfsExtendRestartTable (0x1C0024660 unpatched, 0x1C0024640 patched)
  • Root Cause: When the feature is disabled, the function computes the new entry count with a direct 32-bit add (add edx, edi) instead of the overflow-checked RtlULongAdd. The result feeds InitializeNewTable (pool sizing) while the subsequent memmove copies entry_size * old_count bytes. A wrap would produce an undersized allocation followed by an oversized copy.
  • Reachability: Not demonstrably attacker-controlled. The augend is the 16-bit NumberEntries field ([rbx+2], ≤ 0xFFFF); the addend (edi/arg2) is a small growth increment supplied by callers (0x10 from NtfsAllocateRestartTableIndex; a division-derived count in NtfsAllocateRestartTableFromIndex and PageUpdateAnalysis). A 32-bit wrap requires an addend near 0xFFFFFFFF, which none of the callers produce. The RtlULongAdd is therefore a corruption guard, not a reachable heap-overflow primitive.

Finding 2: Gated zero-size / count-overflow checks in restart-table deallocation

  • Severity: Low
  • Vulnerability Class: Missing Validation of restart-table fields (CWE-20)
  • Affected Function: NtfsAllocateRestartTableFromIndex (0x1C002DC38)
  • Root Cause: Two checks are gated: (1) entry_size == 0 || NumberEntries == 0 and (2) the in-use count ([rbx+4]) reaching 0xFFFF or exceeding NumberEntries after increment. When the feature is disabled both are skipped. Unchecked, a corrupt entry_size/count feeds the memset size and the free-list index arithmetic.

Finding 3: Gated header/count-overflow checks in restart-table allocation

  • Severity: Low
  • Vulnerability Class: Missing Validation / count overflow (CWE-20)
  • Affected Function: NtfsAllocateRestartTableIndex (0x1C000E124)
  • Root Cause: Same pattern as Finding 2: a gated entry_size==0 || NumberEntries==0 check and a gated post-increment count-overflow check (==0xFFFF or > NumberEntries). When skipped, the function can return a corrupt free-list index used by callers in pointer arithmetic.

Finding 4: Gated validation path in the on-disk restart-table validator

  • Severity: Medium
  • Vulnerability Class: Insufficient Validation of on-disk metadata (CWE-20)
  • Affected Function: NtfsCheckRestartTable (0x1C01BC2D4 unpatched, 0x1C01BC2C4 patched)
  • Root Cause: The validator has two paths selected by the feature gate. With the feature enabled it runs the strong checks (entry_size != 0, entry_size <= buffer_size, NumberEntries != 0, NumberInUse != 0, and a division-based check that the declared entry count fits the buffer). With the feature disabled it takes a weaker path, and a second gated check inside the free-list traversal (0x1C01BC49E) relaxes the entry-offset bounds test. This is the routine that parses the attacker-influenceable $LogFile restart area during mount/recovery, so it carries the most meaningful reachable impact. The patched build removes both gates and always runs the strong path.

3. Pseudocode Diff

NtfsExtendRestartTable

// === UNPATCHED (0x1C0024660) ===
// Feature gate: overflow-checked add only when the feature is ENABLED.
if (EvaluateCurrentState(&g_Feature_2859161914_59426036) != 0) {
    if (RtlULongAdd(NumberEntries, arg2, &new_count) < 0)
        ExRaiseStatus(STATUS_DISK_CORRUPT_ERROR);   // 0xC0000032
} else {
    new_count = (uint32_t)NumberEntries + arg2;       // unchecked 32-bit add
}
InitializeNewTable(entry_size, new_count, flags, Scb);
memmove(new_table + 0x18, old_table + 0x18, entry_size * NumberEntries);
// === PATCHED (0x1C0024640) ===
// Gate removed: overflow-checked add is unconditional.
if (RtlULongAdd(NumberEntries, arg2, &new_count) < 0)
    ExRaiseStatus(STATUS_DISK_CORRUPT_ERROR);
InitializeNewTable(entry_size, new_count, flags, Scb);
memmove(new_table + 0x18, old_table + 0x18, entry_size * NumberEntries);

NtfsAllocateRestartTableFromIndex

// === UNPATCHED ===
// Zero-size / count checks gated behind the feature evaluation.
if (EvaluateCurrentState(&g_Feature_2859161914_59426036) != 0
        && (entry_size == 0 || NumberEntries == 0))
    ExRaiseStatus(STATUS_DISK_CORRUPT_ERROR);   // SKIPPED when feature disabled
memset(entry, 0, entry_size);
*entry = 0xffffffff;
in_use_count += 1;
if (EvaluateCurrentState(&g_Feature_2859161914_59426036) != 0) {
    if (in_use_count == 0xffff || in_use_count > NumberEntries)
        ExRaiseStatus(STATUS_DISK_CORRUPT_ERROR); // SKIPPED when feature disabled
}
// === PATCHED ===
if (entry_size == 0 || NumberEntries == 0)
    ExRaiseStatus(STATUS_DISK_CORRUPT_ERROR);
memset(entry, 0, entry_size);
*entry = 0xffffffff;
in_use_count += 1;
if (in_use_count == 0xffff || in_use_count > NumberEntries)
    ExRaiseStatus(STATUS_DISK_CORRUPT_ERROR);

(The same unconditional-check restoration applies to NtfsAllocateRestartTableIndex and to the single-path unification in NtfsCheckRestartTable, detailed in Section 4.)


4. Assembly Analysis

NtfsExtendRestartTable (0x1C0024660, unpatched)

00000001C002469E  lea     rcx, g_Feature_2859161914_59426036_FeatureDescriptorDetails
00000001C00246A5  call    EvaluateCurrentState        ; WIL feature evaluation
00000001C00246AA  test    eax, eax
00000001C00246AC  jz      loc_1C00246F5               ; feature OFF -> skip checked add
; --- feature ON: overflow-checked path ---
00000001C00246AE  movzx   ecx, word ptr [rbx+2]       ; NumberEntries (augend)
00000001C00246B2  lea     r8, [rsp+38h+pulResult]
00000001C00246B7  mov     edx, edi                    ; arg2 (addend)
00000001C00246B9  call    RtlULongAdd                 ; checked addition
00000001C00246BE  test    eax, eax
00000001C00246C0  jns     loc_1C00246EF
00000001C00246C8  mov     ebx, 0C0000032h             ; STATUS_DISK_CORRUPT_ERROR
00000001C00246E2  call    cs:__imp_ExRaiseStatus
; --- feature OFF: unchecked path ---
00000001C00246F5  movzx   edx, word ptr [rbx+2]
00000001C00246F9  add     edx, edi                    ; unchecked 32-bit add
00000001C00246FB  movzx   r8d, word ptr [rbx+6]
00000001C0024703  movzx   ecx, word ptr [rbx]
00000001C0024706  call    InitializeNewTable          ; pool sizing uses new count
00000001C002470B  movzx   ecx, word ptr [rbx]
00000001C0024712  movzx   eax, word ptr [rbx+2]       ; original NumberEntries
00000001C002471D  imul    ecx, eax                    ; entry_size * original count
00000001C0024720  movsxd  r8, ecx
00000001C0024727  call    memmove                     ; copy old table body

NtfsExtendRestartTable (0x1C0024640, patched)

00000001C0024684  movzx   ecx, r9w                    ; NumberEntries (augend)
00000001C0024688  lea     r8, [rsp+38h+pulResult]
00000001C002468D  call    RtlULongAdd                 ; unconditional checked add
00000001C0024692  test    eax, eax
00000001C0024694  jns     loc_1C00246C3
00000001C002469C  mov     ebx, 0C0000032h             ; STATUS_DISK_CORRUPT_ERROR
00000001C00246B6  call    cs:__imp_ExRaiseStatus
00000001C00246D1  call    InitializeNewTable
00000001C00246F2  call    memmove

NtfsAllocateRestartTableFromIndex (0x1C002DC38, unpatched)

00000001C002DDCF  lea     rcx, g_Feature_2859161914_59426036_FeatureDescriptorDetails
00000001C002DDD6  call    EvaluateCurrentState        ; gate #1
00000001C002DDDB  test    eax, eax
00000001C002DDDD  jz      loc_1C002DDFC               ; feature OFF -> skip zero-size check
00000001C002DDDF  cmp     word ptr [rbx], di          ; entry_size == 0 ?
00000001C002DDE2  jz      loc_1C002DDEA
00000001C002DDE4  cmp     word ptr [rbx+2], di        ; NumberEntries == 0 ?
00000001C002DDE8  jnz     loc_1C002DDFC
00000001C002DDFC  movzx   r8d, word ptr [rbx]         ; size for memset
00000001C002DE05  call    memset
00000001C002DE0D  lea     rcx, g_Feature_2859161914_59426036_FeatureDescriptorDetails
00000001C002DE14  add     word ptr [rbx+4], r12w      ; in-use count += 1
00000001C002DE19  call    EvaluateCurrentState        ; gate #2
00000001C002DE1E  test    eax, eax
00000001C002DE20  jz      loc_1C002DE76               ; feature OFF -> skip overflow check
00000001C002DE22  movzx   eax, word ptr [rbx+4]
00000001C002DE26  mov     ecx, 0FFFFh
00000001C002DE2B  cmp     ax, cx                      ; count == 0xffff ?
00000001C002DE2E  jz      loc_1C002DE36
00000001C002DE30  cmp     ax, word ptr [rbx+2]        ; count > NumberEntries ?
00000001C002DE34  jbe     loc_1C002DE76

NtfsCheckRestartTable (0x1C01BC2D4, unpatched)

00000001C01BC2F6  lea     rcx, g_Feature_2859161914_59426036_FeatureDescriptorDetails
00000001C01BC300  call    EvaluateCurrentState        ; path-selection gate
00000001C01BC305  movzx   r9d, word ptr [rdi]         ; entry_size
00000001C01BC30C  test    eax, eax
00000001C01BC30E  jz      loc_1C01BC381               ; feature OFF -> weaker path
; --- feature ON: strong path ---
00000001C01BC310  test    r9w, r9w                    ; entry_size != 0
00000001C01BC31A  cmp     r10d, ebx                   ; entry_size <= buffer_size
00000001C01BC31F  movzx   r8d, word ptr [rdi+2]       ; NumberEntries != 0
00000001C01BC32A  movzx   r11d, word ptr [rdi+4]      ; NumberInUse != 0
; ... second gate inside free-list traversal ...
00000001C01BC49E  lea     rcx, g_Feature_2859161914_59426036_FeatureDescriptorDetails
00000001C01BC4A5  call    EvaluateCurrentState
00000001C01BC4AA  test    eax, eax
00000001C01BC4AC  jz      loc_1C01BC4CE               ; relaxed offset-bounds path

In the patched build (0x1C01BC2C4) the routine has no EvaluateCurrentState calls; it enters directly at the strong checks (entry_size != 0, entry_size <= buffer_size, NumberEntries != 0, NumberInUse != 0, then div to confirm the entry count fits the buffer).


5. Trigger Conditions

  1. Volume/log preparation: The validated table originates from the $LogFile restart area of a mounted NTFS volume. Reaching NtfsCheckRestartTable with attacker-influenced fields requires presenting a crafted NTFS volume (VHD or physical media) whose log restart area is malformed.
  2. Recovery/mount: Mount or otherwise trigger LFS recovery so the restart area is parsed and the restart tables are built, validated (NtfsCheckRestartTable), and grown (NtfsExtendRestartTable, NtfsAllocateRestartTableIndex, NtfsAllocateRestartTableFromIndex).
  3. Feature state: The bypass only exists when g_Feature_2859161914_59426036 evaluates false in the unpatched build. When it evaluates true (or in the patched build, always), the consistency checks run and malformed tables raise STATUS_DISK_CORRUPT_ERROR (0xC0000032).
  4. Observable effect (if checks are absent): A malformed restart table with an inconsistent entry_size/NumberEntries/NumberInUse can drive incorrect free-list index arithmetic and memset/pool sizing. This can surface as STATUS_DISK_CORRUPT_ERROR when the checks are present, or as out-of-bounds access when they are skipped. A controlled, attacker-sized kernel heap overflow is not demonstrable from these binaries because the growth addend in NtfsExtendRestartTable is not attacker-scaled (see Finding 1).

6. Impact Assessment

  • Nature: A staged consistency/overflow-check feature for LFS restart tables is made unconditional. The patched build is strictly stricter; the unpatched build skips the checks only when the WIL feature evaluates false.
  • Reachable impact: Bounded. NtfsCheckRestartTable parses the $LogFile restart area, so its fields are influenceable by a crafted volume; skipping its strong path lets malformed restart tables through to routines that use entry_size/count fields for memset sizing, pool sizing, and free-list index math. That is a plausible out-of-bounds risk on a mount-a-malicious-volume (local, physical/VHD) attack surface.
  • Not demonstrable: The integer overflow in NtfsExtendRestartTable (Finding 1) is not reachable with attacker magnitude — the augend is a 16-bit field and every caller supplies a small growth addend, so NumberEntries + arg2 cannot wrap 32 bits. No arbitrary-size pool overflow, information leak, or code-execution primitive is provable from these two binaries.

7. Debugger Notes

Useful breakpoints for observing the gate behavior in the unpatched build:

bp ntfs!NtfsExtendRestartTable                 ; 0x1C0024660
bp 0x1C00246A5                                 ; call EvaluateCurrentState (feature eval)
bp 0x1C00246F5                                 ; unchecked add path (feature OFF)
bp 0x1C0024727                                 ; memmove of the old table body
bp ntfs!NtfsCheckRestartTable                  ; 0x1C01BC2D4
bp 0x1C01BC300                                 ; path-selection feature eval
  • 0x1C00246A5: after call EvaluateCurrentState, EAX == 0 means the feature is disabled and the checked add is skipped (je 0x1C00246F5).
  • 0x1C00246F9 (add edx, edi): EDX = zero-extended NumberEntries ([rbx+2]), EDI = growth addend. Both are small in practice.
  • 0x1C0024727 (memmove): R8 = entry_size * NumberEntries (original), RCX = new table body.
  • 0x1C01BC300: EAX != 0 selects the strong validation path; EAX == 0 selects the weaker path.

Restart-table header layout (structure at rbx/rdi)

  • +0x00 (WORD) EntrySize
  • +0x02 (WORD) NumberEntries (allocated)
  • +0x04 (WORD) NumberInUse (in-use / free counter)
  • +0x06 (WORD) Flags (bit 0 selects fixed-size vs computed-size table layout)
  • +0x0C (DWORD) FirstFreeEntry offset
  • +0x10 (DWORD) free-list head offset
  • +0x14 (DWORD) last-free / hint offset
  • +0x18 (BYTE[]) entry data

8. Changed Functions — Full Triage

Function Name (address) Similarity Change Type Note
NtfsExtendRestartTable (0x1C0024660) 0.9096 Security Relevant Removes the feature gate; the RtlULongAdd overflow check on NumberEntries + growth is now unconditional. Overflow itself not attacker-reachable (Finding 1).
NtfsAllocateRestartTableFromIndex (0x1C002DC38) 0.9651 Security Relevant Removes the gate; zero entry-size/count and in-use-count overflow checks now unconditional.
NtfsAllocateRestartTableIndex (0x1C000E124) 0.8786 Security Relevant Removes the gate; zero entry-size/count and entry-count overflow checks now unconditional.
NtfsCheckRestartTable (0x1C01BC2D4) 0.8353 Security Relevant Unifies the two feature-selected validation paths into a single strict path (the on-disk $LogFile restart-area validator).
InitializeNewTable (0x1C0019D10) 0.9321 Behavioral Restart-table pool allocator; feature gate removed from a NumberInUse==0 early check before NtfsLinkRestartTable.
PageUpdateAnalysis (0x1C0205D04) 0.9572 Behavioral Removes the gate around a NumberEntries >= required bounds check; caller of the restart-table routines.
EvaluateCurrentState (0x1C0030128) 0.6686 Behavioral The WIL feature-state evaluator itself; refactored (relocated to 0x1C00303D0, signature simplified). Not the vulnerability.
NtfsSetPositionInfo (0x1C01D2D80) 0.9854 Cosmetic Feature call retargeted to the refactored evaluator; separate feature family, still gated in both builds.
NtfsSetAllocationInfo (0x1C00DFA10) 0.9928 Cosmetic Same evaluator retargeting; separate feature, gated in both builds.
NtfsSetEndOfFileInfo (0x1C011B630) 0.9929 Cosmetic Same evaluator retargeting; separate feature, gated in both builds.

9. Unmatched Functions

There are no unmatched functions in either build. All changes occurred inline within existing functions. An independent function-level diff of the two decompilations found no additional security-relevant change outside the ten functions above.


10. Confidence & Caveats

  • Confidence: High on the mechanism and direction. The gate is EvaluateCurrentState(g_Feature_2859161914_59426036) (a WIL staging feature), not a resource-lock query; the unpatched build skips the restart-table checks when the feature is disabled, and the patched build removes the gate so they always run. EvaluateCurrentState call sites drop from 13 to 4 across the two builds, all restart-table sites removed.
  • Scope of impact: The affected structures are LFS restart tables, parsed from the $LogFile restart area during mount/recovery — a local, crafted-volume attack surface. The strongest reachable concern is that NtfsCheckRestartTable's weak path lets malformed restart tables through to size/index computations. A specific, attacker-controlled kernel heap-overflow, information leak, or code-execution primitive is not provable from these binaries.
  • On the integer overflow: The RtlULongAdd in NtfsExtendRestartTable guards NumberEntries (WORD) + growth. Every caller supplies a small growth increment, so a 32-bit wrap is not reachable; the check is defensive hardening.