CVE-2025-1916
Overview
Files Changed
chrome/browser/ui/webui/signin/profile_picker_handler.cc
Patch
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(
Original Bug Report
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
- For ease of reproduction, Apply
patch.diff. - Press the UI button to open the
Chromium Profile Managementwindow. - Open a new tab, move to
chrome://profile-picker, typechrome.send("continueWithoutAccount", [0]);into theDevTools’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],HandleContinueWithoutAccountfunction transfers theProfilePickerHandler::OnProfileCreationFinishedfunction bound by thethispointer to theProfilePicker::SwitchToSignedOutPostIdentityFlowfunction 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 theg_profile_picker_view->SwitchToSignedOutPostIdentityFlowfunctions, the value of theg_profile_picker_viewpointer 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 Managementwindow.
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 theinitialized_callbackfunction, 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], theProfilePickerHandler::OnProfileCreationFinishedfunction posted in the thread pool is called back, and UAF occurs by referring to thejavascript_allowed_variable ofWebUIMessageHandlerthat has already been freed within the function.
Bisect
https://source.chromium.org/chromium/chromium/src/+/4dd469427e8a336a1ca52f9a523b78785bbdb897
- In the process of reorganizing the function
ProfilePickerHandler::HandleCreateProfileAndOpenCustomizationDialogtoHandleContinueWithoutAccount, consideration of the lifetime of the object was omitted.
Recommended Patch
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 theProfilePickerHandlerclass.
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)toweak_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.