Skip to content

Commit 8570531

Browse files
zakharvoitmibrunin
authored andcommitted
[Backport] CVE-2021-30590: Heap buffer overflow in Bookmarks
Partial cherry-pick of patch originally reviewed on https://chromium-review.googlesource.com/c/chromium/src/+/3073100: [M90-LTS] Fix RecentlyUsedFoldersComboModel heap overflows This fixes a few bugs: * RecentlyUsedFoldersComboModel::RemoveNode() would not inform its observers of changes. * RecentlyUsedFoldersComboModel::GetDefaultIndex() did not behave well after model changes (could end up using a cached out-of-bounds index). * BubbleDialogModelHost would not pass on selected-index updates unless the user changed the index by performing a combobox action (not true when an Extension removes a bookmark folder). This also replaces off-by-one index correction changes with CHECKs for index correctness inside views::Combobox. This turns security bugs into crash bugs and also is likelier to get us better crash stacks if this happens in the wild as well. (cherry picked from commit d2e1d6871cf7ca9dbbc82a400be49234d20f98cf) Bug: 1227777 Change-Id: I9b851129fee4bdd249c1db77b01312b6671784be Commit-Queue: Peter Boström <pbos@chromium.org> Cr-Original-Commit-Position: refs/heads/master@{#904551} Reviewed-by: Achuith Bhandarkar <achuith@chromium.org> Commit-Queue: Zakhar Voit <voit@google.com> Owners-Override: Achuith Bhandarkar <achuith@chromium.org> Cr-Commit-Position: refs/branch-heads/4430@{#1562} Cr-Branched-From: e5ce7dc4f7518237b3d9bb93cccca35d25216cbe-refs/heads/master@{#857950} Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
1 parent f982e88 commit 8570531

File tree

3 files changed

+29
-15
lines changed

3 files changed

+29
-15
lines changed

chromium/ui/views/bubble/bubble_dialog_model_host.cc

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,8 @@ class CheckboxControl : public Checkbox {
5656
: label_line_height_(label_line_height) {
5757
auto* layout = SetLayoutManager(std::make_unique<BoxLayout>());
5858
layout->set_between_child_spacing(LayoutProvider::Get()->GetDistanceMetric(
59-
views::DISTANCE_RELATED_LABEL_HORIZONTAL));
60-
layout->set_cross_axis_alignment(
61-
views::BoxLayout::CrossAxisAlignment::kStart);
59+
DISTANCE_RELATED_LABEL_HORIZONTAL));
60+
layout->set_cross_axis_alignment(BoxLayout::CrossAxisAlignment::kStart);
6261

6362
SetAssociatedLabel(label.get());
6463

@@ -254,8 +253,8 @@ BubbleDialogModelHost::BubbleDialogModelHost(
254253
set_close_on_deactivate(model_->close_on_deactivate(GetPassKey()));
255254

256255
set_fixed_width(LayoutProvider::Get()->GetDistanceMetric(
257-
anchor_view ? views::DISTANCE_BUBBLE_PREFERRED_WIDTH
258-
: views::DISTANCE_MODAL_DIALOG_PREFERRED_WIDTH));
256+
anchor_view ? DISTANCE_BUBBLE_PREFERRED_WIDTH
257+
: DISTANCE_MODAL_DIALOG_PREFERRED_WIDTH));
259258

260259
AddInitialFields();
261260
}
@@ -464,17 +463,19 @@ void BubbleDialogModelHost::AddOrUpdateCombobox(
464463
combobox->SetCallback(base::BindRepeating(
465464
[](ui::DialogModelCombobox* model_field,
466465
base::PassKey<DialogModelHost> pass_key, Combobox* combobox) {
467-
// TODO(pbos): This should be a subscription through the Combobox
468-
// directly, but Combobox right now doesn't support listening to
469-
// selected-index changes.
470-
model_field->OnSelectedIndexChanged(pass_key,
471-
combobox->GetSelectedIndex());
472466
model_field->OnPerformAction(pass_key);
473467
},
474468
model_field, GetPassKey(), combobox.get()));
475469

476-
// TODO(pbos): Add subscription to combobox selected-index changes.
477470
combobox->SetSelectedIndex(model_field->selected_index());
471+
property_changed_subscriptions_.push_back(
472+
combobox->AddSelectedIndexChangedCallback(base::BindRepeating(
473+
[](ui::DialogModelCombobox* model_field,
474+
base::PassKey<DialogModelHost> pass_key, Combobox* combobox) {
475+
model_field->OnSelectedIndexChanged(pass_key,
476+
combobox->GetSelectedIndex());
477+
},
478+
model_field, GetPassKey(), combobox.get())));
478479
const gfx::FontList& font_list = combobox->GetFontList();
479480
AddViewForLabelAndField(model_field, model_field->label(GetPassKey()),
480481
std::move(combobox), font_list);

chromium/ui/views/controls/combobox/combobox.cc

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ const gfx::FontList& Combobox::GetFontList() const {
281281
void Combobox::SetSelectedIndex(int index) {
282282
if (selected_index_ == index)
283283
return;
284+
// TODO(pbos): Add (D)CHECKs to validate the selected index.
284285
selected_index_ = index;
285286
if (size_to_largest_label_) {
286287
OnPropertyChanged(&selected_index_, kPropertyEffectsPaint);
@@ -290,6 +291,11 @@ void Combobox::SetSelectedIndex(int index) {
290291
}
291292
}
292293

294+
base::CallbackListSubscription Combobox::AddSelectedIndexChangedCallback(
295+
views::PropertyChangedCallback callback) {
296+
return AddPropertyChangedCallback(&selected_index_, std::move(callback));
297+
}
298+
293299
bool Combobox::SelectValue(const base::string16& value) {
294300
for (int i = 0; i < GetModel()->GetItemCount(); ++i) {
295301
if (value == GetModel()->GetItemAt(i)) {
@@ -430,9 +436,11 @@ bool Combobox::OnKeyPressed(const ui::KeyEvent& e) {
430436
// TODO(oshima): handle IME.
431437
DCHECK_EQ(e.type(), ui::ET_KEY_PRESSED);
432438

439+
// TODO(pbos): Do we need to handle selected_index_ == -1 for unselected here?
440+
// Ditto on handling an empty model?
433441
DCHECK_GE(selected_index_, 0);
434442
DCHECK_LT(selected_index_, GetModel()->GetItemCount());
435-
if (selected_index_ < 0 || selected_index_ > GetModel()->GetItemCount())
443+
if (selected_index_ < 0 || selected_index_ >= GetModel()->GetItemCount())
436444
SetSelectedIndex(0);
437445

438446
bool show_menu = false;
@@ -618,10 +626,13 @@ void Combobox::PaintIconAndText(gfx::Canvas* canvas) {
618626

619627
// Draw the text.
620628
SkColor text_color = GetTextColorForEnableState(*this, GetEnabled());
621-
if (selected_index_ < 0 || selected_index_ > GetModel()->GetItemCount()) {
622-
NOTREACHED();
629+
// TODO(pbos): Do we need to handle selected_index_ == -1 for unselected here?
630+
// Ditto on handling an empty model?
631+
DCHECK_GE(selected_index_, 0);
632+
DCHECK_LT(selected_index_, GetModel()->GetItemCount());
633+
if (selected_index_ < 0 || selected_index_ >= GetModel()->GetItemCount())
623634
SetSelectedIndex(0);
624-
}
635+
625636
base::string16 text = GetModel()->GetItemAt(selected_index_);
626637

627638
int disclosure_arrow_offset = width() - kComboboxArrowContainerWidth;

chromium/ui/views/controls/combobox/combobox.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ class VIEWS_EXPORT Combobox : public View,
7171
// Gets/Sets the selected index.
7272
int GetSelectedIndex() const { return selected_index_; }
7373
void SetSelectedIndex(int index);
74+
base::CallbackListSubscription AddSelectedIndexChangedCallback(
75+
views::PropertyChangedCallback callback) WARN_UNUSED_RESULT;
7476

7577
// Looks for the first occurrence of |value| in |model()|. If found, selects
7678
// the found index and returns true. Otherwise simply noops and returns false.

0 commit comments

Comments
 (0)