Description
WindowChromeWorker._ExtendGlassFrame() dereferences _hwndSource.CompositionTarget without a null check. HwndSource.CompositionTarget returns null when the HwndSource is disposed or when its HwndTarget has been disposed independently, so this throws a NullReferenceException while a window is being created.
The guard in _UpdateFrameState does not cover this case:
if (IntPtr.Zero == _hwnd || _hwndSource.IsDisposed) // WindowChromeWorker.cs:703
{
return;
}
It checks the HwndSource, but CompositionTarget also returns null based on the state of a different object (_hwndTarget). So _hwndSource.IsDisposed == false and CompositionTarget == null can hold at the same time, and the guard lets execution through.
This is the sibling of #11440. That issue reported a COMException from DwmExtendFrameIntoClientArea a few lines later in the same method, and was addressed by the crash containment in #11417. The null dereference that happens before that call is still unguarded.
Unguarded dereference sites on main:
| Location |
Method |
WindowChromeWorker.cs:942 |
_ExtendGlassFrame — DWM-disabled branch |
WindowChromeWorker.cs:952 |
_ExtendGlassFrame — DWM-enabled branch |
WindowChromeWorker.cs:998 |
_ExtendGlassFrame |
WindowChromeWorker.cs:1142 |
_RestoreGlassFrame |
Reproduction Steps
I have not been able to reproduce this deterministically — it is a race, and it was observed in production rather than in a test.
What can be stated precisely is the exposure. Any window whose WindowChrome has a non-zero GlassFrameThickness runs the unguarded code on every window creation, because _ApplyNewCustomChrome() calls _UpdateFrameState(force: true), and force bypasses the state-change check:
_isGlassEnabled = frameState && _chromeInfo.GlassFrameThickness != default(Thickness);
if (!_isGlassEnabled) { _SetRoundingRegion(null); }
else { _ClearRoundingRegion(); _ExtendGlassFrame(); } // <-- always reached while DWM composition is on
So, with a style such as:
<Setter Property="shell:WindowChrome.WindowChrome">
<Setter.Value>
<shell:WindowChrome CaptionHeight="27" ResizeBorderThickness="5"
GlassFrameThickness="5" CornerRadius="5"/>
</Setter.Value>
</Setter>
calling ShowDialog() at a moment when the render target is being torn down or recreated hits the null dereference. We suspect a DWM composition state change (remote-desktop reconnect, session lock/unlock, GPU driver reset) — the same circumstances reported in #11440.
Expected behavior
_ExtendGlassFrame() returns without doing anything when there is no composition target, consistent with the crash containment introduced in #11417.
Actual behavior
NullReferenceException. It is thrown inside Window.CreateSourceWindow, i.e. from within Window.ShowDialog(), so application code cannot meaningfully handle it.
System.NullReferenceException: Object reference not set to an instance of an object.
at System.Windows.Shell.WindowChromeWorker._ExtendGlassFrame()
at System.Windows.Shell.WindowChromeWorker._UpdateFrameState(Boolean force)
at System.Windows.Shell.WindowChromeWorker._ApplyNewCustomChrome()
at System.Windows.Window.CreateSourceWindow(Boolean duringShow)
at System.Windows.Window.ShowHelper(Object booleanBox)
at System.Windows.Window.ShowDialog()
at MyApp.CustomPopupWindow.ShowDialog() <-- application code below this point
at MyApp.SomeScreen.OpenDetailPopup()
Regression?
No. This code path looks unchanged since the WPF Shell Integration Library was folded into the framework.
Known Workarounds
Set GlassFrameThickness="0" on the WindowChrome. _UpdateFrameState then computes _isGlassEnabled == false and _ExtendGlassFrame() is never called, so the crash path becomes unreachable.
The cost is losing the extended glass frame. _SetRoundingRegion is called instead, which applies a window region via SetWindowRgn — and per the rounded corners guidance, a window with a region can never be rounded by DWM, so this also opts the window out of Windows 11 corner rounding.
Impact
Affects any WPF application that uses WindowChrome with a non-zero GlassFrameThickness — a common pattern for custom title bars, including several third-party WPF UI libraries. Because _ApplyNewCustomChrome forces _UpdateFrameState(force: true), every window creation goes through the unguarded code, so the exposure is not limited to unusual windows. The crash cannot be handled by the application.
Configuration
- .NET 10.0 (
net10.0-windows), WPF
- Windows 11, x64
- Appears correlated with environments where DWM composition state changes (remote desktop / VDI), but we cannot confirm this.
Other information
Suggested fix, in the same spirit as the containment added in #11417:
private void _ExtendGlassFrame()
{
Assert.IsNotNull(_window);
if (IntPtr.Zero == _hwnd)
{
// Can't do anything with this call until the Window has been shown.
return;
}
// The composition target can be gone even when the HwndSource itself is not disposed,
// so _UpdateFrameState's _hwndSource.IsDisposed check is not sufficient here.
if (_hwndSource?.CompositionTarget is null)
{
return;
}
// ... unchanged
}
_RestoreGlassFrame (line 1142) needs the same treatment.
Related: #11440, #11417.
Description
WindowChromeWorker._ExtendGlassFrame()dereferences_hwndSource.CompositionTargetwithout a null check.HwndSource.CompositionTargetreturnsnullwhen theHwndSourceis disposed or when itsHwndTargethas been disposed independently, so this throws aNullReferenceExceptionwhile a window is being created.The guard in
_UpdateFrameStatedoes not cover this case:It checks the
HwndSource, butCompositionTargetalso returnsnullbased on the state of a different object (_hwndTarget). So_hwndSource.IsDisposed == falseandCompositionTarget == nullcan hold at the same time, and the guard lets execution through.This is the sibling of #11440. That issue reported a
COMExceptionfromDwmExtendFrameIntoClientAreaa few lines later in the same method, and was addressed by the crash containment in #11417. The null dereference that happens before that call is still unguarded.Unguarded dereference sites on
main:WindowChromeWorker.cs:942_ExtendGlassFrame— DWM-disabled branchWindowChromeWorker.cs:952_ExtendGlassFrame— DWM-enabled branchWindowChromeWorker.cs:998_ExtendGlassFrameWindowChromeWorker.cs:1142_RestoreGlassFrameReproduction Steps
I have not been able to reproduce this deterministically — it is a race, and it was observed in production rather than in a test.
What can be stated precisely is the exposure. Any window whose
WindowChromehas a non-zeroGlassFrameThicknessruns the unguarded code on every window creation, because_ApplyNewCustomChrome()calls_UpdateFrameState(force: true), andforcebypasses the state-change check:So, with a style such as:
calling
ShowDialog()at a moment when the render target is being torn down or recreated hits the null dereference. We suspect a DWM composition state change (remote-desktop reconnect, session lock/unlock, GPU driver reset) — the same circumstances reported in #11440.Expected behavior
_ExtendGlassFrame()returns without doing anything when there is no composition target, consistent with the crash containment introduced in #11417.Actual behavior
NullReferenceException. It is thrown insideWindow.CreateSourceWindow, i.e. from withinWindow.ShowDialog(), so application code cannot meaningfully handle it.Regression?
No. This code path looks unchanged since the WPF Shell Integration Library was folded into the framework.
Known Workarounds
Set
GlassFrameThickness="0"on theWindowChrome._UpdateFrameStatethen computes_isGlassEnabled == falseand_ExtendGlassFrame()is never called, so the crash path becomes unreachable.The cost is losing the extended glass frame.
_SetRoundingRegionis called instead, which applies a window region viaSetWindowRgn— and per the rounded corners guidance, a window with a region can never be rounded by DWM, so this also opts the window out of Windows 11 corner rounding.Impact
Affects any WPF application that uses
WindowChromewith a non-zeroGlassFrameThickness— a common pattern for custom title bars, including several third-party WPF UI libraries. Because_ApplyNewCustomChromeforces_UpdateFrameState(force: true), every window creation goes through the unguarded code, so the exposure is not limited to unusual windows. The crash cannot be handled by the application.Configuration
net10.0-windows), WPFOther information
Suggested fix, in the same spirit as the containment added in #11417:
_RestoreGlassFrame(line 1142) needs the same treatment.Related: #11440, #11417.