Skip to content

wcifs.sys leaks FLT_STREAM_CONTEXT / FLT_FILE_CONTEXT references on every WCI reparse-point open - unbounded kernel pool growth (WCsc/WCfc/WCri/FMwi/FMfc, 0 frees in 7.5M allocations) #646

Description

Describe the bug

wcifs.sys (Windows Container Isolation minifilter) leaks a Filter Manager context reference on
every file open that resolves a WCI reparse point. The contexts are allocated in
wcifs.sys!WcGetSetContextFileObject and are never released, so their reference count never
reaches zero and Filter Manager never frees them. Kernel pool grows monotonically and is
reclaimed only by reboot.

Lifetime counters on the affected machine, sampled 2026-08-20 23:25:23 +02:00 - ~71 h into a
boot that began 2026-08-18 00:46:18 +02:00:

Tag Pool Allocations Frees Outstanding Bytes in use
WCsc (FLT_STREAM_CONTEXT) NonPaged 7,246,092 0 7,246,092 2,782,499,328
WCfc (FLT_FILE_CONTEXT) NonPaged 3,661,981 0 3,661,981 761,692,048
WCri (reparse data buffer) Paged 3,955,081 293,100 3,661,981 1,991,777,248
FMwi (FltMgr work item) NonPaged 7,311,610 73,054 7,238,556 926,535,168
FMfc (FltMgr file list ctrl) NonPaged 7,992,513 753,202 7,239,311 1,389,947,712
WCfn (file name info) Paged 7,380,786 7,380,786 0 0
WCsh (FLT_STREAMHANDLE_CONTEXT) NonPaged 76,758 71,228 5,530 3,981,600
FMfn (FltMgr name cache) Paged 2,825,638,035 2,796,650,645 28,987,390 17,740,318,848

WCsc and WCfc have exactly zero frees across 7.2 million and 3.7 million allocations.

Re-sampled 2026-08-23 16:30:55 +02:00. Same uninterrupted boot - the machine has not been
rebooted in between, uptime is now 5 d 15 h - and the counters are still moving:

Tag Allocations Frees Outstanding Bytes in use Delta allocs vs 08-20
WCsc 7,483,567 0 7,483,567 2,873,689,728 +237,475
WCfc 3,837,378 0 3,837,378 798,174,624 +175,397
WCri 4,130,692 293,314 3,837,378 2,086,188,912 +175,611
FMwi 7,549,171 73,136 7,476,035 956,932,480 +237,561
FMfc 8,383,854 906,920 7,476,934 1,435,571,328 +391,341
WCfn 7,618,263 7,618,263 0 0 +237,477
WCsh 77,788 72,254 5,534 3,984,480 +1,030
FMfn (paged) 2,898,021,385 2,867,966,719 30,054,666 18,472,365,424 +72,383,350

That 65-hour window is the cleanest single statement of the bug. WCsc took 237,475
further allocations and freed none. WCfn - allocated by the same driver on the same
WcPostCreate path, in the same window, on the same machine - took 237,477 and freed
all 237,477. WCsh stayed balanced too (+1,030 allocated, +1,026 freed). The two tags that
never free are not sharing a fate with the tags around them.

The most persuasive element: two internal controls in the same driver, on the same call path, behave correctly

This is not "the workload is holding objects open", and it is not a Filter Manager teardown
failure. Two wcifs allocations prove the surrounding machinery works.

1. WCfn - same function, perfectly balanced. wcifs allocates file-name information on the
same WcPostCreate path and releases it in scope. Lifetime: 7,380,786 allocations, 7,380,786
frees, zero outstanding.
The release is directly visible in the traces as
ExFreePoolWithTag <- FLTMGR.SYS!FltReleaseFileNameInformation <- wcifs.sys!WcPostCreate.
The same creates that produce a leaked WCsc produce a correctly released WCfn.

2. WCsh - the driver's registered context cleanup callback runs, millions of times.
wcifs registers one ContextCleanupCallback (wcifs.sys!WcContextCleanup, RVA 0x353E0) for
all four of its context types. It is reached roughly 3,300 times per 200 ms under load, and
WCsh (FLT_STREAMHANDLE_CONTEXT) is released normally: 76,758 allocations / 71,228 frees.
But it is only ever reached down this path:

FLTMGR.SYS!FltClose
wcifs.sys!WcFreeSourceList
wcifs.sys!WcContextCleanup
FLTMGR.SYS!DoReleaseContext
FLTMGR.SYS!FltpDeleteContextList
FLTMGR.SYS!FltpRemoveAllStreamHandleContextsForFileObject   <== STREAMHANDLE ONLY
FLTMGR.SYS!FltpCleanupStreamListCtrlForFileObjectCloseWithStreamListCtrl
FLTMGR.SYS!FltpPerformPreCallbacksWorker
FLTMGR.SYS!FltpPassThroughInternal
FLTMGR.SYS!FltpPassThrough
FLTMGR.SYS!FltpDispatch
nt!IopfCallDriver -> nt!IofCallDriver -> nt!IopDeleteFile -> nt!ObpRemoveObjectRoutine

Filter Manager tears down streamhandle contexts at FILE_OBJECT close, which is why WCsh is
freed. File and stream contexts are not released there - they are reference counted and freed
only when the count reaches zero. So the cleanup callback is registered, is reachable, and
demonstrably runs; the file and stream contexts simply never reach refcount zero.

Corroborating: the streams themselves do tear down. In one 90-second window, 140,938 wcifs
stream-tracking allocations (WCse/WCss) were freed while WCsc frees stayed at exactly
zero.
WCse/WCss show 12,277,301 lifetime frees. Objects on this path are being destroyed;
the FltMgr contexts attached to them are not.

Root cause analysis

Allocation site

All three leaking wcifs allocations are made back-to-back by one function,
wcifs.sys!WcGetSetContextFileObject, reached from the post-create callback when the create
resolves a WCI reparse point. Symbolized stacks from a WPR "Pool" profile capture
(wcifs.sys 10.0.26100.8972, load base 0xfffff8005fab0000):

(1) WCsc - FLT_STREAM_CONTEXT, 264 B registered / 384 B nonpaged:

nt!ExAllocatePool2 -> nt!ExAllocatePoolWithTag -> nt!ExAllocatePoolEx
nt!ExAllocateFromNPagedLookasideList
FLTMGR.SYS!FltAllocateContext                <- tag WCsc comes from the wcifs registration
wcifs.sys!WcGetSetStreamContext              <== ALLOCATION SITE  (ret 0xfffff8005fadb96d)
wcifs.sys!WcGetSetContextFileObject          (ret 0xfffff8005fadaf65)
wcifs.sys!WcProcessWciReparsePointOpen
wcifs.sys!WcPostCreate
FLTMGR.SYS!FltpPerformPostCallbacksWorker
FLTMGR.SYS!FltpProcessIoCompletion
FLTMGR.SYS!FltpPassThroughCompletionWorker
FLTMGR.SYS!FltpLegacyProcessingAfterPreCallbacksCompleted
FLTMGR.SYS!FltpCreate
nt!IopfCallDriver -> nt!IofCallDriver -> nt!IopParseDevice
nt!ObpLookupObjectName -> nt!ObOpenObjectByNameEx -> nt!IopCreateFile
nt!NtOpenFile -> nt!KiSystemServiceCopyEnd

(2) WCfc - FLT_FILE_CONTEXT, 96 B registered / 208 B nonpaged: identical stack except
frames 12-13:

FLTMGR.SYS!FltAllocateContext
wcifs.sys!WcGetSetFileContext                <== ALLOCATION SITE  (ret 0xfffff8005fadb7a4)
wcifs.sys!WcGetSetContextFileObject          (ret 0xfffff8005fadaef4 - a DIFFERENT return
                                              address than in (1): one function allocates
                                              both contexts, back to back)
wcifs.sys!WcProcessWciReparsePointOpen
wcifs.sys!WcPostCreate

(3) WCri - reparse data buffer, paged, allocated directly:

nt!ExAllocatePool2
wcifs.sys!WcGetReparseData                   <== ALLOCATION SITE  (ret 0xfffff8005fadb589)
wcifs.sys!WcGetSetContextFileObject          (ret 0xfffff8005fadb004)
wcifs.sys!WcProcessWciReparsePointOpen
wcifs.sys!WcPostCreate

WcGetSetContextFileObject additionally causes one FLTMGR.SYS!FltAllocateGenericWorkItem
(FMwi) and one FLTMGR.SYS!FltpAllocateFileListCtrl (FMfc) per file, both of which are then
also retained.

Static structure

wcifs.sys registers four context types. The FLT_CONTEXT_REGISTRATION array is at file offset
0xb000, 56-byte stride, FLT_CONTEXT_END (0xffff) terminator at 0xb0f0:

Type Value Size Tag Behaviour
FLT_INSTANCE_CONTEXT 0x0002 FLT_VARIABLE_SIZED_CONTEXTS WCic n/a
FLT_FILE_CONTEXT 0x0004 0x060 (96) WCfc leaks
FLT_STREAM_CONTEXT 0x0008 0x108 (264) WCsc leaks
FLT_STREAMHANDLE_CONTEXT 0x0010 0x258 (600) WCsh frees correctly

All four share the single ContextCleanupCallback at RVA 0x353E0. The literals WCsc and WCfc
each occur exactly once in the binary (only in this table), so these tags have no other
allocation site in wcifs.

Why we believe it is a leaked reference

Per the documented contract for
FltAllocateContext:

"Contexts are reference-counted, and on a successful return from FltAllocateContext, the
context pointed to by ReturnedContext has been initialized to have a reference count of 1.
A context is freed automatically when its reference count reaches zero."

"ReturnedContext ... The caller is responsible for calling FltReleaseContext to release
this context when it is no longer needed."

and FltReleaseContext:

"Every successful call to FltAllocateContext, FltGetXxxContext, or
FltReferenceContext must eventually be matched by a call to FltReleaseContext. Note
that the OldContext pointer returned by FltSetXxxContext and the Context parameter that
is used to call FltDeleteContext must also be released by calling FltReleaseContext
when they are no longer needed."

The allocation reference taken by FltAllocateContext on this path is never dropped, so the
count never reaches zero, WcContextCleanup never runs for these two context types, and the pool
is never returned.

What we can and cannot state precisely. We have not disassembled
WcGetSetContextFileObject, so we cannot assert that a FltReleaseContext call is literally
absent from the source. What the evidence establishes is that a reference taken on this path is
never released
. Observationally equivalent variants worth ruling out on your side:

  • the release exists but sits on an error/status branch that the success path never takes;
  • an extra FltReferenceContext is taken and matched only in a path this workload never reaches;
  • the OldContext returned by FltSetStreamContext / FltSetFileContext is dropped on the
    floor on the re-open case;
  • the contexts are intentionally held for the instance lifetime and released only at
    detach/unload - which on a running system is never.

The fix direction is the same for all of these, but the exact one matters for the patch.

To Reproduce

There is one gate, plus two properties that make naive measurement misleading. The properties
matter as much as the gate: a walk that reads zero is more often a measurement artifact than a
genuine negative, and we produced several such artifacts ourselves before catching them.

The gate: the layer must be currently attached to a wcifs instance

The store on this machine holds 44 layers. Only the current/live one leaks at all.

Layer Entries ΔWCsc
ab08af0c... (live) 327,565 +231,887
aad58cf4... (superseded) 81,847 0
5c15b7ff... (superseded) 95,727 0
06009466... (superseded) 100,415 0

This is not a difference in store integrity. We checked that by inspection rather than
assuming it:

  • all 44 layers are registered identically - State=3, Flags=24, LayeringMode=Wcifs, same
    descriptor targeting %SYSTEMDRIVE%;
  • the backing store is the C: volume itself, so it cannot have been unmounted;
  • superseded layers' reparse points are intact and readable - TAG=0x80000018, DLEN=48, valid
    target GUID;
  • the chain target layer cc2bc905 exists on disk;
  • the failure is selective, not structural: on a superseded layer, files fail with
    ERROR_CANT_ACCESS_FILE (1920) on a resolving open, but directories open normally.

The one property that distinguishes ab08af0c is OSVersion = 10.0.26200.9168, the running
build, and its StorageId matches a live ContainerStorages entry (849 MB sandbox.vhdx,
written at this boot). Filter-level evidence points the same way: superseded layers'
Files\Windows and its siblings carry the ReparsePoint, Offline attributes, whereas the live
layer's carry neither, and FSCTL_GET_REPARSE_POINT against them returns
ERROR_NOT_A_REPARSE_POINT - i.e. on the live layer wcifs is intercepting and servicing the
path rather than NTFS exposing a reparse point to the caller.

So the gate is whether the layer is currently attached to a wcifs instance, not whether its
backing store is intact.
Every allocation occurs on the success path of
WcProcessWciReparsePointOpen. The ERR1920 path allocates nothing, which means the failure
path already disposes of its references correctly - a working reference-handling path inside the
same function to diff the leaking one against.

Scope is confined to the layer store. Other reparse-heavy or projected locations on this
machine were checked and are clean: WindowsApps (4,344 entries), SystemApps (4,031),
WinSxS (27,672), AppRepository (1,819), ModifiableWindowsApps and E:\WindowsApps all have
zero WCI reparse points and produced zero leak across 40 bounded opens.

Every walker leaks - there is no safe traversal API

Measured one walker per virgin subtree of the live layer, no repetition, no rotation:

Walker Subtree Entries ΔWCsc
Get-ChildItem -Recurse \Windows\INF 839 60
cmd dir /a /s /b \Windows\Help 22 13
Get-ChildItem -Recurse \Windows\Logs 3 3
cmd dir /a /s /b \Windows\Provisioning 39 8
MSYS find \Windows\Migration 3 1
MSYS find \Windows\Globalization 31 7

cmd, PowerShell and MSYS all leak. A second pass over all six subtrees produced 0, 0, 0, 0,
0, 0
, and the same walkers against a superseded layer produced 0.

That second-pass behaviour is the trap. An earlier round of our own testing concluded that find
and Get-ChildItem -Recurse did not reach the leaking path at all - a conclusion drawn from runs
over already-walked trees and over a superseded layer. Both readings were artifacts and the
conclusion was wrong.
We state this explicitly because anyone reproducing this will hit the
same trap, and because a "this walker is safe" result should be treated as unproven until it is
taken on a virgin subtree of the live layer.

The unit: one context triple per layer-chain hop, charged on first touch

The chain on this machine is ab08af0c -> cc2bc905 -> %SYSTEMDRIVE%. What determines the leak is
how many layer-chain hops the resolution traverses - not path depth. Measured under verified
zero ambient allocation with sub-second windows, depth held constant at 3:

File Present in parent layer cc2bc905? ΔWCsc
AccountNotifications.admx no 1
ActiveXInstallService.admx yes 2
AddRemovePrograms.admx yes 2
tswcx.xsd no 1

Negative controls under the same conditions:

Operation ΔWCsc
Re-opening a file already opened 0
Opening a directory 0
Opening with FILE_READ_ATTRIBUTES only 0
Opening with FILE_FLAG_OPEN_REPARSE_POINT 0

One leaked context triple per layer-chain hop the resolution traverses. This reconciles with
the FSCTL invariant rather than contradicting it: one file open can drive several FSCTLs, one per
hop, and the 1:1 relation holds per FSCTL.

First-touch suppression is real but does not persist - and the totals prove it

An immediate re-walk of the same tree leaks nothing, so some per-stream state suppresses the
second resolution. We have not established how long that suppression lasts, and it is
demonstrably not for the life of the boot.
The arithmetic forces this:

  • the live layer holds 327,565 entries, so one complete first-touch pass can cost at most
    ~655,130 contexts even at two hops per file;
  • this machine accumulated 7,246,092 outstanding WCsc on a single boot - between
    11x and 22x what one exhaustive first-touch pass could produce.

So the same files must be re-leaking within one boot. The most likely reading, offered as a
hypothesis rather than a result, is that the suppressing state is the per-stream context itself
and is lost when the stream is torn down under cache pressure - consistent with WCse/WCss
showing 12,277,301 lifetime frees while WCsc shows none. If that is right, "first touch" means
"first touch per resident stream", and any long-running or memory-pressured workload re-pays the
cost indefinitely. Confirming or refuting it is squarely on your side of the fence.

Commands

Leaking case - a virgin subtree of the live layer:

cmd /c dir /a /s /b "C:\ProgramData\Microsoft\Windows\Containers\Layers\<live-layer-guid>\Files\Windows\INF" >NUL

Whole live layer:

cmd /c dir /a /s /b "C:\ProgramData\Microsoft\Windows\Containers\Layers\<live-layer-guid>\Files" >NUL

Get-ChildItem -Recurse and MSYS find over the same virgin subtrees leak equivalently - pick
whichever is convenient. Sample the pool tag table before and after with poolmon -p -t (WDK),
or NtQuerySystemInformation(SystemPoolTagInformation). Every measurement in this report was
taken from an ordinary non-elevated user session; the reproducer needs no privilege beyond read
access to the layer store.

Measurement discipline that turned out to matter: use a virgin subtree, verify ambient
allocation is zero first, and keep the window sub-second. A 20-second settle window on this
machine contributed roughly 22 counts of ambient noise on its own, enough to swamp the
single-file hop measurements above.

Measured deltas across one bracketed reproducer run

1,999 files, window 23:24:11 -> 23:25:23 (+02:00), same machine:

Tag Allocs Frees Net Bytes Bytes/open
WCsc +1,999 0 +1,999 +767,616 384.0
WCfc +1,999 0 +1,999 +415,792 208.0
WCri +1,999 0 +1,999 +740,928 370.6
FMwi +1,999 0 +1,999 +255,872 128.0
FMfc +1,884 0 +1,884 +361,728 180.9
WCfn +1,999 +1,999 0 0 0
WCse/WCss +6,589 +4,590 +1,999 +127,936 64.0
FMfn +68,551 +63,425 +5,126 +521,728 261.0

Every one of the 1,999 files in that run resolved successfully, which is why the counts line up
1:1 with the file count. The general form is the FSCTL invariant above: one leaked stream
context, one file context, one reparse buffer and one FltMgr work item per successfully-resolving
reparse open
, plus an FltMgr file-list-ctrl at 1,884/1,999 = 0.94 (it is allocated per file
list, not per open) - and exactly one correctly released WCfn alongside each.

Do not read this as one leak per file enumerated. That ratio is not constant (1.00 here, 0.71
on a full live-layer pass, 0.27 on the traced subset) because it depends on how many entries were
already touched this boot and on how many chain hops each resolution traverses. The
per-hop/per-FSCTL ratio is the constant.

Permanently leaked pool across the five never-freed tags: ~1,272 bytes per successfully-
resolving open
(~1.3 KB); ~1.5 KB including the correlated FltMgr name-cache growth. Each also
strands one kernel handle in the System process (PID 4).

Note on the FMfn row: that window was measured on a live machine, so the name-cache delta is
not isolated to the reproducer. It is reported as measured and as correlated, not as attributed.

Expected behavior

wcifs releases its allocation reference after FltSetStreamContext / FltSetFileContext
succeed, so the contexts are owned solely by the stream/file and are freed when the stream is
torn down - exactly as WCsh already is. Kernel pool attributable to WCsc, WCfc, WCri,
FMwi and FMfc should be bounded by the number of currently open WCI-projected files, not by
the number of opens performed since boot.

Configuration

  • Edition: Windows 11 Pro, version 25H2, build 10.0.26200.9168 (LCU KB5121003,
    2026-08-11; SSU KB5123304). Client, not Server.
  • Base image / container engine: not required to reproduce. Present on the machine: Docker
    Desktop, WSL2, vmcompute / HNS / HvHost. The reproducer above uses only cmd.exe and the layer
    store on disk.
  • Affected binary: C:\Windows\System32\drivers\wcifs.sys, file version 10.0.26100.8972,
    size 259,544 bytes,
    SHA256 3c232e2012ed15caf633761d6d79ff2305c752b5e4a993bed3ffefc0866ba513.
    This hash is byte-identical to the wcifs.sys Microsoft ships in KB5121003. The binary is
    authentic, current and unmodified - this is not servicing corruption or a third-party
    replacement.
  • fltmgr.sys 10.0.26100.1, bindflt.sys 10.0.26100.8972, UnionFS.sys 10.0.26100.9168,
    cimfs.sys 10.0.26100.8972, ntoskrnl.exe 10.0.26100.9168.
  • No newer wcifs.sys exists for this branch. The only later build published is
    10.0.28000.2336, which is 26H1. Windows Update offers no fix on 25H2.
  • Every loaded minifilter is Microsoft in-box (bindflt, FsDepends, UCPD, WdFilter,
    storqosflt, wcifs, gameflt, CldFlt, bfs, FileCrypt, luafv, UnionFS, npsvctrig, Wof, FileInfo).
    No third-party minifilter participates. UnionFS and CldFlt have zero instances.
  • wcifs instances (from fltmc instances): C: at altitude 189900 ("wcifs Instance"),
    C: at 189899 ("wcifs Outer Instance"), E: at 189900. D: is an NTFS volume with no wcifs
    instance at all
    , and shows none of this growth - consistent with the leak being reachable
    only where wcifs is attached.

Impact

On this machine, at the point the peak figures below were taken - ~3 days into the boot that began
2026-08-18 00:46:18 +02:00, with routine container-isolation file activity:

  • 7,026,883 handles in the System process (PID 4) against a normal steady state of ~5,000.
  • ~48 GB of kernel pool (34 GB paged + 14 GB nonpaged) on a 128 GB machine.
  • Commit charge reached 99.19% with Available MBytes = 0.
  • Windows raised "Virtual Memory Minimum Too Low".
  • Process creation began failing with STATUS_COMMITMENT_LIMIT.
  • Collateral: the Filter Manager name cache (FMfn) reached 28,987,390 outstanding entries /
    16.5 GiB
    , the single largest consumer on the system, apparently pinned by the same object
    graph.
  • Reclaimed only by reboot. Killing the user-mode processes stops further growth but returns
    nothing.

Current state of that same boot, re-read 2026-08-23 16:30:55 +02:00 (5 d 15 h uptime, still not
rebooted):
7,486,978 handles in PID 4; 50.9 GiB of kernel pool (36.2 GiB paged +
14.6 GiB nonpaged); commit charge 117.9 GiB against a 147.0 GiB limit (80.2%), with 38.6 GiB
physical free. Commit pressure eased only because the user-mode processes driving the sweeps were
killed - none of the leaked pool came back, and both pool and handle counts are still rising.

Growth is linear in the number of successfully-resolving reparse opens against the attached
layer. For scale: one full pass over the live layer costs 231,887 contexts, so the 7,246,092
outstanding WCsc here is equivalent to roughly 31 exhaustive passes - which, given the
first-touch suppression described above, means the same files were re-leaked repeatedly within a
single boot rather than traversed 31 times.

Reproduction conditions: how this machine actually reached that state

This was not organic container workload, and saying so is material to reproducing it. We audited
the complete shell history that ran on the machine - 16,110 agent transcripts, 14,010 shell
invocations - and attributed the leak by System-process (PID 4) handle-count deltas against a
total of 7,483,315 handles:

Source Leaked handles Share
An 84-minute cmd /c dir C:\ /s /b with four wildcard patterns, 2026-08-18, during an unrelated file hunt 4,190,252 56.0%
A cmd /c dir /s directory-sizing sweep over every top-level directory of C:\, 2026-08-20 ~2,264,328 30.3%
Deliberate provocations by this investigation (bisection matrix, reproducer, controls) 387,564 5.2%
Unexplained 641,171 8.6%

91.4% of the leak traces to two sessions of an LLM coding agent (Claude Code) issuing recursive
sweeps rooted at a drive root
, neither of which mentions containers anywhere in the command.
35.4% of it - rows two and three, 2,651,892 handles - was inflicted by the diagnostic work
investigating the leak itself.
The sizing sweep in row two was an ordinary dir /s that was not
trying to provoke anything, and it cost 2.26 M unreclaimable handles in a single pass.

We state this plainly rather than burying it, because it is the reproduction condition: any tool
that recursively walks a drive root on an affected machine hits this
- backup agents, search
indexers, AV full scans, disk-usage utilities, build systems, coding agents. Nothing about the
call site is exotic. The reason it took two agent sessions to become visible here is volume, not
privilege or configuration.

Additional context

What would confirm the diagnosis on your side, cheaply

  1. The contexts should block unload. A minifilter cannot unload while stream contexts are
    outstanding. If this analysis is right, fltmc unload wcifs on an affected machine should
    hang or fail. We have not run it - on a machine holding 7.2 M leaked contexts it is likely to
    wedge - so it is offered as a lab test, not as evidence.
  2. Code review of the success path in WcGetSetContextFileObject, between
    WcGetSetFileContext / WcGetSetStreamContext and return, looking for the missing
    FltReleaseContext on both the newly allocated context and any OldContext handed back by
    FltSetStreamContext / FltSetFileContext.
  3. WCri's partial release is a hint. WCri is not never-freed globally - it shows 293,100
    lifetime frees (~7.4%) - but zero on the WCI reparse-open path. Whatever branch frees
    WCri elsewhere is likely adjacent to the branch that should be releasing the contexts.

Anticipated question: why is WCsc roughly 2x WCfc over the machine lifetime?

Lifetime: WCsc 7,246,092 vs WCfc 3,661,981 - very close to 2:1. The layer-chain result
above accounts for this cleanly, and we offer it as the likely reading rather than a proven one:
FLT_STREAM_CONTEXT is per stream, so a resolution that traverses two chain hops takes two of
them, while FLT_FILE_CONTEXT is per file and is taken once. A workload averaging ~2 hops per file
therefore produces exactly the observed 2:1. Two independent checks are consistent with it:
WCri outstanding (3,661,981) equals WCfc outstanding exactly, which is what you would
expect if both are per-file rather than per-hop; and the bounded ETW-traced subset, in which the
sampled files were single-hop, showed ΔWCsc = ΔWCfc = ΔWCri = 169 - a 1:1:1 ratio, as predicted.

WCse/WCss, FMwi and FMfc outstanding counts all agree with WCsc outstanding to within
0.1%, so the leaked objects track one coherent graph.

Capturing a kernel dump, if you want one

Nonpaged pool alone is ~14 GB on the affected machine, so dump sizing matters.

Preferred - live dump, no bugcheck, no reboot.
LiveKD (Sysinternals), using
native live-dump support:

livekd64.exe -ml -o D:\wcifs-live.dmp

-ml asks the OS for a consistent kernel snapshot without crashing the machine (Windows 8.1 and
later). Write to a volume with at least 64 GB free, ideally not C:.

Alternative - configured crash dump. SystemPropertiesAdvanced -> Startup and Recovery ->
Write debugging information -> Kernel memory dump. The page file must be large enough to hold
kernel memory; with 34 GB paged + 14 GB nonpaged, set a fixed page file of at least 64 GB, or
use HKLM\SYSTEM\CurrentControlSet\Control\CrashControl\DedicatedDumpFile (REG_SZ, e.g.
D:\dedicateddump.sys) with DumpFileSize to place it on another volume. A kernel dump captures
resident kernel memory only, so paged-out paged pool will not be present; a complete memory dump
would be ~128 GB here.

Analysis commands we would run on it:

!poolused /t20 2                     top paged-pool consumers by tag
!poolused /t20 4                     top nonpaged-pool consumers by tag
!poolused 2 WCsc                     the leaking stream context tag
!poolfind WCsc                       locate live allocations (slow across a 48 GB pool)
!fltkd.filters                       enumerate registered minifilters
!fltkd.filter <wcifs FLT_FILTER>     wcifs registration incl. context registration table
!fltkd.volumes
!fltkd.instances
!fltkd.contextlist <stream ctx list>
!process 4 0                         System handle count
!handle 0 3 4 File                   stranded File handles in System
!vm 4

Pool tagging is on by default on this build; if it needs forcing, gflags /r +ptg (reboot
required).

Related public report - same failure mode, never resolved

moby/moby#39476 (opened 2019-07-09): unbounded paged
pool growth on Windows containers, poolmon implicating ntfs.sys, bindflt.sys and
fltmgr.sys, on Windows Server 2019 / Windows 10 1903. No Microsoft response appears in the
thread; it was closed without a resolution, and the behaviour is still present seven years and
several Windows releases later. That report lacked call stacks and a tag-level control, which is
why both are supplied here.

Data available on request

Full symbolized stacks, the context registration decode, the raw before/after pool tag tables
(all ~1,500 tags), filter stack enumeration, per-volume callback attribution and the binary
hashes are captured and can be attached to this issue or shared privately.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions