diff --git a/ui/touch_selection/touch_selection_controller_unittest.cc b/ui/touch_selection/touch_selection_controller_unittest.cc index 698a1217dfe83..31413a8d8cfd6 100644 --- a/ui/touch_selection/touch_selection_controller_unittest.cc +++ b/ui/touch_selection/touch_selection_controller_unittest.cc @@ -1907,5 +1907,82 @@ TEST_F(TouchSelectionControllerTest, SwipeToMoveCursor_HandleWasNotShown) { EXPECT_TRUE(test_controller.GetStartVisible()); } +// ----------------------------------------------------------------------------- +// POC: base::AutoReset write-after-free on response_pending_input_event_. +// +// Models the Android embedder contract: the TouchSelectionControllerClient +// (RenderWidgetHostViewAndroid -> JNI -> embedder Java) is permitted to +// synchronously destroy the WebContents — and with it the heap-allocated +// TouchSelectionController — from inside OnSelectionEvent(). +// +// OnSelectionBoundsChanged() constructs a base::AutoReset targeting the heap +// member |response_pending_input_event_| and then calls into the client. If the +// client frees the controller, ~AutoReset writes 4 bytes into the freed slab +// via its RAW_PTR_EXCLUSION scoped_variable_ pointer (not MiraclePtr-guarded). +// ----------------------------------------------------------------------------- +class SelfDeletingClient : public TouchSelectionControllerClient { + public: + SelfDeletingClient() { + TouchSelectionController::Config config; + controller_ = std::make_unique(this, config); + // Precondition in production: one real user touch sets + // show_touch_handles_ = true (touch_selection_controller.cc:383). + controller_->WillHandleTouchEvent( + MockMotionEvent(MotionEvent::Action::DOWN)); + } + + TouchSelectionController* controller() { return controller_.get(); } + + // TouchSelectionControllerClient: + bool SupportsAnimation() const override { return false; } + void SetNeedsAnimate() override {} + void MoveCaret(const gfx::PointF&) override {} + void MoveRangeSelectionExtent(const gfx::PointF&) override {} + void SelectBetweenCoordinates(const gfx::PointF&, + const gfx::PointF&) override {} + void OnDragUpdate(const TouchSelectionDraggable::Type, + const gfx::PointF&) override {} + void DidScroll() override {} + std::unique_ptr CreateDrawable() override { + return std::make_unique(&dragging_enabled_); + } + + void OnSelectionEvent(SelectionEventType event) override { + // SELECTION_HANDLES_SHOWN is the *last* statement in OnSelectionChanged() + // (touch_selection_controller.cc:645). Freeing here means the very next + // operation on return is ~AutoReset writing into the freed allocation. + if (event == SELECTION_HANDLES_SHOWN) { + controller_.reset(); // Embedder destroys WebContents -> RWHVA -> + // touch_selection_controller_. + } + } + + private: + bool dragging_enabled_ = false; + std::unique_ptr controller_; +}; + +TEST(TouchSelectionControllerUAFTest, AutoResetWriteAfterFree) { + SelfDeletingClient client; + + // Renderer-controlled via RenderFrameMetadata.selection over Mojo: + // start.type = LEFT, end.type = RIGHT. + gfx::SelectionBound start, end; + start.set_type(gfx::SelectionBound::LEFT); + start.SetEdge(gfx::PointF(5, 5), gfx::PointF(5, 15)); + start.set_visible(true); + end.set_type(gfx::SelectionBound::RIGHT); + end.SetEdge(gfx::PointF(50, 5), gfx::PointF(50, 15)); + end.set_visible(true); + + // OnSelectionBoundsChanged: + // AutoReset reset(&response_pending_input_event_, ...); + // OnSelectionChanged() + // -> client_->OnSelectionEvent(SELECTION_HANDLES_SHOWN) + // -> controller_.reset() [heap freed] + // return; [~AutoReset writes to freed heap] + client.controller()->OnSelectionBoundsChanged(start, end); +} + } // namespace } // namespace ui