Skip to content

Commit

Permalink
Fix use-after-free in MessageListView.
Browse files Browse the repository at this point in the history
This is caused by calling RemoveNotification() while
'Clear All' operation is in progress. A MessageView
could be deleted twice.

BUG=713983

Review-Url: https://codereview.chromium.org/2836023002
Cr-Commit-Position: refs/heads/master@{#467248}
  • Loading branch information
yhanada authored and Commit bot committed Apr 26, 2017
1 parent 1baa747 commit 4518695
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 5 deletions.
6 changes: 3 additions & 3 deletions ui/message_center/views/message_center_view.cc
Original file line number Diff line number Diff line change
Expand Up @@ -177,15 +177,15 @@ void MessageCenterView::ClearAllClosableNotifications() {
if (is_closing_)
return;

is_clearing_ = true;
is_clearing_all_notifications_ = true;
UpdateButtonBarStatus();
SetViewHierarchyEnabled(scroller_, false);
message_list_view_->ClearAllClosableNotifications(
scroller_->GetVisibleRect());
}

void MessageCenterView::OnAllNotificationsCleared() {
is_clearing_ = false;
is_clearing_all_notifications_ = false;
SetViewHierarchyEnabled(scroller_, true);
button_bar_->SetCloseAllButtonEnabled(false);

Expand Down Expand Up @@ -625,7 +625,7 @@ void MessageCenterView::SetVisibilityMode(Mode mode, bool animate) {

void MessageCenterView::UpdateButtonBarStatus() {
// Disables all buttons during animation of cleaning of all notifications.
if (is_clearing_) {
if (is_clearing_all_notifications_) {
button_bar_->SetSettingsAndQuietModeButtonsEnabled(false);
button_bar_->SetCloseAllButtonEnabled(false);
return;
Expand Down
2 changes: 1 addition & 1 deletion ui/message_center/views/message_center_view.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ class MESSAGE_CENTER_EXPORT MessageCenterView
// ignored.
bool is_closing_;

bool is_clearing_ = false;
bool is_clearing_all_notifications_ = false;
bool is_locked_ = false;

// Current view mode. During animation, it is the target mode.
Expand Down
34 changes: 33 additions & 1 deletion ui/message_center/views/message_list_view.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <algorithm>

#include "base/command_line.h"
#include "base/location.h"
#include "base/single_thread_task_runner.h"
Expand Down Expand Up @@ -100,6 +102,20 @@ void MessageListView::AddNotificationAt(MessageView* view, int index) {
void MessageListView::RemoveNotification(MessageView* view) {
DCHECK_EQ(view->parent(), this);

// TODO(yhananda): We should consider consolidating clearing_all_views_,
// deleting_views_ and deleted_when_done_.
if (std::find(clearing_all_views_.begin(), clearing_all_views_.end(), view) !=
clearing_all_views_.end() ||
deleting_views_.find(view) != deleting_views_.end() ||
deleted_when_done_.find(view) != deleted_when_done_.end()) {
// Let's skip deleting the view if it's already scheduled for deleting.
// Even if we check clearing_all_views_ here, we actualy have no idea
// whether the view is due to be removed or not because it could be in its
// animation before removal.
// In short, we could delete the view twice even if we check these three
// lists.
return;
}

if (GetContentsBounds().IsEmpty()) {
delete view;
Expand All @@ -120,6 +136,11 @@ void MessageListView::RemoveNotification(MessageView* view) {

void MessageListView::UpdateNotification(MessageView* view,
const Notification& notification) {
// Skip updating the notification being cleared
if (std::find(clearing_all_views_.begin(), clearing_all_views_.end(), view) !=
clearing_all_views_.end())
return;

int index = GetIndexOf(view);
DCHECK_LE(0, index); // GetIndexOf is negative if not a child.

Expand Down Expand Up @@ -242,6 +263,15 @@ void MessageListView::ClearAllClosableNotifications(
continue;
if (child->IsPinned())
continue;
if (deleting_views_.find(child) != deleting_views_.end() ||
deleted_when_done_.find(child) != deleted_when_done_.end()) {
// We don't check clearing_all_views_ here, so this can lead to a
// notification being deleted twice. Even if we do check it, there is a
// problem similar to the problem in RemoveNotification(), it could be
// currently in its animation before removal, and we could similarly
// delete it twice. This is a bug.
continue;
}
clearing_all_views_.push_back(child);
}
if (clearing_all_views_.empty()) {
Expand Down Expand Up @@ -298,7 +328,9 @@ bool MessageListView::IsValidChild(const views::View* child) const {
deleting_views_.find(const_cast<views::View*>(child)) ==
deleting_views_.end() &&
deleted_when_done_.find(const_cast<views::View*>(child)) ==
deleted_when_done_.end();
deleted_when_done_.end() &&
std::find(clearing_all_views_.begin(), clearing_all_views_.end(),
child) == clearing_all_views_.end();
}

void MessageListView::DoUpdateIfPossible() {
Expand Down

0 comments on commit 4518695

Please sign in to comment.