Repository navigation
Description
NtFC Non-Paged Pool Leak — Evidence Report
Machine: Windows 11 Pro 25H2, build 26200.9457
Captured: 2026-09-18 15:39 (CST)
Status: 21.35 GB pinned in non-paged pool; currently static (not advancing)
Follow-up to #40804 (
ntfs.sys NtFC non-paged pool leak: ~21 GB/hr with WSL2 on Windows 11 26200.x), which was closed asNOT_PLANNEDon
2026-06-17 without a stated fix plan.This report confirms the defect is still present on the latest servicing
build (26200.9457 + KB5129195 installed 2026-09-17), adds two pieces of
evidence not present in the original thread, and restates the ask.Also relevant to #41296 (
wcifs.syspool leak) and
microsoft/Windows-Containers#646.
0. Environment (issue template fields)
| Field | Value |
|---|---|
| Windows Version | Microsoft Windows [Version 10.0.26200.9457] |
| WSL Version | 2.7.10.0 |
| WSL 1 or WSL 2? | WSL 2 |
| Kernel Version | 6.18.33.2-2 |
| Distro Version | docker-desktop (v2), Ubuntu-24.04 (v2) |
| Other Software | Docker Desktop (VHDX 34.9 GB), Claude Desktop CoworkVMService |
| Field | Value |
|---|---|
| OS Edition | Windows 11 Pro, 25H2, build 26200.9457 |
| CPU / RAM | Intel Core i7-7700HQ / 32 GB |
ntfs.sys |
10.0.26100.8875 |
wcifs.sys |
10.0.26100.9278 (Start=2, altitude 189900) |
| Fast Startup | Enabled (HiberbootEnabled = 1) |
1. Summary
NtFC (owned by ntfs.sys) holds 21,353.2 MB in non-paged pool on a
32 GB machine, with no user-space process accounting for it. All 384
processes' working sets sum to 7,301 MB. The kernel pool is ~3x larger than
all user-space memory combined.
Available physical memory: ~500 MB.
Stability evidence: NtFC read 21,353.2 MB byte-identical across two
independent captures ~1 hour apart (14:37 and 15:39 CST), while aggregate
non-paged pool and available memory fluctuated by tens of MB. This
distinguishes the condition from a normal cache: the 21 GB is pinned, not
merely resident, and is not being recycled.
This matches the pattern reported in #40804 and
microsoft/Windows-Containers#646 (wcifs.sys retaining
FLT_STREAM_CONTEXT / FLT_FILE_CONTEXT references above NTFS, preventing
ntfs.sys from freeing FCBs tagged NtFC).
2. Memory state
| Metric | Value |
|---|---|
| Physical total | 32,619 MB (31.85 GB) |
| Physical used | 32,106 MB (98.4 %) |
| Physical available | 513 MB |
| Commit charge | 45,086 MB / 62,292 MB |
| Non-paged pool | 23,322 MB |
| Paged pool | 1,119 MB |
| Kernel pool total | 24,441 MB |
| SUM of all process working sets | 7,301 MB |
| SUM of all process private bytes | 17,190 MB |
| Process count | 384 |
| Uptime | ~22 h |
The kernel holds more memory than every user-space process combined.
3. Pool tag attribution
Query method: NtQuerySystemInformation(SystemPoolTagInformation) = class 22,
structure stride 40 bytes (x64), NonPagedUsed at +32.
Total tagged non-paged pool: 21,982 MB
Top 15 non-paged tags
| Tag | NonPaged (MB) | Share |
|---|---|---|
| NtFC | 21,353.2 | 97.1 % |
| HalD | 64.4 | 0.29 % |
| EtwB | 57.9 | 0.26 % |
| smCB | 44.4 | 0.20 % |
| NVRM | 43.6 | 0.20 % |
| smNp | 40.6 | 0.18 % |
| Cont | 30.8 | 0.14 % |
| FMsl | 26.2 | 0.12 % |
| ConT | 25.0 | 0.11 % |
| Thre | 23.6 | 0.11 % |
| smBt | 21.4 | 0.10 % |
| File | 11.6 | 0.05 % |
| NtxI | 11.4 | 0.05 % |
| k1o | 11.1 | 0.05 % |
| NvKP | 10.9 | 0.05 % |
NtFC = 97.1 % of all tagged non-paged pool, = 65.5 % of installed RAM.
Tag owner confirmed by binary scan: the 4-byte sequence 4E 74 46 43 is
present in C:\Windows\System32\drivers\ntfs.sys. NtFC is listed in
Microsoft's pool tag reference as "NTFS File Context" (also cited as
"ntfs.sys Fcb" on some builds).
Paged pool (for contrast — this is not the leak)
| Tag | Paged (MB) |
|---|---|
| MmSt | 83.8 |
| Toke | 68.8 |
| NtfF | 62.4 |
| FMfn | 52.5 |
| Gpbm | 45.2 |
Paged pool is unremarkable (1,119 MB total). For contrast, the machine in
#41296 (comment by spencer-shadley) showed 30 GB of paged
pool dominated by Toke / FMfn — a different manifestation. Here the
anomaly is exclusively non-paged and exclusively NtFC.
4. Growth rate
60-second observation window:
NP pool T0 : 23518 MB
NP pool T1 : 23518 MB
Delta over 60s : 0.066 MB => 4.0 MB/hr => 95.6 MB/day
Interpretation : STATIC - leak not currently advancing
Earlier 15-second and 120-second windows showed +0.03 MB and +0.34 MB
respectively. The 21 GB is pinned/retained, not actively growing right now.
NtFC itself did not move at all between captures: 21,353.2 MB at both
14:37 and 15:39 CST. Only aggregate pool moved by rounding-level amounts.
This is the signature of retention, not of an active leak.
Growth is expected to resume at the reported 21–23 GB/hr once the triggering
workload (WSL2 / VHDX I/O) runs again — this machine had WSL stopped during
the entire observation window, which is why the rate reads ~0.
5. Environment — why this machine matches the trigger profile
Filter drivers
| Driver | File version | Service Start | Group |
|---|---|---|---|
ntfs.sys |
10.0.26100.8875 | 3 (Manual) | Boot File System |
wcifs.sys |
10.0.26100.9278 | 2 (Auto — always loaded) | FSFilter Virtualization |
bindflt.sys |
10.0.26100.9278 | 2 (Auto) | FSFilter Top |
fltmgr.sys |
10.0.26100.8875 | — | — |
vhdmp.sys |
10.0.26100.8875 | 3 (Manual) | SCSI miniport |
wcifs.sys sits at altitude 189900, above NTFS. Per the analysis in
microsoft/Windows-Containers#646, a minifilter holding a stream context on a
file prevents NTFS from freeing that FCB — the FCB is still referenced.
WSL2 / Docker
WSL version : 2.7.10.0
WSL kernel : 6.18.33.2-2
WSLg : 1.0.73.2
Distros : docker-desktop (v2), Ubuntu-24.04 (v2)
wslservice : Running, StartType = Automatic
Docker Desktop : HKCU\...\Run autostart enabled
Virtual disk placement (the reported trigger)
All VHDX files are on NTFS volumes. Note the Docker path is a
junction — C:\Users\23546\AppData\Local\Docker\wsl is a reparse point
to E:\UserData\Docker\wsl-data, so the payload physically lives on E:.
| VHDX | Size | Actual location |
|---|---|---|
docker_data.vhdx |
34.9 GB | E:\UserData\Docker\wsl-data\disk\ (via C: junction) |
main\ext4.vhdx |
0.1 GB | E:\UserData\Docker\wsl-data\main\ (via C: junction) |
Ubuntu-24.04-ext4.vhdx |
4.9 GB | E:\UserData\WSL\Ubuntu-24.04\ |
rootfs.vhdx (Claude VM) |
7.7 GB | E:\UserData\Claude\vm_bundles\claudevm.bundle\ |
sessiondata.vhdx (Claude VM) |
0.7 GB | E:\UserData\Claude\vm_bundles\claudevm.bundle\ |
Volume layout: C: 231.8 GB NTFS, D: 931.5 GB NTFS, E: 931.5 GB NTFS.
Journalled junction resolution:
> Get-Item 'C:\Users\23546\AppData\Local\Docker\wsl' | Select LinkType, Target
LinkType : Junction
Target : E:\UserData\Docker\wsl-data
WSL registry BasePath values confirm: docker-desktop resolves to
\\?\C:\Users\23546\AppData\Local\Docker\wsl\main (the junction path),
Ubuntu-24.04 to E:\UserData\WSL\Ubuntu-24.04.
Upstream report #40804 identifies Hyper-V VHDX block I/O against NTFS as
the dominant source, not the 9P/DrvFS bridge.
Second VHDX consumer present: Claude Cowork VM
This machine also runs a second, independent Hyper-V-backed VM
subsystem that holds VHDX files open:
Service : CoworkVMService ("Claude")
Status : Running
StartType: Automatic
Bundle : E:\UserData\Claude\vm_bundles\claudevm.bundle\
rootfs.vhdx 7.7 GB, sessiondata.vhdx 0.7 GB, smol-bin.vhdx 36 MB
This is directly relevant: anthropics/claude-code#94198 reports
"CoworkVMService non-paged pool leak (NtFC/ntfs.sys) — Restart clears it,
Shutdown does not", and #91265 reports the same service causing
persistent kernel pool growth on Windows.
At capture time no VM was actively running (vmmem/vmwp absent,
Msvm_ComputerSystem VM count = 0; vmcompute running as a stub), so this
service is a latent second trigger path, not the active cause of the
current retained pool. It is listed here because it must be accounted for
when attributing the leak and when testing any workaround.
Users diagnosing this on Claude Desktop machines should therefore isolate
which of the two VM subsystems (WSL2/Docker vs Cowork) drives a given
growth episode — the pool tag is the same for both.
Updates installed (bug still present)
| KB | Type | Installed |
|---|---|---|
| KB5129195 | Security Update | 2026-09-17 |
| KB5126052 | Update | 2026-09-09 |
| KB5124007 | Security Update | 2026-09-08 |
| KB5095189 | Update | 2026-06-25 |
Build is 26200.9457 — the most recent CU does not fix this.
Fast Startup is ON
HiberbootEnabled = 1
This matters: a shutdown + power-on restores the hibernated kernel
session and the 21 GB comes back. Only a true restart clears it.
Users should be told this explicitly — it is a common failure mode when
following "reboot to fix it" advice.
6. Reproduction / verification steps
Any reader can reproduce the measurement:
# Pool tag table (no admin required)
Add-Type -TypeDefinition @"
using System; using System.Runtime.InteropServices;
public class P { [DllImport("ntdll.dll")] public static extern int
NtQuerySystemInformation(int c, IntPtr b, int l, out int r); }
"@
$buf = [Runtime.InteropServices.Marshal]::AllocHGlobal(1MB)
$r = 0
[P]::NtQuerySystemInformation(22, $buf, 1MB, [ref]$r)
$n = [Runtime.InteropServices.Marshal]::ReadInt32($buf, 0)
for ($i=0; $i -lt $n; $i++) {
$o = 8 + $i*40
$t = -join (0..3 | ForEach-Object { [char][Runtime.InteropServices.Marshal]::ReadByte($buf,$o+$_) })
$np = [Runtime.InteropServices.Marshal]::ReadInt64($buf, $o+32)
if ($np -gt 100MB) { "{0,-6} {1,10:N1} MB" -f $t, ($np/1MB) }
}Expected (current machine): NtFC at ~21,353 MB.
Corroborating counters:
(Get-CimInstance Win32_PerfRawData_PerfOS_Memory).PoolNonpagedBytes / 1MB
# -> 233227. Impact
- On a 32 GB machine, 65 % of RAM is permanently unavailable until restart.
- Available physical memory sits at 513 MB; any memory spike
(a build, a large browser session, a container start) can trigger
OOM or a full system freeze. wsl --shutdowndoes not release it (measured upstream: ~6.5 GB residual).- A shutdown + power-on does not clear it either on this machine:
HiberbootEnabled = 1(Fast Startup) restores the hibernated kernel
session, bringing the 21 GB straight back. Only a true restart clears it —
and it returns as soon as WSL2 is used again.
8. Workarounds available to the user (none fix the root cause)
| Measure | Effect |
|---|---|
| Restart (not shutdown) | Clears the 21 GB. Bug returns on next WSL2 use. |
wsl --shutdown when idle |
Stops further growth; does not release existing pool. |
| Disable Docker Desktop autostart | Reduces exposure window. |
| Keep VHDX off the busiest NTFS volume | Reduces trigger frequency (workload-dependent). |
| Non-paged pool watchdog → restart at threshold | Contains impact of OOM; does not fix. |
Disabling wcifs (Start=4) is theoretically the nearest thing to a
root-cause avoidance, but it would break Windows Containers, Windows
Sandbox, MSIX mount paths, and likely Docker Desktop. Not recommended
and not tested here.
9. Upstream references
| Issue | State | Notes |
|---|---|---|
| #40804 | CLOSED / NOT_PLANNED | ntfs.sys NtFC, 21–23 GB/hr with WSL2. Closed 2026-06-17 by MS. |
| #41296 | OPEN | wcifs.sys pool leak, DrvFS path. Last update 2026-09-16. |
| microsoft/Windows-Containers#646 | OPEN | wcifs.sys leaks FLT_STREAM_CONTEXT / FLT_FILE_CONTEXT refs on every WCI reparse-point open. |
| microsoft/Windows-Sandbox#126 | OPEN | Sandbox sessions leak kernel file handles in host System process. |
| openai/codex#16786 | OPEN | Repeated git ls-files spawn → NtFC growth. |
| anthropics/claude-code#94198 | OPEN | CoworkVMService non-paged pool leak. This machine runs that service. |
| anthropics/claude-code#91265 | OPEN | Cowork VM causes persistent kernel pool leak (Toke/File/SeAt). |
The wcifs-above-NTFS ownership theory is from a contributor analysis in
#41296: five wcifs context tags (WCsc, WCfc, WCse, WCss, WCri)
settle at identical live counts with 0 frees, while WCfn on the same
per-file path frees 100 % — implying contexts are pinned, not leaked in
the NTFS free path.
10. Ask
- Is this a known regression in the 26200.x servicing branch?
- Can the NTFS FCB release path be fixed to free
NtFCon final
Hyper-V handle close, or canwcifsbe fixed to release its
stream/file contexts? - If a fix is planned, what is the target servicing build?
- In the interim, is a supported way to force-release pinned
NtFC
structures without a full restart? (wsl --shutdownis insufficient.)
Evidence can be extended on request: PoolMon / pool-tag time series,
fltmc filters + fltmc instances output (requires elevation), WPR ETL
storage trace, or a live allocation-stack capture on a second debug host.
Generated from automated instrumentation on the affected machine.
Raw snapshot: evidence-snapshot.txt.
Activity
github-actions commentedon Sep 18, 2026
Logs are required for review from WSL team
If this a feature request, please reply with '/feature'. If this is a question, reply with '/question'.
Otherwise, please attach logs by following the instructions below, your issue will not be reviewed unless they are added. These logs will help us understand what is going on in your machine.
How to collect WSL logs
Download and execute collect-wsl-logs.ps1 in an administrative powershell prompt:
The script will output the path of the log file once done.
If this is a networking issue, please use
.\collect-wsl-logs.ps1 -LogProfile networkinginstead, following the instructions in Collect WSL logs for networking issuesOnce completed please upload the output files to this GitHub issue.
See Collect WSL logs (recommended method).
If you choose to email these logs instead of attaching them to the bug, please send them to wsl-gh-logs@microsoft.com with the GitHub issue number in the subject, and include a link to your GitHub issue comment in the message body, and reply with '/emailed-logs'.
luxueqi123 commentedon Sep 18, 2026
Logs will follow. Two notes on why they cannot be attached from the current session, and one observation that may be worth recording.
The defect currently prevents its own diagnosis
Collecting
wsl-logsrequires starting WSL. WSL 2 defaults to ~50% of physical RAM (≈15.9 GB on this 32 GB host). Current state:Starting WSL right now would immediately OOM / hard-freeze the machine. So the retained pool blocks the very collection needed to report it. That is a practical consequence worth noting beyond the raw memory numbers: the failure mode is self-concealing, and users hitting it may be unable to file a complete report at all.
Planned collection sequence
HiberbootEnabled = 1, so Fast Startup restores the hibernated kernel session and the pool returns unchanged)NtQuerySystemInformationclass 22 time series)collect-wsl-logs.ps1 -LogProfile storageI am using
storagerather thannetworkingbecause this is a non-paged pool / NTFS block-I/O issue, not a network issue.-RestartWslReproModeis deliberately not used, since it callsRemove-HnsNetworkand resets the WSL network.One thing the logs may not show
Because the pool is only released by a restart, any log captured before a restart reflects a machine that is already in the pathological state; and any log captured after a restart necessarily starts from a clean pool. The growth only becomes visible once WSL/VHDX I/O resumes.
For that reason I am capturing a pool-tag time series alongside the WSL logs, so the archive contains both the WSL diagnostics and the growth curve that attributes them to
NtFC.NtFCis readable without elevation, so this half of the evidence does not depend on the collector at all.Cross-reference: #41296, microsoft/Windows-Containers#646.
chemwolf6922 commentedon Sep 21, 2026
Hi luxueqi123 . Please file this in the Windows feedback center. Thanks.
Hi Ben Hillis (@benhillis) . Do we have any updates on this from the NTFS owners?
3nterTheV0id commentedon Nov 15, 2026
Same NtFC nonpaged pool leak here, with an allocation stack that may pin it down.
Env: Windows 11 Pro 26H2, build 10.0.26300.9974 · bindflt.sys 10.0.26100.9974 · ntfs.sys 10.0.26100.8875 · WSL 2.3.26.0 (kernel 5.15.167.4-1) · Docker Desktop 4.99.0 (WSL2 backend) · Core Ultra 7 155H, 32 GB. Workload files are on a ReFS Dev Drive VHDX (stored on NTFS C:).
Trigger: file-existence checks from native Windows processes (python.exe, git.exe, Git-for-Windows bash) while the WSL2 VM is up. NtFC reached 8.9 GB / 23.5M live allocations before a reboot.
Leaked NtFC allocations per run (same workload: os.path.exists over a repo ×3 + 20×
git status):The workload also runs ~2× faster with WSL2 down.
WPR trace (184 s, tag NtFC, with stacks): net +64,898 allocations (≈8.5 MB/min), 99.6% 400-byte blocks. Every never-freed allocation comes through one path:
The same Ntfs allocation through bindflt's other nested-open path (
BfCreateFile) is always freed (101k allocations), while theBfpDoesFileExistWithCrossVolumeECPpath never is (64.7k). This looks like a missing free of the reparse buffer in the bindflt cross-volume existence check.Workaround:
wsl --shutdown/ stop Docker Desktop when not needed, then reboot. The 227 MB ETL and per-run CSV are available on request.3nterTheV0id commentedon Nov 15, 2026
Update: enabling full Hyper-V made the leak disappear. Until now this machine had only Virtual Machine Platform (the WSL2 default), not the full Hyper-V feature. After
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-Alland a reboot, the same fixed workload leaked 0 NtFC allocations with Docker Desktop's WSL2 engine running, including with a container bind-mounting the Dev Drive. NtFC stayed at 16 MB. The leak was still present after an earlier plain reboot the same day, so the reboot alone doesn't explain it. This suggests the leakingBfpDoesFileExistWithCrossVolumeECPpath is only reached when WSL2 runs on Virtual Machine Platform without full Hyper-V.