microsoft / microsoft/terminal

Crash: null `IControlInteractivity` dereferenced in `TermControl::_PointerMovedHandler` (AV read @ 0x0)

Open
#20,477 2 comments 1 reaction 0 assignees View on GitHub
Area-TerminalControl Issue-Bug Priority-3 Product-Terminal Severity-Crash
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.