1. Overview

Field Value
Unpatched binary ksrext_unpatched.sys
Patched binary ksrext_patched.sys
Overall similarity 0.3722 (low — substantial refactoring)
Matched functions 119
Changed functions 69
Identical functions 50
Unmatched (either direction) 0 / 0

Verdict: The change to the Soft Restart loader-callback interface is an architectural refactor, not a security fix. In both builds the teardown path (KsrRegisterLoaderCallbacks(NULL)) and the API dispatch path are correctly serialized against each other, so no race condition or NULL-pointer dereference is reachable. The unpatched build holds the loader-interface push lock shared across the entire dispatch (pointer read and indirect call) while the teardown path holds the exclusive variant of the same lock; the reader/writer lock already prevents the teardown from clearing a pointer while any dispatch is in progress. The patched build replaces this "lock held across the callback" model with a reference-count-plus-drain model and stores built-in default implementations instead of the previous NULL sentinel. Both models are race-free.


2. Change Summary

Finding 1 — Loader-callback interface synchronization refactor in KsrRegisterLoaderCallbacks

Attribute Detail
Severity None (no security-relevant change)
Class Synchronization refactor / expanded callback contract
Function KsrRegisterLoaderCallbacks (exported; unpatched @ 0x1C0007D40, patched @ 0x14000B080)
Entry point Exported kernel API KsrRegisterLoaderCallbacks, used by the Windows Soft Restart loader subsystem

What the code actually does

ksrext.sys keeps a set of named loader-callback function-pointer globals: KsrpQueryMetadataRoutinePointer, KsrpPersistMemoryPartitionRoutinePointer, KsrpFreePersistentMemoryBlockRoutinePointer, KsrpClaimPersistentMemoryRoutinePointer, KsrpFreePersistentMemoryRoutinePointer, KsrpPersistMemoryWithMetadataRoutinePointer, and KsrpEnumeratePersistentMemoryRoutinePointer. KsrRegisterLoaderCallbacks populates them from the caller's registration structure when arg1 != NULL, and clears/resets them when arg1 == NULL.

Every exported dispatch API (KsrQueryMetadata, KsrPersistMemoryPartition, KsrFreePersistedMemoryBlock, KsrClaimPersistedMemory, KsrFreePersistedMemory, KsrPersistMemoryWithMetadata) reads the relevant pointer and, if it is non-NULL, calls through it with a CFG-guarded indirect call. If the pointer is NULL, the dispatch falls back to the driver's own built-in implementation (the KsrPd* routines) under KsrpPdDatabaseLock. NULL is a valid sentinel meaning "loader not registered, use built-in path" — it is not a crash condition.

Why there is no race (unpatched build)

In every unpatched dispatch function, the pointer read and the indirect call both occur while holding KsrpLoaderInterfaceLock shared:

  • KsrQueryMetadata: ExAcquirePushLockSharedEx(KsrpLoaderInterfaceLock) @ 0x1C00072EE, pointer read @ 0x1C00072FA, guarded call @ 0x1C0007368, ExReleasePushLockSharedEx @ 0x1C0007379.
  • KsrFreePersistedMemoryBlock: shared acquire @ 0x1C0007C7A, pointer read @ 0x1C0007C86, guarded call @ 0x1C0007CD7, shared release @ 0x1C0007CE8.
  • KsrClaimPersistedMemory, KsrFreePersistedMemory, KsrPersistMemoryWithMetadata, KsrPersistMemoryPartition: same acquire-read-call-release-under-shared-lock structure.

The teardown path in KsrRegisterLoaderCallbacks clears the pointers while holding the exclusive variant of the same KsrpLoaderInterfaceLock (acquired @ 0x1C0007DB3, pointers cleared @ 0x1C0007EDA0x1C0007F04, released @ 0x1C0007F88). Because a push lock's exclusive owner cannot coexist with any shared holder, the teardown cannot zero a pointer while a dispatch holds the shared lock across its check-and-call. This is the standard, correct reader/writer serialization; no thread can observe a non-NULL pointer and then call through a freshly-zeroed one.

What changed in the patched build

The patched build does not add a missing guard; it changes the locking discipline:

  1. Reference count instead of lock-held-across-callback. Patched dispatch functions call KsrpAcquireLoaderInterfaceReference (@ 0x14000B5DC), which takes KsrpLoaderInterfaceLock shared only long enough to lock inc KsrpLoaderInterfaceRefCount, then releases it. The callback then runs without the push lock held, and KsrpReleaseLoaderInterfaceReference (@ 0x14000BDE4) decrements the count afterward. This avoids holding a push lock across a potentially long loader callback.
  2. Drain at teardown. Before clearing the pointers, patched KsrRegisterLoaderCallbacks runs a drain loop (0x14000B0FD0x14000B16D): it checks KsrpLoaderInterfaceRefCount under the exclusive lock and, while non-zero, releases the lock and blocks in ExBlockOnAddressPushLock on KsrpLoaderInterfaceRefCount / KsrpLoaderInterfaceEvent until the count reaches zero.
  3. Default implementations instead of NULL. The unregister path now stores the driver's built-in database routines (KsrDbQueryMetadata, KsrDbPersistMemoryPartition, KsrDbUnpersistMemoryPartition, KsrDbClaimPersistedPages, KsrDbFreePersistedPages, KsrDbFreePersistedMemoryBlock, KsrDbEnumeratePersistedPages, KsrDbPersistPagesWithMetadata) into the routine pointers (@ 0x14000B2D40x14000B34C) instead of zeroing them. These are real implementations, not no-op stubs; the effect is that dispatch always calls through a valid pointer and no longer needs a NULL fallback branch.
  4. Expanded callback contract. The minimum registration-structure size grows from 0x58 (unpatched cmp ecx, 58h @ 0x1C0007D65) to 0x88 (patched cmp ecx, 88h @ 0x14000B0B3), and the register path reads additional callback fields at offsets 0x580x80 (KsrpPullRemoteFileRoutinePointer, KsrpSaveKeyToBufferPointer, KsrpIsBugcheckActive, KsrpOpenKeyForBugCheckRecoveryPointer, KsrpSetSystemBootMetadata, and an unpersist routine at [rdi+80h]). This is feature growth of the loader interface.

ExBlockOnAddressPushLock is not new to the patched build: the unpatched KsrpWaitForPartitionsRestored (@ 0x1C00091E4) already calls it (@ 0x1C0009230) to wait on the boot-time KsrpPartitionRestoreInProgress flag. That routine waits for partition restoration to finish; it is not an in-flight-dispatch counter.


3. Pseudocode Diff

KsrRegisterLoaderCallbacks — Unregistration Path

// UNPATCHED (@ 0x1C0007D40) — clears pointers under exclusive lock
NTSTATUS KsrRegisterLoaderCallbacks(void *arg1)
{
    if (arg1 && (*(uint32_t*)arg1 < 0x58 ... )) { /* size/flag checks */ }
    KeEnterCriticalRegion();
    KsrpWaitForPartitionsRestored();              // waits for boot-time restore
    ExAcquirePushLockExclusiveEx(&KsrpLoaderInterfaceLock, 0);

    if (arg1 == NULL) {                            // unregister
        if (!KsrpLoaderRegistered) KeBugCheckEx(0x70, 0x201, 0, 0, 0);
        KsrpLoaderRegistered = 0;
        KsrpPersistMemoryWithMetadataRoutinePointer = 0;   // seven pointers
        KsrpPersistMemoryPartitionRoutinePointer   = 0;    // set to 0 by
        KsrpClaimPersistentMemoryRoutinePointer    = 0;    // individual movs
        KsrpFreePersistentMemoryRoutinePointer     = 0;
        KsrpFreePersistentMemoryBlockRoutinePointer= 0;
        KsrpEnumeratePersistentMemoryRoutinePointer= 0;
        KsrpQueryMetadataRoutinePointer            = 0;
        // ZwQuerySystemInformation(0x96 ...) + KsrpPdInitializeDatabase under KsrpPdDatabaseLock
    }
    ExReleasePushLockExclusiveEx(&KsrpLoaderInterfaceLock, 0);
    KeLeaveCriticalRegion();
}

// PATCHED (@ 0x14000B080) — drains a refcount, stores default impls
NTSTATUS KsrRegisterLoaderCallbacks(void *arg1)
{
    if (arg1 && (*(uint32_t*)arg1 < 0x88 ... )) { /* larger struct required */ }
    KeEnterCriticalRegion();
    KsrpWaitForPartitionsRestored();
    for (;;) {                                     // drain in-flight references
        ExAcquirePushLockExclusiveEx(&KsrpLoaderInterfaceLock, 0);
        if (KsrpLoaderInterfaceRefCount == 0) break;
        ExReleasePushLockExclusiveEx(&KsrpLoaderInterfaceLock, 0);
        while (KsrpLoaderInterfaceRefCount > 0)
            ExBlockOnAddressPushLock(&KsrpLoaderInterfaceEvent,
                                     &KsrpLoaderInterfaceRefCount, &n, 4, 0);
    }
    if (arg1 == NULL) {                            // unregister
        if (!KsrpLoaderRegistered) KeBugCheckEx(0x70, 0x201, 0, 0, 0);
        KsrpLoaderRegistered = 0;
        KsrpQueryMetadataRoutinePointer            = KsrDbQueryMetadata;      // default
        KsrpPersistMemoryPartitionRoutinePointer   = KsrDbPersistMemoryPartition;
        KsrpUnpersistMemoryPartitionRoutinePointer = KsrDbUnpersistMemoryPartition;
        KsrpClaimPersistentMemoryRoutinePointer    = KsrDbClaimPersistedPages;
        KsrpFreePersistentMemoryRoutinePointer     = KsrDbFreePersistedPages;
        KsrpFreePersistentMemoryBlockRoutinePointer= KsrDbFreePersistedMemoryBlock;
        KsrpEnumeratePersistentMemoryRoutinePointer= KsrDbEnumeratePersistedPages;
        KsrpPersistMemoryWithMetadataRoutinePointer= KsrDbPersistPagesWithMetadata;
        // ZwQuerySystemInformation(0x96 ...) + KsrDbpAttachDatabase under KsrpPartitionLock
    }
    ExReleasePushLockExclusiveEx(&KsrpLoaderInterfaceLock, 0);
    KeLeaveCriticalRegion();
}

Representative Dispatch Pattern

// UNPATCHED KsrQueryMetadata (@ 0x1C0007290) — check-and-call fully under shared lock
NTSTATUS KsrQueryMetadata(...)
{
    KeEnterCriticalRegion();
    ExAcquirePushLockSharedEx(&KsrpLoaderInterfaceLock, 0);   // shared held...
    void *p = KsrpQueryMetadataRoutinePointer;
    if (p != NULL)
        r = guard_dispatch_icall(p)(...);                    // ...across the call
    else {                                                    // NULL = valid fallback
        ExAcquirePushLockExclusiveEx(&KsrpPdDatabaseLock, 0);
        r = KsrPdQueryRegionMetadata(...);                   // built-in implementation
        ExReleasePushLockExclusiveEx(&KsrpPdDatabaseLock, 0);
    }
    ExReleasePushLockSharedEx(&KsrpLoaderInterfaceLock, 0);   // released after call
    KeLeaveCriticalRegion();
    return r;
}

// PATCHED KsrQueryMetadata (@ 0x14000AE40) — refcount instead of lock-across-callback
NTSTATUS KsrQueryMetadata(...)
{
    KeEnterCriticalRegion();
    KsrpAcquireLoaderInterfaceReference();                    // shared lock only to inc refcount
    void *p = KsrpQueryMetadataRoutinePointer;               // always non-NULL now
    r = guard_dispatch_icall(p)(...);                        // callback runs w/o lock
    KsrpReleaseLoaderInterfaceReference();                    // dec refcount
    KeLeaveCriticalRegion();
    return r;
}

4. Assembly Analysis

Unpatched KsrRegisterLoaderCallbacks unregister path (@ 0x1C0007D40)

00000001C0007DA5  call    KsrpWaitForPartitionsRestored
00000001C0007DAC  lea     rcx, KsrpLoaderInterfaceLock
00000001C0007DB3  call    cs:__imp_ExAcquirePushLockExclusiveEx   ; exclusive acquire
00000001C0007DBF  test    rdi, rdi                                ; arg1 == NULL?
00000001C0007DC2  jz      loc_1C0007EA2                           ; -> unregister
...
00000001C0007EA2  cmp     cs:KsrpLoaderRegistered, bl
00000001C0007EA8  jnz     short loc_1C0007ECB
00000001C0007EBE  call    cs:__imp_KeBugCheckEx                   ; not registered -> bugcheck
00000001C0007ECD  mov     cs:KsrpLoaderRegistered, bl             ; bl = 0
00000001C0007EDA  mov     cs:KsrpPersistMemoryWithMetadataRoutinePointer, rbx  ; rbx = 0
00000001C0007EE1  mov     cs:KsrpPersistMemoryPartitionRoutinePointer, rbx
00000001C0007EE8  mov     cs:KsrpClaimPersistentMemoryRoutinePointer, rbx
00000001C0007EEF  mov     cs:KsrpFreePersistentMemoryRoutinePointer, rbx
00000001C0007EF6  mov     cs:KsrpFreePersistentMemoryBlockRoutinePointer, rbx
00000001C0007EFD  mov     cs:KsrpEnumeratePersistentMemoryRoutinePointer, rbx
00000001C0007F04  mov     cs:KsrpQueryMetadataRoutinePointer, rbx

The seven pointers are cleared by seven individual mov stores of the zeroed register rbx, all under the exclusive KsrpLoaderInterfaceLock. There is no memset/rep stosb and no contiguous fixed-size table zeroing in this function.

Patched KsrRegisterLoaderCallbacks drain + default-impl store (@ 0x14000B080)

000000014000B0F6  call    KsrpWaitForPartitionsRestored
000000014000B117  mov     r9d, 4
000000014000B126  lea     rdx, KsrpLoaderInterfaceRefCount
000000014000B12D  lea     rcx, KsrpLoaderInterfaceEvent
000000014000B134  call    cs:__imp_ExBlockOnAddressPushLock       ; drain wait
000000014000B140  mov     eax, cs:KsrpLoaderInterfaceRefCount
000000014000B14B  jg      short loc_14000B117
000000014000B14F  lea     rcx, KsrpLoaderInterfaceLock
000000014000B156  call    cs:__imp_ExAcquirePushLockExclusiveEx   ; exclusive after drain
000000014000B162  mov     eax, cs:KsrpLoaderInterfaceRefCount
000000014000B16D  jnz     short loc_14000B0FD                     ; re-drain if raced up
...
000000014000B2D4  lea     rax, KsrDbPersistPagesWithMetadata
000000014000B2E1  mov     cs:KsrpPersistMemoryWithMetadataRoutinePointer, rax  ; default impl
000000014000B2EF  lea     rax, KsrDbPersistMemoryPartition
000000014000B2F8  mov     cs:KsrpPersistMemoryPartitionRoutinePointer, rax
000000014000B2FF  lea     rax, KsrDbUnpersistMemoryPartition
000000014000B306  mov     cs:KsrpUnpersistMemoryPartitionRoutinePointer, rax
000000014000B30D  lea     rax, KsrDbClaimPersistedPages
000000014000B314  mov     cs:KsrpClaimPersistentMemoryRoutinePointer, rax
000000014000B31B  lea     rax, KsrDbFreePersistedPages
000000014000B322  mov     cs:KsrpFreePersistentMemoryRoutinePointer, rax
000000014000B329  lea     rax, KsrDbFreePersistedMemoryBlock
000000014000B330  mov     cs:KsrpFreePersistentMemoryBlockRoutinePointer, rax
000000014000B337  lea     rax, KsrDbEnumeratePersistedPages
000000014000B33E  mov     cs:KsrpEnumeratePersistentMemoryRoutinePointer, rax
000000014000B345  lea     rax, KsrDbQueryMetadata
000000014000B34C  mov     cs:KsrpQueryMetadataRoutinePointer, rax

Unpatched dispatch (KsrQueryMetadata @ 0x1C0007290) — lock spans check and call

00000001C00072EE  call    cs:__imp_ExAcquirePushLockSharedEx      ; KsrpLoaderInterfaceLock (shared)
00000001C00072FA  mov     rax, cs:KsrpQueryMetadataRoutinePointer
00000001C0007301  test    rax, rax
00000001C0007304  jnz     short loc_1C0007352                     ; non-NULL -> call via ptr
00000001C000730F  call    cs:__imp_ExAcquirePushLockExclusiveEx   ; NULL -> built-in path
00000001C0007334  call    KsrPdQueryRegionMetadata                ; (KsrpPdDatabaseLock)
00000001C0007368  call    cs:__guard_dispatch_icall_fptr          ; guarded indirect call
00000001C0007379  call    cs:__imp_ExReleasePushLockSharedEx      ; released after call

Patched dispatch reference helper (KsrpAcquireLoaderInterfaceReference @ 0x14000B5DC)

000000014000B5E2  lea     rcx, KsrpLoaderInterfaceLock
000000014000B5E9  call    cs:__imp_ExAcquirePushLockSharedEx
000000014000B5F5  lock inc cs:KsrpLoaderInterfaceRefCount         ; atomic increment
000000014000B5FE  lea     rcx, KsrpLoaderInterfaceLock
000000014000B605  call    cs:__imp_ExReleasePushLockSharedEx

Key Differences at a Glance

Aspect Unpatched Patched
Dispatch serialization KsrpLoaderInterfaceLock held shared across pointer read and indirect call Shared lock held only to lock inc KsrpLoaderInterfaceRefCount; callback runs without the lock
Teardown serialization KsrpLoaderInterfaceLock held exclusive while clearing pointers Drain KsrpLoaderInterfaceRefCount to zero via ExBlockOnAddressPushLock, then exclusive lock
Pointer teardown value 0 (NULL sentinel; dispatch falls back to KsrPd* built-in) Built-in KsrDb* default implementations
Registration struct min size 0x58 0x88 (new callback fields at 0x580x80)
Race between teardown and dispatch Prevented by reader/writer lock Prevented by refcount drain

Both models are race-free; the pointer is never observed non-NULL and then called after being cleared.


5. Reachability

  1. Load state: ksrext.sys is the Soft Restart kernel extension, loaded on supported systems when the feature is enabled.

  2. Entry point: KsrRegisterLoaderCallbacks is an export with no internal callers; it is invoked by the Windows Soft Restart loader subsystem during registration/unregistration. The dispatch APIs (KsrQueryMetadata, KsrPersistMemory*, KsrFreePersisted*, KsrClaimPersistedMemory) are the driver's exported service routines.

  3. Concurrency: Even with concurrent calls to a dispatch API on one thread and KsrRegisterLoaderCallbacks(NULL) on another, the shared/exclusive KsrpLoaderInterfaceLock (unpatched) or the reference-count drain (patched) serializes teardown against in-progress dispatch. A dispatch that reads a non-NULL pointer holds the shared lock until after its indirect call returns, so teardown cannot clear that pointer mid-call.

  4. NULL is not a fault: When the loader is unregistered, the routine pointers are either NULL (unpatched, handled by the built-in KsrPd* fallback branch) or set to the built-in KsrDb* implementations (patched). Neither state produces a NULL indirect call.

No attacker-reachable crash or memory-safety primitive follows from this change.


6. Exploit Primitive & Development Notes

No exploit primitive is present. The claimed teardown-versus-dispatch NULL-pointer dereference is not reachable in either build:

  • The unpatched dispatch functions read the routine pointer and perform the indirect call entirely under KsrpLoaderInterfaceLock held shared; the teardown clears the pointers only under the exclusive variant of the same lock. The reader/writer lock makes the two mutually exclusive.
  • A NULL routine pointer in the unpatched build is a valid state that routes dispatch to the driver's built-in KsrPd* implementation, not a dereference of address 0.

The patched changes (reference-count drain, built-in default implementations, larger registration contract) are a robustness/architecture refactor and a feature expansion of the loader interface, not a security fix. There is no denial-of-service, information-disclosure, or code-execution primitive to develop.


7. Debugger Notes

The following symbols and addresses describe the relevant code paths for confirmation. They do not correspond to any exploitable condition.

Symbol / Address Purpose
0x1C0007D40 KsrRegisterLoaderCallbacks (unpatched); unregister path at 0x1C0007EA2
0x14000B080 KsrRegisterLoaderCallbacks (patched); drain loop 0x14000B0FD0x14000B16D, default-impl stores 0x14000B2D40x14000B34C
0x1C00091E4 KsrpWaitForPartitionsRestored (unpatched); waits on KsrpPartitionRestoreInProgress via ExBlockOnAddressPushLock @ 0x1C0009230
0x14000B5DC KsrpAcquireLoaderInterfaceReference (patched); lock inc KsrpLoaderInterfaceRefCount @ 0x14000B5F5
0x14000BDE4 KsrpReleaseLoaderInterfaceReference (patched)
0x1C0007290 KsrQueryMetadata (unpatched); shared lock 0x1C00072EE, pointer read 0x1C00072FA, guarded call 0x1C0007368, release 0x1C0007379
0x14000AE40 KsrQueryMetadata (patched); reference acquire 0x14000AE85, guarded call 0x14000AEA7, reference release 0x14000AEAE
KsrpLoaderInterfaceLock Reader/writer push lock guarding the routine-pointer globals
KsrpLoaderInterfaceRefCount / KsrpLoaderInterfaceEvent Patched in-flight reference count and drain wait object
KsrpLoaderRegistered Registration flag (1 = registered, 0 = unregistered)

To confirm the serialization: disassemble any unpatched dispatch export and observe that ExAcquirePushLockSharedEx(KsrpLoaderInterfaceLock) precedes the routine-pointer read and ExReleasePushLockSharedEx follows the guarded indirect call, with the exclusive acquisition in KsrRegisterLoaderCallbacks targeting the same lock.


8. Changed Functions — Full Triage

Loader-interface synchronization refactor (not a security fix)

Function Similarity Note
KsrRegisterLoaderCallbacks 0.74 Teardown changed from clearing pointers to NULL under the exclusive KsrpLoaderInterfaceLock to draining KsrpLoaderInterfaceRefCount and storing built-in KsrDb* default implementations. Registration-structure minimum size grew 0x580x88 with new callback fields. Both builds are race-free.
KsrQueryMetadata 0.12 Dispatch changed from "shared lock held across the indirect call" to "acquire/release a reference count around the call." Pointer is non-NULL in the patched model (defaults to KsrDbQueryMetadata), so the NULL fallback branch is removed.
KsrFreePersistedMemoryBlock 0.81 Same dispatch refactor (KsrpFreePersistentMemoryBlockRoutinePointer).
KsrPersistMemoryPartition 0.58 Same dispatch refactor (KsrpPersistMemoryPartitionRoutinePointer); lock usage restructured around the reference count.
KsrClaimPersistedMemory 0.64 Same dispatch refactor.
KsrFreePersistedMemory 0.74 Same dispatch refactor.
KsrPersistMemoryWithMetadata 0.67 Same dispatch refactor.
KsrpDestoryPartitionList (sub_1C0009A70) 0.976 Lock acquisition adjusted as part of the surrounding refactor; caller-side locking changed. No reachable memory-safety impact.
KsrpAllocatePagesFromMemoryRange (sub_1C00088A0) 0.83 Page-mapping loop (MmMapLockedPagesWithReservedMapping) extracted to a helper in the patched build. Behavior preserved.

Cosmetic / equivalent changes

  • sub_1C00084A4 (sim 0.967): flag test changed from & 0x10 to a sign-based < 0 check — semantically identical.
  • sub_1C000BE1C (sim 0.967): SSE-inlined memset replacing a call — equivalent behavior.

Note on the low overall similarity (0.3722): most of the 69 changed functions reflect the loader-interface architecture change (routine-pointer globals now carry built-in defaults, dispatch uses a reference count, the registration contract grew) plus general recompilation. None of this constitutes a security fix; the pre-existing reader/writer serialization already prevented the teardown-versus-dispatch race.


9. Unmatched Functions

Direction Count Notes
Removed (unpatched only) 0 None
Added (patched only) 0 None

All function changes are matched pairs. The patched KsrpAcquireLoaderInterfaceReference / KsrpReleaseLoaderInterfaceReference reference helpers and the KsrDb* default implementations are matched against existing regions rather than reported as standalone additions.


10. Confidence & Caveats

Confidence: High (no security-relevant change)

  • The unpatched dispatch functions were verified to hold KsrpLoaderInterfaceLock shared across both the routine-pointer read and the guarded indirect call, and the teardown to hold the same lock exclusive while clearing the pointers. The reader/writer lock prevents the teardown-versus-dispatch race in the unpatched build.
  • A NULL routine pointer in the unpatched build is handled by an explicit fallback to the built-in KsrPd* implementation, so it is not a dereference of address 0.
  • The patched build's ExBlockOnAddressPushLock drain and KsrDb* default implementations replace the lock-across-callback model; ExBlockOnAddressPushLock itself already existed in the unpatched build inside KsrpWaitForPartitionsRestored.

Notes

  1. The registration-structure minimum-size increase (0x580x88) and the added callback fields reflect an expanded loader contract, not a security boundary change.
  2. KsrpWaitForPartitionsRestored waits on the boot-time KsrpPartitionRestoreInProgress flag; it is not an in-flight-dispatch counter, and the unpatched teardown's use of it is unrelated to dispatch serialization (which the push lock provides).

End of report.