mrxdav.sys — Uninitialized FCB field returned as NumberOfLinks in FileStandardInformation (CWE-908) info disclosure fixed
KB5073723
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 entry0x1C001E820, patched entry0x1C001E870) - 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:
MRxDAVCreatenever callsRxDisableLocalBuffering, so RDBSS leaves local buffering enabled for WebDAV file objects. The patch addsRxDisableLocalBuffering(FCB).MRxDAVCreateContinuationopens the backing local file withZwCreateFile(..., CreateOptions | 8, ...)— forcingFILE_NO_INTERMEDIATE_BUFFERING. The patch drops the| 8.DavReadWriteFileExunconditionally executesIrp->Flags |= 1(IRP_NOCACHE) on every IRP it builds. The patch makes that OR conditional on a caller-supplied flag threaded throughMRxDAVReadContinuation/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],eax → mov 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) addscall cs:__imp_RxDisableLocalBufferingat0x1C0010753, with the FCB (*(RX_CONTEXT + 0x38)) as argument; there is no such call in the unpatched build (the import is absent).MRxDAVCreateContinuation: unpatched executesor r8d, 8at0x1C0012AB1(r8d =*(arg2 + 0x20C)) beforeZwCreateFileat0x1C0012B08; the patched build has no such OR beforeZwCreateFileat0x1C0012B12.MRxDAVReadContinuation/MRxDAVWriteContinuationadd the new*(*(RX_CONTEXT + 0x28) + 0x10)argument to everyDavReadWriteFileExcall site; the Read stack frame grows0x88 → 0x98.
5. Trigger Conditions
Finding #1 — Info disclosure
- Ensure the WebDAV client (
WebClient) service is running. - Make a WebDAV file reachable via UNC (e.g.
\\server\DavWWWRoot\file.txt). - Open it with
CreateFileW(..., GENERIC_READ, ..., OPEN_EXISTING, ...). - Call
NtQueryInformationFile(h, &iosb, &fsi, sizeof(fsi), FileStandardInformation)(orGetFileInformationByHandle). - Read
fsi.NumberOfLinks— under the unpatched driver this DWORD is the value from FCB+0xE8rather 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+
0xE8to the caller once per query, in theNumberOfLinksfield. - 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
DeletePendingchange 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 (becomesrsi).poi(@rcx+0x1b0)→ file information class (must equal5forFileStandardInformation).poi(@rcx+0x1b8)→ output buffer (becomesrdi).dwo(@rcx+0x1c8)→ output buffer length (becomesebp; must be ≥0x18).
At the read instruction (0x1C001EBD1):
rsi= FCB;dd rsi+0xe8 L1— the value being returned asNumberOfLinks.rdi= output buffer; after the next instruction (0x1C001EBD7),dd rdi+0x10 L1holds the returned value.
Key offsets:
0x1C001EB6F—cmp ebp, 18h(output-buffer size check; must pass).0x1C001EBCA—mov [rdi+14h], bl(theDeletePending = 0bug).0x1C001EBD1— the read of FCB+0xE8(removed by the patch).0x1C001EBFC(patched) —mov ecx, 1(constantNumberOfLinks).0x1C001EC19(patched) —mov al, [rsi+8Ch](realDeletePending).
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
0x1C001BAB2uses the caller-supplied flag loaded at0x1C001BAA8. MRxDAVCreate: theRxDisableLocalBufferingcall at0x1C0010753exists 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 theFileStandardInformationbranch,NumberOfLinkschanges from a read of FCB+0xE8to the constant1, andDeletePendingchanges from a hardcoded0to*(FCB + 0x8C) & 1.DavReadWriteFileEx(sim 0.987) — Signature gains a newint32parameter. The unconditionalIrp->Flags |= 1becomesIrp->Flags |= (arg & 1).MRxDAVCreate(sim 0.9852) — AddsRxDisableLocalBuffering(*(RX_CONTEXT + 0x38))(new RDBSS import, patched only).MRxDAVCreateContinuation(sim 0.9931) — RemovesFILE_NO_INTERMEDIATE_BUFFERING(| 8) from theZwCreateFileCreateOptions.MRxDAVReadContinuation(sim 0.948) — Forwards the new caching flag*(*(RX_CONTEXT + 0x28) + 0x10)to everyDavReadWriteFileExcall site; stack frame grows0x88 → 0x98. The register reallocation is a side effect of the added parameter.MRxDAVWriteContinuation(sim 0.9733) — Forwards the same caching flag to everyDavReadWriteFileExcall 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:
- Trace the WebDAV FCB allocation/initialization paths and confirm FCB+
0xE8is not written before theFileStandardInformationquery, and characterize what it typically contains. - For the caching change, determine empirically whether the unpatched cached/uncached mismatch produced any observable data-consistency effect.