microsoft / microsoft/terminal
Crash: null `IControlInteractivity` dereferenced in `TermControl::_PointerMovedHandler` (AV read @ 0x0)
- Dominant language
- C++
- Stars
- 105k
- Forks
- 9.6k
- Avg merge
- 3d 17h
- Merged PRs (30d)
- 29
Description
### Windows Terminal version
1.24.11911.0
### Windows build number
10.0.26200.8893
### Other Software
Nothing relevant — this is not an interaction bug with other software. Included for completeness:
- Host: LG Gram 17Z90S-H, Intel Core Ultra 7 155H, Intel Arc iGPU (driver 31.0.101.5382)
- Windows 11 Pro 25H2 (10.0.26200.8893)
- Default profile is PowerShell 7 (`Windows.Terminal.PowershellCore`); WSL Fedora 42 also in use
I specifically verified that **no third-party code was loaded into the crashed process**. The minidump's module list has 131 entries; the only non-Microsoft modules are the Intel graphics UMD (`igd10iumd64.dll`, `igc64.dll`, `igdgmm64.dll`) and `icu.dll`. `AppInit_DLLs` is empty. There is no keyboard-hook / security-agent / injector involved.
Non-default settings, in case any of them matter: `backgroundImage: desktopWallpaper` + `backgroundImageOpacity: 0.2/0.15`, `FiraCode Nerd Font Mono`, `experimental.detectURLs: false`. Notably **`experimental.repositionCursorWithMouse` is NOT set**, so it is at its default of `false` — mentioning this up front because #19667/#20433 were attributed to that feature and this crash happens without it.
### Steps to reproduce
I don't have a deterministic repro, so I'm filing a symbolized crash analysis instead. Two occurrences with matching root cause, both with dumps in hand.
What I can say about the conditions:
1. Normal interactive use with the mouse over a terminal pane.
2. The crash lands in the `PointerMoved` (mouse-move) routed-event path, so the mouse is moving over the control at the moment it dies.
3. It is a race against control/pane teardown — `_interactivity` has already been released while a XAML pointer event is still in flight. I could not force the timing by hand.
4. Frequency: 13 crashes on this machine between 2025-12-29 and 2026-07-28 (table in Actual Behavior). Terminal hosts every window/tab in one process, so each crash takes down all windows at once.
I have minidumps for both occurrences and can capture a TTD trace as well. The dumps contain environment blocks and terminal memory, so I'd rather not attach them to a public issue — happy to send them privately by email, or to file a Feedback Hub report so you can pull them off the backend. Just say which you'd prefer.
### Expected Behavior
`TermControl::_PointerMovedHandler` should tolerate `_interactivity` being empty — either null-check before calling into it, or detach the `PointerMoved` handler before the `ControlInteractivity` object is released, so an in-flight XAML pointer event cannot reach a torn-down control.
Either way, moving the mouse should never be able to terminate the process.
### Actual Behavior
`TermControl::_PointerMovedHandler` invokes `_interactivity.PointerMoved(...)` while `_interactivity` is **null**. The C++/WinRT `consume_*` stub reads the vtable pointer from address 0 and the process dies instantly.
Analyzed with `cdb` against the public Microsoft symbol server.
### Exception record
```
ExceptionAddress: 00007ffd79bc031c
(Microsoft_Terminal_Control!winrt::impl::consume_Microsoft_Terminal_Control_IControlInteractivity
::PointerMoved+0x54)
ExceptionCode: c0000005 (Access violation)
NumberParameters: 2
Parameter[0]: 0000000000000000 <- read
Parameter[1]: 0000000000000000 <- address
Attempt to read from address 0000000000000000
AV.Dereference: NullPtr
AV.Type: Read
FAILURE_BUCKET_ID: INVALID_POINTER_READ_c0000005_Microsoft.Terminal.Control.dll!
winrt::impl::consume_Microsoft_Terminal_Control_IControlInteractivity_
winrt::Microsoft::Terminal::Control::IControlInteractivity_::PointerMoved
```
A second WER report for the same PID follows two seconds later with `c000041d` (`FATAL_USER_CALLBACK_EXCEPTION`) at the identical offset — the same AV escaping the `KiUserCallbackDispatcher` window-proc callback.
### Call stack
```
Microsoft_Terminal_Control!...consume_..IControlInteractivity::PointerMoved+0x54 <-- AV, read @ 0x0
Microsoft_Terminal_Control!...implementation::TermControl::_PointerMovedHandler+0x255
Microsoft_Terminal_Control!...TermControlT::Connect::__l22::::operator()+0x23 (inlined)
Microsoft_Terminal_Control!winrt::impl::delegate::Invoke+0x41
Windows_UI_Xaml!CEventManager::RaiseRoutedEvent+0x258
Windows_UI_Xaml!ContentRootInput::PointerInputProcessor::ProcessPointerInput+0x1101
Windows_UI_Xaml!CInputServices::ProcessInput+0x96
Windows_UI_Xaml!CCoreServices::ProcessInput+0x27 (inlined)
Windows_UI_Xaml!CXcpBrowserHost::HandleInputMessage+0x145
Windows_UI_Xaml!CJupiterControl::HandlePointerMessage+0xc1 (inlined)
Windows_UI_Xaml!CJupiterWindow::OnIslandPointerMessage+0x1aa
Windows_UI_Xaml!CXamlIslandRoot::InjectPointerMessage+0x4c (inlined)
Windows_UI_Xaml!CXamlIslandRoot::OnIslandPointerMoved+0x4c (inlined)
InputHost!PointerInputObserverWinRT::OnWindowMessage_Callback+0x12f
InputHost!SystemIslandInputSiteWinRT::OnWindowMessage_Callback+0x2ed
InputHost!WindowsMessageDeliveryAdapter::ProcessWindowMessage_NoLock_Callback+0x37c
user32!UserCallWinProcCheckWow+0x356
user32!DispatchMessageWorker+0x1dd
WindowsTerminal!
WindowsTerminal!wWinMain+0x1e2
```
Faulting module file version: `Microsoft.Terminal.Control.dll` 1.24.2607.10001, offset `0x10031c`.
### A second occurrence — same class, different call site
2026-06-30, Terminal 1.24.11321.0 (`TerminalApp.dll` 1.24.2605.12001):
```
ExceptionAddress: TerminalApp!winrt::impl::consume_TerminalApp_IPaneContent
::MinimumSize+0x2e
ExceptionCode: c0000005 Attempt to read from address 0000000000000000
FAILURE_BUCKET_ID: INVALID_POINTER_READ_c0000005_TerminalApp.dll!
winrt::impl::consume_TerminalApp_IPaneContent_winrt::TerminalApp::IPaneContent_::MinimumSize
```
Same shape: a projected WinRT interface is empty when a method is called on it — here a layout query against a pane whose content was already released. Also followed by a `c000041d` report at the identical offset.
### Crash history on this machine
13 incidents since 2025-12-29, all in the XAML/UI layer, across four Terminal versions:
| Date | Terminal (file ver) | Faulting module | Exception | Offset |
|---|---|---|---|---|
| 2026-07-28 | 1.24.2607.10001 | Microsoft.Terminal.Control.dll | c0000005 → c000041d | 0x10031c |
| 2026-06-30 | 1.24.2605.12001 | TerminalApp.dll | c0000005 → c000041d | 0xa96e |
| 2026-05-14 | 1.24.2604.2001 | Windows.UI.Xaml.dll | c0000005 | 0x3d96c |
| 2026-05-13 | 1.24.2604.2001 | Windows.UI.Xaml.dll | c0000005 | 0x3d96c |
| 2026-04-09 | 1.23.2601.21001 | Windows.UI.Xaml.dll | c0000005 | 0x9b970 |
| 2026-03-10 (×6) | 1.23.2601.21001 | Windows.UI.Xaml.dll | c0000005 | 0x1874a0 / 0x1874a6 |
| 2026-01-27 | 1.23.2512.16003 | Windows.UI.Xaml.dll | c000027b | 0x8fadf3 |
| 2026-01-26 | 1.23.2512.16003 | Windows.UI.Xaml.dll | c000027b | 0x8fadf3 |
| 2026-01-19 | 1.23.2512.16003 | TerminalApp.dll | c0000005 → c000041d | 0x1698a7 |
| 2026-01-15 | 1.23.2512.16003 | TerminalApp.dll | c0000005 → c000041d | 0x1698a7 |
| 2026-01-13 | 1.23.2512.16003 | Windows.UI.Xaml.dll | c000027b | 0x8fadf3 |
| 2026-01-09 / 01-08 / 01-07 | 1.23.2512.16003 | Windows.UI.Xaml.dll | c000027b | 0x8fadf3 |
| 2025-12-29 | 1.23.2512.16003 | Windows.UI.Xaml.dll | c000027b | 0x8fadf3 |
### Diagnostic identifiers (in lieu of a Feedback Hub link)
To be precise about what exists on your side: WER reported the **bucket signatures only**. All four reports came back with `Cab ID: 0` / `Cab Guid: 0` and this machine is on Basic (`AllowTelemetry=1`) diagnostic data, so **no cab was ever collected — there is no dump on the backend to pull**. The bucket records should still let you see how many other users are landing in the same bucket, which is why I'm including them:
| Date | Exception | Report ID | Bucket ID |
|---|---|---|---|
| 2026-07-28 | c0000005 | `f8ebcb9e-3354-404a-84c8-8a5b08767f1b` | `2057686308220423815` |
| 2026-07-28 | c000041d | `82735d47-fe5f-4b5b-9c9f-f863c0267a7e` | `1790230109502622945` |
| 2026-06-30 | c0000005 | `972a8989-2f41-439e-995c-a9be66e623d5` | — |
| 2026-06-30 | c000041d | `482c68fb-d8be-41ba-8c1b-7e9e50a334d3` | — |
### Still reachable in `main` (checked 2026-07-28)
The call site has no guard for an empty `_interactivity`:
- `src/cascadia/TerminalControl/TermControl.h:252` — the member is declared with null as its default:
```cpp
Control::ControlInteractivity _interactivity{ nullptr };
```
So "empty" is a representable state of this member, not an impossible one.
- `src/cascadia/TerminalControl/TermControl.cpp:272-273` — the converting constructor stores whatever it is handed, with no validation:
```cpp
TermControl::TermControl(Control::ControlInteractivity content) :
_interactivity{ content },
```
- `src/cascadia/TerminalControl/TermControl.cpp:2017-2042` — `_PointerMovedHandler` guards only on `_IsClosing()`, then calls straight through:
```cpp
if (_IsClosing()) { return; }
...
auto suppressFurtherHandling = _interactivity.PointerMoved(...); // line 2038, unguarded
```
- `src/cascadia/TerminalControl/TermControl.h:331-340` — `_IsClosing()` returns `_closing` and nothing else, so it cannot stand in for an emptiness check on `_interactivity`.
- `TermControl::Close()` calls `_interactivity.Close()` (`TermControl.cpp:2673`) but does not clear the member, so the null observed in the dump is not coming from that path — it is a `TermControl` whose `_interactivity` was already empty.
I have not been able to prove *which* path leaves it empty (detach/reattach and content-process attach are my suspicions), but the dump proves it happens, and the call site is unguarded regardless of how it got there.
### Why I don't think this duplicates #19667 / #20433
Adjacent area, but a different failure mode, and the fix that closed them does not touch this path:
- #19667 / #20433 were both closed by **PR #20442** ("Clamp the cursor position in bounds during 'reposition with click'", commit `de3fc87`). That PR is **+4/-1 in `src/cascadia/TerminalControl/ControlCore.cpp` only** — it does not touch `TermControl::_PointerMovedHandler`, `_interactivity`, or `IControlInteractivity`.
- Those issues were traced to the experimental `repositionCursorWithMouse` feature, and the failure there was a **thrown C++ exception** originating in `TextBufferCellIterator`.
- This is a **null interface pointer read at address 0** in the `consume_*` stub. Nothing is thrown; `_interactivity` is simply empty when `_PointerMovedHandler` runs. And `experimental.repositionCursorWithMouse` is unset (default `false`) on this machine.
I also could not find an existing issue for this signature — searches for `_PointerMovedHandler`, for a `PointerMoved` crash, and for `INVALID_POINTER_READ` + `Microsoft.Terminal.Control` all came back empty, and none of the currently open PRs that touch `TermControl.cpp` modify this handler.
### Also ruled out
- **GPU / display driver** — not a single graphics frame on the faulting stack; it is `user32` → `InputHost` → XAML input dispatch end to end.
- **OS-level fault** — no `Kernel-Power 41` / `6008`; the machine had 12 days of uptime spanning the crash. Only the Terminal process died.
Contributor guide
Research direction
Start with src/cascadia/TerminalControl/TermControl.h, TermControl.cpp, and the _PointerMovedHandler and Close paths identified in the report. Trace how _interactivity is initialized and released during pane or control teardown, then use the existing TerminalControl test and build entry points to verify that pointer movement cannot reach an empty interface; done means the reported null dereference is guarded or prevented and the regression is covered.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- desktop
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100