mrxsmb.sys — Race on a not-fully-initialized session entry during concurrent tree-connect (CWE-476) local denial of service, fixed
KB5083768
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) / newSmbCeFindSessionEntry(patched@ 0x1400334F0); the pre-patch behavior is retained asSmbCeFindSessionEntry_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); newMRxSmbValidateIncomingPortsAgainstEffective(@ 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)
- 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.
- Action: Multiple threads call
CreateFile/NtCreateFileon the same UNC path (\\server\share) concurrently, released via a barrier. - Race Execution: One thread constructs a session entry and begins session/transport setup; another thread matches the entry via
SmbCeFindSessionEntrybefore setup completes. - Result: If the matched entry's security-context pointer (
+0x80) is NULL at that instant,CheckCredentialsfaults at0x140008A56, 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
SeAccessCheckcall site guards both+0x80and the descriptor at+0x38, so it cannot be coerced into evaluating attacker data. - Reachability caveat: Because construction sets
+0x80and 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_DcrwithSmbCeFindSessionEntry_Oldretained 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;rbxis the entry. Inspectrbx+0x80andrbx+0x1a8.mrxsmb!SmbCeFindSessionEntry+0x14d(0x14000884D):mov rcx,[rbx+0x80]after the spinlock release, feeding the security-context pointer intoCheckCredentials.mrxsmb!CheckCredentials+0x5a(0x140008A56):cmp [rax+0x38],rdx— the NULL-deref sink whenrax(the+0x80pointer) is zero.- Trigger Setup: From user mode, issue concurrent
CreateFilerequests to the same\\server\sharefrom many threads released by aCreateEventbarrier; 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) at0x140008A56. TheSeAccessCheckcall at0x14005A53Bis not reached with an invalid descriptor because of the null checks at0x1400088CF/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
TransportInfointo the caller's own connection configuration; in the patched feature pathMRxSmbValidateIncomingPortsAgainstEffectiveadditionally 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 asSmbCeFindSessionEntry_Old @ 0x1400476D4) (Security-relevant): AddsExBlockOnAddressPushLockwaits 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+0x231and callsExUnblockOnAddressPushLockEx(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_Oldlookup via the feature gate.MRxSmbValidateAndGetTransportParameters(@ 0x140030D30, old retained asMRxSmbValidateAndGetTransportParameters_Old @ 0x14003E2D0) (Behavioral, not security-relevant): Feature path adds a boolean gate and the newMRxSmbValidateIncomingPortsAgainstEffectiveport-consistency check on the caller's own connection parameters. See Finding 2.RxCeEstablishConnection(old retained asRxCeEstablishConnection_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 theRxCe*_Old/Vct*_Oldtwins.
10. Confidence & Caveats
- Confidence: High on the mechanism and direction. The patched build adds
ExBlockOnAddressPushLockwaits on new+0x230/+0x231flags in the session-entry lookup, with the constructing side signaling viaExUnblockOnAddressPushLockEx, and the pre-patch path retained asSmbCeFindSessionEntry_Old. - Finding 1 rating: Low. The reachable worst case is a local kernel bugcheck (DoS). The
SeAccessCheckcall 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_Dcrwith the old paths retained — a staged rollout.