Critical watch CVSS 8.1 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

Critical
Severity
8.1
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Chromoting
DescriptionUse after free in Chromoting
ComponentChromoting
Sub-componentServices (Use Subcomponents)>Chromoting
Bug ClassUAF
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker505204771
Fix commit897153e8be5e (chromium/src) +203/-1
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-04-22 (per vendor advisory)
Fix landed2026-04-29 (public git)
Disclosed2026-06-02
Patch gap34 days — fix public in source before the advisory

Background

Chromoting / It2Me
Chrome Remote Desktop’s host; “It2Me” (Help-me) sessions require a local confirmation (“continue”) dialog before a remote user is granted control.
`It2MeConfirmationDialogProxy`
Proxies the confirmation dialog between the caller thread and the UI thread; its inner Core runs on ui_task_runner_ and owns the wrapped It2MeConfirmationDialog.
`MessageBox` / `It2MeConfirmationDialogChromeOS`
The ChromeOS dialog implementation, where MessageBoxCore controls the OK/Cancel buttons via SetButtonEnabled.
Input draining
Flushing pending input events through the task queue before showing a dialog, so queued clicks/keystrokes cannot land on freshly shown dialog buttons.

Root Cause Analysis

The confirmation dialog was shown synchronously with its buttons immediately active, so input events already queued at the moment the dialog appeared could be delivered to the dialog, and the deferred show logic the fix introduces (Core::Show now posts ShowAfterDrain) had no lifetime guard on the Core that would run that posted work.

The fix adds a SetDisableInputs capability threaded through It2MeConfirmationDialog, It2MeConfirmationDialogChromeOS/its Core, and MessageBox/MessageBoxCore (which disables the kOk and kCancel buttons via SetButtonEnabled), and changes It2MeConfirmationDialogProxy::Core::Show to first set is_disabled_for_drain_ = true, call UpdateDialogInputs(), and then PostTask the actual show (ShowAfterDrain) so pending events drain before the dialog becomes interactive. Crucially, that posted task is bound with weak_factory_.GetWeakPtr(), so if the Core is destroyed before the drained show runs, the task is dropped rather than executing against freed memory — the use-after-free guard. The regression tests assert the ordered sequence OnSetDisableInputs(true) → OnShow() → OnSetDisableInputs(false) and that inputs are already disabled by the time Show() reaches the dialog, confirming both the draining and the disable/re-enable behavior.

Key insight
The core mistake was showing the confirmation dialog immediately with live buttons and (in the new deferred-show design) without a weak-pointer guard, so queued input could auto-dismiss it and a posted show task could outlive its Core; the fix disables the buttons, drains pending events by posting the show, and binds that posted task to a WeakPtr<Core>.

Attack Path

  1. Establish a remote session A remote user connects to an It2Me host, reaching the point where the local confirmation/continue dialog is about to be shown.
  2. Queue input ahead of the dialog Input events are already pending in the UI task queue when the dialog is created; without draining, these events would be processed against the newly shown dialog buttons.
  3. Accidental/forced acceptance A queued click/keystroke lands on the enabled OK button the instant the dialog appears, dismissing the confirmation without a deliberate local decision (the patch disables buttons and drains events to prevent exactly this).
  4. Lifetime race on the posted show In the new deferred-show flow, if the Core is torn down between posting and running ShowAfterDrain, the posted task would reference a freed Core — a use-after-free (inferred) that the added weak_factory_ guard prevents.

Impact Assessment

The patch hardens the host process against a remote user’s queued input events dismissing the confirmation dialog before a local decision, and against a use-after-free of the dialog Core via a posted show task that outlives it (inferred). The vendor advisory classifies this as a critical use-after-free; it does not report in-the-wild exploitation. Reachability requires an active It2Me/Chromoting session reaching the confirmation prompt, with the input-timing race between pending events and dialog display as the precondition; escalation beyond the UAF is (inferred).

Changed Functions

FunctionChangeNotes
It2MeConfirmationDialogFactory
remoting/host/it2me/it2me_confirmation_dialog.h
modified
if
remoting/host/it2me/it2me_confirmation_dialog_chromeos.cc
modified

Files Changed

  • remoting/host/chromeos/message_box.cc
  • remoting/host/chromeos/message_box.h
  • remoting/host/it2me/it2me_confirmation_dialog.h
  • remoting/host/it2me/it2me_confirmation_dialog_chromeos.cc
  • remoting/host/it2me/it2me_confirmation_dialog_chromeos.h
  • remoting/host/it2me/it2me_confirmation_dialog_proxy.cc

Audit Directions

  • Security dialogs shown with live controls
    Audit confirmation/permission prompts that become interactive immediately; pending input should be drained and accept buttons disabled briefly after display.
  • Posted tasks without weak-pointer guards
    Review PostTask calls bound with Unretained(this)/raw pointers in UI objects that can be destroyed before the task runs; prefer WeakPtr + InvalidateWeakPtrs.
  • Cross-thread proxy lifetimes
    Check proxy/Core patterns that hop between caller and UI threads for teardown that can race deferred work targeting the UI-thread object.
From 897153e8be5ef2716c5b12dbeda554ed16ec279c Mon Sep 17 00:00:00 2001
From: Joe Downing <joedow@google.com>
Date: Tue, 28 Apr 2026 22:47:39 -0700
Subject: [PATCH] Add input disabling and event draining to confirmation dialogs

This CL ensures that any pending input events are injected prior to
the continue dialog being shown. This change affects all platforms.
I have additional CLs staged which will disable input on the dialog
for ~1 second after it becomes visible.

Bug: 505204771, 505083297

Change-Id: I5318f9d60d78086c5b90cf527e4c6e57db0360ce
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7793507
Reviewed-by: Yuwei Huang <yuweih@chromium.org>
Commit-Queue: Joe Downing <joedow@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1622243}
---

diff --git a/remoting/host/chromeos/message_box.cc b/remoting/host/chromeos/message_box.cc
index a2d335f..18df8b18 100644
--- a/remoting/host/chromeos/message_box.cc
+++ b/remoting/host/chromeos/message_box.cc
@@ -61,6 +61,8 @@
 
   void SetMessageLabel(const std::u16string& message_label);
 
+  void SetDisableInputs(bool disable);
+
   // Called by MessageBox when it is destroyed.
   void OnMessageBoxDestroyed();
 
@@ -174,6 +176,11 @@
   message_box_view_->SetMessageLabel(message_label);
 }
 
+void MessageBoxCore::SetDisableInputs(bool disable) {
+  SetButtonEnabled(ui::mojom::DialogButton::kOk, !disable);
+  SetButtonEnabled(ui::mojom::DialogButton::kCancel, !disable);
+}
+
 void MessageBoxCore::OnMessageBoxDestroyed() {
   DCHECK(message_box_);
   message_box_ = nullptr;
@@ -214,6 +221,10 @@
   core_->SetMessageLabel(message_label);
 }
 
+void MessageBox::SetDisableInputs(bool disable) {
+  core_->SetDisableInputs(disable);
+}
+
 views::DialogDelegate& MessageBox::GetDialogDelegate() {
   return CHECK_DEREF(core_->AsDialogDelegate());
 }
diff --git a/remoting/host/chromeos/message_box.h b/remoting/host/chromeos/message_box.h
index 6967502..09b7ae1 100644
--- a/remoting/host/chromeos/message_box.h
+++ b/remoting/host/chromeos/message_box.h
@@ -65,6 +65,8 @@
 
   void SetMessageLabel(const std::u16string& message_label);
 
+  void SetDisableInputs(bool disable);
+
   views::DialogDelegate& GetDialogDelegate();
 
   // Called by MessageBoxCore when it is about to be destroyed.
diff --git a/remoting/host/it2me/it2me_confirmation_dialog.h b/remoting/host/it2me/it2me_confirmation_dialog.h
index 1f3ece1..f35e112 100644
--- a/remoting/host/it2me/it2me_confirmation_dialog.h
+++ b/remoting/host/it2me/it2me_confirmation_dialog.h
@@ -33,6 +33,9 @@
   // |callback| will not be called if the dialog is destroyed.
   virtual void Show(const std::string& remote_user_email,
                     ResultCallback callback) = 0;
+
+  // Set whether the dialog's inputs are disabled.
+  virtual void SetDisableInputs(bool disable) {}
 };
 
 class It2MeConfirmationDialogFactory {
diff --git a/remoting/host/it2me/it2me_confirmation_dialog_chromeos.cc b/remoting/host/it2me/it2me_confirmation_dialog_chromeos.cc
index a0f0128..08306b6a 100644
--- a/remoting/host/it2me/it2me_confirmation_dialog_chromeos.cc
+++ b/remoting/host/it2me/it2me_confirmation_dialog_chromeos.cc
@@ -206,6 +206,8 @@
 
   void ShowConfirmationDialog();
 
+  void SetDisableInputs(bool disable);
+
   views::DialogDelegate& GetDialogDelegate();
 
  private:
@@ -268,6 +270,10 @@
   }
 }
 
+void It2MeConfirmationDialogChromeOS::Core::SetDisableInputs(bool disable) {
+  message_box_->SetDisableInputs(disable);
+}
+
 void It2MeConfirmationDialogChromeOS::Core::OnConfirmationDialogResult(
     MessageBox::Result result) {
   std::move(callback_).Run(result == MessageBox::Result::OK ? Result::OK
@@ -346,12 +352,20 @@
         base::BindOnce(
             &It2MeConfirmationDialogChromeOS::OnConfirmationDialogResult,
             base::Unretained(this)));
+    core_->SetDisableInputs(inputs_disabled_);
     core_->ShowConfirmationDialog();
   } else {
     ShowConfirmationNotification(remote_user_email);
   }
 }
 
+void It2MeConfirmationDialogChromeOS::SetDisableInputs(bool disable) {
+  inputs_disabled_ = disable;
+  if (core_) {
+    core_->SetDisableInputs(disable);
+  }
+}
+
 void It2MeConfirmationDialogChromeOS::ShowConfirmationNotification(
     const std::string& remote_user_email) {
   message_center::RichNotificationData data;
diff --git a/remoting/host/it2me/it2me_confirmation_dialog_chromeos.h b/remoting/host/it2me/it2me_confirmation_dialog_chromeos.h
index 322380d..56a7260e 100644
--- a/remoting/host/it2me/it2me_confirmation_dialog_chromeos.h
+++ b/remoting/host/it2me/it2me_confirmation_dialog_chromeos.h
@@ -34,6 +34,7 @@
   // It2MeConfirmationDialog implementation.
   void Show(const std::string& remote_user_email,
             ResultCallback callback) override;
+  void SetDisableInputs(bool disable) override;
 
   views::DialogDelegate& GetDialogDelegateForTest();
 
@@ -54,6 +55,7 @@
   ResultCallback callback_;
   DialogStyle style_;
   base::TimeDelta auto_accept_timeout_;
+  bool inputs_disabled_ = false;
 };
 
 }  // namespace remoting
diff --git a/remoting/host/it2me/it2me_confirmation_dialog_proxy.cc b/remoting/host/it2me/it2me_confirmation_dialog_proxy.cc
index 35e5c0d..0dfb035 100644
--- a/remoting/host/it2me/it2me_confirmation_dialog_proxy.cc
+++ b/remoting/host/it2me/it2me_confirmation_dialog_proxy.cc
@@ -28,6 +28,10 @@
   // Shows the wrapped dialog. Must be called on the UI thread.
   void Show(const std::string& remote_user_email);
 
+  // Sets whether the wrapped dialog's inputs are disabled. Must be called on
+  // the UI thread.
+  void SetDisableInputs(bool disable);
+
   scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner() {
     return ui_task_runner_;
   }
@@ -40,10 +44,22 @@
   // Reports the dialog result on the caller's thread.
   void ReportResult(It2MeConfirmationDialog::Result result);
 
+  // Shows the wrapped dialog. Must be called on the UI thread.
+  void ShowAfterDrain(const std::string& remote_user_email);
+
+  // Updates the wrapped dialog's inputs state based on |is_disabled_by_caller_|
+  // and |is_disabled_for_drain_|.
+  void UpdateDialogInputs();
+
   scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_;
   scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner_;
   base::WeakPtr<It2MeConfirmationDialogProxy> parent_;
   std::unique_ptr<It2MeConfirmationDialog> dialog_;
+
+  bool is_disabled_by_caller_ = false;
+  bool is_disabled_for_drain_ = false;
+
+  base::WeakPtrFactory<Core> weak_factory_{this};
 };
 
 It2MeConfirmationDialogProxy::Core::Core(
@@ -64,10 +80,43 @@
     const std::string& remote_user_email) {
   DCHECK(ui_task_runner_->BelongsToCurrentThread());
 
+  // Set inputs to disabled before showing the dialog to avoid accidental
+  // clicks.
+  is_disabled_for_drain_ = true;
+  UpdateDialogInputs();
+
+  // Post a task to actually show the dialog. This allows any pending events in
+  // the queue to be processed before the dialog is shown.
+  ui_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&It2MeConfirmationDialogProxy::Core::ShowAfterDrain,
+                     weak_factory_.GetWeakPtr(), remote_user_email));
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/remoting/host/it2me/it2me_confirmation_dialog_proxy_unittest.cc b/remoting/host/it2me/it2me_confirmation_dialog_proxy_unittest.cc
index e54f476..953c3a4 100644
--- a/remoting/host/it2me/it2me_confirmation_dialog_proxy_unittest.cc
+++ b/remoting/host/it2me/it2me_confirmation_dialog_proxy_unittest.cc
@@ -7,12 +7,14 @@
 #include <memory>
 
 #include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/ref_counted.h"
 #include "base/run_loop.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/test/task_environment.h"
 #include "base/threading/thread.h"
+#include "base/synchronization/waitable_event.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -38,6 +40,8 @@
     std::move(callback_).Run(result);
   }
 
+  bool inputs_disabled() const { return inputs_disabled_; }
+
   MOCK_METHOD(void, OnShow, ());
 
   // It2MeConfirmationDialog implementation.
@@ -46,13 +50,24 @@
     EXPECT_TRUE(callback_.is_null());
     EXPECT_TRUE(task_runner_->BelongsToCurrentThread());
     EXPECT_EQ(remote_user_email.compare(kTestEmailAddress), 0);
+    // At the moment Show() is called, inputs should have been disabled by
+    // the proxy's draining logic.
+    EXPECT_TRUE(inputs_disabled_);
     callback_ = std::move(callback);
     OnShow();
   }
 
+  void SetDisableInputs(bool disable) override {
+    inputs_disabled_ = disable;
+    OnSetDisableInputs(disable);
+  }
+
+  MOCK_METHOD1(OnSetDisableInputs, void(bool));
+
  private:
   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
   ResultCallback callback_;
+  bool inputs_disabled_ = false;
 };
 
 // Encapsulates a target for It2MeConfirmationDialog::ResultCallback.
@@ -130,6 +145,9 @@
   ResultCallbackTarget callback_target(main_task_runner());
 
   StubIt2MeConfirmationDialog* confirm_dialog = dialog();
+
+  EXPECT_CALL(*dialog(), OnSetDisableInputs(::testing::_))
+      .Times(::testing::AtLeast(2));
   EXPECT_CALL(*dialog(), OnShow())
       .WillOnce(InvokeWithoutArgs([confirm_dialog]() {
         confirm_dialog->ReportResult(It2MeConfirmationDialog::Result::CANCEL);
@@ -145,4 +163,98 @@
   Run();
 }
 
+TEST_F(It2MeConfirmationDialogProxyTest, DrainingAndDisabling) {
+  ResultCallbackTarget callback_target(main_task_runner());
+
+  StubIt2MeConfirmationDialog* confirm_dialog = dialog();
+
+  // Sequence of events we expect on the dialog thread:
+  // 1. OnSetDisableInputs(true)
+  // 2. OnShow()
+  // 3. OnSetDisableInputs(false)
+
+  ::testing::InSequence s;
+  EXPECT_CALL(*dialog(), OnSetDisableInputs(true));
+  EXPECT_CALL(*dialog(), OnShow());
+  EXPECT_CALL(*dialog(), OnSetDisableInputs(false))
+      .WillOnce(InvokeWithoutArgs([confirm_dialog]() {
+        confirm_dialog->ReportResult(It2MeConfirmationDialog::Result::OK);
+      }));
+
+  EXPECT_CALL(callback_target,
+              OnDialogResult(It2MeConfirmationDialog::Result::OK))
+      .WillOnce(
+          InvokeWithoutArgs(this, &It2MeConfirmationDialogProxyTest::Quit));
+
+  dialog_proxy()->Show(kTestEmailAddress, callback_target.MakeCallback());
+
+  Run();
+  }
+
+  TEST_F(It2MeConfirmationDialogProxyTest, EventDraining) {
+  ResultCallbackTarget callback_target(main_task_runner());
+  StubIt2MeConfirmationDialog* confirm_dialog = dialog();
+
+  // Setup expectations FIRST to avoid race conditions with background thread.
+  // We expect these calls on the dialog thread.
+  EXPECT_CALL(*dialog(), OnSetDisableInputs(true));
+  EXPECT_CALL(*dialog(), OnShow());
+  EXPECT_CALL(*dialog(), OnSetDisableInputs(false));
+
+  // Setup the default action for OnShow to report the result.
+  ON_CALL(*dialog(), OnShow())
+      .WillByDefault(InvokeWithoutArgs([confirm_dialog]() {
+        confirm_dialog->ReportResult(It2MeConfirmationDialog::Result::OK);
+      }));
+
+  // We expect this call on the main thread.
+  EXPECT_CALL(callback_target,
+              OnDialogResult(It2MeConfirmationDialog::Result::OK))
+      .WillOnce(
+          InvokeWithoutArgs(this, &It2MeConfirmationDialogProxyTest::Quit));
+
+  base::WaitableEvent blocker_event;
+
+  // We want to guarantee this exact order on the dialog thread:
+  // 1. blocker_task (blocks until signaled)
+  // 2. Core::Show (posted by proxy->Show)
+  // 3. simulated_input_task (posted by test)
+  // 4. Core::ShowAfterDrain (posted by Core::Show)
+
+  // 1. Post blocker.
+  dialog_task_runner()->PostTask(
+      FROM_HERE, base::BindOnce(
+                     [](base::WaitableEvent* event) {
+                       event->Wait();
+                     },
+                     base::Unretained(&blocker_event)));
+
+  // 2. Call Show(). This posts Core::Show to the dialog thread.
+  dialog_proxy()->Show(kTestEmailAddress, callback_target.MakeCallback());
+
+  // 3. Post a "simulated input" task to the dialog thread.
+  dialog_task_runner()->PostTask(
+      FROM_HERE, base::BindOnce(
+                     [](StubIt2MeConfirmationDialog* dialog) {
+                       // At this point, Core::Show should have executed,
+                       // calling SetDisableInputs(true) and posting
+                       // ShowAfterDrain.
+                       EXPECT_TRUE(dialog->inputs_disabled());
+                     },
+                     base::Unretained(confirm_dialog)));
+
+  // Now release the blocker. Tasks will run in the guaranteed order.
+  blocker_event.Signal();
+
+  Run();
+
+  // After the run loop quits, we need to ensure the dialog thread has
+  // finished its remaining work (like re-enabling inputs) before the
+  // test ends and mocks are destroyed.
+  base::RunLoop fencing_run_loop;
+  dialog_task_runner()->PostTaskAndReply(FROM_HERE, base::DoNothing(),
+                                         fencing_run_loop.QuitClosure());
+  fencing_run_loop.Run();
+}
+
 }  // namespace remoting
Loading diff…

Original Bug Report

reported by li...@chromium.org

Potential Use-After-Free in ContinueWindowMacController during Chrome Remote Desktop session

Flapjack, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the Chrome Security team. Please see go/chrome-ai-generated-security-bugs-faq for more information.

Overview: A potential Use-After-Free (UAF) vulnerability exists in the macOS Chrome Remote Desktop (CRD) IT2Me host process. The issue occurs when the ContinueWindowMacController synchronously destroys its own instance while handling a button’s action message, leaving a dangling pointer that AppKit accesses after the method returns. A remote attacker could potentially exploit this by racing the session timeout to inject a mouse click, bypassing input restrictions and achieving Remote Code Execution.

Affected files:

  • remoting/host/continue_window_mac.mm
  • remoting/host/continue_window.cc

Estimated timestamp from git blame: 2023-06-12

Summary

A potential Use-After-Free (UAF) vulnerability exists in the macOS Chrome Remote Desktop (CRD) IT2Me host (remote_assistance_host). The vulnerability is triggered during the 30-minute session continuation prompt. The Objective-C controller responsible for the prompt synchronously destroys itself inside its own button handler. When the handler returns to the native AppKit event loop, AppKit attempts to access the freed target object, resulting in a UAF.

Vulnerability Details

In remoting/host/continue_window_mac.mm, the ContinueWindowMacController class handles the UI for the session expiration warning. When the “Continue” button is clicked, the following sequence occurs:

  1. AppKit calls -[NSApplication sendAction:to:from:], which dispatches to [ContinueWindowMacController onContinue:].
  2. onContinue: calls _continue_window->ContinueSession().
  3. ContinueSession() (in remoting/host/continue_window.cc) synchronously calls HideUi().
  4. ContinueWindowMac::HideUi() clears the only strong reference to the controller: controller_ = nil;.
  5. Because Objective-C ARC does not automatically retain self for the duration of an instance method, setting controller_ = nil drops the reference count to zero. The controller is immediately deallocated while onContinue: is still executing.
  6. onContinue: returns. Control flows back to AppKit’s sendAction:to:from: mechanism.
  7. AppKit internally references the action’s target (the freed controller) or sender (the freed NSButton) to clean up state or manage the responder chain, resulting in a Use-After-Free.

Bypassing Input Restrictions

The CRD host attempts to block remote input while the prompt is visible by calling client_session_control_->SetDisableInputs(true) when the 30-minute timer expires. However, this check can be bypassed due to a cross-thread race condition:

  • The 30-minute timer fires on the UI thread and posts an asynchronous task to the Host thread to disable inputs.
  • If a remote WebRTC input message arrives at the Host thread before the disable task is processed, it is accepted.
  • The Host thread then posts an input injection task (CGEventPost) back to the UI thread.
  • The UI thread shows the dialog, then immediately processes the queued CGEventPost task. The native macOS WindowServer then delivers the click to the newly visible “Continue” button.

Suggested Attacker Steps

Note: These are potential steps based on code analysis; our tooling agent cannot execute a working proof-of-concept.

  1. An attacker establishes a remote assistance (IT2Me) connection to a macOS host.
  2. The attacker tracks the session duration. Just before the 30-minute kSessionExpirationTimeout fires, the attacker sprays the macOS heap with controlled Objective-C object payloads via remote WebRTC messages.
  3. The attacker sends a perfectly timed remote “Mouse Down” event aimed at the expected coordinates of the “Continue” button.
  4. The input message races the SetDisableInputs IPC, successfully posting a CGEventPost task to the UI thread just behind the ShowUi() task.
  5. The “Continue” dialog appears, and the queued click is immediately injected and processed by AppKit.
  6. onContinue: executes, controller_ = nil is called, and the object is freed. The attacker’s heap spray reclaims the freed memory.
  7. When onContinue: returns, AppKit attempts to use the freed target pointer, invoking a virtual method on the attacker’s fake object and yielding Remote Code Execution (RCE) in the remote_assistance_host process.

Suggested Fix

The destruction of the ContinueWindowMacController (or the underlying ContinueWindowMac C++ object) should not happen synchronously while the UI is handling an event.

  1. In ContinueWindowMac::HideUi(), defer the destruction using base::SingleThreadTaskRunner::GetCurrentDefault()->DeleteSoon() or a PostTask to clear the controller_ pointer.
  2. Alternatively, use the NS_VALID_UNTIL_END_OF_SCOPE macro or a local __strong reference inside onContinue: to ensure the object survives the synchronous teardown until the method returns to AppKit.

Evaluated with Chrome root at commit: 4a3e9db74111a3c6c4b3acfd70050a05077cf27a


Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker