microsoft / microsoft/WSL

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

Open
#41,644 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
C++
Stars
33.7k
Forks
1.8k
Avg merge
3d 17h
Merged PRs (30d)
116

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 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 microsoft/WSL#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
microsoft/WSL#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:

```powershell
# 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:

```powershell
(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 |
|---|---|---|
| microsoft/WSL#40804 | **CLOSED / NOT_PLANNED** | ntfs.sys `NtFC`, 21–23 GB/hr with WSL2. Closed 2026-06-17 by MS. |
| microsoft/WSL#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`.*

Contributor guide

Open the contributing guide

Research direction

Start by running the PowerShell pool-tag query and confirming the NtFC reading on the stated Windows build. Compare WSL2/Docker VHDX activity with the idle CoworkVMService path, then review microsoft/WSL#40804, #41296, and Windows-Containers#646. Done requires a reproducible attribution and a confirmed fix or mitigation.

Written by the indexing model from the issue text.

Assessment

Tech stack
docker, powershell
Domain
infrastructure, operating-systems
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.