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 (unpatched 0x1C0008780, patched 0x14000A820)
  • 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 KsrPersistMemoryPartition the object is referenced by both the caller and the open handle for the duration of the call, so the in-function uses of a2 are 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). Uses r12/a2 (the caller pointer) for ObQueryNameString, ObGetObjectSecurity, KsrpQueryMemoryRuns, and KsrpQueryIoSpaceRuns; stores the raw pointer and handle into the descriptor at 0x1C00089F1 / 0x1C0008A01; dispatches the external routine at 0x1C0008A45; contains no ObReferenceObjectByHandle, no ObfDereferenceObjectWithTag, and no KsrpAddPartitionToList.
  • Patched KsrPersistMemoryPartition (0x14000A820). Adds ObReferenceObjectByHandle at 0x14000A98D, switches all queries to the referenced object rsi, clears the descriptor object/handle fields at 0x14000AA99 / 0x14000AA9D, inserts inline via KsrpAddPartitionToList at 0x14000AB0A, and releases the reference via ObfDereferenceObjectWithTag at 0x14000AB64.
  • KsrpAddPartitionToList (0x14000D58C). Exists only in the patched build; allocates the list entry and stores object at +0x10, handle at +0x18.
  • KsrpDestoryPartitionList (unpatched 0x1C000BF68). 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:

  1. The behavior of the external KsrpPersistMemoryPartitionRoutinePointer routine (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 outside ksrext.sys and is not demonstrable as a defect.
  2. The precise caller chain that reaches KsrPersistMemoryPartition and whether any part of it is reachable by an unprivileged user is not established by these images.
  3. Unpatched base 0x1C0000000 and patched base 0x140000000 are not directly comparable; addresses above are relative to each image as supplied.