Repository: electron/electron
Pull request: fix: uaf in non-client hittest during view teardown

#### Description of Change

Closes https://github.com/electron/electron/issues/50040

After https://github.com/electron/electron/commit/2ffb9e1e05c55975112ff6b23037af0eeda79142 non-client hittest on draggable regions can access partially destroyed view during shutdown

Refs https://github.com/microsoft/vscode/issues/298179

<details>
<summary>Crash stack</summary>

```
Crash reason:  EXCEPTION_ACCESS_VIOLATION_READ
Crash address: 0xd0
Crash parameters:
    value: 0x0000000000000000  description: 
    value: 0x00000000000000d0  description: 
Process uptime: 54 seconds

Thread 0 (crashed)
 0  Code - Insiders.exe!content::WebContentsImpl::GetDelegate() [inspectable_web_contents.cc : 352 + 0x0]
    rax = 0x000035c4002b0b00   rdx = 0x000000e5c21fd998
    rcx = 0x0000000000000000   rbx = 0x0000000000000000
    rsi = 0x000035c4013b8d80   rdi = 0x000000e5c21fd998
    rbp = 0x000000004a1eb601   rsp = 0x000000e5c21fd788
     r8 = 0x0000000000000000    r9 = 0x0000000000000010
    r10 = 0x00000ffee9db4aee   r11 = 0x0001404111100000
    r12 = 0x000000e5c21fdb00   r13 = 0x000035c400495e00
    r14 = 0x000035c401914b80   r15 = 0x000035c4004959a0
    rip = 0x00007ff74a1239f0
    Found by: given as instruction pointer in context
 1  Code - Insiders.exe!electron::api::WebContentsView::NonClientHitTest(gfx::Point const &) [electron_api_web_contents_view.cc : 91 + 0x5]
    rax = 0x000035c4002b0b00   rdx = 0x000000e5c21fd998
    rcx = 0x0000000000000000   rbx = 0x0000000000000000
    rsi = 0x000035c4013b8d80   rdi = 0x000000e5c21fd998
    rbp = 0x000000004a1eb601   rsp = 0x000000e5c21fd790
     r8 = 0x0000000000000000    r9 = 0x0000000000000010
    r10 = 0x00000ffee9db4aee   r11 = 0x0001404111100000
    r12 = 0x000000e5c21fdb00   r13 = 0x000035c400495e00
    r14 = 0x000035c401914b80   r15 = 0x000035c4004959a0
    rip = 0x00007ff74a0988a0
    Found by: simulating a return from leaf function
 2  Code - Insiders.exe!electron::NativeWindow::NonClientHitTest(gfx::Point const &) [native_window.cc : 686 + 0x8]
    rbx = 0x0000000000000000   rbp = 0x000000004a1eb601
    rsp = 0x000000e5c21fd7e0   r12 = 0x000000e5c21fdb00
    r13 = 0x000035c400495e00   r14 = 0x000035c401914b80
    r15 = 0x000035c4004959a0   rip = 0x00007ff74a0edfae
    Found by: call frame info
 3  Code - Insiders.exe!electron::FramelessView::NonClientHitTest(gfx::Point const &) [frameless_view.cc : 80 + 0x8]
    rbx = 0x0000000000000000   rbp = 0x000000004a1eb601
    rsp = 0x000000e5c21fd830   r12 = 0x000000e5c21fdb00
    r13 = 0x000035c400495e00   r14 = 0x000035c401914b80
    r15 = 0x000035c4004959a0   rip = 0x00007ff74a1f1152
    Found by: call frame info
 4  Code - Insiders.exe!electron::WinFrameView::NonClientHitTest(gfx::Point const &) [win_frame_view.cc : 149 + 0xb]
    rbx = 0x0000000000000000   rbp = 0x000000004a1eb601
    rsp = 0x000000e5c21fd870   r12 = 0x000000e5c21fdb00
    r13 = 0x000035c400495e00   r14 = 0x000035c401914b80
    r15 = 0x000035c4004959a0   rip = 0x00007ff74a1dba08
    Found by: call frame info
 5  Code - Insiders.exe!views::Widget::GetNonClientComponent(gfx::Point const &) [widget.cc : 2074 + 0x7]
    rbx = 0x0000000000000000   rbp = 0x000000004a1eb601
    rsp = 0x000000e5c21fd930   r12 = 0x000000e5c21fdb00
    r13 = 0x000035c400495e00   r14 = 0x000035c401914b80
    r15 = 0x000035c4004959a0   rip = 0x00007ff74d7b5b21
    Found by: call frame info
 6  Code - Insiders.exe!views::DesktopWindowTreeHostWin::GetNonClientComponent(gfx::Point const &) [desktop_window_tree_host_win.cc : 962 + 0x15]
    rbx = 0x0000000000000000   rbp = 0x000000004a1eb601
    rsp = 0x000000e5c21fd970   r12 = 0x000000e5c21fdb00
    r13 = 0x000035c400495e00   r14 = 0x000035c401914b80
    r15 = 0x000035c4004959a0   rip = 0x00007ff74d795337
    Found by: call frame info
 7  Code - Insiders.exe!views::HWNDMessageHandler::OnNCHitTest(gfx::Point const &) [hwnd_message_handler.cc : 2579 + 0x16]
    rbx = 0x0000000000000000   rbp = 0x000000004a1eb601
    rsp = 0x000000e5c21fd9d0   r12 = 0x000000e5c21fdb00
    r13 = 0x000035c400495e00   r14 = 0x000035c401914b80
    r15 = 0x000035c4004959a0   rip = 0x00007ff74d7a9c70
    Found by: call frame info
 8  Code - Insiders.exe!views::HWNDMessageHandler::HandleNcHitTestMessage(unsigned int,unsigned __int64,__int64,bool *) [hwnd_message_handler.cc : 1322 + 0x8]
    rbx = 0x0000000000000000   rbp = 0x000000004a1eb601
    rsp = 0x000000e5c21fda40   r12 = 0x000000e5c21fdb00
    r13 = 0x000035c400495e00   r14 = 0x000035c401914b80
    r15 = 0x000035c4004959a0   rip = 0x00007ff74d7a9b8d
    Found by: call frame info
 9  Code - Insiders.exe!static int content::LegacyRenderWidgetHostHWND::_ProcessWindowMessage(struct HWND__ *, unsigned int, unsigned __int64, __int64, __int64 & const, unsigned long) [legacy_render_widget_host_win.h : 106 + 0x54]
    rbx = 0x0000000000000000   rbp = 0x000000004a1eb601
    rsp = 0x000000e5c21fdac0   r12 = 0x000000e5c21fdb00
    r13 = 0x000035c400495e00   r14 = 0x000035c401914b80
    r15 = 0x000035c4004959a0   rip = 0x00007ff74b821b96
    Found by: call frame info
10  Code - Insiders.exe!content::LegacyRenderWidgetHostHWND::ProcessWindowMessage(HWND__ *,unsigned int,unsigned __int64,__int64,__int64 &,unsigned long) [legacy_render_widget_host_win.h : 87 + 0x21]
    rbx = 0x0000000000000000   rbp = 0x000000004a1eb601
    rsp = 0x000000e5c21fdb90   r12 = 0x000000e5c21fdb00
    r13 = 0x000035c400495e00   r14 = 0x000035c401914b80
    r15 = 0x000035c4004959a0   rip = 0x00007ff74b821313
    Found by: call frame info
11  Code - Insiders.exe!static __int64 ATL::CWindowImplBaseT<ATL::CWindow,ATL::CWinTraits<1073741824,0> >::WindowProc(struct HWND__ *, unsigned int, unsigned __int64, __int64) [atlwin.h : 3573 + 0x1c]
    rbx = 0x0000000000000000   rbp = 0x000000004a1eb601
    rsp = 0x000000e5c21fdc50   r12 = 0x000000e5c21fdb00
    r13 = 0x000035c400495e00   r14 = 0x000035c401914b80
    r15 = 0x000035c4004959a0   rip = 0x00007ff74b821f83
    Found by: call frame info
12  atlthunk.dll + 0x1028
    rbx = 0x0000000000000000   rbp = 0x000000004a1eb601
    rsp = 0x000000e5c21fdd10   r12 = 0x000000e5c21fdb00
    r13 = 0x000035c400495e00   r14 = 0x000035c401914b80
    r15 = 0x000035c4004959a0   rip = 0x00007ffd03b51028
    Found by: call frame info
13  USER32.dll + 0x17846
    rsp = 0x000000e5c21fdd50   rip = 0x00007ffd35887846
    Found by: stack scanning
14  Code - Insiders.exe!display::win::ScreenWin::DIPToScreenPoint(gfx::Point const &) [screen_win.cc : 684 + 0x8]
    rsp = 0x000000e5c21fde20   rip = 0x00007ff74c4df67a
    Found by: stack scanning
15  Code - Insiders.exe!display::win::ScreenWin::GetWindowAtScreenPoint(gfx::Point const &) [screen_win.cc : 881 + 0x14]
    rsp = 0x000000e5c21fe000   rip = 0x00007ff74c4e082f
    Found by: call frame info
16  Code - Insiders.exe!static void aura::WindowEventDispatcher::PostSynthesizeMouseMove(class aura::Window *) [window_event_dispatcher.cc : 865 + 0x1b]
    rsp = 0x000000e5c21fe060   rip = 0x00007ff74c8b59f3
    Found by: call frame info
17  Code - Insiders.exe!aura::WindowEventDispatcher::OnWindowVisibilityChanged(aura::Window *,bool) [window_event_dispatcher.cc : 723 + 0xb]
    rsp = 0x000000e5c21fe0f0   rip = 0x00007ff74c8b7a38
    Found by: call frame info
18  Code - Insiders.exe!static bool aura::Window::NotifyWindowVisibilityChangedAtReceiver(class aura::Window *, bool) [window.cc : 1304 + 0x13]
    rsp = 0x000000e5c21fe140   rip = 0x00007ff74c8c2b33
    Found by: call frame info
19  Code - Insiders.exe!static bool aura::Window::NotifyWindowVisibilityChangedDown(class aura::Window *, bool) [window.cc : 1310 + 0x5]
    rsp = 0x000000e5c21fe240   rip = 0x00007ff74c8c2944
    Found by: call frame info
20  Code - Insiders.exe!static void aura::Window::SetVisibleInternal(bool) [window.cc : 1106 + 0xe]
    rsp = 0x000000e5c21fe310   rip = 0x00007ff74c8befcf
    Found by: call frame info
21  Code - Insiders.exe!views::NativeViewHostAura::RemovedFromWidget() [native_view_host_aura.cc : 180 + 0x5]
    rsp = 0x000000e5c21fe3e0   rip = 0x00007ff74d7d3f51
    Found by: call frame info
22  Code - Insiders.exe!static void views::View::PropagateRemoveNotifications(class views::View *, class views::View *, bool) [view.cc : 3218 + 0x1e]
    rsp = 0x000000e5c21fe410   rip = 0x00007ff74edae524
    Found by: call frame info
23  Code - Insiders.exe!static void views::View::PropagateRemoveNotifications(class views::View *, class views::View *, bool) [view.cc : 3207 + 0xe]
    rsp = 0x000000e5c21fe540   rip = 0x00007ff74edae2f9
    Found by: call frame info
24  Code - Insiders.exe!static void views::View::PropagateRemoveNotifications(class views::View *, class views::View *, bool) [view.cc : 3207 + 0xe]
    rsp = 0x000000e5c21fe670   rip = 0x00007ff74edae2f9
    Found by: call frame info
25  Code - Insiders.exe!static void views::View::DoRemoveChildView(class views::View *, bool, bool, class views::View *) [view.cc : 3164 + 0x11]
    rsp = 0x000000e5c21fe7a0   rip = 0x00007ff74d7bccb7
    Found by: call frame info
26  Code - Insiders.exe!views::View::~View() [view.cc : 251 + 0x17]
    rsp = 0x000000e5c21fe830   rip = 0x00007ff74d7bb62a
    Found by: call frame info
27  Code - Insiders.exe!static void electron::InspectableWebContentsView::~InspectableWebContentsView() [inspectable_web_contents_view.cc : 98 + 0x8]
    rsp = 0x000000e5c21fe980   rip = 0x00007ff74a12a193
    Found by: call frame info
28  Code - Insiders.exe!void electron::InspectableWebContentsView::~InspectableWebContentsView() [inspectable_web_contents_view.cc : 94 + 0x5]
    rsp = 0x000000e5c21fe9e0   rip = 0x00007ff74a12ac30
    Found by: call frame info
29  Code - Insiders.exe!static void electron::InspectableWebContents::~InspectableWebContents() [inspectable_web_contents.cc : 349 + 0xdb]
    rsp = 0x000000e5c21fea20   rip = 0x00007ff74a12389f
    Found by: call frame info
30  Code - Insiders.exe!void electron::InspectableWebContents::~InspectableWebContents() [inspectable_web_contents.cc : 344 + 0x5]
    rsp = 0x000000e5c21fea80   rip = 0x00007ff74a128410
    Found by: call frame info
31  Code - Insiders.exe!static void electron::api::WebContents::~WebContents() [electron_api_web_contents.cc : 1075 + 0x47]
    rsp = 0x000000e5c21feac0   rip = 0x00007ff74a067d19
    Found by: call frame info
32  Code - Insiders.exe!static void electron::api::WebContents::DeleteThisIfAlive() [electron_api_web_contents.cc : 1087 + 0x8]
    rsp = 0x000000e5c21feb30   rip = 0x00007ff74a0683de
    Found by: call frame info
33  Code - Insiders.exe!base::TaskAnnotator::RunTaskImpl(base::PendingTask &) [task_annotator.cc : 229 + 0x20]
    rsp = 0x000000e5c21feba0   rip = 0x00007ff74e7228c0
    Found by: call frame info
34  Code - Insiders.exe!base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWork() [thread_controller_with_message_pump_impl.cc : 346 + 0x3e5]
    rsp = 0x000000e5c21fec40   rip = 0x00007ff74e71d1ba
    Found by: call frame info
35  Code - Insiders.exe!base::MessagePumpForUI::DoRunLoop() [message_pump_win.cc : 260 + 0x10]
    rsp = 0x000000e5c21fee70   rip = 0x00007ff74e6f669d
    Found by: call frame info
36  Code - Insiders.exe!base::MessagePumpWin::Run(base::MessagePump::Delegate *) [message_pump_win.cc : 87 + 0x10]
    rsp = 0x000000e5c21fef20   rip = 0x00007ff74beaa151
    Found by: call frame info
37  Code - Insiders.exe!base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::Run(bool,base::TimeDelta) [thread_controller_with_message_pump_impl.cc : 647 + 0x11]
    rsp = 0x000000e5c21fef90   rip = 0x00007ff74bee3fbe
    Found by: call frame info
38  Code - Insiders.exe!base::RunLoop::Run(base::Location const &) [run_loop.cc : 134 + 0x17]
    rsp = 0x000000e5c21ff020   rip = 0x00007ff74bef9b4f
    Found by: call frame info
39  Code - Insiders.exe!content::BrowserMainLoop::RunMainMessageLoop() [browser_main_loop.cc : 1114 + 0x2c]
    rsp = 0x000000e5c21ff0f0   rip = 0x00007ff74b38e6a2
    Found by: call frame info
40  Code - Insiders.exe!content::BrowserMainRunnerImpl::Run() [browser_main_runner_impl.cc : 150 + 0x5]
    rsp = 0x000000e5c21ff160   rip = 0x00007ff74b390341
    Found by: call frame info
41  Code - Insiders.exe!content::BrowserMain(content::MainFunctionParams) [browser_main.cc : 32 + 0x5]
    rsp = 0x000000e5c21ff190   rip = 0x00007ff74b38ba1f
    Found by: call frame info
42  Code - Insiders.exe!static int content::RunBrowserProcessMain(struct content::MainFunctionParams, class content::ContentMainDelegate *) [content_main_runner_impl.cc : 705 + 0x20]
    rsp = 0x000000e5c21ff240   rip = 0x00007ff74a62123b
    Found by: call frame info
43  Code - Insiders.exe!static int content::ContentMainRunnerImpl::RunBrowser(struct content::MainFunctionParams, bool) [content_main_runner_impl.cc : 1292 + 0x16]
    rsp = 0x000000e5c21ff380   rip = 0x00007ff74a622392
    Found by: call frame info
44  Code - Insiders.exe!content::ContentMainRunnerImpl::Run() [content_main_runner_impl.cc : 1131 + 0x23]
    rsp = 0x000000e5c21ff4c0   rip = 0x00007ff74a6221aa
    Found by: call frame info
45  Code - Insiders.exe!static int content::RunContentProcess(struct content::ContentMainParams, class content::ContentMainRunner *) [content_main.cc : 344 + 0x8]
    rsp = 0x000000e5c21ff610   rip = 0x00007ff74a620a1f
    Found by: call frame info
46  Code - Insiders.exe!content::ContentMain(content::ContentMainParams) [content_main.cc : 357 + 0x5]
    rsp = 0x000000e5c21ff790   rip = 0x00007ff74a620bcd
    Found by: call frame info
47  Code - Insiders.exe!wWinMain [electron_main_win.cc : 312 + 0x13]
    rsp = 0x000000e5c21ff820   rip = 0x00007ff749f9b2f4
    Found by: call frame info
48  Code - Insiders.exe!static int __scrt_common_main_seh() [exe_common.inl : 288 + 0x21]
    rsp = 0x000000e5c21ff9d0   rip = 0x00007ff74edbd7e2
    Found by: call frame info
49  KERNEL32.DLL + 0x2e8d7
    rsp = 0x000000e5c21ffa10   rip = 0x00007ffd3393e8d7
    Found by: call frame info
50  ntdll.dll + 0x8c53c
    rsp = 0x000000e5c21ffa40   rip = 0x00007ffd35b4c53c
    Found by: stack scanning
```
</details>

Explicit destroy the view so we can perform null checks during the re-entrancy

#### Release Notes

Notes: fix shutdown crash on windows when hidden titlebar is enabled

Changed files:
filename: shell/browser/api/electron_api_web_contents_view.cc
patch: @@ -83,13 +83,17 @@ void WebContentsView::ApplyBorderRadius() {
 
 int WebContentsView::NonClientHitTest(const gfx::Point& point) {
   if (api_web_contents_) {
+    auto* iwc = api_web_contents_->inspectable_web_contents();
+    if (!iwc)
+      return HTNOWHERE;
     // Convert the point to the contents view's coordinate space rather than
     // the InspectableWebContentsView's coordinate space, because the draggable
     // region is relative to the web content area. When DevTools is docked
     // (e.g. to the left), the contents view is offset within the parent,
     // so we need to account for that offset.
-    auto* inspectable_view =
-        api_web_contents_->inspectable_web_contents()->GetView();
+    auto* inspectable_view = iwc->GetView();
+    if (!inspectable_view)
+      return HTNOWHERE;
     auto* contents_view = inspectable_view->GetContentsView();
     gfx::Point local_point(point);
     views::View::ConvertPointFromWidget(contents_view, &local_point);

filename: shell/browser/ui/inspectable_web_contents.cc
patch: @@ -343,6 +343,10 @@ InspectableWebContents::InspectableWebContents(
 }
 
 InspectableWebContents::~InspectableWebContents() {
+  // Explicitly destroy the view first to ensure that any callbacks triggered
+  // during view teardown (like NonClientHitTest) does not access a
+  // partially-destroyed view.
+  view_.reset();
   // Unsubscribe from devtools and Clean up resources.
   if (GetDevToolsWebContents())
     WebContentsDestroyed();

filename: spec/fixtures/crash-cases/frameless-windows-close/index.js
patch: @@ -0,0 +1,26 @@
+const { app, BrowserWindow } = require('electron');
+
+if (process.platform !== 'win32') {
+  process.exit(0);
+}
+
+app.on('window-all-closed', () => {
+  app.quit();
+});
+
+app.whenReady().then(() => {
+  let win = new BrowserWindow({
+    frame: false,
+    show: true
+  });
+
+  win.loadURL('about:blank');
+
+  setTimeout(() => {
+    win.close();
+  }, 5000);
+
+  win.on('closed', () => {
+    win = null;
+  });
+});