1. Overview

  • Unpatched Binary: mrxsmb_unpatched.sys
  • Patched Binary: mrxsmb_patched.sys
  • Overall Similarity Score: 0.9536
  • Diff Statistics: Matched: 1246 | Changed: 44 | Identical: 1202 | Unmatched: 0 (Unpatched) / 2 (Patched)

Verdict: The patch adds ExBlockOnAddressPushLock synchronization to the SMB redirector's session-entry lookup so that a thread which matches a session entry whose initialization has not completed waits for the constructing thread instead of proceeding. The worst demonstrable outcome of the unsynchronized code is a NULL/invalid-pointer dereference in the credential check — a local bugcheck (denial of service). The change is delivered behind the WIL feature gate Feature_Servicing_AlternativePort_Dcr, with the pre-patch code path retained verbatim as SmbCeFindSessionEntry_Old — a staged rollout. A second change in the same feature, added transport-port consistency validation, is not a security fix (see Finding 2).


2. Vulnerability Summary

Finding 1: Use of a not-fully-initialized session entry in the VNetRoot lookup

  • Severity: Low
  • Vulnerability Class: Race condition on a partially-initialized object; demonstrable impact is a NULL pointer dereference (CWE-476, arising from CWE-362)
  • Affected Function: SmbCeFindSessionEntry (unpatched @ 0x140008700) / new SmbCeFindSessionEntry (patched @ 0x1400334F0); the pre-patch behavior is retained as SmbCeFindSessionEntry_Old (@ 0x1400476D4)

Root Cause & Reachable Impact: The unpatched SmbCeFindSessionEntry walks the server entry's session list (head at server_entry+0x210) under the exclusive spinlock at server_entry+0x780. Once it matches an entry by state (+0xc <= 5), a non-zero session refcount (+0x1a8), and SessionID/LogonID (+0x60/+0x64 or +0x68/+0x6c), it increments the reference counts, releases the spinlock, and calls CheckCredentials with the entry's security-context pointer at +0x80 and, on the session-setup path, SeAccessCheck. The unpatched code has no "initialization complete" gate.

The patched build adds two per-entry flags (+0x230 session-initialized, +0x231 transport-initialized) and a pushlock (+0x238), and inserts ExBlockOnAddressPushLock wait loops: a thread that matches an entry whose flag is not yet set releases the spinlock, blocks until the constructing thread signals completion, then re-acquires the spinlock and re-checks. The signaling side is SmbCeDecrementActiveVNetRootContextsOnSession (patched @ 0x14001BA30), which sets +0x231 and calls ExUnblockOnAddressPushLockEx(entry+0x238, 0).

The reachable impact is a local kernel bugcheck. The construction routine SmbCeFindOrConstructSessionEntry (@ 0x1400081E0) populates +0x80, the match keys, and +0x1a8 before it links the new entry into the searchable +0x210 list, and it does that link under the same server_entry+0x780 spinlock the finder holds, so a concurrent finder cannot observe an entry with an unset +0x80. The initialization the patch waits on is a later stage (session/transport setup with network I/O, after the entry is already listed). On the unsynchronized path the only unguarded dereference of the security-context pointer is inside CheckCredentials at 0x140008A56 (cmp [rax+0x38], rdx, rax = the +0x80 pointer): if that pointer is NULL, this faults and bugchecks. The SeAccessCheck path is not a sink — it explicitly null-checks +0x80 (at 0x1400088CF) and the descriptor at +0x38 (at 0x1400088D8) before calling SeAccessCheck.

This is therefore a local denial of service at worst. There is no attacker-controlled memory content (the entry is zero-initialized pool), so it is not an authentication bypass, not an information leak, not a memory-corruption primitive, and not remotely reachable.

Call Chain: 1. MRxSmbCreateVNetRoot (RDBSS minirdr tree-connect callback) 2. SmbCeFindOrConstructVNetRootContext (@ 0x1400079F0) 3. SmbCeFindOrConstructSessionEntry (@ 0x1400081E0) 4. SmbCeFindSessionEntry (@ 0x140008700) — the lookup that lacks the initialization wait 5. CheckCredentials (@ 0x1400089FC) — the NULL-deref sink at 0x140008A56

Finding 2: Added transport-port consistency validation (not a security fix)

  • Severity: None (No security-relevant change)
  • Vulnerability Class: Feature behavior — port-consistency validation for the alternative-port feature
  • Affected Function: MRxSmbValidateAndGetTransportParameters (@ 0x140030D30); new MRxSmbValidateIncomingPortsAgainstEffective (@ 0x14003E430)

Analysis: The unpatched MRxSmbValidateAndGetTransportParameters looks up a 16-byte TransportInfo value in the file's Extended Attributes via RxFindEa and writes it to the caller-supplied target (the connection context at +0x344), gated by size == 0x10, a first-dword consistency check against the existing +0x344 value, and a +0x348 byte that gates a further second-field check. The patched build retains this parser verbatim as MRxSmbValidateAndGetTransportParameters_Old (@ 0x14003E2D0) and, on the feature path, adds MRxSmbValidateIncomingPortsAgainstEffective (@ 0x14003E430), which re-reads the same EA and compares its embedded 16-bit port fields against the connection's own effective ports at +0x354/+0x356/+0x358, returning 0xC05D000D on mismatch. Both the parser choice and the extra check are selected by Feature_Servicing_AlternativePort_Dcr.

The TransportInfo EA and the +0x344 transport configuration belong to the caller's own outbound SMB connection. Choosing a transport port for one's own connection crosses no privilege or security boundary, and the write target is the connection's own context, not another principal's. The added validation is a consistency check that the requested port matches the effective/allowed port for the alternative-port feature — feature behavior, not a fix for an attacker-reachable primitive. The earlier framing of the +0x348 branch as a "validation bypass allowing arbitrary port redirection / firewall bypass" is not supported: +0x348 merely gates an additional field comparison in the normal parse, and the parsed value is not a cross-boundary input. No security impact is demonstrable.


3. Pseudocode Diff

SmbCeFindSessionEntry (session-entry lookup)

// === UNPATCHED SmbCeFindSessionEntry @ 0x140008700 ===
// Under the server-entry spinlock, on a matched entry:
entry->vnr_active_refcount++;   // +0x1ac
entry->session_refcount++;      // +0x1a8
_InterlockedAdd(&entry->refcount, 1);   // +0x8
ExReleaseSpinLockExclusive(server_entry + 0x780, OldIrql);

// No wait for initialization completion:
CheckCredentials(entry->security_context /* +0x80 */, arg2, flag);
// SeAccessCheck path below null-checks +0x80 and [+0x80]+0x38 before the call

// === PATCHED SmbCeFindSessionEntry @ 0x1400334F0 ===
if (!entry->session_init /* +0x230 */) {           // NEW
    _InterlockedAdd(&entry->refcount, 1);
    ExReleaseSpinLockExclusive(server_entry + 0x780, OldIrql);
    if (prev) SmbCeDereferenceSessionEntryEx(prev, 0);
    do {                                            // NEW: block until initialized
        ExBlockOnAddressPushLock(entry + 0x238, entry + 0x230, &v, 1, ...);
    } while (!entry->session_init);
    ExAcquireSpinLockExclusive(server_entry + 0x780);
}
if (entry->session_init || entry->state <= 5) {
    if (!entry->transport_init /* +0x231 */) {      // NEW: same wait on +0x231
        do { ExBlockOnAddressPushLock(entry + 0x238, entry + 0x231, &v, 1, ...); }
        while (!entry->transport_init);
    }
    // ... proceed to refcount + CheckCredentials on a fully-initialized entry ...
}

MRxSmbValidateAndGetTransportParameters (transport EA parsing) — Finding 2

// === UNPATCHED (retained verbatim in patch as ..._Old @ 0x14003E2D0) ===
ea = RxFindEa(arg1, arg2, "TransportInfo", 0xd);
if (!ea) return 0;                       // no EA -> nothing written
if (ea->value_size != 0x10) return 0xC000000D;
xmm1 = load_16_bytes(ea->value);
if (ctx->existing_344 != 0 && ctx->existing_344 != first_dword(xmm1))
    return 0xC00001B4;                   // must match existing value
if (ctx->byte_348 != 0 && second_field(xmm1) == 0)
    return 0xC00001B5;
*write_target = xmm1;                     // write to caller's own connection ctx +0x344
return 0;

// === PATCHED feature path (selected by Feature_Servicing_AlternativePort_Dcr) ===
// Same parse, plus a separate consistency check:
status = MRxSmbValidateIncomingPortsAgainstEffective(arg1, arg2, ctx);
// compares EA ports vs ctx->effective_ports at +0x354/+0x356/+0x358,
// returns 0xC05D000D on mismatch. This is feature consistency, not a security boundary.

4. Assembly Analysis

Finding 1: SmbCeFindSessionEntry

; --- UNPATCHED SmbCeFindSessionEntry @ 0x140008700 (vulnerable lookup path) ---
0x140008750  lea   rcx, [rdi+780h]                 ; server-entry spinlock
0x140008757  call  cs:__imp_ExAcquireSpinLockExclusive
0x140008773  lea   rdx, [rbx+210h]                 ; session-entry list head
0x140008780  mov   rbx, [rdx]                       ; first entry
0x140008797  cmp   dword ptr [rbx+0Ch], 5           ; state <= 5 gate
0x1400087A1  mov   r9d, [rbx+1A8h]                  ; session refcount
0x1400087AB  jz    0x140008972                       ; skip if zero
0x1400087B4  cmp   [rbx+60h], ecx                    ; match SessionID ([rsi+48h])
0x1400087C0  cmp   [rbx+64h], eax                    ; match LogonID   ([rsi+4Ch])
0x1400087C9  inc   dword ptr [rbx+1ACh]              ; VnrCtxts++
0x1400087E8  mov   [rbx+1A8h], r9d                   ; session refcount++
0x140008805  lock xadd [rbx+8], r8d                  ; refcount++ (atomic)
0x140008841  call  cs:__imp_ExReleaseSpinLockExclusive   ; spinlock released, no init wait
0x14000884D  mov   rcx, [rbx+80h]                    ; security-context pointer
0x14000885B  call  CheckCredentials                  ; @ 0x1400089FC
0x14000888F  mov   rax, [rbx+80h]
0x1400088CF  test  rax, rax                          ; null-check +0x80
0x1400088D4  mov   rcx, [rax+38h]                    ; security descriptor
0x1400088D8  test  rcx, rcx                          ; null-check descriptor
0x14005A53B  call  cs:__imp_SeAccessCheck            ; only reached with non-NULL SD

; --- NULL-deref sink in CheckCredentials @ 0x1400089FC ---
0x140008A12  mov   rax, rcx                          ; rax = +0x80 pointer
0x140008A32  jz    0x140008A3C                        ; only the [rax+8] read is guarded
0x140008A56  cmp   [rax+38h], rdx                     ; faults if rax == 0

; --- PATCHED SmbCeFindSessionEntry @ 0x1400334F0 adds ---
0x1400335DF  movzx eax, byte ptr [rbx+230h]           ; session-init flag
0x1400336A2  call  cs:__imp_ExBlockOnAddressPushLock   ; wait on +0x230 (pushlock rbx+0x238)
0x1400337D2  call  cs:__imp_ExBlockOnAddressPushLock   ; wait on +0x231

Finding 2: MRxSmbValidateAndGetTransportParameters

; --- UNPATCHED MRxSmbValidateAndGetTransportParameters @ 0x140030D30 ---
0x140030D3A  mov   rdi, r9                           ; rdi = arg4 (write target = ctx+0x344)
0x140030D3D  mov   rbx, r8                           ; rbx = arg3 (connection context)
0x140030D4D  call  cs:__imp_RxFindEa                 ; find "TransportInfo" EA
0x140030D6C  cmp   word ptr [rax+6], 10h             ; EA value size must be 0x10
0x14005EF4A  mov   r8d, [rbx+344h]                   ; existing transport dword
0x14005EF51  movups xmm1, [rcx+rax+9]                ; load 16 bytes from EA
0x14005EF60  cmp   r8d, r9d                          ; first-dword consistency check
0x14005EFA9  movzx edx, byte ptr [rbx+348h]          ; gates a further field check
0x14005F00D  movups [rdi], xmm1                      ; write to caller's own connection ctx

; --- PATCHED new MRxSmbValidateIncomingPortsAgainstEffective @ 0x14003E430 ---
0x14003E4D1  cmp   ax, [rbx+354h]                    ; EA port vs effective port +0x354
0x14003E4E5  cmp   r8w, [rbx+356h]                   ; EA port vs effective port +0x356
0x14003E504  cmp   ax, [rbx+358h]                    ; EA port vs effective port +0x358
0x14003E57B  mov   eax, 0C05D000Dh                   ; status on mismatch

5. Trigger Conditions

Finding 1 (session-entry race)

  1. Setup: A local process in a standard logon session; optionally a slow-negotiating SMB server to widen the setup window. The path is not remotely reachable.
  2. Action: Multiple threads call CreateFile / NtCreateFile on the same UNC path (\\server\share) concurrently, released via a barrier.
  3. Race Execution: One thread constructs a session entry and begins session/transport setup; another thread matches the entry via SmbCeFindSessionEntry before setup completes.
  4. Result: If the matched entry's security-context pointer (+0x80) is NULL at that instant, CheckCredentials faults at 0x140008A56, bugchecking the machine (local DoS). No unauthorized access is granted.

Finding 2 (transport ports)

There is no attacker-reachable security condition to trigger. Supplying a TransportInfo EA affects only the caller's own outbound connection configuration; the added check enforces consistency with the effective port for the alternative-port feature.


6. Exploit Primitive & Development Notes

Finding 1

  • Primitive: None beyond a local denial of service. The matched entry is zero-initialized pool memory whose contents are not attacker-controlled, so there is no read/write primitive, no information leak, and no way to forge a security descriptor. The SeAccessCheck call site guards both +0x80 and the descriptor at +0x38, so it cannot be coerced into evaluating attacker data.
  • Reachability caveat: Because construction sets +0x80 and the match keys before publishing the entry into the searchable list under the same spinlock, even the NULL-dereference outcome depends on the security-context pointer being legitimately NULL for the matched entry (e.g., a credential-less session) at the moment of the race — a narrow, timing-dependent condition.
  • Rollout: Gated by Feature_Servicing_AlternativePort_Dcr with SmbCeFindSessionEntry_Old retained as the pre-patch path; a staged rollout.

Finding 2

  • No exploit primitive. The change is feature-consistency validation on the caller's own connection parameters.

7. Debugger PoC Playbook

Finding 1: SmbCeFindSessionEntry

  • Breakpoints:
  • mrxsmb!SmbCeFindSessionEntry+0xc9 (0x1400087C9): a matching entry is found; rbx is the entry. Inspect rbx+0x80 and rbx+0x1a8.
  • mrxsmb!SmbCeFindSessionEntry+0x14d (0x14000884D): mov rcx,[rbx+0x80] after the spinlock release, feeding the security-context pointer into CheckCredentials.
  • mrxsmb!CheckCredentials+0x5a (0x140008A56): cmp [rax+0x38],rdx — the NULL-deref sink when rax (the +0x80 pointer) is zero.
  • Trigger Setup: From user mode, issue concurrent CreateFile requests to the same \\server\share from many threads released by a CreateEvent barrier; use a slow server to widen the setup window.
  • Expected Observation: If the race is won against a credential-less entry, a bugcheck (e.g. 0x50) at 0x140008A56. The SeAccessCheck call at 0x14005A53B is not reached with an invalid descriptor because of the null checks at 0x1400088CF/0x1400088D8.

Finding 2: MRxSmbValidateAndGetTransportParameters

  • Breakpoints:
  • mrxsmb!MRxSmbValidateAndGetTransportParameters (0x140030D30): entry; r8 = connection context, r9 = write target.
  • 0x14005F00D: movups [rdi],xmm1, the write into the caller's own connection context at +0x344.
  • Observation: The write copies the caller-supplied TransportInfo into the caller's own connection configuration; in the patched feature path MRxSmbValidateIncomingPortsAgainstEffective additionally requires the ports to match the effective ports at +0x354/+0x356/+0x358. No cross-boundary effect is observable.

8. Changed Functions — Full Triage

The diff is dominated by the Feature_Servicing_AlternativePort_Dcr rollout (alternative-port / transport-address plumbing), plus a few unrelated WIL feature stagings (Feature_Servicing_AllowFileIdZeroOnShareRoot, Feature_Servicing_SmbCompressionOverQuicFix). Every pre-patch behavior the feature changes is retained verbatim under an _Old twin and selected at runtime by a feature check (staged rollout).

  • SmbCeFindSessionEntry (@ 0x140008700 → new @ 0x1400334F0, old retained as SmbCeFindSessionEntry_Old @ 0x1400476D4) (Security-relevant): Adds ExBlockOnAddressPushLock waits on init flags +0x230/+0x231. Worst demonstrable impact of the unsynchronized path is a local NULL-deref bugcheck. See Finding 1.
  • SmbCeDecrementActiveVNetRootContextsOnSession (unpatched @ 0x140008F60 / patched @ 0x14001BA30) (Security-relevant, companion): The patched version sets +0x231 and calls ExUnblockOnAddressPushLockEx(entry+0x238, 0) to wake threads blocked in the new lookup; the unpatched version does neither.
  • SmbCeFindOrConstructSessionEntry (unpatched @ 0x1400081E0 / patched @ 0x14001A980) (Behavioral): Dispatches new vs _Old lookup via the feature gate.
  • MRxSmbValidateAndGetTransportParameters (@ 0x140030D30, old retained as MRxSmbValidateAndGetTransportParameters_Old @ 0x14003E2D0) (Behavioral, not security-relevant): Feature path adds a boolean gate and the new MRxSmbValidateIncomingPortsAgainstEffective port-consistency check on the caller's own connection parameters. See Finding 2.
  • RxCeEstablishConnection (old retained as RxCeEstablishConnection_Old @ 0x140050770) (Behavioral): Feature-gated split into helper routines.
  • SmbCeInitializeConnectionInfo (Behavioral): Adds a pointer-overflow guard and dialect/port-aware selection; no standalone vulnerability fix.
  • MRxSmbProcessClientMoveNotification, sub_14008CE80/sub_14008D060, sub_14008A220, MRxSmbCreateVNetRoot, sub_140042254, sub_140009D00 (Behavioral/cosmetic): Dialect/port-aware dispatch, alternative-port registry/policy parsing, structure-offset adjustments consistent with the expanded transport structures.

9. Unmatched Functions

  • Added: SmbCeFindSessionEntry_Old — the pre-patch session-entry lookup retained verbatim for the feature-disabled path.
  • Added: MRxSmbValidateAndGetTransportParameters_Old — the pre-patch transport parser retained verbatim for the feature-disabled path.
  • Added: MRxSmbValidateIncomingPortsAgainstEffective — the new port-consistency check (Finding 2).
  • Added (alternative-port plumbing): SmbCeComputeEffectivePorts / SmbCeComputeEffectivePortForTransport, SmbCe{Insert,Lookup,Remove}PortScopeEntry, SmbCeGetPortFrom{Policy,Registry}ServerTransportPortMappings, and the RxCe*_Old / Vct*_Old twins.

10. Confidence & Caveats

  • Confidence: High on the mechanism and direction. The patched build adds ExBlockOnAddressPushLock waits on new +0x230/+0x231 flags in the session-entry lookup, with the constructing side signaling via ExUnblockOnAddressPushLockEx, and the pre-patch path retained as SmbCeFindSessionEntry_Old.
  • Finding 1 rating: Low. The reachable worst case is a local kernel bugcheck (DoS). The SeAccessCheck call site null-checks its inputs (no authentication bypass); the entry is zero-initialized pool (no information-leak or memory-corruption primitive); the path is local-only; and even the NULL-deref requires winning a narrow timing window against a credential-less entry.
  • Finding 2 rating: No security-relevant change. The added transport-port validation operates on the caller's own connection parameters and crosses no boundary; it is feature-consistency logic for the alternative-port feature.
  • Rollout: Both changes are gated by Feature_Servicing_AlternativePort_Dcr with the old paths retained — a staged rollout.