mountmgr.sys — Cluster-Shared-Volume characteristic tracking field + IoCreateSymbolicLink2 modernization
KB5094128
1. Overview
- Unpatched Binary:
mountmgr_unpatched.sys - Patched Binary:
mountmgr_patched.sys - Overall Similarity Score: 0.9587
- Diff Statistics:
- Matched Functions: 156
- Changed Functions: 36
- Identical Functions: 120
- Unmatched (Added/Removed): 0
- Verdict: No security-relevant change was found. The patch adds one new byte field at offset
0x7din the mounted-device information structure that records whether a volume carries theFILE_CHARACTERISTIC_CSV(Cluster Shared Volume) characteristic, and uses that field to exclude such volumes from the remote-database reconciliation path. It also switchesGlobalCreateSymbolicLinkfromIoCreateSymbolicLinktoIoCreateSymbolicLink2. Neither change fixes a demonstrable memory-safety, privilege, or input-validation defect that is reachable from an attacker-controlled entry point.
2. Change Summary
Finding 1: New CSV-characteristic field gates remote-database reconciliation
- Severity: Informational (no security-relevant change)
- Class: State-management / correctness change; not a memory-safety or access-control fix
- Affected Functions:
QueryDeviceInformation(@0x1C00157D0unpatched),ReconcileThisDatabaseWithMasterWorker(@0x1C00170E0unpatched /0x1C0017130patched)
What actually changed:
1. New byte field at offset 0x7d. In QueryDeviceInformation, the patched build adds *(a2 + 0x7d) = BYTE2(DeviceObject->Characteristics) & 1, i.e. it records bit 0x10000 of the device Characteristics (FILE_CHARACTERISTIC_CSV, Cluster Shared Volume). The pre-existing byte at 0x7c continues to record bit 0x1 (FILE_REMOVABLE_MEDIA). Inserting this byte shifts the structure fields at offset 0x7d and above by +1 (e.g. the word previously written at 0x82 is written at 0x83), which is why many functions that touch this structure show a mechanical +1 offset change.
2. New flag consulted before reconciliation. In ReconcileThisDatabaseWithMasterWorker, the unpatched skip condition
if (MntMgrFindDevice(...) == listHead || *(entry + 0x7c) != 0) gains a third term in the patched build:
|| *(entry + 0x7d) != 0. When any term is true the function releases its locks and returns without calling IoGetDeviceObjectPointer and without reconciling the entry. The added term therefore causes CSV volumes to be skipped, not processed.
Why this is not a security fix:
- The direction is a tightening of a skip/bail-out condition: the patched build reconciles fewer entries, not more. It does not add or remove any bounds check, ACL check, or pointer validation.
- The return value of IoGetDeviceObjectPointer is checked identically in both builds (see Section 4). There is no unchecked-pointer path in either build.
- The new field is derived from a device characteristic bit reported by the storage stack, not from attacker-supplied IOCTL data.
Finding 2: GlobalCreateSymbolicLink switched to IoCreateSymbolicLink2
- Severity: Informational (defense-in-depth / API modernization)
- Affected Function:
GlobalCreateSymbolicLink(@0x1C000DDC0unpatched /0x1C000DE30patched)
The unpatched build calls IoCreateSymbolicLink(&SymbolicLinkName, TargetName). The patched build calls IoCreateSymbolicLink2(&SymbolicLinkName, ¶ms) where params is a small structure whose leading dword is 1 and whose remaining 16 bytes carry the target UNICODE_STRING. This is an API modernization; the surrounding logic (string construction, error tracing, pool free) is unchanged. No reachable pre/post difference in what link is created or who may create it was demonstrated, so this is classified as defense-in-depth rather than a vulnerability fix.
3. Source Diff (decompilation)
ReconcileThisDatabaseWithMasterWorker — the added 0x7d term in the skip condition:
// === UNPATCHED (0x1C00170E0) ===
Device = MntMgrFindDevice(v1, (_QWORD *)v2);
if ( Device == v9 || *(_BYTE *)(v2 + 124) != 0 ) // 124 = 0x7c
{
KeReleaseMutex((PRKMUTEX)(v1 + 56), 0);
return KeReleaseSemaphore((PRKSEMAPHORE)(v1 + 112), 0, 1, 0);
}
DeviceObjectPointer = IoGetDeviceObjectPointer((PUNICODE_STRING)(v2 + 80), 0x80u, &FileObject, &DeviceObject);
if ( DeviceObjectPointer < 0 ) // return value IS checked
...bail out...
// === PATCHED (0x1C0017130) ===
Device = MntMgrFindDevice(v1, (_QWORD *)v2);
if ( Device == v9 || *(_BYTE *)(v2 + 124) != 0 || *(_BYTE *)(v2 + 125) != 0 ) // added 125 = 0x7d
{
KeReleaseMutex((PRKMUTEX)(v1 + 56), 0);
return KeReleaseSemaphore((PRKSEMAPHORE)(v1 + 112), 0, 1, 0);
}
DeviceObjectPointer = IoGetDeviceObjectPointer((PUNICODE_STRING)(v2 + 80), 0x80u, &FileObject, &DeviceObject);
if ( DeviceObjectPointer < 0 ) // return value IS checked (unchanged)
...bail out...
QueryDeviceInformation — where the 0x7d field is populated:
// === UNPATCHED (0x1C00157D0) ===
*(_BYTE *)(a2 + 124) = v4->Characteristics & 1; // 0x7c = FILE_REMOVABLE_MEDIA bit
// === PATCHED ===
v8 = v4->Characteristics & 1;
*(_BYTE *)(a2 + 124) = v8; // 0x7c unchanged
*(_BYTE *)(a2 + 125) = BYTE2(v4->Characteristics) & 1; // 0x7d = FILE_CHARACTERISTIC_CSV (bit 0x10000)
GlobalCreateSymbolicLink — API modernization:
// === UNPATCHED (0x1C000DDC0) ===
v3 = IoCreateSymbolicLink(&SymbolicLinkName, a2);
// === PATCHED (0x1C000DE30) ===
LODWORD(v10[0]) = 1;
*(_OWORD *)&v10[1] = *a2;
StringWithGlobal = IoCreateSymbolicLink2(P, v10);
4. Assembly Analysis
Real disassembly for the added 0x7d gate and the return-value check in ReconcileThisDatabaseWithMasterWorker.
Unpatched (0x1C00170E0):
00000001C0017216 call MntMgrFindDevice
00000001C0017226 cmp [r15+7Ch], al ; only the 0x7c flag is tested
00000001C001723F call cs:__imp_IoGetDeviceObjectPointer
00000001C001724E mov esi, eax
00000001C0017250 test eax, eax ; return value IS checked
00000001C0017252 jns short loc_1C0017290 ; branch on success
Patched (0x1C0017130):
00000001C0017264 call MntMgrFindDevice
00000001C0017274 cmp [r15+7Ch], al ; 0x7c flag
00000001C001727E cmp [r15+7Dh], al ; NEW: 0x7d (CSV) flag also tested
00000001C0017297 call cs:__imp_IoGetDeviceObjectPointer
00000001C00172A3 mov esi, eax
00000001C00172A5 test eax, eax ; return value checked (unchanged)
00000001C00172A7 jns short loc_1C00172E2
The only assembly-level difference on this path is the added cmp [r15+7Dh], al at 0x1C001727E. The test eax, eax / jns pair after the call is present in both builds, so there is no unchecked-return-value path in either binary.
The 0x7d field is written in QueryDeviceInformation at 0x1C00158D3 (mov [rbx+7Dh], al).
5. Trigger / Reachability
The changed reconciliation path is reachable at driver initialization and during volume arrival/removal handling, but the behavioral difference is limited to whether a Cluster-Shared-Volume device (as reported by the storage stack via FILE_CHARACTERISTIC_CSV) is included in remote-database reconciliation. This value is not attacker-controlled from a user-mode IOCTL, and skipping reconciliation for such volumes does not create or remove any memory-safety or access-control condition. No user-mode trigger produces a security-relevant divergence between the two builds.
6. Exploit Primitive
None. No use-after-free, uninitialized-pointer use, out-of-bounds access, or privilege boundary crossing was demonstrated in either build on the changed paths. The unpatched build validates the IoGetDeviceObjectPointer return value before dereferencing the returned device object, and both builds bound the mount-point buffers they process.
7. Analyst Notes
For an analyst comparing the two builds:
ReconcileThisDatabaseWithMasterWorker: unpatched0x1C00170E0, patched0x1C0017130. Compare the skip condition at thecmp [r15+7Ch]/cmp [r15+7Dh]sequence just before theIoGetDeviceObjectPointercall.QueryDeviceInformation: unpatched0x1C00157D0. The storemov [rbx+7Dh], alat0x1C00158D3(patched) records the CSV characteristic bit.- Structure fields at offset
0x7dand above shift by+1between builds because of the inserted byte; a raw per-offset comparison must account for this shift.
8. Changed Functions — Triage
Out of 36 changed functions, none is a security fix. The only semantically meaningful, non-cosmetic changes are the two above; the remainder are the mechanical +1 structure-offset shift propagated across functions that touch the mounted-device structure, WPP tracing/global-data reshuffling, and CRT/intrinsic churn (a new vectorized memset dispatch plus CRT helper functions __cpu_features_init, __memset_query, __memset_repmovs, _guard_xfg_dispatch_icall_nop).
Genuinely changed logic
QueryDeviceInformation(@0x1C00157D0): Populates the new0x7dbyte fromFILE_CHARACTERISTIC_CSV; also keeps writing the pre-existing0x7cremovable-media byte. Not security-relevant.ReconcileThisDatabaseWithMasterWorker(@0x1C00170E0): Adds|| *(entry + 0x7d)to the reconciliation skip condition. Remote-database reconciliation, not volume-arrival processing. Not security-relevant.GlobalCreateSymbolicLink(@0x1C000DDC0):IoCreateSymbolicLink->IoCreateSymbolicLink2. Defense-in-depth / API modernization.MountMgrDriverReinitialization(@0x1C000F2D0): Removes an init-timeif (byte==0)guard beforeReconcileThisDatabaseWithMaster; the reconcile is now always called during driver reinitialization. Reached only from the boot/reinitialization callback, not from an IOCTL. Not security-relevant.WorkerThread(@0x1C0002DF0): Shutdown-detection loop reads a different global after data-section reorganization. Not security-relevant.
Offset-shift / cosmetic changes
The following changed only through the +1 structure-offset shift, register reallocation, control-flow layout, WPP tracing, or CRT churn, with no logic change: MountMgrMountedDeviceArrival (@ 0x1C000F7E4), MountMgrMountedDeviceNotification, MountMgrMountedDeviceRemoval, QueryDeviceSortOrderInternal (@ 0x1C0015B44, adds device type 0xEF to a sort-order table plus memset size shift), MountMgrNextDriveLetter (bounds check len+2 <= buflen present in both, branch inverted), MountMgrNotifyNameChange, MountMgrPrepareVolumeDelete, MountMgrQueryDosVolumePath, MountMgrQueryDosVolumePaths, QueryPointsFromMemory, QueryPointsFromSymbolicLinkName, MountMgrVolumeArrivalNotification, MountMgrVolumeMountPointChanged, MountMgrVolumeMountPointCreated, MountMgrVolumeMountPointDeleted, OnlineMountedVolumes, OpenRemoteDatabase, CreateRemoteDatabase, CreateNewDriveLetterName, GetDriveLayoutInternal, IsUniqueIdPresent, MountMgrCheckUnprocessedVolumes, MountMgrCleanup, MountMgrCreatePoint, MountMgrDeviceControl (IOCTL dispatch), MountMgrValidateBackPointer, RedirectSavedLink, RemoveSavedLinks, SendLinkDeleted, VerifyEpoch, the WPP_SF_* tracing helpers, WppInitKm, WppLoadTracingSupport, and memset.
9. Unmatched Functions
- Added: 0 (the CRT helpers
__cpu_features_init,__memset_query,__memset_repmovs,_guard_xfg_dispatch_icall_nopappear as split-outs of the rebuiltmemset/CFG intrinsics) - Removed: 0
This is a targeted in-place rebuild, not an architectural rewrite.
10. Confidence & Caveats
- Confidence Level: High. Both builds' decompilation and disassembly were compared directly. The
IoGetDeviceObjectPointerreturn value is checked in both builds (test eax, eax; jnsat0x1C0017250unpatched and0x1C00172A5patched), and the sole path-level difference is the addedcmp [r15+7Dh], alCSV-flag test at0x1C001727E. - Caveat: The
IoCreateSymbolicLink2switch is a hardened-API adoption whose full behavioral contract is internal tontoskrnl; it is reported here as defense-in-depth. No attacker-reachable difference in symbolic-link creation between the two builds was demonstrated.