CRD client-side wire sequence to trigger the race in production ================================================================ Target: Windows Me2Me CRD host (remoting_desktop.exe runs as SYSTEM with uiAccess; see remoting/host/win/BUILD.gn:487-506 and remoting/host/win/launch_process_with_token.cc:76-111). Attacker: an authenticated CRD client (host owner / PIN holder). Protocol path on host: network process → Mojo DesktopSessionAgent::InjectTouchEvent (UI thread) → SessionInputInjectorWin::InjectTouchEvent → InputInjectorWin::Core::InjectTouchEvent (posts to Input thread) → TouchInjectorWin::InjectTouchEvent ------------------------------------------------------------------ Step 1 — populate touches_in_contact_ WITHOUT InputEventTracker noticing ------------------------------------------------------------------ Send over the CRD event channel (protocol::EventMessage / event.proto): TouchEvent { event_type: TOUCH_POINT_MOVE # NOT START touch_points: [ { id: 1, x: 100, y: 100 }, { id: 2, x: 110, y: 100 }, { id: 3, x: 120, y: 100 }, { id: 4, x: 130, y: 100 }, { id: 5, x: 140, y: 100 }, { id: 6, x: 150, y: 100 }, { id: 7, x: 160, y: 100 }, { id: 8, x: 170, y: 100 } ] } Why MOVE: InputEventTracker::InjectTouchEvent ignores MOVE (remoting/protocol/input_event_tracker.cc:139-155, default: break), so InputEventTracker::touch_point_ids_ stays empty and ReleaseAll() will NOT post a TOUCH_POINT_CANCEL during teardown. Meanwhile TouchInjectorWin::MoveTouchPoints uses std::map::operator[] which INSERTS the ids (touch_injector_win.cc:255-261) and calls InjectTouchInput → UpdateKeepAliveTimer → starts the 100 ms keep_alive_timer_ bound to the Input thread sequence. ------------------------------------------------------------------ Step 2 — let OnKeepAlive() start firing ------------------------------------------------------------------ Idle ≥100 ms. The Input thread now periodically runs TouchInjectorWin::OnKeepAlive(): iterates touches_in_contact_ and dispatches a virtual call on delegate_. ------------------------------------------------------------------ Step 3 — force DesktopSessionAgent::Stop() on the UI thread ------------------------------------------------------------------ Either: (a) Hard-close the WebRTC data channel / TCP socket so the network process drops the desktop-session Mojo pipe → DesktopProcess::OnNetworkProcessDisconnected() → DesktopSessionAgent::Stop() on caller_task_runner_ (= UI thread). (b) Kill the network process (less stealthy). Because touch_point_ids_ is empty, input_tracker_->ReleaseAll() sends nothing → ~SessionInputInjectorWin drops the only Core ref on the UI thread → ~InputInjectorWin → Core::Stop() runs synchronously on the UI thread → TouchInjectorWin::Deinitialize(): touches_in_contact_.clear(); // frees map nodes delegate_ = ...::Create(); // frees old delegate_ keep_alive_timer_.Stop(); // cross-sequence Stop() …while the Input thread is concurrently inside OnKeepAlive(). ------------------------------------------------------------------ Step 4 — retry ------------------------------------------------------------------ remoting_desktop.exe survives OnNetworkProcessDisconnected() and calls ReconnectNetworkChannel() (desktop_process.cc:61-70,212-237). Reconnect and repeat steps 1-3 to win the race. Expected sanitizer signature on a Windows ASAN build: heap-use-after-free on TouchInjectorWinDelegate vtable read in TouchInjectorWin::InjectTouchInput (touch_injector_win.cc:312) or heap-use-after-free in std::_Tree::_Rb_tree_increment during AppendMapValuesToVector (touch_injector_win.cc:41-44).