Commit message: None
CVE: CVE-2018-6169
CWE: CWE-20
Vulnerability description: Lack of timeout on extension install prompt in Extensions in Google Chrome prior to 68.0.3440.75 allowed a remote attacker to trigger installation of an unwanted extension via a crafted HTML page.

Project: Chrome

bool ExtensionInstallDialogView::Accept() {
  DCHECK(!handled_result_);

  handled_result_ = true;
  UpdateInstallResultHistogram(true);
  if (sampling_event_)
    sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kProceed);
  base::ResetAndReturn(&done_callback_)
      .Run(ExtensionInstallPrompt::Result::ACCEPTED);
  return true;
}

Project: Chrome

bool ExtensionInstallDialogView::AddPermissions(
    views::GridLayout* layout,
    ui::ResourceBundle& rb,
    int column_set_id,
    int left_column_width,
    ExtensionInstallPrompt::PermissionsType perm_type) {
  if (prompt_->GetPermissionCount(perm_type) == 0)
    return false;

  const int vertical_padding = LayoutDelegate::Get()->GetMetric(
      LayoutDelegate::Metric::RELATED_CONTROL_VERTICAL_SPACING);
  layout->AddPaddingRow(0, vertical_padding);

  layout->StartRow(0, column_set_id);
  views::Label* permissions_header =
      new views::Label(prompt_->GetPermissionsHeading(perm_type));
  permissions_header->SetMultiLine(true);
  permissions_header->SetHorizontalAlignment(gfx::ALIGN_LEFT);
  permissions_header->SizeToFit(left_column_width);
  layout->AddView(permissions_header);

  for (size_t i = 0; i < prompt_->GetPermissionCount(perm_type); ++i) {
    layout->AddPaddingRow(0, vertical_padding);
    layout->StartRow(0, column_set_id);
    views::Label* permission_label =
        new views::Label(prompt_->GetPermission(i, perm_type));

    permission_label->SetMultiLine(true);
    permission_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    permission_label->SizeToFit(left_column_width - kBulletWidth);
    layout->AddView(new BulletedView(permission_label));

    if (!prompt_->GetPermissionsDetails(i, perm_type).empty()) {
      layout->StartRow(0, column_set_id);
      PermissionDetails details;
      details.push_back(PrepareForDisplay(
          prompt_->GetPermissionsDetails(i, perm_type), false));
      ExpandableContainerView* details_container =
          new ExpandableContainerView(details, left_column_width, true);
      layout->AddView(details_container);
    }
  }
  return true;
}

Project: Chrome

ExpandableContainerView::DetailsView::DetailsView(int horizontal_space,
                                                  bool parent_bulleted)
    : layout_(new views::GridLayout(this)),
      state_(0) {
  SetLayoutManager(layout_);
  views::ColumnSet* column_set = layout_->AddColumnSet(0);
  const int padding = GetLeftPaddingForBulletedItems(parent_bulleted);
  column_set->AddPaddingColumn(0, padding);
  column_set->AddColumn(views::GridLayout::LEADING, views::GridLayout::LEADING,
                        0, views::GridLayout::FIXED, horizontal_space - padding,
                        0);
}

Project: Chrome

int GetLeftPaddingForBulletedItems(bool parent_bulleted) {
  return LayoutDelegate::Get()->GetMetric(
             LayoutDelegate::Metric::RELATED_CONTROL_HORIZONTAL_SPACING) *
          (parent_bulleted ? 2 : 1);
 }

Project: Chrome

ui::ModalType ExtensionInstallDialogView::GetModalType() const {
  return prompt_->ShouldUseTabModalDialog() ? ui::MODAL_TYPE_CHILD
                                            : ui::MODAL_TYPE_WINDOW;
}

Project: Chrome

ExtensionInstallPrompt::GetViewsShowDialogCallback() {
  return base::Bind(&ShowExtensionInstallDialogImpl);
}

Project: Chrome

void ExtensionInstallDialogView::InitView() {

  int left_column_width =
      (prompt_->ShouldShowPermissions() || prompt_->GetRetainedFileCount() > 0)
          ? kPermissionsLeftColumnWidth
          : kNoPermissionsLeftColumnWidth;
  if (is_external_install())
    left_column_width = kExternalInstallLeftColumnWidth;

  int column_set_id = 0;
  views::GridLayout* layout = CreateLayout(left_column_width, column_set_id);

  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();

  if (prompt_->has_webstore_data()) {
    layout->StartRow(0, column_set_id);
    views::View* rating = new views::View();
    rating->SetLayoutManager(new views::BoxLayout(
        views::BoxLayout::kHorizontal, 0, 0, 0));
    layout->AddView(rating);
    prompt_->AppendRatingStars(AddResourceIcon, rating);

    int rating_text_context, user_count_text_context;
    if (LayoutDelegate::Get()->IsHarmonyMode()) {
      rating_text_context = CONTEXT_BODY_TEXT_LARGE;
      user_count_text_context = CONTEXT_BODY_TEXT_SMALL;
    } else {
      rating_text_context = user_count_text_context = CONTEXT_DEPRECATED_SMALL;
    }
    views::Label* rating_count =
        new views::Label(prompt_->GetRatingCount(), rating_text_context,
                         views::style::STYLE_PRIMARY);
    rating_count->SetBorder(views::CreateEmptyBorder(0, 2, 0, 0));
    rating->AddChildView(rating_count);

    layout->StartRow(0, column_set_id);
    views::Label* user_count = new views::Label(
        prompt_->GetUserCount(), user_count_text_context, STYLE_SECONDARY);
    user_count->SetAutoColorReadabilityEnabled(false);
    user_count->SetEnabledColor(SK_ColorGRAY);
    layout->AddView(user_count);
  }

  LayoutDelegate* layout_delegate = LayoutDelegate::Get();
  const int vertical_padding = layout_delegate->GetMetric(
      LayoutDelegate::Metric::RELATED_CONTROL_VERTICAL_SPACING);
  if (prompt_->ShouldShowPermissions()) {
    layout->AddPaddingRow(0, vertical_padding);
    layout->StartRow(0, column_set_id);
    layout->AddView(new views::Separator(), 3, 1, views::GridLayout::FILL,
                    views::GridLayout::FILL);
  }

  const int content_width =
      left_column_width +
      layout_delegate->GetMetric(LayoutDelegate::Metric::PANEL_CONTENT_MARGIN) +
      kIconSize;

  CustomScrollableView* scrollable = new CustomScrollableView();
  views::GridLayout* scroll_layout = new views::GridLayout(scrollable);
  scrollable->SetLayoutManager(scroll_layout);

  views::ColumnSet* scrollable_column_set =
      scroll_layout->AddColumnSet(column_set_id);

  scrollable_column_set->AddColumn(
      views::GridLayout::LEADING, views::GridLayout::LEADING,
      0,  // no resizing
      views::GridLayout::USE_PREF, content_width, content_width);

  const int button_margin =
      layout_delegate->GetMetric(LayoutDelegate::Metric::DIALOG_BUTTON_MARGIN);
  scrollable_column_set->AddPaddingColumn(0, button_margin);

  layout->StartRow(0, column_set_id);
  scroll_view_ = new views::ScrollView();
  scroll_view_->set_hide_horizontal_scrollbar(true);
  scroll_view_->SetContents(scrollable);
  layout->AddView(scroll_view_, 4, 1);

  if (prompt_->ShouldShowPermissions()) {
    bool has_permissions =
        prompt_->GetPermissionCount(
            ExtensionInstallPrompt::PermissionsType::ALL_PERMISSIONS) > 0;
    if (has_permissions) {
      AddPermissions(
          scroll_layout, rb, column_set_id, content_width,
          ExtensionInstallPrompt::PermissionsType::REGULAR_PERMISSIONS);
      AddPermissions(
          scroll_layout, rb, column_set_id, content_width,
          ExtensionInstallPrompt::PermissionsType::WITHHELD_PERMISSIONS);
    } else {
      scroll_layout->AddPaddingRow(0, vertical_padding);
      scroll_layout->StartRow(0, column_set_id);
      views::Label* permission_label = new views::Label(
          l10n_util::GetStringUTF16(IDS_EXTENSION_NO_SPECIAL_PERMISSIONS));
      permission_label->SetMultiLine(true);
      permission_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
      permission_label->SizeToFit(content_width);
      scroll_layout->AddView(permission_label);
    }
  }

  if (prompt_->GetRetainedFileCount()) {
    scroll_layout->AddPaddingRow(0, vertical_padding);

    scroll_layout->StartRow(0, column_set_id);
    views::Label* retained_files_header =
        new views::Label(prompt_->GetRetainedFilesHeading());
    retained_files_header->SetMultiLine(true);
    retained_files_header->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    retained_files_header->SizeToFit(content_width);
    scroll_layout->AddView(retained_files_header);

    scroll_layout->StartRow(0, column_set_id);
    PermissionDetails details;
    for (size_t i = 0; i < prompt_->GetRetainedFileCount(); ++i) {
      details.push_back(prompt_->GetRetainedFile(i));
    }
    ExpandableContainerView* issue_advice_view =
        new ExpandableContainerView(details, content_width, false);
    scroll_layout->AddView(issue_advice_view);
  }

  if (prompt_->GetRetainedDeviceCount()) {
    scroll_layout->AddPaddingRow(0, vertical_padding);

    scroll_layout->StartRow(0, column_set_id);
    views::Label* retained_devices_header =
        new views::Label(prompt_->GetRetainedDevicesHeading());
    retained_devices_header->SetMultiLine(true);
    retained_devices_header->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    retained_devices_header->SizeToFit(content_width);
    scroll_layout->AddView(retained_devices_header);

    scroll_layout->StartRow(0, column_set_id);
    PermissionDetails details;
    for (size_t i = 0; i < prompt_->GetRetainedDeviceCount(); ++i) {
      details.push_back(prompt_->GetRetainedDeviceMessageString(i));
    }
    ExpandableContainerView* issue_advice_view =
        new ExpandableContainerView(details, content_width, false);
    scroll_layout->AddView(issue_advice_view);
  }

  DCHECK_GE(prompt_->type(), 0);
  UMA_HISTOGRAM_ENUMERATION("Extensions.InstallPrompt.Type",
                            prompt_->type(),
                            ExtensionInstallPrompt::NUM_PROMPT_TYPES);

  scroll_view_->ClipHeightTo(
      0,
      std::min(kScrollViewMaxHeight, scrollable->GetPreferredSize().height()));

  dialog_size_ = gfx::Size(content_width + 2 * button_margin,
                           container_->GetPreferredSize().height());

  std::string event_name = ExperienceSamplingEvent::kExtensionInstallDialog;
  event_name.append(
      ExtensionInstallPrompt::PromptTypeToString(prompt_->type()));
  sampling_event_ = ExperienceSamplingEvent::Create(event_name);
}

Project: Chrome

void ExtensionInstallDialogView::LinkClicked(views::Link* source,
                                             int event_flags) {
  GURL store_url(extension_urls::GetWebstoreItemDetailURLPrefix() +
                 prompt_->extension()->id());
  OpenURLParams params(store_url, Referrer(),
                       WindowOpenDisposition::NEW_FOREGROUND_TAB,
                       ui::PAGE_TRANSITION_LINK, false);

  if (navigator_) {
    navigator_->OpenURL(params);
  } else {
    chrome::ScopedTabbedBrowserDisplayer displayer(profile_);
    displayer.browser()->OpenURL(params);
  }
  GetWidget()->Close();
}

Project: Chrome

void ExtensionInstallDialogView::OnNativeThemeChanged(
    const ui::NativeTheme* theme) {
  scroll_view_->SetBackgroundColor(
      theme->GetSystemColor(ui::NativeTheme::kColorId_DialogBackground));
}

Project: Chrome

void ExpandableContainerView::UpdateArrowToggle(bool expanded) {
  gfx::ImageSkia icon = gfx::CreateVectorIcon(
      expanded ? kCaretUpIcon : kCaretDownIcon, gfx::kChromeIconGrey);
  arrow_toggle_->SetImage(views::Button::STATE_NORMAL, &icon);
}

Project: Chrome

ExtensionInstallDialogView::~ExtensionInstallDialogView() {
  if (!handled_result_ && !done_callback_.is_null()) {
    base::ResetAndReturn(&done_callback_)
        .Run(ExtensionInstallPrompt::Result::USER_CANCELED);
  }
}

Project: Chrome

  views::DialogDelegateView* CreateAndShowPrompt(
      ExtensionInstallPromptTestHelper* helper) {
    std::unique_ptr<ExtensionInstallDialogView> dialog(
        new ExtensionInstallDialogView(profile(), web_contents(),
                                       helper->GetCallback(), CreatePrompt()));
    views::DialogDelegateView* delegate_view = dialog.get();

    views::Widget* modal_dialog = views::DialogDelegate::CreateDialogWidget(
        dialog.release(), nullptr,
        platform_util::GetViewForWindow(
            browser()->window()->GetNativeWindow()));
    modal_dialog->Show();

    return delegate_view;
  }

Project: Chrome

ExtensionInstallDialogViewTestBase::CreatePrompt() {
  std::unique_ptr<ExtensionInstallPrompt::Prompt> prompt(
      new ExtensionInstallPrompt::Prompt(prompt_type_));
  prompt->set_extension(extension_);

  std::unique_ptr<ExtensionIconManager> icon_manager(
      new ExtensionIconManager());
  prompt->set_icon(icon_manager->GetIcon(extension_->id()));

  return prompt;
}

Project: Chrome

  ExtensionInstallDialogViewTest()
      : ExtensionInstallDialogViewTestBase(
            ExtensionInstallPrompt::INSTALL_PROMPT) {}

Project: Chrome

ExtensionInstallDialogViewTestBase::ExtensionInstallDialogViewTestBase(
    ExtensionInstallPrompt::PromptType prompt_type)
    : extension_(NULL), prompt_type_(prompt_type), web_contents_(NULL) {}

Project: Chrome

bool ScrollbarTest::IsScrollbarVisible(
    std::unique_ptr<ExtensionInstallPrompt::Prompt> prompt) {
  ExtensionInstallDialogView* dialog = new ExtensionInstallDialogView(
      profile(), web_contents(), ExtensionInstallPrompt::DoneCallback(),
      std::move(prompt));

  views::Widget* modal = constrained_window::CreateBrowserModalDialogViews(
      dialog, web_contents()->GetTopLevelNativeWindow());
  modal->Show();
  content::RunAllBlockingPoolTasksUntilIdle();

  return dialog->scroll_view()->vertical_scroll_bar()->visible();
}

Project: Chrome

ScrollbarTest::ScrollbarTest()
    : ExtensionInstallDialogViewTestBase(
          ExtensionInstallPrompt::PERMISSIONS_PROMPT) {
}

Project: Chrome

  content::WebContents* web_contents() { return web_contents_; }