Skip to content

Commit

Permalink
Fix UAF when exiting a nested run loop in TabDragContextImpl::OnGestu…
Browse files Browse the repository at this point in the history
…reEvent.

OnGestureEvent may call ContinueDrag, which may run a nested run loop. After the nested run loop returns, multiple seconds of time may have passed, and the world may be in a very different state; in particular, the window that contains this TabDragContext may have closed.

This CL checks if this has happened, and returns early in that case.

Bug: 1453465
Change-Id: I6095c0afeb5aa5f422717f1bbd93b96175e52afa
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4657527
Reviewed-by: Darryl James <dljames@chromium.org>
Commit-Queue: Taylor Bergquist <tbergquist@chromium.org>
Code-Coverage: Findit <findit-for-me@appspot.gserviceaccount.com>
Cr-Commit-Position: refs/heads/main@{#1164449}
  • Loading branch information
Taylor Bergquist authored and Chromium LUCI CQ committed Jun 30, 2023
1 parent 33be21b commit 63d6b8b
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 22 deletions.
6 changes: 6 additions & 0 deletions chrome/browser/ui/views/tabs/fake_tab_slot_controller.cc
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ bool FakeTabSlotController::IsFocusInTabs() const {
return false;
}

TabSlotController::Liveness FakeTabSlotController::ContinueDrag(
views::View* view,
const ui::LocatedEvent& event) {
return Liveness::kAlive;
}

bool FakeTabSlotController::EndDrag(EndDragReason reason) {
return false;
}
Expand Down
4 changes: 2 additions & 2 deletions chrome/browser/ui/views/tabs/fake_tab_slot_controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ class FakeTabSlotController : public TabSlotController {
TabSlotView* source,
const ui::LocatedEvent& event,
const ui::ListSelectionModel& original_selection) override {}
void ContinueDrag(views::View* view, const ui::LocatedEvent& event) override {
}
Liveness ContinueDrag(views::View* view,
const ui::LocatedEvent& event) override;
bool EndDrag(EndDragReason reason) override;
Tab* GetTabAt(const gfx::Point& point) override;
const Tab* GetAdjacentTab(const Tab* tab, int offset) override;
Expand Down
11 changes: 8 additions & 3 deletions chrome/browser/ui/views/tabs/tab_slot_controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ class TabSlotController {
kEvent
};

enum class Liveness { kAlive, kDeleted };

virtual const ui::ListSelectionModel& GetSelectionModel() const = 0;

// Returns the tab at |index|.
Expand Down Expand Up @@ -126,9 +128,12 @@ class TabSlotController {
const ui::LocatedEvent& event,
const ui::ListSelectionModel& original_selection) = 0;

// Continues dragging a Tab.
virtual void ContinueDrag(views::View* view,
const ui::LocatedEvent& event) = 0;
// Continues dragging a Tab. May enter a nested event loop - returns
// Liveness::kDeleted if `this` was destroyed during this nested event loop,
// and Liveness::kAlive if `this` is still alive.
[[nodiscard]] virtual Liveness ContinueDrag(
views::View* view,
const ui::LocatedEvent& event) = 0;

// Ends dragging a Tab. Returns whether the tab has been destroyed.
virtual bool EndDrag(EndDragReason reason) = 0;
Expand Down
43 changes: 27 additions & 16 deletions chrome/browser/ui/views/tabs/tab_strip.cc
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ class TabStrip::TabDragContextImpl : public TabDragContext,
}

bool OnMouseDragged(const ui::MouseEvent& event) override {
ContinueDrag(this, event);
(void)ContinueDrag(this, event);
return true;
}

Expand All @@ -189,6 +189,7 @@ class TabStrip::TabDragContextImpl : public TabDragContext,
void OnMouseCaptureLost() override { EndDrag(END_DRAG_CAPTURE_LOST); }

void OnGestureEvent(ui::GestureEvent* event) override {
Liveness tabstrip_alive = Liveness::kAlive;
switch (event->type()) {
case ui::ET_GESTURE_SCROLL_END:
case ui::ET_SCROLL_FLING_START:
Expand All @@ -202,7 +203,8 @@ class TabStrip::TabDragContextImpl : public TabDragContext,
}

case ui::ET_GESTURE_SCROLL_UPDATE:
ContinueDrag(this, *event);
// N.B. !! ContinueDrag may enter a nested run loop !!
tabstrip_alive = ContinueDrag(this, *event);
break;

case ui::ET_GESTURE_TAP_DOWN:
Expand All @@ -214,6 +216,12 @@ class TabStrip::TabDragContextImpl : public TabDragContext,
}
event->SetHandled();

// If tabstrip was destroyed (during ContinueDrag above), return early to
// avoid UAF below.
if (tabstrip_alive == Liveness::kDeleted) {
return;
}

// TabDragContext gets event capture as soon as a drag session begins, which
// precludes TabStrip from ever getting events like tap or long tap. Forward
// this on to TabStrip so it can respond to those events.
Expand Down Expand Up @@ -302,20 +310,21 @@ class TabStrip::TabDragContextImpl : public TabDragContext,
std::move(drag_controller_set_callback_).Run(drag_controller_.get());
}

void ContinueDrag(views::View* view, const ui::LocatedEvent& event) {
if (drag_controller_.get() &&
drag_controller_->event_source() == EventSourceFromEvent(event)) {
gfx::Point screen_location(event.location());
views::View::ConvertPointToScreen(view, &screen_location);
[[nodiscard]] Liveness ContinueDrag(views::View* view,
const ui::LocatedEvent& event) {
if (!drag_controller_.get() ||
drag_controller_->event_source() != EventSourceFromEvent(event)) {
return Liveness::kAlive;
}

// Note: |tab_strip_| can be destroyed during drag, also destroying
// |this|.
base::WeakPtr<TabDragContext> weak_ptr(weak_factory_.GetWeakPtr());
drag_controller_->Drag(screen_location);
gfx::Point screen_location(event.location());
views::View::ConvertPointToScreen(view, &screen_location);

if (!weak_ptr)
return;
}
// Note: `tab_strip_` can be destroyed during drag, also destroying `this`.
base::WeakPtr<TabDragContext> weak_ptr(weak_factory_.GetWeakPtr());
drag_controller_->Drag(screen_location);

return weak_ptr ? Liveness::kAlive : Liveness::kDeleted;
}

bool EndDrag(EndDragReason reason) {
Expand Down Expand Up @@ -1593,8 +1602,10 @@ void TabStrip::MaybeStartDrag(
drag_context_->MaybeStartDrag(source, event, original_selection);
}

void TabStrip::ContinueDrag(views::View* view, const ui::LocatedEvent& event) {
drag_context_->ContinueDrag(view, event);
TabSlotController::Liveness TabStrip::ContinueDrag(
views::View* view,
const ui::LocatedEvent& event) {
return drag_context_->ContinueDrag(view, event);
}

bool TabStrip::EndDrag(EndDragReason reason) {
Expand Down
3 changes: 2 additions & 1 deletion chrome/browser/ui/views/tabs/tab_strip.h
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,8 @@ class TabStrip : public views::View,
TabSlotView* source,
const ui::LocatedEvent& event,
const ui::ListSelectionModel& original_selection) override;
void ContinueDrag(views::View* view, const ui::LocatedEvent& event) override;
[[nodiscard]] Liveness ContinueDrag(views::View* view,
const ui::LocatedEvent& event) override;
bool EndDrag(EndDragReason reason) override;
Tab* GetTabAt(const gfx::Point& point) override;
const Tab* GetAdjacentTab(const Tab* tab, int offset) override;
Expand Down

0 comments on commit 63d6b8b

Please sign in to comment.