Skip to content

ntfs.sys NtFC non-paged pool leak persists on 26200.9457 (re #40804, closed NOT_PLANNED) #41644

Open

Description

@luxueqi123

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 as NOT_PLANNED on
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.sys pool 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
# -> 23322

7. 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 --shutdown does 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

  1. Is this a known regression in the 26200.x servicing branch?
  2. Can the NTFS FCB release path be fixed to free NtFC on final
    Hyper-V handle close, or can wcifs be fixed to release its
    stream/file contexts?
  3. If a fix is planned, what is the target servicing build?
  4. In the interim, is a supported way to force-release pinned NtFC
    structures without a full restart? (wsl --shutdown is 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

  1. github-actions commented on Sep 18, 2026

    @github-actions
    Contributor

    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:

    Invoke-WebRequest -UseBasicParsing "https://raw.githubusercontent.com/microsoft/WSL/master/diagnostics/collect-wsl-logs.ps1" -OutFile collect-wsl-logs.ps1
    Set-ExecutionPolicy Bypass -Scope Process -Force
    .\collect-wsl-logs.ps1
    

    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 networking instead, following the instructions in Collect WSL logs for networking issues

    Once 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'.

  2. luxueqi123 commented on Sep 18, 2026

    @luxueqi123
    Author

    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-logs requires starting WSL. WSL 2 defaults to ~50% of physical RAM (≈15.9 GB on this 32 GB host). Current state:

    Available physical : 273 MB
    Non-paged pool     : 23377 MB   (NtFC = 21353 MB)
    

    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

    1. Restart (a shutdown + power-on is insufficient here — HiberbootEnabled = 1, so Fast Startup restores the hibernated kernel session and the pool returns unchanged)
    2. Confirm the pool is actually clear post-boot
    3. Start the pool-tag monitor (NtQuerySystemInformation class 22 time series)
    4. Run collect-wsl-logs.ps1 -LogProfile storage
    5. Attach the archive

    I am using storage rather than networking because this is a non-paged pool / NTFS block-I/O issue, not a network issue. -RestartWslReproMode is deliberately not used, since it calls Remove-HnsNetwork and 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. NtFC is readable without elevation, so this half of the evidence does not depend on the collector at all.

    Cross-reference: #41296, microsoft/Windows-Containers#646.

  3. chemwolf6922 commented on Sep 21, 2026

    @chemwolf6922
    Contributor

    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?

  4. 3nterTheV0id commented on Nov 15, 2026

    @3nterTheV0id

    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):

    • Docker Desktop + WSL2: 9,607 / 19,383
    • WSL2 VM only: 4,682
    • Neither: 802 / 75

    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:

    ExAllocatePool2+0x8a
    ExAllocatePoolWithTag+0xa4
    NtfsGetReparsePointValue+0x85c
    NtfsCommonCreate+0x194d
    NtfsFsdCreate+0x2c4
    IofCallDriver+0x84
    FltpLegacyProcessingAfterPreCallbacksCompleted+0x16d
    FltpCreate+0x700
    IofCallDriver+0x84
    IopParseDevice+0xf5f
    ObpLookupObjectName+0xbcd
    ObOpenObjectByNameEx+0x224
    IopCreateFile+0x4d6
    IoCreateFileEx+0x13d
    FltpCreateFile+0x16a
    FltCreateFileEx2+0xe3
    BfpDoesFileExistWithCrossVolumeECP+0xe7
    BfPreCreate+0xd28
    FltpPerformPreCallbacksWorker+0x583
    FltpPassThroughInternal+0xc0
    FltpCreate+0x6ea
    IofCallDriver+0x84
    IopParseDevice+0xf5f
    ObpLookupObjectName+0xc95
    ObOpenObjectByNameEx+0x224
    IoQueryInformationByName+0x225
    

    The same Ntfs allocation through bindflt's other nested-open path (BfCreateFile) is always freed (101k allocations), while the BfpDoesFileExistWithCrossVolumeECP path 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.

  5. 3nterTheV0id commented on Nov 15, 2026

    @3nterTheV0id

    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-All and 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 leaking BfpDoesFileExistWithCrossVolumeECP path is only reached when WSL2 runs on Virtual Machine Platform without full Hyper-V.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    externalIssue exists outside of WSL components

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions