[release/3.1] Prevent NULL HWND's from being parented under SystemResources listener windows #2101
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Addresses #2089
.NET 5 PR: #2100
Description (Summary)
When
HwndHosthostedHWND's need to be "parked" while theHwndHostis not being actively shown, WPF will reparent suchHWND's under temporary message-only windows maintained inside theSystemResourcesclass.There is a bug that causes null
HWND's to be parented under such message-only windows. When this happens in High-DPI applications that use mixed-mode DPI capabilities introduced by WPF in .NET 4.8 (parent and child windows with differentDPI_AWARENESS_CONTEXTvalues), a crash ensues.Customer Impact
This is a fix for a crash affecting several applications including Visual Studio, and Azure Information Protection Add-in for Office
This was fixed recently in .NET 4.8, and is being forwarded ported to .NET Core for consistency.
Regresssion
Not a regression in .NET Core, but this was a regression introduced by .NET 4.8.
Risk
The fix is small and well understood, and has been tested well. The .NET Framework version of this fix has been validated by Visual Studio (the codebases are identical in this area and .NET Framework testing is a reliable proxy for this change in .NET Core).
Details
When an
HwndHostreceivesSourceChangedevent, it goes throughBuildOrReparentWindow. When the hosted window is invisible, it is usually reparented under a temporary windows maintained by WPF in theSystemResourcesclass, until later on the window can be rebuilt and parented back to a valid parent.There is a latent bug in this logic where in
NULLHWND'sare attempted to be parented toSystemResourcesmanaged temporary windows. This bug goes back quite a while (.NET 4.5 likely). WPF seems to ignore the return value fromkernel32!SetParentand not deal with this failure. This has not been a crashing failure until now.Starting .NET 4.8, there have been some changes to this codepath that has resulted in the current bug becoming a crash. In addition to calling
kernel32!SetParenton aNULLHWND, WPF attempts to obtain a DPI-specific parking-window. This process of querying a DPI-specific parking window fails because WPF is unable to use theDPI_AWARENESS_CONTEXTvalue returned by the system for(HWND)nullptr.The only necessary part of this fix is in
HwndHost: WPF should not attempt to reparent the hosted window under a parking-window if the hosted window is(HWND)nullptr. This only requires a simple check :else if (_hwnd.Handle != IntPtr.Zero)). All other changes inSystemResourcesandHwndHostare defensive improvements.SystemResources.EnsureResourceChangeListener(HwndDpiInfo)can attempt to create a parking-window corresponding toDPI_AWARENESS_CONTEXT_VALUEthat is invalid/meaningless. This should not be allowed. A few additional checks are added to ensure this. Further,GetDpiAwarenessCompatibleNotificationWindowis augmented to be more defensive.Also, variant of
EnsureResourceChangeListeneris dead code - it is being removed.If for some unknown reason
SystemResources.GetDpiAwarenessCompatibleNotificationWindowfails and returnsnulltoHwndHost.BuildOrReparentWindow, WPF will fail to reparent the hosted window, and it will be 'lost'. This seems very unlikely - I have added a Trace to ensure that we can debug this situation if it does occur.