fsdepends.sys — Missing authorization / access check (CWE-862) in DepFSPreCreate fixed
KB5073723
1. Overview
- Unpatched Binary:
fsdepends_unpatched.sys - Patched Binary:
fsdepends_patched.sys - Overall Similarity Score: 0.9817 (98.17%)
- Diff Statistics: 87 matched functions (84 identical, 3 changed), 0 unmatched in both directions.
- Verdict: The patch fixes a medium-severity missing authorization vulnerability (CWE-862) in the minifilter's pre-create callback. In the post-startup steady state the unpatched driver performs no access check of its own against the managed volume's security descriptor before letting create operations proceed; the patch adds that driver-level
SeAccessCheckgate.
2. Vulnerability Summary
- Severity: Medium
- Vulnerability Class: Missing Authorization / Access Check (CWE-862)
- Affected Function:
DepFSPreCreate
Root Cause Analysis:
The FsDepends minifilter driver intercepts IRP_MJ_CREATE (file open/create) operations on managed dependency volumes. The unpatched version only performs volume-context and autochk handling before system startup applications complete (FsRtlAreVolumeStartupApplicationsComplete() returns FALSE). Once startup completes (normal system steady-state), the unpatched driver falls straight through to an unconditional return of FLT_PREOP_SUCCESS_WITH_CALLBACK (value 1). As a result, in steady-state operation the minifilter performs no authorization check of its own against the managed volume's security descriptor before letting the create proceed; the driver-level access gate that the patch adds is simply absent. (Note: the minifilter returning FLT_PREOP_SUCCESS_WITH_CALLBACK does not disable the underlying filesystem's own access check — it only means the minifilter does not impose its own additional restriction.)
The patch introduces an else branch for the post-startup state. It fetches the minifilter instance context, extracts the volume's security descriptor (stored at offset 0x78), and calls SeAccessCheck() with AccessMode = UserMode (1). If access is denied, the driver updates the IoStatus.Status and returns FLT_PREOP_COMPLETE (4), successfully blocking the unauthorized access.
Attacker-Reachable Entry Point & Data Flow:
1. A low-privileged user initiates an NtCreateFile / CreateFile request targeting a file on a volume managed by the FsDepends minifilter (typically VHD or cloud files).
2. The Windows Filter Manager intercepts the IRP and dispatches it to the DepFSPreCreate callback.
3. DepFSPreCreate calls FsRtlAreVolumeStartupApplicationsComplete(), which returns TRUE in the normal steady-state.
4. The unpatched binary immediately returns FLT_PREOP_SUCCESS_WITH_CALLBACK (1), overriding any standard filesystem DACL denials and allowing the operation to proceed.
3. Pseudocode Diff
Below is a high-level comparison of the vulnerable logic path. The unpatched version completely lacks the else block that enforces authorization in the post-startup phase.
// === UNPATCHED DepFSPreCreate ===
uint64_t DepFSPreCreate(void* arg1, void* arg2) {
int32_t rbx = 1; // FLT_PREOP_SUCCESS_WITH_CALLBACK
if (!FsRtlAreVolumeStartupApplicationsComplete()) {
// Pre-startup logic: handles autochk/volume verification
// (Not vulnerable)
}
// VULNERABILITY: No 'else' block.
// When FsRtlAreVolumeStartupApplicationsComplete() == TRUE,
// control falls straight through to the return without any access check.
return rbx; // Always returns 1 (SUCCESS)
}
// === PATCHED DepFSPreCreate ===
uint64_t DepFSPreCreate(void* arg1, void* arg2) {
int32_t rbx = 1;
if (!FsRtlAreVolumeStartupApplicationsComplete()) {
// Pre-startup logic...
} else {
// FIX: Post-startup authorization check
FltGetInstanceContext(..., &Context);
if (Context && *(Context + 0x78)) { // Check if SD exists
GENERIC_MAPPING* gm = IoGetFileObjectGenericMapping();
BOOLEAN granted = SeAccessCheck(
*(Context + 0x78), // SecurityDescriptor
..., // SubjectSecurityContext
..., // DesiredAccess
gm, // GenericMapping
1, // AccessMode = UserMode
&GrantedAccess,
&AccessStatus
);
if (!granted) {
// Access Denied
*(arg1 + 0x18) = AccessStatus; // Set IoStatus.Status
rbx = 4; // FLT_PREOP_COMPLETE (block)
}
}
}
return rbx;
}
4. Assembly Analysis
The vulnerability is a straight-line bypass. In the unpatched binary, the result of FsRtlAreVolumeStartupApplicationsComplete() dictates whether the function jumps straight to the return epilogue.
Unpatched Assembly (fsdepends_unpatched!DepFSPreCreate):
0x1c0007034: call qword [rel 0x1c0006238] ; FsRtlAreVolumeStartupApplicationsComplete()
0x1c000703b: nop dword [rax+rax]
0x1c0007040: test al, al
0x1c0007042: jne 0x1c0007185 ; if TRUE (Startup Complete), JUMP TO EPILOGUE
...
; === EPILOGUE (Reached when startup apps are complete) ===
0x1c0007185: mov rbp, qword [rsp+0x38]
0x1c000718a: mov eax, ebx ; eax = 1 (FLT_PREOP_SUCCESS_WITH_CALLBACK)
0x1c000718c: mov rbx, qword [rsp+0x30]
0x1c0007191: mov rsi, qword [rsp+0x40]
0x1c0007196: add rsp, 0x20
0x1c000719a: pop rdi
0x1c000719b: retn ; Returns SUCCESS unconditionally
Patched Assembly Redirect:
In the patched binary the same test is restructured: test al, al sits at 0x1c000704b followed by jz 0x1c00071c3 at 0x1c0007051. When startup applications are complete (al != 0) control now falls through into the newly inserted FltGetInstanceContext (0x1c000708b) and SeAccessCheck (0x1c0007111) call sequence instead of returning immediately. The startup-not-complete autochk path is preserved unchanged at 0x1c00071c3.
5. Trigger Conditions
To trigger the vulnerability, an attacker must:
- Ensure the target Windows system has fully booted (such that
FsRtlAreVolumeStartupApplicationsComplete()returnsTRUE). - Identify a volume managed by the
FsDependsminifilter (typically virtual disk/VHD-backed or Cloud Files placeholder volumes). - Identify a target file on such a managed volume whose access the volume's security descriptor would deny to the caller.
- Execute a standard
CreateFileorNtCreateFilecall from an unprivileged user context against the target file. - Observable Effect: Against the unpatched binary the minifilter does not apply its own authorization gate —
DepFSPreCreatereturnsFLT_PREOP_SUCCESS_WITH_CALLBACKand does not complete the create withSTATUS_ACCESS_DENIED. Against the patched binary the same create is completed by the minifilter with theSeAccessCheckfailure status (FLT_PREOP_COMPLETE).
6. Exploit Primitive & Development Notes
- Primitive: Missing driver-level authorization gate. In steady state the unpatched minifilter does not run its own
SeAccessCheckagainst the managed volume's security descriptor before allowing a create on that volume to proceed. - Scope of demonstrable impact: What is provable from the binaries is limited to the presence/absence of this minifilter-level check. The patched build can complete the create with an access-denied status (
FLT_PREOP_COMPLETE) when the volume's security descriptor denies the caller; the unpatched build performs no such check. The broader real-world impact depends on what these managed dependency volumes contain and on how the volume's security descriptor differs from the file's own DACL — neither of which is determinable from the driver binaries alone. No memory-corruption, information-leak, or code-execution primitive is present. - Required Steps: No heap spraying, race conditions, or memory manipulation are involved; this is a logical authorization-check difference.
7. Debugger PoC Playbook
For a researcher using WinDbg/KD on the unpatched binary to verify the flaw:
Breakpoints:
Set a breakpoint on the pre-create callback:
bp fsdepends_unpatched!DepFSPreCreate
Set a breakpoint specifically on the vulnerable conditional jump:
bp fsdepends_unpatched!DepFSPreCreate+0x32
What to inspect at each breakpoint:
- At DepFSPreCreate entry: rcx holds the FLT_CALLBACK_DATA. rdx holds PFLT_RELATED_OBJECTS.
- At DepFSPreCreate+0x32 (0x1c0007042): Step over (p). The instruction is jne 0x1c0007185. Because startup is complete, AL will be 1 (ZF=0), and the jump will be taken.
- Check the return value: The execution will land at 0x1c000718a: mov eax, ebx. Inspect ebx; it will be 1 (FLT_PREOP_SUCCESS_WITH_CALLBACK).
Trigger setup:
1. Open an elevated command prompt and a normal user command prompt.
2. From the normal user prompt, attempt to read or write a restricted file located on a mounted VHD or Cloud Files volume managed by FsDepends.
3. Observe in the debugger that DepFSPreCreate intercepts the request, takes the jump at 0x1c0007042, and returns 1 (FLT_PREOP_SUCCESS_WITH_CALLBACK) without calling SeAccessCheck (which the unpatched binary does not even import).
4. On the patched binary, observe the same create instead reach SeAccessCheck and, when the volume's security descriptor denies the caller, be completed with FLT_PREOP_COMPLETE (ebx=4) and an access-denied IoStatus.Status.
Key Instructions / Offsets:
- 0x1c0007034: Call to FsRtlAreVolumeStartupApplicationsComplete().
- 0x1c0007042: The missing-check jump (jne 0x1c0007185).
- 0x1c0007185 to 0x1c000719b: The vulnerable success epilogue.
8. Changed Functions — Full Triage
DepFSPreCreate(Similarity: 0.6365)- Change: Security-relevant. Added the entire
SeAccessChecklogic block for post-startupIRP_MJ_CREATEoperations. Fixed the CWE-862 vulnerability. DepFSInstanceSetup(Similarity: 0.9329)- Change: Security-relevant. Added 16 bytes to the stack frame. Added calls to
FltQuerySecurityObjectandExAllocatePoolWithTag. This queries the volume's security descriptor during instance setup and stores it atContext+0x78soDepFSPreCreatehas the data it needs to enforce access control. DepFSCleanupVolumeContext(Similarity: 0.9447)- Change: Security-relevant (resource management). Added cleanup logic (
ExFreePoolWithTagwith tag0x73447044/'DpDs', at0x1c001042d) to free the newly allocated security descriptor stored atContext+0x78when the context is destroyed, preventing a kernel memory leak.
9. Unmatched Functions
There are no added or removed functions between the unpatched and patched binaries. All modifications occurred within existing functions.
10. Confidence & Caveats
- Confidence Level: High. The logic gap is unambiguous in the assembly and pseudocode. The patched code introduces standard Windows Filter Manager
SeAccessChecksemantics. - Assumptions: It is assumed that the
FsDependsdriver attaches automatically to VHD-backed or Cloud Files volumes. A researcher developing a PoC should verify which specific mount operations triggerDepFSInstanceSetupto attach the minifilter to the target volume. - Verification needed: A researcher should verify the exact flags required in the
CreateFilecall. WhileGENERIC_READ/GENERIC_WRITEshould trigger the bypass easily, verifying the exactDesiredAccessmasks mapped byIoGetFileObjectGenericMappingwill help in building a clean, deterministic PoC.