ksrext.sys — memory-partition-list ownership and reference counting refactored into the driver
KB5082200
1. Overview
| Field | Value |
|---|---|
| Unpatched binary | ksrext_unpatched.sys |
| Patched binary | ksrext_patched.sys |
| Overall similarity | 0.4998 |
| Matched functions | 149 |
| Changed functions | 74 |
| Identical functions | 75 |
| Unmatched (unpatched → patched) | 0 / 0 |
Verdict: No attacker-reachable vulnerability is fixed by this patch. The most substantive change in KsrPersistMemoryPartition moves ownership of the persisted-partition list from an externally registered loader routine into ksrext.sys itself: the patched build takes its own object reference via ObReferenceObjectByHandle, performs the list insertion inline through the new helper KsrpAddPartitionToList, and releases that reference in error paths via ObfDereferenceObjectWithTag. In the unpatched build the same object pointer and handle are handed to the external KsrpPersistMemoryPartitionRoutinePointer, which owns the insertion and lifetime. The remaining changed functions are renames/relocations, pool-API modernization (ExAllocatePoolWithTag → ExAllocatePool2), lock scope adjustments, tracing changes, and soft-restart feature paths. There is no demonstrable use-after-free, out-of-bounds access, or other reachable primitive in the unpatched image.
2. Assessment Summary
Finding 1: KsrPersistMemoryPartition — object-lifetime/ownership refactor (no reachable vulnerability)
- Severity: None (no security-relevant change)
- Prior classification considered: Use-After-Free (CWE-416) / Missing Reference Count
- Affected function:
ksrext.sys!KsrPersistMemoryPartition(unpatched0x1C0008780, patched0x14000A820) - Entry point: Internal driver routine registered through the KSR (kernel soft restart) loader interface; not a directly attacker-invokable syscall surface that these binaries demonstrate.
What the two builds actually do
Both builds walk the global KsrpMemoryPartitionList to reject a partition that is already persisted (i[2] == a2, i.e. the object pointer stored at list-entry offset +0x10), open a handle to the caller-supplied partition object with ObOpenObjectByPointerWithTag(a2, …, PsPartitionType, …), then query the object's name, security descriptor, and memory/IO-space runs, and finally invoke the externally registered routine KsrpPersistMemoryPartitionRoutinePointer with a stack-built descriptor structure.
Unpatched build. The queries operate on the raw caller pointer a2. The descriptor structure carries the raw object pointer and the opened handle to the external routine:
v40[3] = a2; // raw partition object pointer -> descriptor offset 0x18
v40[9] = Handle; // opened handle -> descriptor offset 0x48
...
ObjectSecurity = ((__int64 (__fastcall *)(_QWORD *))KsrpPersistMemoryPartitionRoutinePointer)(v40);
The driver never inserts into KsrpMemoryPartitionList here and never takes its own object reference; that responsibility belongs to the external loader routine. During the entire call the object is kept alive by the caller's own reference and by the freshly opened handle, so the driver does not dereference a dangling pointer.
Patched build. The driver takes an independent reference through the just-opened handle and uses that referenced pointer (v8 = Object) for every subsequent operation; it clears the object and handle fields in the descriptor passed to the external routine, and after that routine succeeds it performs the list insertion itself and transfers ownership of the reference and handle to the new list entry:
Object = nullptr;
v13 = ObReferenceObjectByHandle(Handle, 2u, PsPartitionType, 0, &Object, nullptr);
v8 = Object;
... // ObQueryNameString(v8,…), ObGetObjectSecurity(v8,…),
// KsrpQueryMemoryRuns(v8,…), KsrpQueryIoSpaceRuns(v8,…)
v39[3] = 0; // object pointer no longer handed to external routine
v39[9] = 0; // handle no longer handed to external routine
...
ObjectSecurity = ((__int64 (__fastcall *)(_QWORD *))KsrpPersistMemoryPartitionRoutinePointer[0])(v39);
if ( ObjectSecurity >= 0 )
{
ObjectSecurity = KsrpAddPartitionToList(Handle, v8, v34); // inline insertion
if ( ObjectSecurity >= 0 )
{
Handle = nullptr; // ownership transferred to the list entry
v8 = nullptr;
}
}
...
if ( v8 != nullptr )
ObfDereferenceObjectWithTag(v8, 0x2072734Bu); // release on failure
if ( Handle != nullptr )
ZwClose(Handle);
KsrpAddPartitionToList (patched 0x14000D58C) allocates a 0x30-byte entry, stores the referenced object at +0x10 and the handle at +0x18, and links it into KsrpMemoryPartitionList. This exactly matches the teardown already present in both builds: KsrpDestoryPartitionList iterates the list and calls ObfDereferenceObject on [entry+0x10] and ZwClose on [entry+0x18]. The added ObReferenceObjectByHandle is therefore the natural consequence of moving the insertion (and its reference/handle ownership) into the driver, not a repair of a dangling pointer that existed in the unpatched driver.
Why this is not an attacker-reachable vulnerability
- The unpatched driver does not store the caller's raw pointer in its own persistent list; the insertion and lifetime management are performed by the external
KsrpPersistMemoryPartitionRoutinePointer, which is not present in these binaries and cannot be shown to omit a reference. - Within
KsrPersistMemoryPartitionthe object is referenced by both the caller and the open handle for the duration of the call, so the in-function uses ofa2are not use-after-free. - No function in the unpatched image is shown to dereference a list-stored raw pointer after the underlying object could be freed.
The direction of the change is consistent with hardening (the patched build is stricter about ownership), but no vulnerable, reachable pattern is demonstrable in the unpatched build.
3. Decompilation Diff
Unpatched KsrPersistMemoryPartition (0x1C0008780)
// object kept alive by caller reference + open handle during the call
ObjectSecurity = ObOpenObjectByPointerWithTag(a2, 0x200u, nullptr, 1u, PsPartitionType, 0, 0x2072734Bu, &Handle);
if ( ObjectSecurity < 0 ) goto LABEL_28;
// queries use the raw caller pointer a2
ObQueryNameString(a2, p_ObjectNameInfo, v15, &ReturnLength);
ObGetObjectSecurity(a2, &SecurityDescriptor, &MemoryAllocated);
KsrpQueryMemoryRuns((__int64)a2, (unsigned __int64 **)&P, &v33);
KsrpQueryIoSpaceRuns((__int64)a2, (unsigned __int64 **)&v36, &v34);
// raw object pointer and handle handed to the external loader routine
v40[3] = a2;
v40[9] = Handle;
ObjectSecurity = ((__int64 (__fastcall *)(_QWORD *))KsrpPersistMemoryPartitionRoutinePointer)(v40);
// cleanup only closes the handle on failure; no driver-held object reference exists
if ( ObjectSecurity < 0 && Handle != nullptr )
ZwClose(Handle);
Patched KsrPersistMemoryPartition (0x14000A820)
ObjectSecurity = ObOpenObjectByPointerWithTag(a2, 0x200u, nullptr, 1u, PsPartitionType, 0, 0x2072734Bu, &Handle);
if ( ObjectSecurity >= 0 )
{
Object = nullptr;
v13 = ObReferenceObjectByHandle(Handle, 2u, PsPartitionType, 0, &Object, nullptr); // added
v8 = Object;
ObjectSecurity = v13;
if ( v13 >= 0 )
{
// all queries now use the driver-owned referenced pointer v8
ObQueryNameString(v8, p_ObjectNameInfo, v14, &ReturnLength);
ObGetObjectSecurity(v8, &SecurityDescriptor, &MemoryAllocated);
KsrpQueryMemoryRuns(v8, &v33, &v30);
KsrpQueryIoSpaceRuns(v8, &v35, &v31);
v39[3] = 0; // object pointer no longer passed to external routine
v39[9] = 0; // handle no longer passed to external routine
ObjectSecurity = ((__int64 (__fastcall *)(_QWORD *))KsrpPersistMemoryPartitionRoutinePointer[0])(v39);
if ( ObjectSecurity >= 0 )
{
ObjectSecurity = KsrpAddPartitionToList(Handle, v8, v34); // added inline insertion
if ( ObjectSecurity >= 0 )
{
Handle = nullptr; // ownership transferred to the list entry
v8 = nullptr;
}
}
}
}
...
if ( v8 != nullptr )
ObfDereferenceObjectWithTag(v8, 0x2072734Bu); // added reference release
if ( Handle != nullptr )
ZwClose(Handle);
In one sentence: the unpatched driver delegates persistent storage and lifetime of the partition object to an external loader routine; the patched driver takes over that job, holding its own reference and inserting into the list itself.
4. Assembly Analysis
Unpatched KsrPersistMemoryPartition (0x1C0008780)
; duplicate-entry check over KsrpMemoryPartitionList (read path, shared lock)
00000001C0008856 call cs:__imp_ExAcquirePushLockSharedEx ; KsrpPdDatabaseLock
00000001C0008862 mov rax, cs:KsrpMemoryPartitionList
00000001C0008872 cmp [rax+10h], r12 ; entry object == a2 ?
; open handle to caller-supplied object (r12 = a2)
00000001C00088D7 mov rcx, r12 ; Object
00000001C00088DA call cs:__imp_ObOpenObjectByPointerWithTag
; queries use the raw caller pointer r12
00000001C0008907 mov rcx, r12
00000001C000890A call cs:__imp_ObQueryNameString
00000001C000897D mov rcx, r12
00000001C0008980 call cs:__imp_ObGetObjectSecurity
00000001C000899B mov rcx, r12
00000001C00089A2 call KsrpQueryMemoryRuns
00000001C00089B6 mov rcx, r12
00000001C00089BD call KsrpQueryIoSpaceRuns
; raw object pointer stored into the descriptor handed to the external routine
00000001C00089F1 mov [rbp+0E0h+var_118], r12 ; descriptor object field = a2
00000001C0008A01 mov [rbp+0E0h+var_E8], rax ; descriptor handle field
00000001C0008A3E mov rax, cs:KsrpPersistMemoryPartitionRoutinePointer
00000001C0008A45 call cs:__guard_dispatch_icall_fptr ; external routine(descriptor)
; failure cleanup: handle only, no object dereference
00000001C0008A83 call cs:__imp_ZwClose
Patched KsrPersistMemoryPartition (0x14000A820)
; write path now takes the list lock exclusively
000000014000A8EA call cs:__imp_ExAcquirePushLockExclusiveEx ; KsrpPartitionLock
000000014000A932 mov rcx, rbx ; Object = a2
000000014000A94F call cs:__imp_ObOpenObjectByPointerWithTag
; added: take an independent reference through the handle
000000014000A97D mov r8, [rax] ; PsPartitionType
000000014000A980 lea edx, [r9+2] ; DesiredAccess = 2
000000014000A98D call cs:__imp_ObReferenceObjectByHandle
000000014000A999 mov rsi, [rbp+0E0h+Object] ; rsi = referenced object
; queries now use the referenced object rsi
000000014000A9BE mov rcx, rsi
000000014000A9C1 call cs:__imp_ObQueryNameString
000000014000AA3B mov rcx, rsi
000000014000AA3E call cs:__imp_ObGetObjectSecurity
000000014000AA59 mov rcx, rsi
000000014000AA61 call KsrpQueryMemoryRuns
000000014000AA75 mov rcx, rsi
000000014000AA7C call KsrpQueryIoSpaceRuns
; descriptor object/handle fields cleared (r12d = 0)
000000014000AA99 and [rbp+0E0h+var_118], r12
000000014000AA9D and [rbp+0E0h+var_E8], r12
000000014000AAEC mov rax, cs:KsrpPersistMemoryPartitionRoutinePointer
000000014000AAF3 call _guard_dispatch_icall ; external routine(descriptor)
; added: inline insertion; ownership transferred on success
000000014000AB02 mov rdx, rsi ; referenced object
000000014000AB05 mov rcx, [rsp+1E0h+var_170] ; handle
000000014000AB0A call KsrpAddPartitionToList
000000014000AB15 and [rsp+1E0h+var_170], 0 ; handle = 0 (transferred)
000000014000AB1B xor esi, esi ; object = 0 (transferred)
; added: release the reference on the failure path
000000014000AB64 call cs:__imp_ObfDereferenceObjectWithTag ; (rsi, 0x2072734B)
000000014000AB7A call cs:__imp_ZwClose
KsrpAddPartitionToList (0x14000D58C) allocates the 0x30-byte entry and stores the referenced object at +0x10 and the handle at +0x18 before linking it into KsrpMemoryPartitionList — mirroring the ObfDereferenceObject/ZwClose teardown in KsrpDestoryPartitionList.
5. Reachability
KsrPersistMemoryPartition is invoked through the KSR loader interface (its guard is KsrpPersistMemoryPartitionRoutinePointer, populated during loader-interface registration). The partition object it receives is a PsPartitionType object supplied by its caller, which holds a reference across the call. These binaries do not demonstrate an unprivileged user-mode path that reaches this routine, nor a path that dereferences a list-stored raw pointer after the object could be freed. No reachable trigger for a memory-safety fault is established.
6. Exploitability
No exploitable primitive is demonstrable from these binaries. The unpatched driver does not persist a raw, unreferenced object pointer in its own list, and its in-function uses of the caller pointer occur while the object is referenced by the caller and by an open handle. The lifetime and insertion behavior that the patched build brings inline is performed by an external loader routine in the unpatched build, which is not part of these images and cannot be shown to mishandle the reference. Accordingly there is no controllable freed-object reuse, no primitive to build on, and no privilege-escalation chain to describe.
7. Verification Notes
The assessment rests on the following, all anchored in the supplied builds:
- Unpatched
KsrPersistMemoryPartition(0x1C0008780). Usesr12/a2(the caller pointer) forObQueryNameString,ObGetObjectSecurity,KsrpQueryMemoryRuns, andKsrpQueryIoSpaceRuns; stores the raw pointer and handle into the descriptor at0x1C00089F1/0x1C0008A01; dispatches the external routine at0x1C0008A45; contains noObReferenceObjectByHandle, noObfDereferenceObjectWithTag, and noKsrpAddPartitionToList. - Patched
KsrPersistMemoryPartition(0x14000A820). AddsObReferenceObjectByHandleat0x14000A98D, switches all queries to the referenced objectrsi, clears the descriptor object/handle fields at0x14000AA99/0x14000AA9D, inserts inline viaKsrpAddPartitionToListat0x14000AB0A, and releases the reference viaObfDereferenceObjectWithTagat0x14000AB64. KsrpAddPartitionToList(0x14000D58C). Exists only in the patched build; allocates the list entry and stores object at+0x10, handle at+0x18.KsrpDestoryPartitionList(unpatched0x1C000BF68). Already dereferences[entry+0x10](ObfDereferenceObject) and closes[entry+0x18](ZwClose), confirming that list entries are designed to own a reference and a handle in both builds.
8. Changed Functions — Triage
| Function | Similarity | Change Type | Note |
|---|---|---|---|
KsrPersistMemoryPartition |
0.8637 | Refactor (not security) | Adds ObReferenceObjectByHandle + inline KsrpAddPartitionToList + ObfDereferenceObjectWithTag; moves persisted-partition-list ownership/lifetime from the external KsrpPersistMemoryPartitionRoutinePointer into the driver. List lock changed shared → exclusive because the driver now writes the list. No reachable vulnerability fixed. |
KsrRegisterLoaderCallbacks |
0.7649 | Behavioral | Context/registration structure expanded for soft-restart persistence. Non-security. |
KsrConnectSecureLoader |
0.7788 | Behavioral | ExAllocatePoolWithTag → ExAllocatePool2; allocation-failure path adjusted. Non-security. |
KsrMmAllocatePagesForMdlEx |
0.80 | Behavioral | arg6/a8 masked with 0x7FFFFFFF before use (present only in patched). Non-security hardening. |
KsrQueryPersistedMemoryPartition |
0.88 | Behavioral | Read path lock changed exclusive → shared (0x1C0008BF7 exclusive vs 0x14000AF0F shared). Non-security. |
KsrQueryMetadata |
0.1225 | Behavioral | Global-pointer indirection refactored into a wrapper call. Non-security. |
KsrInitPageDatabase |
0.5657 | Behavioral | Adds soft-restart persistence/state-restoration path. Non-security feature. |
KsrEnumeratePersistedMemoryPartitions |
0.85 | Behavioral | Adds soft-restart enumeration path with active/persisted filtering. Non-security feature. |
Remaining changed functions exhibit renames/relocations (e.g. new KsrDb*/KsrDbp*-prefixed families), pool-API modernization, tracing changes, and register/compiler churn. None alter control flow in a way that introduces or removes a security-relevant check.
9. Unmatched Functions
Both removed and added lists are empty. No sanitizer or bounds check was removed, and no isolated new mitigation was introduced. KsrpAddPartitionToList is new in the patched build but was matched as part of the partition-persistence refactor, not as a standalone security control.
10. Confidence & Caveats
Confidence: High that no attacker-reachable vulnerability is fixed here.
The delta in KsrPersistMemoryPartition is fully accounted for in both the decompilation and the disassembly: an object reference and an inline list insertion were added because the driver took over persisted-partition-list ownership that previously lived in an external loader routine. The unpatched driver does not store an unreferenced raw pointer in its own list and does not dereference a freed object.
Points that cannot be resolved from these two binaries:
- The behavior of the external
KsrpPersistMemoryPartitionRoutinePointerroutine (registered from a separate loader module) is not present here; whether it took an object reference in the unpatched design cannot be observed, but any such handling is outsideksrext.sysand is not demonstrable as a defect. - The precise caller chain that reaches
KsrPersistMemoryPartitionand whether any part of it is reachable by an unprivileged user is not established by these images. - Unpatched base
0x1C0000000and patched base0x140000000are not directly comparable; addresses above are relative to each image as supplied.