dfs.sys — interface refactoring
KB5073723
1. Overview
- Unpatched Binary:
dfs_unpatched.sys - Patched Binary:
dfs_patched.sys - Overall Similarity Score: 0.9075
- Diff Statistics:
- Matched Functions: 112
- Changed Functions: 79
- Identical Functions: 33
- Unmatched (Unpatched/Patched): 0 / 0
- Verdict: The patch contains no security-relevant fix. The most-changed function,
DfsReferralParseStringEntry, is refactored from a 6-argument to a 3-argument interface, but the alignment math and bounds checks are functionally identical in both builds. The remaining changes are routine modernizations: pool API updates (ExAllocatePoolWithTag→ExAllocatePool2), CFG_guard_dispatch_icallenforcement, stack zero-initialization, and WPP/ETW tracing refactoring.
2. Vulnerability Summary
- Severity: None (no security-relevant change)
- Vulnerability Class: None — this patch makes no security-relevant change to the referral-parsing path
- Affected Functions:
DfsReferralParseStringEntry(sub_1C0001E04 / sub_140002210),DfsFltFsctlGetReferralEx(sub_1C0002240 / sub_140001CB8)
Root Cause Analysis:
There is no truncation vulnerability. The 2-byte alignment padding computed for the referral string is the value ((ptr + 1) & 0xFFFE) - ptr, which is 0 or 1 depending solely on the low bit of the pointer. Because only the low bit matters, computing it in 16-bit registers is exact; there is no loss of information versus 64-bit arithmetic. Both builds compute this value the same way using 16-bit register operations:
- Unpatched: the caller
DfsFltFsctlGetReferralExcomputes it (and r9w, di; sub r9w, dx) and passes it toDfsReferralParseStringEntryasr9w(arg4), along with the remaining size (arg5) and start pointer (arg6). - Patched:
DfsReferralParseStringEntryreads the pointer from*arg1and the size from*arg2, then computes the identical alignment internally (and ax, di; sub ax, r11w— still 16-bit register operations).
Both builds then run the identical four bounds checks on the parsed length: (size - alignment) >= 2, length even, (size - alignment - 2) >= length, and remaining >= second_alignment. The parser returns the same validated UNICODE_STRING.Length in both builds, and the caller's downstream pool allocation (length1 + length2 + 0xBC) and two memmove copies operate on the same validated lengths. The change is a pure interface refactoring (moving the alignment computation and the initial size check from caller into callee); it does not add, remove, or alter any bounds check, and it does not fix an out-of-bounds condition.
Entry Point and Call Chain (for reference):
1. FSCTL_DFS_GET_REFERRALS_EX (0x601b0) is delivered to the DFS minifilter control device.
2. DfsFltHandleDeviceRequests routes the I/O control request.
3. DfsFltHandleFsControl identifies the FSCTL and dispatches it.
4. DfsFltFsctlGetReferralEx handles the request and calls the parser.
5. DfsReferralParseStringEntry validates and parses the referral string with the bounds checks described above.
6. Control returns to DfsFltFsctlGetReferralEx, which allocates a pool buffer sized to the validated lengths and copies the strings into it.
3. Pseudocode Diff
The diff below shows the alignment computation being shifted from the caller into the callee. The computed value is identical in both builds; the copy size is the validated out_str.Length returned by the parser.
// === UNPATCHED: DfsFltFsctlGetReferralEx (Caller) ===
// alignment padding is 0 or 1 (depends only on the low bit of data_ptr)
int alignment = ((data_ptr + 1) & 0xFFFE) - (data_ptr & 0xFFFF); // == data_ptr & 1
// caller passes alignment, size and pointer to the parser
result = DfsReferralParseStringEntry(&ptr, &size, &out_str, alignment, remaining_size, data_ptr);
if (result >= 0) {
// allocation is sized to the validated parsed lengths
int32_t alloc_size = out_str.Length + out_str_2.Length + 0xBC;
void* pool_buf = ExAllocatePoolWithTag(NonPagedPoolNx, alloc_size, tag);
// copy uses the same validated length the allocation was sized from
memmove(&pool_buf[0xB0], out_str.Buffer, out_str.Length);
}
// === PATCHED: DfsFltFsctlGetReferralEx (Caller) ===
// alignment/size handling now done inside the parser
result = DfsReferralParseStringEntry(&ptr, &size, &out_str);
// ...
// === PATCHED: DfsReferralParseStringEntry (Callee) ===
int64_t r11 = *arg1; // read pointer
int32_t r9 = *arg2; // read size
// same 0/1 alignment padding, computed with 16-bit register ops (and ax,di; sub ax,r11w)
uint16_t alignment = ((r11 + 1) & 0xFFFE) - (uint16_t)r11;
4. Assembly Analysis
Unpatched DfsFltFsctlGetReferralEx (sub_1C0002240, Caller)
The alignment calculation for the first call occurs at 0x1c0002343. The 16-bit register ops (r9w, dx) compute the 2-byte alignment padding (0 or 1); this is exact because the padding depends only on the pointer's low bit.
; UNPATCHED CALLER KEY SEQUENCE (first parser call)
00000001C000232C lea rdx, [rbx+8Ch] ; rdx = data pointer (arg1+0x8c)
00000001C0002333 mov [rbp+47h+var_80], ecx ; save size
00000001C0002336 lea r9d, [rdx+1] ; addr + 1
00000001C000233A mov [rbp+47h+Buf2], rdx ; save data pointer
00000001C000233E mov edi, 0FFFEh
00000001C0002343 and r9w, di ; (addr+1) & 0xfffe (16-bit)
00000001C0002347 sub r9w, dx ; alignment padding = 0 or 1
00000001C000234B movzx eax, r9w ; alignment (0 or 1)
00000001C000234F cmp ecx, eax
00000001C0002351 jb loc_1C00026FA ; error if size < alignment
00000001C0002357 mov [rsp+0C0h+var_98], rdx ; arg6 = data pointer
00000001C000235C lea r8, [rbp+47h+DestinationString] ; arg3 = output UNICODE_STRING
00000001C0002360 mov dword ptr [rsp+0C0h+var_A0], ecx ; arg5 = size
00000001C0002364 lea rdx, [rbp+47h+var_80] ; arg2 = &size
00000001C0002368 lea rcx, [rbp+47h+Buf2] ; arg1 = &pointer
00000001C000236C call DfsReferralParseStringEntry
Unpatched DfsReferralParseStringEntry (sub_1C0001E04, Callee)
The function receives the alignment padding (0 or 1) in r9w and uses it to index the length field. The caller has already checked size >= alignment before this call.
; UNPATCHED CALLEE ENTRY (0x1c0001e04)
00000001C0001E0E mov r10d, [rsp+arg_20] ; arg5 = remaining size
00000001C0001E16 mov r11, [rsp+arg_28] ; arg6 = start pointer (data_ptr)
00000001C0001E1E movzx eax, r9w ; alignment padding (0 or 1)
00000001C0001E22 sub r10d, eax ; r10d = size - alignment
00000001C0001E25 movzx eax, r9w ; reload alignment
00000001C0001E29 cmp r10d, 2 ; check if (size - align) >= 2
00000001C0001E2D jb short loc_1C0001E8B ; if not, error
00000001C0001E2F movzx r9d, word ptr [rax+r11] ; read length from [alignment + start_ptr]
00000001C0001E34 test r9b, 1 ; length must be even
00000001C0001E38 jnz short loc_1C0001E8B
; ... returns the validated length in DestinationString.
; The patched callee (0x140002210) computes the same 0/1 alignment
; internally (and ax,di; sub ax,r11w) and runs the identical checks.
5. Trigger Conditions
This section lists the input the FSCTL path parses. No overflow results: both builds enforce the same bounds, and the and r9w, di; sub r9w, dx sequence can only ever yield 0 or 1 (the 2-byte alignment padding), so it cannot make the pool allocation smaller than the copied data.
- Context: The function checks
ExGetPreviousMode(); the path is reached fromKernelModeor through a kernel-mode DFS component viaIRP_MJ_FILE_SYSTEM_CONTROL. - Device Handle: A handle to the DFS minifilter control device (e.g.,
\\.\DfsFltor equivalent). - IOCTL:
FSCTL_DFS_GET_REFERRALS_EX(0x601b0). - Buffer Layout: The input buffer must be at least
0x88bytes, containing:Offset 0x00: Version field (2or0x17).Offset 0x08-0x0b: Non-zero fields to pass version-specific validation.Offset 0x80: 32-bit size field (>= 9andsize + 0x84 <= total buffer size).Offset 0x86: Flags (bit 0 controls secondary string parsing).Offset 0x88: Second 32-bit size field (size + 0x8c <= total buffer size).Offset 0x8c: String referral data begins.
- Alignment value: The
and r9w, di; sub r9w, dxsequence produces0or1regardless of the full 64-bit address, because 2-byte alignment padding depends only on the pointer's low bit. - Length validation: The parser rejects the string unless
(size - alignment) >= 2, the length is even, and(size - alignment - 2) >= length. The pool allocation is then sizedlength1 + length2 + 0xBCand eachmemmovecopies exactlylengthNbytes, so the copy always fits. - Observable Effect: None. The request is parsed and either succeeds or returns
STATUS_INVALID_PARAMETER/STATUS_INSUFFICIENT_RESOURCES; there is no memory-safety violation on this path in either build.
6. Exploit Primitive & Development Notes
- Primitive: None on this path. The pool allocation is sized to
length1 + length2 + 0xBCand the twomemmovecopies move exactlylength1andlength2bytes into that buffer, so the copy size cannot exceed the allocation size. There is no attacker-controlled size/allocation mismatch, and the alignment value is bounded to0/1in both builds. No non-paged pool overflow is reachable here. - Modernizations present in the patch (not exploitable conditions):
ExAllocatePool2: Pool allocations were migrated fromExAllocatePoolWithTagtoExAllocatePool2, which zero-initializes by default.- CFG (
_guard_dispatch_icall): Indirect-call sites (e.g., inFinalizeRequest) were routed through the CFG dispatch stub. - Zero-init: Several functions now
memsetstack structures before use. These are hardening/refactoring changes, not fixes for a specific reachable bug.
7. Debugger PoC Playbook
Attach a kernel debugger (WinDbg/KD) to the target machine running dfs_unpatched.sys. These breakpoints let a reviewer confirm that the alignment value is always 0 or 1 and that the copy size never exceeds the allocation, i.e. that there is no overflow on this path.
Breakpoints
Set these breakpoints to observe the parse and allocation path:
; 1. Break on FSCTL dispatch to catch 0x601b0 specifically
bp dfs_unpatched!DfsFltHandleFsControl ".if (@ecx == 0x601b0) { echo '[+] Hit FSCTL_DFS_GET_REFERRALS_EX' } .else { gc }"
; 2. Break at the alignment computation in the caller (first parser call)
bp dfs_unpatched!DfsFltFsctlGetReferralEx+0x103
; 3. Break at the parser function entry
bp dfs_unpatched!DfsReferralParseStringEntry
; 4. Break at the pool allocation to check sizes
bp dfs_unpatched!DfsFltFsctlGetReferralEx+0x25a
What to Inspect at Each Breakpoint
- At
DfsFltFsctlGetReferralEx+0x103(0x1c0002343):- Inspect
rdx(the data pointer,arg1+0x8c). - Single-step through the
and r9w, di; sub r9w, dxinstructions. - Confirm
r9wis0whenrdxis even and1whenrdxis odd — it is the 2-byte alignment padding and can be nothing else.
- Inspect
- At
DfsReferralParseStringEntryEntry:- Inspect
r9w(arg4): always0or1. - Inspect
r11(qword [rsp+0x30]) to see the base pointer.
- Inspect
- At
DfsReferralParseStringEntryInternal Instruction (0x1c0001e2f):- Examine
word [rax+r11]. This is the 16-bit length field read at[alignment + base]. The subsequent checks require it to be even and to fit within the remaining size.
- Examine
- At
DfsFltFsctlGetReferralEx+0x25a(0x1c000249a, theExAllocatePoolWithTagcall):- Inspect
r13d(or trace intoExAllocatePoolWithTag). The size islength1 + length2 + 0xBC. - Trace to the
memmovecalls (0x1c0002548and0x1c0002587). TheSizeargument (r8) islengthN, which is always<= allocation size. No overflow occurs.
- Inspect
Struct/Offset Notes
Input Buffer + 0x00: Version (2or0x17)Input Buffer + 0x80:Size_Field_1(Offset/Size validation)Input Buffer + 0x86: FlagsInput Buffer + 0x88:Size_Field_2(Data bounds)Input Buffer + 0x8c: String Data Origin
8. Changed Functions — Full Triage
Below is the triage of all changed functions. Many represent broader security modernizations.
DfsReferralParseStringEntry(sub_1C0001E04 / sub_140002210, Sim: 0.7521): [Behavioral/Refactor] Interface changed from 6 arguments to 3. The alignment computation and the initial size check moved from the caller into this function, but the alignment value (0/1 padding, 16-bit register ops in both builds) and all four bounds checks are functionally identical. No security-relevant change.DfsFltFsctlGetReferralEx(sub_1C0002240 / sub_140001CB8, Sim: 0.8267): [Behavioral] Updated the call to match the new 3-argument parser interface and updated pool APIs fromExAllocatePoolWithTagtoExAllocatePool2. The allocation/copy path is unchanged in behavior.DfsFltFormPrefix(sub_1C000B104 / sub_14000D68C, Sim: 0.9126): [Behavioral] The*arg1 <= 0empty-prefix guard (cmp [ptr],0; jbe skip) beforeDfsFltConcatenateFilePathis present in BOTH builds. The only change is the pool API (ExAllocatePoolWithTag→ExAllocatePool2). Not security-relevant.DfsFltFsctlGetReferral(Sim: 0.9154): [Behavioral] Routine API updates toExAllocatePool2and WPP tracing refactoring.DfspFltAddDfsVolume(Sim: 0.8023): [Behavioral] Updated pool APIs (ExAllocatePool2) and refactored lock release primitives.UpdateUserBuffer(Sim: 0.8889): [Behavioral] Refactored loop structures for MDL-based buffer mapping. Functionally equivalent.UpCallCreateQueueEx/UpCallCompleteQueueRequest(Sim: ~0.65-0.88): [Behavioral] Queue structure layout changed slightly (field offsets shifted). Pool APIs updated.FinalizeRequest(Sim: 0.9291): [Behavioral] Added_guard_dispatch_icall()for Control Flow Guard (CFG) enforcement on indirect calls.DfsFltFsctlAttachToFileSystem(Sim: 0.9315): [Behavioral] Improved cleanup paths and addedmemsetzero-initialization of stack variables before use.DfsFltFsctlTranslatePath(Sim: 0.9888): [Behavioral] Corrected UNICODE_STRING pointer arithmetic and zero-initialized stack memory.- Cosmetic/Minor Changes (
DfsFltFsctlSetDfsnShareInfo,DfspFltConvertStringSidToSid,DfsFltHandleFsControl,DfsFltFsctlDetachFromFileSystem): Primarily consist of zero-initializing structures before use, WPP tracing updates, and control flow flattening.
9. Unmatched Functions
There were 0 added and 0 removed functions between the unpatched and patched binaries. All remediations occurred inline within existing functions.
10. Confidence & Caveats
- Confidence Level: High that this patch contains no security fix on the referral-parsing path. The alignment padding is
0/1(low bit only) and is computed with 16-bit register operations in both builds; the four bounds checks and the allocation/copy sizing are identical. - Nature of the change:
DfsReferralParseStringEntryand its callerDfsFltFsctlGetReferralExwere refactored (6-arg → 3-arg interface) with no behavioral difference in validation. Remaining changes across the binary are modernizations:ExAllocatePool2migration, CFG_guard_dispatch_icall, stack zero-initialization, and WPP/ETW tracing refactoring. - Context note:
ExGetPreviousMode() == KernelModegatesDfsFltFsctlGetReferralEx(0x1c0002240), so the path is reached from kernel context; this is unchanged between builds and is not itself a vulnerability.