ntfs.sys — version-drift recompilation; misattributed EDP telemetry
KB5082142
1. Overview
| Field | Value |
|---|---|
| Unpatched binary | ntfs_unpatched.sys (image base 0x1C0000000) |
| Patched binary | ntfs_patched.sys (image base 0x140000000) |
| Overall similarity | 0.5882 |
| Matched functions | 3938 |
| Changed functions | 3137 |
| Identical functions | 801 |
| Unmatched (either direction) | 0 / 0 |
Verdict: No security-relevant change was found. The two binaries are different builds of ntfs.sys (different image base, and the patched image is roughly 750 KB larger), which is why the overall similarity is only 0.59 and 3137 functions differ. The differences examined are recompilation drift: renamed locals, changed structure offsets, reordered corruption checks, different internal debug-message constant IDs, and reference-counting/feature-gate refactors. The reparse-point out-of-bounds read described in the original draft could not be reproduced against the binaries: the function it named is an Enterprise Data Protection (EDP/WIP) telemetry routine, the telemetry string it quoted does not exist in either build, and the real stat-fill routine already performs the relevant reparse validation identically in both builds.
2. Vulnerability Summary
| Severity | Class | Affected function |
|---|---|---|
| None (informational) | No security-relevant change | NtfsFillStatInfoFromMftRecord (reparse path unchanged) |
Why the claimed out-of-bounds read is not present
Symbols are present in both disassemblies, so functions can be matched by name rather than by reused address.
-
The named "helper" is EDP telemetry, not a reparse/stat helper. The address
0x1C02A8BC8in the unpatched build isEdpEnforcementLog_FileRequiresEncryption, an Enterprise Data Protection enforcement-logging routine (it callsEEL_GetProcessInfo). It has nothing to do with reparse-point parsing or with filling stat information. -
The "patched" counterpart is a different function. The address
0x140252D28in the patched build isEdpEnforcementLog_AutomaticEncryption, a separate EDP telemetry routine — not a patched version ofEdpEnforcementLog_FileRequiresEncryption(which lives at0x14011B804in the patched build). The "addedpush r12/push r13, frame grown by0x10" prologue difference is simply the difference between two unrelated EDP functions, not an inserted bounds check. -
The telemetry string does not exist. No string
NtfsFillStatInfoFromMftRecordBailedBecauseOfNonResReparsePointCount, nor any...Bailed.../NonResReparsePointCountstring, appears in either build. -
The real stat-fill routine validates reparse attributes identically in both builds.
NtfsFillStatInfoFromMftRecordis at0x1C0200F98(unpatched) and0x140237014(patched). In both builds, after locating the$REPARSE_POINT(type0xC0) attribute it: - rejects the attribute if it is non-resident (the byte at attribute-header offset
+8), and - caps the resident reparse data length at
0x4000(16 KB).
There is no missing check and no direction change: the unpatched build is exactly as strict as the patched build on this path.
3. Actual Diff
NtfsFillStatInfoFromMftRecord differs between the two builds only by recompilation artifacts. Same logic, different codegen:
- Base structure offset changed (
*(a1 + 96)in the unpatched build vs*(a1 + 104)in the patched build) — a layout change consistent with a different build, not a security fix. - Locals renumbered/reallocated; the order of the "bad or orphan FRS" corruption checks was rearranged; internal debug-message constant IDs shifted (for example
216868→216909). - The shared-security release at the end changed shape: the unpatched build calls
NtfsDereferenceSharedSecuritygated byEnableFeatureServicing_48459730, while the patched build inlines an interlocked decrement plusExFreePoolWithTag. This is a reference-counting/feature-staging refactor, not a memory-safety fix.
The reparse-point handling itself is unchanged. Both builds contain the same two guards before any reparse data is consumed:
// Both builds, NtfsFillStatInfoFromMftRecord, reparse ($REPARSE_POINT = 0xC0) path:
// 1. reject non-resident reparse attribute (attribute-header +8 byte != 0)
// 2. cap resident reparse data length at 0x4000
if ( *(_BYTE *)(reparse_attr + 8) != 0 ) // non-resident -> bail
/* status = 0xC0000004-class error, return */;
if ( *(_DWORD *)(reparse_attr + 16) > 0x4000u ) // length > 16 KB -> corruption
/* raise corruption, return */;
4. Assembly Analysis
The function the draft named (EdpEnforcementLog_FileRequiresEncryption @ 0x1C02A8BC8, unpatched)
Real prologue (this is EDP telemetry, not a reparse handler):
00000001C02A8BC8 mov rax, rsp
00000001C02A8BCB mov [rax+8], rbx
00000001C02A8BCF mov [rax+10h], rsi
00000001C02A8BD3 mov [rax+18h], rdi
00000001C02A8BD7 push rbp
00000001C02A8BD8 push r14
00000001C02A8BDA push r15
00000001C02A8BDC lea rbp, [rax-268h]
00000001C02A8BE3 sub rsp, 350h
00000001C02A8C1B call EEL_GetProcessInfo
The instructions the draft quoted as the "vulnerable reads" and "telemetry-string load" do exist at those addresses, but they are not what was claimed and they are inside this EDP routine:
| Address | Real instruction | Draft claimed |
|---|---|---|
0x1C02A8D31 |
mov rax, [rbx+8] |
(matches operand form, but this is EDP logging, not reparse) |
0x1C02A8D5B |
mov eax, [rbx+4] |
(same) |
0x1C02A8D81 |
jz short loc_1C02A8D94 |
falsely quoted as lea rax,[rel telemetry_str] |
0x1C02A8DA6 |
mov rax, r8 |
falsely quoted as lea rax,&data_... |
The real stat-fill routine reparse guards
Unpatched NtfsFillStatInfoFromMftRecord @ 0x1C0200F98:
00000001C0201355 mov [rsp+0D8h+PreviouslyGrantedAccess], 0C0h ; look up $REPARSE_POINT
00000001C0201388 cmp [r14+8], r9b ; non-resident? -> bail
00000001C0201393 cmp dword ptr [r14+10h], 4000h ; length cap 0x4000
Patched NtfsFillStatInfoFromMftRecord @ 0x140237014:
0000000140237416 mov [rsp+0D8h+PreviouslyGrantedAccess], 0C0h ; look up $REPARSE_POINT
0000000140237449 cmp [r14+8], r8b ; non-resident? -> bail
00000001402377E6 cmp dword ptr [r14+10h], 4000h ; length cap 0x4000
Both guards are present in both builds. The unpatched build is not missing a bounds check.
5. Trigger Conditions
None. There is no reachable out-of-bounds read on this path to trigger. Non-resident reparse attributes are rejected before any reparse data is read, and resident reparse data length is capped at 0x4000 in both builds.
6. Exploit Primitive & Development Notes
None. No kernel-pool out-of-bounds read, information-disclosure, or denial-of-service primitive is supported by the binaries on this path. The originally described primitive, pool-shaping/KASLR-bypass chain, and mitigation table were not backed by the code and are not reproduced here.
7. Debugger PoC Playbook
Not applicable. There is no vulnerable code path to instrument. The addresses the draft proposed as breakpoints belong to EDP telemetry (EdpEnforcementLog_FileRequiresEncryption), not to reparse-point stat processing.
8. Changed Functions — Triage
The single entry the draft treated as a security fix does not hold up:
| Reported pairing | Reality |
|---|---|
sub_1c02a8bc8 (unpatched) ↔ sub_140252d28 (patched), described as an "ETW telemetry / reparse bail-out helper" |
These are two different EDP telemetry functions: EdpEnforcementLog_FileRequiresEncryption (0x1C02A8BC8, unpatched) and EdpEnforcementLog_AutomaticEncryption (0x140252D28, patched). The prologue "delta" is the natural difference between two unrelated functions, not an inserted validation block. Not security-relevant. |
The remaining ~3136 differing functions are consistent with a full rebuild (different image base, different structure layout, codegen churn). An independent name-matched diff of the untrusted-metadata parsing routines (reparse, extended-attribute, attribute-list, index, and FSCTL paths) surfaced no function where the patched build adds a bounds/length/overflow check that the unpatched build lacks.
9. Unmatched Functions
removed: []
added: []
No functions were added or removed.
10. Confidence & Caveats
Overall confidence: High that there is no security-relevant change on the reported path.
- The named "helper" resolves to EDP telemetry, not reparse handling.
- The quoted telemetry string is absent from both builds.
- Two of the quoted "vulnerable" instructions were misquoted (
0x1C02A8D81,0x1C02A8DA6). - The real
NtfsFillStatInfoFromMftRecordrejects non-resident reparse attributes and caps reparse length at0x4000in both builds, at the addresses listed in section 4.
Caveat: the two images are substantially different builds, so the 3137 "changed" functions are dominated by recompilation drift. This report resolves the one claimed finding and the untrusted-metadata parsing paths; a broader per-function security diff of an entire build-to-build delta is out of scope and was not exhaustively performed.