1. Overview

Field Value
Unpatched binary mrxdav_unpatched.sys
Patched binary mrxdav_patched.sys
Overall similarity 0.9903
Matched functions 230
Changed functions 6
Identical functions 224
Unmatched (unpatched) 0
Unmatched (patched) 0

One-line verdict: The substantive part of this patch fixes a genuine information-disclosure bug in the WebDAV redirector: MRxDAVQueryFileInformation returned a non-initialized FCB field as FILE_STANDARD_INFORMATION.NumberOfLinks (CWE-908), reachable by any user that can query a WebDAV file handle. The remaining five changed functions are a related caching-behavior correctness change (stop forcing uncached I/O while RDBSS local buffering is enabled); no security impact for that change is demonstrable from the diff, so it is not rated as a vulnerability.

An independent per-function comparison of the two builds (normalizing instruction addresses and WPP trace-GUID label churn) found exactly the six functions below with real instruction differences; every other function is identical apart from relocation and WPP label renaming. No security-relevant change was omitted from this report.


2. Vulnerability Summary

Finding #1 — Kernel memory disclosure via uninitialized FCB field in FileStandardInformation

  • Severity: Medium
  • Class: CWE-908 (Use of Uninitialized Resource) / CWE-200 (Information Exposure)
  • Function: MRxDAVQueryFileInformation (unpatched entry 0x1C001E820, patched entry 0x1C001E870)
  • Reachable from: Any caller that can open and query a WebDAV file handle.

Root cause. When servicing a FileStandardInformation (info class 5) query, the unpatched code copies NumberOfLinks from FCB offset 0xE8. That offset does not hold a valid link count on the WebDAV FILE_STANDARD_INFORMATION path; the patch replaces the read with the constant 1 (the correct link count for a non-hardlinked file), which indicates the source field was not a maintained, initialized value. The 4-byte value from FCB+0xE8 is written into the NumberOfLinks field of the caller's FILE_STANDARD_INFORMATION and returned to user mode.

A second defect in the same branch hardcodes DeletePending = 0, so a file that is actually pending deletion is reported as not pending. The patch replaces the hardcoded 0 with *(FCB + 0x8C) & 1, the real FCB delete state. This is a correctness fix, not a memory-safety issue.

Call chain (open → query → leak): 1. Open a file over the WebDAV redirector, e.g. CreateFileW(L"\\\\server\\DavWWWRoot\\file.txt"). 2. RDBSS top-level dispatch routes the query to the registered MRxQueryFileInfo callback. 3. MRxDAVQueryFileInformation reaches the FileStandardInformation branch (loc_1C001EB6F, size check cmp ebp, 18h). 4. On the success path the handler executes mov eax, [rsi+0E8h] / mov [rdi+10h], eax — the FCB+0xE8 value becomes NumberOfLinks.

Finding #2 — Forced-uncached I/O while RDBSS local buffering is enabled (caching correctness change)

  • Severity: None (no demonstrated security impact)
  • Class: Caching-behavior correctness change. No reachable, demonstrable vulnerability is shown by the diff.
  • Functions: DavReadWriteFileEx, MRxDAVCreate, MRxDAVCreateContinuation, MRxDAVReadContinuation, MRxDAVWriteContinuation.

What changed. The unpatched driver combines three behaviors that the patch makes consistent:

  1. MRxDAVCreate never calls RxDisableLocalBuffering, so RDBSS leaves local buffering enabled for WebDAV file objects. The patch adds RxDisableLocalBuffering(FCB).
  2. MRxDAVCreateContinuation opens the backing local file with ZwCreateFile(..., CreateOptions | 8, ...) — forcing FILE_NO_INTERMEDIATE_BUFFERING. The patch drops the | 8.
  3. DavReadWriteFileEx unconditionally executes Irp->Flags |= 1 (IRP_NOCACHE) on every IRP it builds. The patch makes that OR conditional on a caller-supplied flag threaded through MRxDAVReadContinuation / MRxDAVWriteContinuation.

Net effect: the patch stops forcing uncached I/O on the backing file while RDBSS believes the WebDAV file is locally buffered. This is a caching-consistency correctness change. Whether it produced any observable stale-data or cross-user condition cannot be established from the binary diff, so no vulnerability class or exploit primitive is asserted for it.


3. Pseudocode Diff

Finding #1 — MRxDAVQueryFileInformation

// ============================================================
// UNPATCHED — FileStandardInformation branch (info class 5)
// ============================================================
// rsi = FCB = *(RX_CONTEXT + 0x38)
// rdi = output FILE_STANDARD_INFORMATION buffer
// ebp = output buffer length
if (ebp >= 0x18) {
    *(DWORD*)(rdi + 0x00) = *(DWORD*)(rsi + 0x18);  // AllocationSize.LowPart
    *(DWORD*)(rdi + 0x04) = *(DWORD*)(rsi + 0x1c);  // AllocationSize.HighPart
    *(DWORD*)(rdi + 0x08) = *(DWORD*)(rsi + 0x20);  // EndOfFile.LowPart
    *(DWORD*)(rdi + 0x0c) = *(DWORD*)(rsi + 0x24);  // EndOfFile.HighPart
    *(BYTE*)(rdi + 0x14) = 0;                        // BUG: DeletePending hardcoded to 0
    *(BYTE*)(rdi + 0x15) = r15b & 0x10;              // Directory/reparse flag
    *(DWORD*)(rdi + 0x10) = *(DWORD*)(rsi + 0xe8);   // BUG: NumberOfLinks = FCB+0xE8 (not a link count)
}

// ============================================================
// PATCHED
// ============================================================
if (ebp >= 0x18) {
    *(DWORD*)(rdi + 0x00) = *(DWORD*)(rsi + 0x18);
    *(DWORD*)(rdi + 0x04) = *(DWORD*)(rsi + 0x1c);
    *(DWORD*)(rdi + 0x08) = *(DWORD*)(rsi + 0x20);
    *(DWORD*)(rdi + 0x0c) = *(DWORD*)(rsi + 0x24);
    *(DWORD*)(rdi + 0x10) = 1;                        // FIX: NumberOfLinks = 1 (constant)
    *(BYTE*)(rdi + 0x14) = *(BYTE*)(rsi + 0x8c) & 1;  // FIX: DeletePending = real FCB state
    *(BYTE*)(rdi + 0x15) = r15b & 0x10;
}

Finding #2 — DavReadWriteFileEx

// ============================================================
// UNPATCHED
// ============================================================
Irp->Flags |= 1;                     // IRP_NOCACHE set unconditionally

// ============================================================
// PATCHED — new caller-supplied int32 parameter
// ============================================================
Irp->Flags |= (a10 & 1);             // IRP_NOCACHE only when caller's flag says so
// a10 is supplied by callers as *(*(RX_CONTEXT + 0x28) + 0x10)

Supporting changes

// MRxDAVCreate (patched only) — disables RDBSS local buffering for WebDAV files
RxDisableLocalBuffering(*(RX_CONTEXT + 0x38));  // FCB

// MRxDAVCreateContinuation — drops FILE_NO_INTERMEDIATE_BUFFERING
//   UNPATCHED: ZwCreateFile(..., *(arg2 + 0x20c) | 8, ...)
//   PATCHED:   ZwCreateFile(..., *(arg2 + 0x20c),     ...)

// MRxDAVReadContinuation / MRxDAVWriteContinuation — thread the flag into DavReadWriteFileEx
//   UNPATCHED: DavReadWriteFileEx(...)
//   PATCHED:   DavReadWriteFileEx(..., *(*(RX_CONTEXT + 0x28) + 0x10), ...)

4. Assembly Analysis

Finding #1 — MRxDAVQueryFileInformation

Unpatched, FileStandardInformation success path (loc_1C001EB6F, size check passed at 0x1C001EB6F: cmp ebp, 18h):

; rsi = FCB, rdi = output buffer, bl = 0 (xor ebx,ebx at entry)
00000001C001EBA9  mov     eax, [rsi+18h]      ; AllocationSize.LowPart
00000001C001EBAC  and     r15b, 10h           ; mask Directory/reparse flag
00000001C001EBB0  mov     [rdi], eax
00000001C001EBB2  mov     r14d, 18h           ; output size = 24
00000001C001EBB8  mov     eax, [rsi+1Ch]      ; AllocationSize.HighPart
00000001C001EBBB  mov     [rdi+4], eax
00000001C001EBBE  mov     eax, [rsi+20h]      ; EndOfFile.LowPart
00000001C001EBC1  mov     [rdi+8], eax
00000001C001EBC4  mov     eax, [rsi+24h]      ; EndOfFile.HighPart
00000001C001EBC7  mov     [rdi+0Ch], eax
00000001C001EBCA  mov     [rdi+14h], bl       ; DeletePending = 0 (bl was zeroed at entry)
00000001C001EBCD  mov     [rdi+15h], r15b     ; Directory/reparse
00000001C001EBD1  mov     eax, [rsi+0E8h]     ; <<< reads FCB+0xE8 (not a link count)
00000001C001EBD7  mov     [rdi+10h], eax      ; -> NumberOfLinks = returned to user
00000001C001EBDA  jmp     loc_1C001EC80       ; return

Patched counterpart (loc_1C001EBF9):

00000001C001EBF9  mov     eax, [rsi+18h]      ; AllocationSize.LowPart
00000001C001EBFC  mov     ecx, 1              ; NumberOfLinks = 1 (constant)
00000001C001EC01  mov     [rdi], eax
00000001C001EC03  mov     eax, [rsi+1Ch]
00000001C001EC06  mov     [rdi+4], eax
00000001C001EC09  mov     eax, [rsi+20h]
00000001C001EC0C  lea     r14d, [rcx+17h]     ; output size = 24
00000001C001EC10  mov     [rdi+8], eax
00000001C001EC13  mov     eax, [rsi+24h]
00000001C001EC16  mov     [rdi+0Ch], eax
00000001C001EC19  mov     al, [rsi+8Ch]       ; real FCB delete state
00000001C001EC1F  and     al, cl              ; mask bit 0
00000001C001EC21  mov     [rdi+10h], ecx      ; -> NumberOfLinks = 1
00000001C001EC24  and     r15b, 10h
00000001C001EC28  mov     [rdi+14h], al       ; -> DeletePending = real state
00000001C001EC2B  mov     [rdi+15h], r15b
00000001C001EC2F  jmp     loc_1C001ECD5

Diff in one line: - mov eax,[rsi+0E8h] ; mov [rdi+10h],eaxmov ecx,1 ; mov [rdi+10h],ecx - mov byte [rdi+14h], bl (bl=0) → mov al,[rsi+8Ch] ; and al,cl ; mov [rdi+14h],al

Finding #2 — DavReadWriteFileEx

Unpatched (entry 0x1C001B7C8):

00000001C001BA57  or      dword ptr [rsi+10h], 1   ; IRP->Flags |= IRP_NOCACHE (unconditional)

Patched (entry 0x1C001B818):

00000001C001BAA8  mov     eax, [rsp+0A8h+arg_48]   ; caller-supplied caching flag (a10)
00000001C001BAAF  and     eax, 1                    ; mask bit 0
00000001C001BAB2  or      [r14+10h], eax            ; IRP->Flags |= (flag & 1)

Supporting changes:

  • MRxDAVCreate (patched) adds call cs:__imp_RxDisableLocalBuffering at 0x1C0010753, with the FCB (*(RX_CONTEXT + 0x38)) as argument; there is no such call in the unpatched build (the import is absent).
  • MRxDAVCreateContinuation: unpatched executes or r8d, 8 at 0x1C0012AB1 (r8d = *(arg2 + 0x20C)) before ZwCreateFile at 0x1C0012B08; the patched build has no such OR before ZwCreateFile at 0x1C0012B12.
  • MRxDAVReadContinuation / MRxDAVWriteContinuation add the new *(*(RX_CONTEXT + 0x28) + 0x10) argument to every DavReadWriteFileEx call site; the Read stack frame grows 0x88 → 0x98.

5. Trigger Conditions

Finding #1 — Info disclosure

  1. Ensure the WebDAV client (WebClient) service is running.
  2. Make a WebDAV file reachable via UNC (e.g. \\server\DavWWWRoot\file.txt).
  3. Open it with CreateFileW(..., GENERIC_READ, ..., OPEN_EXISTING, ...).
  4. Call NtQueryInformationFile(h, &iosb, &fsi, sizeof(fsi), FileStandardInformation) (or GetFileInformationByHandle).
  5. Read fsi.NumberOfLinks — under the unpatched driver this DWORD is the value from FCB+0xE8 rather than a valid link count.

Finding #2 — Caching consistency change

The five-function change alters whether the backing local file is opened cached and whether IRPs carry IRP_NOCACHE, together with whether RDBSS local buffering is disabled. No specific attacker-triggerable condition is demonstrated by the diff, so no trigger sequence is claimed here.


6. Impact Notes

Finding #1:

  • Discloses the 4-byte contents of FCB+0xE8 to the caller once per query, in the NumberOfLinks field.
  • The disclosed bytes are whatever the driver left at that FCB offset; the diff does not establish that the offset contains any specific value or structure, so no further claim about the disclosed contents is made.
  • The secondary DeletePending change is a correctness fix (the unpatched build always reported "not pending deletion").

Finding #2:

  • A caching-consistency correctness change. No exploit primitive, stale-read primitive, or cross-user disclosure is demonstrated by the diff.

7. Debugger Playbook

Finding #1 — Uninitialized NumberOfLinks

Breakpoints (WinDbg/KD):

bp mrxdav!MRxDAVQueryFileInformation                  ; entry 0x1C001E820, rcx = RX_CONTEXT
ba r4 <FCB+0xE8>                                       ; watch the source read, or:
bp 0x1C001EBD1                                         ; the read of FCB+0xE8 (unpatched)

At the entry breakpoint (0x1C001E820):

  • rcx = arg1 = RX_CONTEXT*.
  • poi(@rcx+0x38) → FCB pointer (becomes rsi).
  • poi(@rcx+0x1b0) → file information class (must equal 5 for FileStandardInformation).
  • poi(@rcx+0x1b8) → output buffer (becomes rdi).
  • dwo(@rcx+0x1c8) → output buffer length (becomes ebp; must be ≥ 0x18).

At the read instruction (0x1C001EBD1):

  • rsi = FCB; dd rsi+0xe8 L1 — the value being returned as NumberOfLinks.
  • rdi = output buffer; after the next instruction (0x1C001EBD7), dd rdi+0x10 L1 holds the returned value.

Key offsets:

  • 0x1C001EB6Fcmp ebp, 18h (output-buffer size check; must pass).
  • 0x1C001EBCAmov [rdi+14h], bl (the DeletePending = 0 bug).
  • 0x1C001EBD1 — the read of FCB+0xE8 (removed by the patch).
  • 0x1C001EBFC (patched) — mov ecx, 1 (constant NumberOfLinks).
  • 0x1C001EC19 (patched) — mov al, [rsi+8Ch] (real DeletePending).

Trigger setup from user mode (C):

HANDLE h = CreateFileW(L"\\\\server\\DavWWWRoot\\file.txt",
                       GENERIC_READ, FILE_SHARE_READ, NULL,
                       OPEN_EXISTING, 0, NULL);
FILE_STANDARD_INFORMATION fsi = {0};
IO_STATUS_BLOCK iosb = {0};
NtQueryInformationFile(h, &iosb, &fsi, sizeof(fsi), FileStandardInformation);
printf("NumberOfLinks = 0x%08X\n", fsi.NumberOfLinks);

Observation: Under the unpatched binary, fsi.NumberOfLinks reflects the value at FCB+0xE8. Under the patched binary the field is the constant 1.

Struct/offset notes:

FCB (relevant fields):
  +0x18  AllocationSize (LARGE_INTEGER)
  +0x20  EndOfFile       (LARGE_INTEGER)
  +0x8c  byte: bit 0 = DeletePending
  +0xe8  read by unpatched code as NumberOfLinks (not a maintained link count)

FILE_STANDARD_INFORMATION (output buffer):
  +0x00  AllocationSize  (8 bytes)
  +0x08  EndOfFile       (8 bytes)
  +0x10  NumberOfLinks   (4 bytes)  <- disclosed field
  +0x14  DeletePending   (1 byte)
  +0x15  Directory       (1 byte)

RX_CONTEXT (relevant fields):
  +0x38   FCB pointer
  +0x1b0  FsInformationClass
  +0x1b8  output buffer pointer
  +0x1c8  output buffer length

Finding #2 — Caching consistency change

Breakpoints:

bp mrxdav!DavReadWriteFileEx          ; unpatched entry 0x1C001B7C8 / patched 0x1C001B818
bp 0x1C001BA57                        ; unpatched 'or dword [rsi+10h], 1'
bp mrxdav!MRxDAVCreate                ; 0x1C0010670

What to inspect:

  • At 0x1C001BA57 (unpatched), rsi = allocated IRP; dd rsi+0x10 L1 (IRP.Flags) — bit 0 is set unconditionally.
  • In the patched build the equivalent OR at 0x1C001BAB2 uses the caller-supplied flag loaded at 0x1C001BAA8.
  • MRxDAVCreate: the RxDisableLocalBuffering call at 0x1C0010753 exists only in the patched build.

8. Changed Functions — Full Triage

Six functions changed. One (MRxDAVQueryFileInformation) carries the info-disclosure fix; the other five are the caching-consistency change with no demonstrated security impact.

  • MRxDAVQueryFileInformation (sim 0.9924) — Info-disclosure fix. In the FileStandardInformation branch, NumberOfLinks changes from a read of FCB+0xE8 to the constant 1, and DeletePending changes from a hardcoded 0 to *(FCB + 0x8C) & 1.
  • DavReadWriteFileEx (sim 0.987) — Signature gains a new int32 parameter. The unconditional Irp->Flags |= 1 becomes Irp->Flags |= (arg & 1).
  • MRxDAVCreate (sim 0.9852) — Adds RxDisableLocalBuffering(*(RX_CONTEXT + 0x38)) (new RDBSS import, patched only).
  • MRxDAVCreateContinuation (sim 0.9931) — Removes FILE_NO_INTERMEDIATE_BUFFERING (| 8) from the ZwCreateFile CreateOptions.
  • MRxDAVReadContinuation (sim 0.948) — Forwards the new caching flag *(*(RX_CONTEXT + 0x28) + 0x10) to every DavReadWriteFileEx call site; stack frame grows 0x88 → 0x98. The register reallocation is a side effect of the added parameter.
  • MRxDAVWriteContinuation (sim 0.9733) — Forwards the same caching flag to every DavReadWriteFileEx call site.

9. Unmatched Functions

None. unmatched_unpatched = 0 and unmatched_patched = 0. No function was removed or added. The RxDisableLocalBuffering import resolves to an existing RDBSS export, not a new internal function.


10. Confidence & Caveats

Confidence: High that the code changes are as described — the assembly is unambiguous in both builds (mov eax,[rsi+0E8h] vs mov ecx,1; unconditional or [rsi+10h],1 vs the gated OR; the added RxDisableLocalBuffering call; the dropped | 8). An independent per-function comparison confirms exactly these six functions differ substantively; all other differences are relocation and WPP trace-GUID label churn.

Finding #1: The patch substituting a constant 1 for the FCB+0xE8 read shows the source field was not a maintained link count, and its contents were returned to user mode, consistent with CWE-908. What the disclosed 4 bytes actually contain is not established by the diff, and no claim about their contents or any downstream exploitation is made.

Finding #2: The caching-consistency change is real, but the diff does not demonstrate any stale-read or cross-user data condition, so it is not rated as a vulnerability.

What a researcher should verify before claiming impact:

  1. Trace the WebDAV FCB allocation/initialization paths and confirm FCB+0xE8 is not written before the FileStandardInformation query, and characterize what it typically contains.
  2. For the caching change, determine empirically whether the unpatched cached/uncached mismatch produced any observable data-consistency effect.