1. Overview

Field Value
Unpatched Binary mrxsmb_unpatched.sys
Patched Binary mrxsmb_patched.sys
Overall Similarity 96.05%
Matched Functions 1094
Changed Functions 59
Identical Functions 1035
Unmatched (Unpatched) 0
Unmatched (Patched) 0

Verdict: No delivered security fix was found in this diff. The two changes that were initially suspected as security-relevant do not survive verification against the two builds:

  • In FsRtlValidateReparsePointBufferEx the only logic change is the removal of a single tag-acceptance predicate term. It does not touch the symbolic-link / mount-point offset-and-length validation at all (that code is byte-identical in both builds), and it applies only to a class of reserved-bit reparse tags that are never subjected to a field walk. The net effect is a reparse-tag classification change, not a bounds fix, and if anything it is a relaxation (more tags accepted as valid).
  • In SmbQuicDisconnectConnection the patched build adds a new push-lock-guarded teardown-notification callback. There is no pre-existing unlocked callback in the unpatched build for it to protect; the lock and the callback are introduced together as new SMB-over-QUIC teardown code (feature-staging), not a fix to an existing race.

The remainder of the diff is servicing/feature churn: SMB-over-QUIC feature code (including wholesale removal of the QUIC certificate-mapping / server-suffix feature), WIL feature-staging helpers (wil_details_*, Feature_*), WPP/ETW tracing GUID and event-id changes, and struct-offset relocations. None of it adds or tightens a bounds, length, overflow, type, or lock check on SMB-server-supplied data.


2. Vulnerability Summary

Finding #1 — Reparse-tag acceptance predicate change (No security-relevant change)

  • Severity: None (downgraded from High)
  • Vulnerability Class: None demonstrable. Classified as a behavioral change to reparse-tag acceptance.
  • Affected Function: FsRtlValidateReparsePointBufferEx (unpatched @ 0x1C0034840, patched @ 0x1C0032620)

What actually changed:

FsRtlValidateReparsePointBufferEx validates REPARSE_DATA_BUFFER structures. Early in the function a predicate decides whether a reparse tag is eligible for the tag-specific validation block (if it is not eligible, the function returns STATUS_IO_REPARSE_TAG_NOT_HANDLED, 0xC0000276). The unpatched predicate is:

(tag & 0xFFF0000) == 0
&& (unsigned)tag > 2
&& (tag & 0xC0000000) != 0x40000000      // <-- present only in the unpatched build
&& (tag & 0x30000000) != 0x30000000
&& tag != 0xA0000019

The patched build removes the single term (tag & 0xC0000000) != 0x40000000. Everything else in the function — including the entire symbolic-link and mount-point offset/length accumulation using RtlULongAdd and the associated bounds comparisons — is identical between the two builds (the only other deltas are WPP tracing GUID names).

Why this is not the claimed OOB fix:

  • The removed term only distinguishes tags where bit 31 is clear and bit 30 is set (the 0x40000000 reserved-bit pattern). IO_REPARSE_TAG_SYMLINK (0xA000000C) and IO_REPARSE_TAG_MOUNT_POINT (0xA0000003) both have bit 31 set, so tag & 0xC0000000 == 0x80000000 for them — the removed term never excluded symlink or mount-point tags in either build. The symlink/mount-point validation path is therefore unaffected.
  • A reparse tag matching the 0x40000000 pattern is not a symbolic link or mount point, so inside the block it reaches neither field-walk handler. It falls through to return STATUS_REPARSE (0x80000005) without any offset/length arithmetic or memory access.
  • Direction: removing a term from the accepting && chain makes the predicate true for more tags, so the patched build treats these reserved-bit tags as a valid generic reparse point instead of returning "tag not handled". That is a relaxation of classification, not a tightening, and it involves no reachable out-of-bounds read or write.

There is no demonstrable memory-safety primitive here in either build.


Finding #2 — Added push-lock-guarded QUIC teardown callback (No security-relevant change)

  • Severity: None (downgraded from Medium)
  • Vulnerability Class: None demonstrable. Classified as SMB-over-QUIC teardown feature code added in the patched build.
  • Affected Function: SmbQuicDisconnectConnection (unpatched @ 0x1C0046A74, patched @ 0x1C0046464)

What actually changed:

The unpatched SmbQuicDisconnectConnection only emits WPP trace events and, guarded by a NULL check on *(a1+0x70), calls the MsQuic connection-shutdown vtable slot (MsQuic + 0x88) through the CFG guard dispatcher. It contains no push lock and no callback-object dereference.

The patched build adds a new block after the shutdown call:

ExAcquirePushLockExclusiveEx(a1 + 0xA8, 0);
if ( *(_DWORD *)(a1 + 0xB4) != 0 ) {
    v2 = *(_DWORD ***)(a1 + 0xB8);
    v3 = *(unsigned int *)(a1 + 0xB0);
    (*v2)[45] = 0;                                   // clear a field in the target object
    ((...)v2[3])(v2, 0xC000020C, v3);                // invoke slot 3 with STATUS_CONNECTION_DISCONNECTED
}
ExReleasePushLockExclusiveEx(a1 + 0xA8, 0);

Why this is not the claimed UAF fix:

  • The push lock (ExAcquirePushLockExclusiveEx/ExReleasePushLockExclusiveEx) and the callback invocation are introduced together as new code. The unpatched build has no callback dereference at a1+0xB8 and no vtable-slot call at this site, so there is no pre-existing unlocked callback for the lock to protect.
  • The status value passed to the new callback is 0xC000020C (STATUS_CONNECTION_DISCONNECTED), consistent with new teardown-notification logic being staged for the SMB-over-QUIC transport, not a retrofit of synchronization onto an existing race.

This is added feature/teardown code, not a fix to a demonstrable use-after-free in the unpatched build.


3. Code Diff

Finding #1 — FsRtlValidateReparsePointBufferEx

// UNPATCHED @ 0x1C0034840  and  PATCHED @ 0x1C0032620
// The tag-acceptance predicate differs by exactly one term:

// UNPATCHED:
if ( (a2->ReparseTag & 0xFFF0000) == 0
  && (unsigned int)ReparseTag > 2
  && (ReparseTag & 0xC0000000) != 0x40000000    // removed in patched
  && (ReparseTag & 0x30000000) != 0x30000000
  && ReparseTag != -1610612711 )                 // 0xA0000019
{ ... }

// PATCHED:
if ( (a2->ReparseTag & 0xFFF0000) == 0
  && (unsigned int)ReparseTag > 2
  && (ReparseTag & 0x30000000) != 0x30000000
  && ReparseTag != -1610612711 )
{ ... }

// The symbolic-link (0xA000000C) and mount-point (0xA0000003) handlers inside
// the block, including every RtlULongAdd offset/length accumulation and bounds
// comparison, are IDENTICAL in both builds.

Finding #2 — SmbQuicDisconnectConnection

// UNPATCHED @ 0x1C0046A74 (WPP trace omitted):
if ( *(_QWORD *)(a1 + 0x70) != 0 )
    (*(void (__fastcall **)(_QWORD, __int64))(MsQuic + 0x88))(*(_QWORD *)(a1 + 0x70), 1);
return 0;
// No push lock, no callback dereference at a1+0xB8.

// PATCHED @ 0x1C0046464 (WPP trace omitted):
(*(void (__fastcall **)(_QWORD, __int64))(MsQuic + 0x88))(*(_QWORD *)(a1 + 0x70), 1);
ExAcquirePushLockExclusiveEx(a1 + 0xA8, 0);          // ADDED (new code)
if ( *(_DWORD *)(a1 + 0xB4) != 0 ) {                  // ADDED
    v2 = *(_DWORD ***)(a1 + 0xB8);
    v3 = *(unsigned int *)(a1 + 0xB0);
    (*v2)[45] = 0;
    ((void (__fastcall *)(_DWORD **, __int64, __int64))v2[3])(v2, 0xC000020C, v3);
}
ExReleasePushLockExclusiveEx(a1 + 0xA8, 0);           // ADDED
return 0;

4. Assembly Analysis

Finding #1 — FsRtlValidateReparsePointBufferEx

The removed predicate term is present in the unpatched disassembly and absent from the patched disassembly:

; UNPATCHED mrxsmb_unpatched.sys : FsRtlValidateReparsePointBufferEx @ 0x1C0034840
00000001C0034919  and     eax, 0C0000000h        ; tag & 0xC0000000
00000001C003491E  cmp     eax, 40000000h         ; == 0x40000000 ?  (removed in patched)
00000001C0034987  cmp     ecx, 0A000000Ch        ; IO_REPARSE_TAG_SYMLINK
00000001C0034CB5  cmp     ecx, 0A0000003h        ; IO_REPARSE_TAG_MOUNT_POINT
; PATCHED mrxsmb_patched.sys : FsRtlValidateReparsePointBufferEx @ 0x1C0032620
; No 'and eax,0C0000000h / cmp eax,40000000h' pair exists in the function.
00000001C0032755  cmp     ecx, 0A000000Ch        ; IO_REPARSE_TAG_SYMLINK
00000001C0032A83  cmp     ecx, 0A0000003h        ; IO_REPARSE_TAG_MOUNT_POINT

The 0x40000000 comparison appears exactly once in the unpatched function and zero times in the patched function; the symlink and mount-point tag comparisons appear in both. This confirms the change is limited to the tag-acceptance predicate and does not alter the symlink/mount-point field validation.

Finding #2 — SmbQuicDisconnectConnection

The unpatched function contains no push-lock calls; its only guarded indirect call is the MsQuic shutdown vtable dispatch:

; UNPATCHED mrxsmb_unpatched.sys : SmbQuicDisconnectConnection @ 0x1C0046A74
00000001C0046AB1  call    WPP_SF_
00000001C0046AE8  call    WPP_SF_q
00000001C0046B06  call    cs:__guard_dispatch_icall_fptr   ; MsQuic shutdown vtable call
00000001C0046B35  call    WPP_SF_
; No ExAcquirePushLockExclusiveEx / ExReleasePushLockExclusiveEx anywhere.

The patched function introduces the push lock and the new status-carrying callback:

; PATCHED mrxsmb_patched.sys : SmbQuicDisconnectConnection @ 0x1C0046464
00000001C0046508  call    cs:__imp_ExAcquirePushLockExclusiveEx
00000001C0046524  mov     edx, 0C000020Ch                  ; STATUS_CONNECTION_DISCONNECTED
00000001C0046549  call    cs:__imp_ExReleasePushLockExclusiveEx

Both push-lock calls exist only in the patched build (count: unpatched 0, patched 2), confirming the lock and the callback block are added together as new code rather than wrapped around pre-existing logic.


5. Trigger Conditions

No reachable trigger exists for a memory-safety issue in either finding.

  • Finding #1: the changed predicate only reclassifies certain reserved-bit reparse tags (bit 31 clear, bit 30 set) between STATUS_IO_REPARSE_TAG_NOT_HANDLED and STATUS_REPARSE. These tags reach no field-walk handler and no offset/length arithmetic, so no crafted REPARSE_DATA_BUFFER produces an out-of-bounds access via this change. The symlink/mount-point validation that a malicious server could target is identical in both builds.
  • Finding #2: the added block is new teardown-notification code guarded by a newly added push lock; there is no unlocked-callback window in the unpatched build to race.

6. Exploit Primitive & Development Notes

No exploit primitive is supported by the binaries. Neither change introduces or removes a reachable out-of-bounds read/write, integer-overflow-to-undersized-allocation, or use-after-free on SMB-server-controlled data. There is no basis for a kernel read/write primitive, KASLR defeat, pool-grooming strategy, or privilege-escalation chain, so none is described.


7. Debugger Playbook

Because no security-relevant primitive is present, there is no proof-of-concept to reproduce. The relevant address anchors for confirming the actual (non-security) changes are:

  • Finding #1: mrxsmb!FsRtlValidateReparsePointBufferEx — unpatched 0x1C0034919/0x1C003491E (and eax,0C0000000h / cmp eax,40000000h, absent in the patched build at 0x1C0032620).
  • Finding #2: mrxsmb!SmbQuicDisconnectConnection — patched 0x1C0046508 (ExAcquirePushLockExclusiveEx), 0x1C0046524 (mov edx,0C000020Ch), 0x1C0046549 (ExReleasePushLockExclusiveEx); none of these exist in the unpatched function at 0x1C0046A74.

8. Changed Functions — Full Triage

Security-Relevant

None. No changed function adds or tightens a bounds, length, overflow, type, or lock check on SMB-server-supplied data.

Behavioral / Cosmetic (representative)

Function Similarity Note
FsRtlValidateReparsePointBufferEx 0.987 Removed one tag-acceptance predicate term ((tag & 0xC0000000) != 0x40000000). Reparse-tag classification change; symlink/mount-point validation identical. Not security-relevant.
SmbQuicDisconnectConnection 0.690 Added a push-lock-guarded teardown-notification callback (new SMB-over-QUIC teardown code). No pre-existing unlocked callback existed. Not a UAF fix.
FsRtlValidateChangeNotifyBuffer The standalone FsRtlValidateChangeNotifyBufferEx/FsRtlValidateRelativeFileName helpers were inlined into a single validator; the patched inlined form performs no more validation than the unpatched pair. Shared OS library refactor, not an mrxsmb attacker-data tightening.
SmbCompressionDecompress Refactor: the unpatched server-claimed-size check *a2 > a5 is removed, but the patched call passes the true output buffer size a5 to RtlDecompressBufferEx2 directly, so the decompress write remains bounded by a5 in both builds. Equivalent safety, not a tightening.
sub_1C0021F50 0.329 Code reordering artifact (request-sending vs notification dispatch). Not security-relevant.
sub_1C006AF70 0.786 Connection setup; added __security_cookie. Standard hardening; no logical change.
sub_1C00147B0 0.719 Reconnection logic; struct offset shifts. Refactoring only.
sub_1C0048E24 0.852 IOCTL buffer alloc; overflow-checked size math replaced with direct arithmetic on values capped upstream. Not a regression.
sub_1C00204E8 0.818 Capability-negotiation bitmask constant changes. Protocol bookkeeping.
sub_1C001651C 0.634 Session state processing; inlined status-code lookup. Equivalent behavior.
sub_1C0010520 0.909 Connection establishment; offset shifts and added negotiation gate. Not security-relevant.
sub_1C0016824 0.828 Status update with telemetry; ETW offsets changed.
sub_1C00151F4 0.901 SMB response magic checks; offset shifts only.
sub_1C0011E60 0.821 Multi-connection dispatch; struct offset shifts.
sub_1C002009C 0.882 Capability flag interlocked ops; arg removed; logic simplified.
sub_1C000E09C 0.905 Object cleanup; rundown-protection wait reordered; memset size changed. Teardown-ordering refactor.
SmbCryptoAllocateAndPopulateCipherSuiteOrder 0.643 Cipher-suite allocation; same size formula, cleaner error path.
sub_1C007E078 0.360 Feature-configuration init; __security_cookie added; WIL feature-staging integration. Servicing churn.

Servicing / feature-staging cluster: SMB-over-QUIC feature code (including removal of the QUIC certificate-mapping and server-suffix feature — ValidateSmbQuicActionBufferAndGetServerSuffix, SmbQuicGetUnicodeServerSuffix, SmbQuic*Certificate*, SmbQuic*Configuration* no longer present in the patched build), WIL feature-staging helpers (wil_details_*, Feature_*__private_IsEnabled*), WPP/ETW tracing GUID and event-id changes (McTemplate*, WPP_SF_*), and struct-offset relocations account for the remainder of the changed functions. Removing the QUIC cert-mapping feature reduces attack surface but adds no new check, so it is not a delivered security fix.


9. Unmatched Functions

Direction Count Notes
Removed (unpatched only) 0 Per the matched-function accounting.
Added (patched only) 0 Per the matched-function accounting.

Note: at the source-symbol level the patched build drops the SMB-over-QUIC certificate-mapping / server-suffix helper functions and gains a small number of WIL feature-staging helpers; these are servicing/feature changes and do not represent a security fix.


10. Confidence & Caveats

Overall Confidence: High that neither change is a security fix.

  • Finding #1 was verified by a full body diff of FsRtlValidateReparsePointBufferEx across both builds: the sole logic delta is the removed (tag & 0xC0000000) != 0x40000000 term, confirmed in the disassembly (0x1C0034919/0x1C003491E present only in the unpatched build). The symlink/mount-point offset/length validation is identical, so the previously suspected out-of-bounds primitive does not exist.
  • Finding #2 was verified by reading both function bodies and disassembly: the unpatched SmbQuicDisconnectConnection has no push lock and no callback dereference, so the added push-lock/callback block is new teardown code rather than a retrofit onto a pre-existing race.
  • An independent function-by-function review of the security-relevant-looking changed functions (validation, parsing of server responses, allocation-size and copy-length computation, receive/decompress/decrypt paths) found no function where the patched build adds or tightens a bounds/length/overflow/type/lock check on attacker-controlled data.