microsoft / microsoft/microsoft-ui-xaml
Use-after-free in UIAffinityReleaseQueue::DoCleanup - OnParentUpdated AddRefs freed peer (WASDK 2.2.0)
- Dominant language
- C++
- Stars
- 8.4k
- Forks
- 942
- Avg merge
- 2d 7h
- Merged PRs (30d)
- 105
Description
### Describe the bug
During `UIAffinityReleaseQueue::DoCleanup` on the render tick, teardown of a `CButton` subtree
reaches `CMultiParentShareableDependencyObject::RemoveParent -> CDependencyObject::OnParentChange -> FxCallbacks::FrameworkCallbacks_OnParentUpdated -> DirectUI::DependencyObject::OnParentUpdated -> DXamlCore::TryGetPeer/GetPeerPrivate -> ctl::addref_interface`, where the framework peer being
AddRef'd has **already been freed** — apparently by an earlier release in the same cleanup batch.
The indirect call goes through a dangling vtable, so the process dies in one of several ways
depending on where the wild call lands.
The failure is entirely within native WinUI teardown machinery — no managed or MAUI frames appear in the stack. The 2.2.0 fix for re-entrant dispatch during cleanup (`UIAffinityReleaseQueue_PauseDispatchDuringCleanup`) is present and active in this build but does **not** prevent this variant.
| # | Failure | WER bucket | Failure hash |
| --- | --- | --- | --- |
| 1 | `c0000409` / FAST_FAIL_GUARD_ICALL_CHECK_FAILURE | `FAIL_FAST_GUARD_ICALL_CHECK_FAILURE_c0000409_Microsoft.UI.Xaml.dll!DirectUI::DependencyObject::OnParentUpdated` | `{78134b6d-ebd8-ea31-78d2-e7b09c4158f4}` |
| 2 | `c0000005` read AV in `OnParentUpdated+0xf4`, freed object was `ctl::ComObject` | `INVALID_POINTER_READ_c0000005_...EasingColorKeyFrame_::Release` | `{5723fca6-977c-3977-b9a4-64fdfa2f13fb}` |
| 3 | `c0000005` **write** AV — the wild vtable call landed in `Microsoft.Graphics.Canvas` / `dcompi!CSurfaceFactory::CreateGradientSurface` and wrote through a bad pointer | `INVALID_POINTER_WRITE_c0000005_dcompi.dll!...CreateGradientSurface` | `{8d520244-b787-01ab-955e-aa7c571361d0}` |
| 4 | `c0000374` heap corruption detected later by LFH delayed-free validation during background heap compaction | `HEAP_CORRUPTION_ACTIONABLE_Internal_c0000374_ntdll.dll!RtlReportFatalFailure` | `{099f1bff-accd-a8c4-c18d-0881a82d9f4a}` |
All four share the identical mid-stack; six crash dumps collected within days. Bucket #3 shows the
defect is **memory-corruption class** (wild call into live code performing writes), not just a crash.
Representative stack (bucket 1; symbols from the public server, WASDK 2.1.3/2.2.0 — identical shape on both):
```
Microsoft_UI_Xaml!ctl::addref_interface (inline) <- freed peer
Microsoft_UI_Xaml!DirectUI::DXamlCore::GetPeerPrivate+0x101 [DXamlCore.cpp @ 1676]
Microsoft_UI_Xaml!DirectUI::DXamlCore::TryGetPeer (inline)
Microsoft_UI_Xaml!DirectUI::DependencyObject::OnParentUpdated+0x8f [DependencyObject.cpp @ 3896]
Microsoft_UI_Xaml!CMultiParentShareableDependencyObject::RemoveParent+0x215
Microsoft_UI_Xaml!CDependencyObject::ResetReferenceFromChild+0xba
Microsoft_UI_Xaml!CDependencyObject::ResetReferencesFromChildren+0xe5
Microsoft_UI_Xaml!CShape::~CShape+0x44 <- Rectangle inside the Button template
... (destructor cascade: Rectangle -> Image -> ContentPresenter -> Border -> Viewbox -> Grid -> Grid) ...
Microsoft_UI_Xaml!CButton::`scalar deleting destructor'
Microsoft_UI_Xaml!DirectUI::DependencyObject::DisconnectFrameworkPeerCore+0xd4
Microsoft_UI_Xaml!DirectUI::DependencyObject::OnFinalRelease+0x323
Microsoft_UI_Xaml!ctl::ComBase::ReleaseImpl+0x84
Microsoft_UI_Xaml!DirectUI::UIAffinityReleaseQueue::DoCleanup+0x1aa [UIAffinityReleaseQueue.cpp @ 218]
Microsoft_UI_Xaml!DirectUI::UIAffinityReleaseQueue::BuildTree+0x1e
Microsoft_UI_Xaml!DirectUI::BuildTreeService::BuildTrees+0x119
Microsoft_UI_Xaml!CCoreServices::NWDrawTree+0x54e
Microsoft_UI_Xaml!CXcpBrowserHost::OnTick+0x58
```
The dying subtree is always a Button template hosting a Viewbox-wrapped icon Image, and in bucket 2
the freed object is a color-animation keyframe — consistent with buttons being destroyed while
their pointer-over/pressed state animations are still live.
### Why is this important?
This is a hard crash (process killed by OS, no managed exception) triggered by normal user interaction - navigating between pages and scrolling lists. It is not an edge case; users hit it within minutes of active use. The failure is memory-corruption class (bucket #3 shows writes through a dangling pointer), meaning even non-crashing runs may silently corrupt state. There is no managed-side workaround since the defect is entirely within native WinUI teardown.
### Steps to reproduce the bug
App context: packaged .NET 10 MAUI desktop app (WinUI 3), virtualized lists (~950 rows) whose rows contain Buttons with icon Images; list refreshes replace the ObservableCollection contents; pages carry ToolbarItems (AppBarButtons).
1. Navigate rapidly between pages containing such lists (sub-second intervals).
2. Simultaneously scroll and click list-row buttons as fast as possible while refreshes replace rows.
### Actual behavior
Within minutes, the app hard-crashes with one of the four buckets above (no managed exception — the failure is native).
It is a timing race (destroyed-while-animating subtrees + deferred off-UI-thread final releases draining on a later tick), so reproduction is probabilistic; we reproduce within minutes of "frenzied" interaction. We can provide crash dumps privately on request, and can attempt a minimal non-MAUI repro if that would help.
### Expected behavior
Deferred cleanup of released framework peers must not AddRef peers already destroyed within the same (or an earlier) cleanup batch.
### Screenshots
N/A (native crash; WinDbg outputs available for all buckets).
### NuGet package version
Microsoft.WindowsAppSDK 2.2.0 / Microsoft.UI.Xaml 3.2.2.0 Also observed identically when the app declared WASDK 2.1.3 — the `Microsoft.WindowsAppRuntime.2` framework package resolved to the installed 2.2.0 runtime.
### Windows version
Windows 11 (21H2): Build 22000
### Additional context
Possibly related family: #[10194 ](https://github.com/microsoft/microsoft-ui-xaml/issues/10194) (_free_base AV during Image cleanup at window close, WASDK 1.6.3, closed needs-author-feedback) — same OnParentUpdated-during-teardown neighborhood, different bucket.
Contributor guide
Research direction
Start by reading UAffinityReleaseQueue.cpp around DoCleanup and DependencyObject.cpp around OnParentUpdated, then trace the listed RemoveParent and GetPeerPrivate calls. Reproduce the timing-sensitive teardown with the described WinUI list scenario and use the supplied crash buckets and dumps to verify that deferred cleanup no longer AddRefs a freed peer.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- desktop
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100