#11906·wpf

WindowChromeWorker._ExtendGlassFrame dereferences HwndSource.CompositionTarget without a null check (NullReferenceException during window creation)

Author: m-smkimCreated Sep 7, 2026Updated Sep 9, 2026
LabelsBugPriority:1Cost:S

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:

csharp
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:

csharp
_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:

xml
<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:

csharp
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.