Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Profiles
DescriptionUse after free in Profiles
ComponentProfiles
Bug ClassUAF
Tracker376493203
Fix commita7214df6d6c2 (chromium/src) +4/-6
CISA KEVNot listed
Creditedparkminchan, SSD Labs Korea
Disclosed2025-03-04

Files Changed

  • chrome/browser/ui/webui/signin/profile_picker_handler.cc
From a7214df6d6c2bed2db9260f4f3bdac93e3e46342 Mon Sep 17 00:00:00 2001
From: Ryan Sultanem <[email protected]>
Date: Mon, 03 Feb 2025 06:58:07 -0800
Subject: [PATCH] [ProfilePicker] Fix potential UAF in the handler

Details of the analysis and reason for the fix in the linked bug.

Fixed: 376493203
Change-Id: Id42f0af3f4e6a4318ed97c617c38a4ba75bea397
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6225319
Reviewed-by: David Roger <[email protected]>
Commit-Queue: Ryan Sultanem <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1414896}
---

diff --git a/chrome/browser/ui/webui/signin/profile_picker_handler.cc b/chrome/browser/ui/webui/signin/profile_picker_handler.cc
index 30a71d4b..5beb496 100644
--- a/chrome/browser/ui/webui/signin/profile_picker_handler.cc
+++ b/chrome/browser/ui/webui/signin/profile_picker_handler.cc
@@ -514,12 +514,10 @@
       ProfileMetrics::ADD_NEW_PROFILE_PICKER_LOCAL);
   ProfilePicker::SwitchToSignedOutPostIdentityFlow(
       profile_color,
-      base::BindOnce(
-          &ProfilePickerHandler::OnProfileCreationFinished,
-          // `OnProfileCreationFinished` is called when we want to close the
-          // profile picker. `ProfilePickerHandler` will always be initialized
-          // when we get to that call because the picker will still be open.
-          base::Unretained(this)));
+      base::BindOnce(&ProfilePickerHandler::OnProfileCreationFinished,
+                     // `OnProfileCreationFinished` is called when we want to
+                     // close the profile picker.
+                     weak_factory_.GetWeakPtr()));
 }
 
 void ProfilePickerHandler::HandleGetSwitchProfile(
Loading diff…

Original Bug Report

reported by [email protected]

ProfilePickerHandler UAF via UI

Summary

Use after free in Profiles in Google Chrome allows a remote attacker who convinced a user to install a malicious extension to potentially exploit heap corruption via a crafted HTML page.


Reproduction Case

  1. For ease of reproduction, Apply patch.diff.
  2. Press the UI button to open the Chromium Profile Management window.
  3. Open a new tab, move to chrome://profile-picker, type chrome.send("continueWithoutAccount", [0]); into the DevTools’s console, and close the tab.

Root Cause Analysis

chrome/browser/ui/webui/signin/profile_picker_handler.cc

void ProfilePickerHandler::HandleContinueWithoutAccount(
    const base::Value::List& args) {
  CHECK_EQ(1U, args.size());

  // profileColor is undefined for the default theme.
  std::optional<SkColor> profile_color;
  if (args[0].is_int())
    profile_color = args[0].GetInt();

  RecordProfilePickerAction(ProfilePickerAction::kLaunchNewProfile);
  ProfileMetrics::LogProfileAddNewUser(
      ProfileMetrics::ADD_NEW_PROFILE_PICKER_LOCAL);
  ProfilePicker::SwitchToSignedOutPostIdentityFlow(
      profile_color, profile_picked_time_on_startup_,
      base::BindOnce(
          &ProfilePickerHandler::OnProfileCreationFinished,
          // `OnProfileCreationFinished` is called when we want to close the
          // profile picker. `ProfilePickerHandler` will always be initialized
          // when we get to that call because the picker will still be open.
          base::Unretained(this))); // [0]
}
  • [0],HandleContinueWithoutAccount function transfers the ProfilePickerHandler::OnProfileCreationFinished function bound by the this pointer to the ProfilePicker::SwitchToSignedOutPostIdentityFlow function as a callback param.

chrome/browser/ui/views/profiles/profile_picker_view.cc

void ProfilePicker::SwitchToSignedOutPostIdentityFlow(
    std::optional<SkColor> profile_color,
    base::TimeTicks profile_picked_time_on_startup,
    base::OnceCallback<void(bool)> switch_finished_callback) {
  if (g_profile_picker_view) {
    g_profile_picker_view->SwitchToSignedOutPostIdentityFlow(
        profile_color, profile_picked_time_on_startup,
        std::move(switch_finished_callback)); // [1]
  }
}
  • [1], In order to execute the g_profile_picker_view->SwitchToSignedOutPostIdentityFlow functions, the value of the g_profile_picker_view pointer must be set in a normal flow.

  • It is possible to create a complete chain that does not require user gestures by utilizing a chrome extension, but for simple proof, it is replaced by pressing a button directly to open the Chromium Profile Management window.

chrome/browser/profiles/profile_manager.cc

void ProfileManager::CreateMultiProfileAsync(
    const std::u16string& name,
    size_t icon_index,
    bool is_hidden,
    base::OnceCallback<void(Profile*)> initialized_callback,
    base::OnceCallback<void(Profile*)> created_callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(!name.empty());
  DCHECK(profiles::IsDefaultAvatarIconIndex(icon_index));

  ProfileManager* profile_manager = g_browser_process->profile_manager();

  ProfileAttributesStorage& storage =
      profile_manager->GetProfileAttributesStorage();
  base::FilePath new_path;
  ProfileAttributesEntry* entry = nullptr;

  do {
    new_path = profile_manager->GenerateNextProfileDirectoryPath();
    // The generated path should be unused and free to use.
    DCHECK_EQ(profile_manager->GetProfileByPath(new_path), nullptr);
    DCHECK(profile_manager->CanCreateProfileAtPath(new_path));

    entry = storage.GetProfileAttributesWithPath(new_path);
  } while (entry != nullptr);

  ProfileAttributesInitParams init_params;
  init_params.profile_path = new_path;
  init_params.profile_name = name;
  init_params.icon_index = icon_index;
  init_params.is_ephemeral = is_hidden;
  init_params.is_omitted = is_hidden;
  storage.AddProfile(std::move(init_params));

  base::ThreadPool::PostTask(
      FROM_HERE,
      {base::MayBlock(), base::TaskPriority::USER_BLOCKING,
       base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
      base::BindOnce(&NukeProfileFromDisk, new_path,
                     base::BindOnce(&ProfileManager::CreateProfileAsync,
                                    profile_manager->weak_factory_.GetWeakPtr(),
                                    new_path, std::move(initialized_callback),
                                    std::move(created_callback)))); // [2]
}
  • [2], The callback function delivered in [0] was executed within the initialized_callback function, and it was posted to the thread pool to asynchronously process tasks.

content/browser/webui/web_ui_message_handler.cc

bool WebUIMessageHandler::IsJavascriptAllowed() {
  return javascript_allowed_ && web_ui() && web_ui()->CanCallJavascript(); // [3]
}
  • [3], the ProfilePickerHandler::OnProfileCreationFinished function posted in the thread pool is called back, and UAF occurs by referring to the javascript_allowed_ variable of WebUIMessageHandler that has already been freed within the function.

Bisect

https://source.chromium.org/chromium/chromium/src/+/4dd469427e8a336a1ca52f9a523b78785bbdb897

  • In the process of reorganizing the function ProfilePickerHandler::HandleCreateProfileAndOpenCustomizationDialog to HandleContinueWithoutAccount, consideration of the lifetime of the object was omitted.

chrome/browser/ui/webui/signin/profile_picker_handler.h

class ProfilePickerHandler : public content::WebUIMessageHandler,
                             public content::WebContentsObserver,
#if BUILDFLAG(IS_CHROMEOS_LACROS)
                             public AccountProfileMapper::Observer,
#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
                             public ProfileAttributesStorage::Observer {
...

  base::WeakPtrFactory<ProfilePickerHandler> weak_factory_{this};
};
  • weak_factory_ has already been declared within the ProfilePickerHandler class.

fix.diff

--- a/chrome/browser/ui/webui/signin/profile_picker_handler.cc	
+++ b/chrome/browser/ui/webui/signin/profile_picker_handler.cc	
@@ -617,7 +617,7 @@
           // `OnProfileCreationFinished` is called when we want to close the
           // profile picker. `ProfilePickerHandler` will always be initialized
           // when we get to that call because the picker will still be open.
-          base::Unretained(this)));
+          weak_factory_.GetWeakPtr()));
 }

 void ProfilePickerHandler::HandleGetSwitchProfile(
  • UAF can be prevented by modifying the existing base::Unretained(this) to weak_factory_.GetWeakPtr() to verify that the object of the callback function is valid.

It is dangerous because an attacker can use Extension to create PoCs that do not require separate user gestures.


Version

131.0.6776.0

Credit

parkminchan, working for SSD Labs Korea.

View on issue tracker