Chrome · CredentialProvider
CVE-2026-87530
Logic Error in CredentialProvider
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Files Changed
chrome/credential_provider/setup/BUILD.gnchrome/credential_provider/setup/setup.ccchrome/credential_provider/setup/setup_lib.ccchrome/credential_provider/setup/setup_utils.ccchrome/credential_provider/setup/setup_utils.hchrome/credential_provider/test/gcp_setup_unittest.cc
Patch
From 5a583ca6b6a2fac3b481b1cd7744534531965d6c Mon Sep 17 00:00:00 2001 From: Gabriel Charette <[email protected]> Date: Mon, 17 Aug 2026 00:56:59 -0700 Subject: [PATCH] Reland "[gcpw] Relaunch the uninstaller from the system temp directory" This reverts commit 16832512e03a6e690ef18007c56dfc9538b049e7. Reason for reland: Fix test under component build. Original change's description: > Revert "[gcpw] Relaunch the uninstaller from the system temp directory" > > This reverts commit af7d71b118cb2b0f8a623258ac777726e05217e8. > > Reason for revert: Failing on Win10 > > Failure Link: https://ci.chromium.org/ui/p/chromium/builders/ci/Win10%20Tests%20x64%20(dbg)/54781/overview > > Original change's description: > > [gcpw] Relaunch the uninstaller from the system temp directory > > > > RelaunchUninstaller() copies gcp_setup.exe into a fresh temp directory > > and relaunches it from there with that directory as both the > > application directory and working directory of the child. The > > directory was obtained via base::CreateNewTempDirectory(), which for > > an elevated admin with a split token resolves to the per-user %TEMP%. > > > > This function only runs after the IsUserAnAdmin() gate, so stage the > > relaunched copy under base::DIR_SYSTEM_TEMP instead, matching what > > chrome/installer/setup, chrome/updater, and > > chrome/enterprise_companion already do for the same operation. > > > > Additionally, this change: > > - Restricts DLL loading to full paths or %SYSTEM32% by calling > > EnableSecureDllLoading() (SetDefaultDllDirectories( > > LOAD_LIBRARY_SEARCH_SYSTEM32) or LOAD_LIBRARY_SEARCH_DEFAULT_DIRS > > in component builds) in setup.cc. > > - Adds /DEPENDENTLOADFLAG:0x800 to gcp_setup ldflags in BUILD.gn to > > enforce secure dependent DLL loading at the PE header level > > (matching gcp_sfx). > > - Fails early with E_FAIL in RelaunchUninstaller() for component > > builds since copying only gcp_setup.exe into an isolated temp > > directory does not copy dependent component DLLs. > > - Adds a regression test > > (GcpSetupTest.RelaunchUninstallerStagesUnderSystemTemp) that > > overrides DIR_SYSTEM_TEMP and verifies the staging directory is > > created beneath it. > > > > [email protected] > > > > TAG=agy > > CONV=9b81bd43-a335-43ec-81e4-e60832a65aed > > > > Bug: 518081914 > > Change-Id: I76501070ac866647c48ea9612720b72da8e4580c > > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8240372 > > Reviewed-by: Greg Thompson <[email protected]> > > Auto-Submit: Gabriel Charette <[email protected]> > > Commit-Queue: Gabriel Charette <[email protected]> > > Cr-Commit-Position: refs/heads/main@{#1678221} > > Bug: 518081914 > No-Presubmit: true > No-Tree-Checks: true > No-Try: true > Change-Id: I08ee9a9b2a8079a87733c48b151d7c103b361f14 > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8247942 > Auto-Submit: Kenichi Ishibashi <[email protected]> > Owners-Override: Kenichi Ishibashi <[email protected]> > Commit-Queue: [email protected] <[email protected]> > Bot-Commit: [email protected] <[email protected]> > Cr-Commit-Position: refs/heads/main@{#1678376} Fixed: 518081914 Change-Id: Ie25b253c88dc28efcbf7d92efd8307c8adee82c7 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8256641 Auto-Submit: Gabriel Charette <[email protected]> Commit-Queue: Greg Thompson <[email protected]> Reviewed-by: Greg Thompson <[email protected]> Cr-Commit-Position: refs/heads/main@{#1680390} --- diff --git a/chrome/credential_provider/setup/BUILD.gn b/chrome/credential_provider/setup/BUILD.gn index 063669a..4325145 100644 --- a/chrome/credential_provider/setup/BUILD.gn +++ b/chrome/credential_provider/setup/BUILD.gn @@ -98,4 +98,5 @@ "//content/public/common:static_switches", ] configs += [ "//build/config/win:windowed" ] + ldflags = [ "/DEPENDENTLOADFLAG:0x800" ] } diff --git a/chrome/credential_provider/setup/setup.cc b/chrome/credential_provider/setup/setup.cc index 3abe46d9..ab45c25 100644 --- a/chrome/credential_provider/setup/setup.cc +++ b/chrome/credential_provider/setup/setup.cc @@ -62,6 +62,8 @@ HINSTANCE /*hPrevInstance*/, wchar_t* lpCmdLine, int /*nCmdShow*/) { + credential_provider::EnableSecureDllLoading(); + HRESULT hr = S_OK; // Initialize base. Command line will be set from GetCommandLineW(). diff --git a/chrome/credential_provider/setup/setup_lib.cc b/chrome/credential_provider/setup/setup_lib.cc index 407d412..7f01b57 100644 --- a/chrome/credential_provider/setup/setup_lib.cc +++ b/chrome/credential_provider/setup/setup_lib.cc @@ -9,6 +9,7 @@ #include <iomanip> #include <string> +#include "base/base_paths.h" #include "base/command_line.h" #include "base/compiler_specific.h" #include "base/file_version_info.h" @@ -274,10 +275,24 @@ } HRESULT RelaunchUninstaller(const base::FilePath& installer_path) { +#if defined(COMPONENT_BUILD) + // In component builds, dependent DLLs are not copied to the temporary + // directory, so the executable cannot launch. + return E_FAIL; +#else + // This function only runs elevated, so stage and relaunch the copy of the + // installer from the system temp directory rather than the per-user one. + base::FilePath system_temp; + if (!base::PathService::Get(base::DIR_SYSTEM_TEMP, &system_temp)) { + LOGFN(ERROR) << "PathService::Get(DIR_SYSTEM_TEMP) failed"; + return E_FAIL; + } + base::FilePath temp_path; - if (!base::CreateNewTempDirectory(FILE_PATH_LITERAL("gcp"), &temp_path)) { + if (!base::CreateTemporaryDirInDir(system_temp, FILE_PATH_LITERAL("gcp"), + &temp_path)) { HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); - LOGFN(ERROR) << "CreateNewTempDirectory hr=" << putHR(hr); + LOGFN(ERROR) << "CreateTemporaryDirInDir hr=" << putHR(hr); return hr; } @@ -317,6 +332,7 @@ base::Process process(base::LaunchProcess(cmdline, options)); return process.IsValid() ? S_OK : E_FAIL; +#endif // defined(COMPONENT_BUILD) } int EnableStatsCollection(const base::CommandLine& cmdline) { diff --git a/chrome/credential_provider/setup/setup_utils.cc b/chrome/credential_provider/setup/setup_utils.cc index 8b9df89c..32d28bd5 100644 --- a/chrome/credential_provider/setup/setup_utils.cc +++ b/chrome/credential_provider/setup/setup_utils.cc @@ -323,4 +323,14 @@ return true; } +bool EnableSecureDllLoading() { +#if defined(COMPONENT_BUILD) + const DWORD directory_flags = LOAD_LIBRARY_SEARCH_DEFAULT_DIRS; +#else + const DWORD directory_flags = LOAD_LIBRARY_SEARCH_SYSTEM32; +#endif + + return ::SetDefaultDllDirectories(directory_flags); +} + } // namespace credential_provider diff --git a/chrome/credential_provider/setup/setup_utils.h b/chrome/credential_provider/setup/setup_utils.h index a619b01..269e847 100644 --- a/chrome/credential_provider/setup/setup_utils.h +++ b/chrome/credential_provider/setup/setup_utils.h @@ -84,6 +84,9 @@ base::DictValue installer_data_dictionary_; }; +// Restricts DLL loads to either full paths or %SYSTEM32%. +bool EnableSecureDllLoading(); + } // namespace credential_provider #endif // CHROME_CREDENTIAL_PROVIDER_SETUP_SETUP_UTILS_H_ diff --git a/chrome/credential_provider/test/gcp_setup_unittest.cc b/chrome/credential_provider/test/gcp_setup_unittest.cc index bba356f..59823bb 100644 --- a/chrome/credential_provider/test/gcp_setup_unittest.cc +++ b/chrome/credential_provider/test/gcp_setup_unittest.cc @@ -18,6 +18,7 @@ #include "base/environment.h" #include "base/file_version_info.h" #include "base/files/file.h" +#include "base/files/file_enumerator.h" #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" #include "base/process/launch.h" @@ -327,6 +328,10 @@ }
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/chrome/credential_provider/test/gcp_setup_unittest.cc b/chrome/credential_provider/test/gcp_setup_unittest.cc
index bba356f..59823bb 100644
--- a/chrome/credential_provider/test/gcp_setup_unittest.cc
+++ b/chrome/credential_provider/test/gcp_setup_unittest.cc
@@ -18,6 +18,7 @@
#include "base/environment.h"
#include "base/file_version_info.h"
#include "base/files/file.h"
+#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/process/launch.h"
@@ -327,6 +328,10 @@
}
void GcpSetupTest::SetUp() {
+ if (!::IsUserAnAdmin()) {
+ GTEST_SKIP() << "Test requires administrative privileges.";
+ }
+
// Get the path to the setup exe (this exe during unit tests) and the
// chrome version.
GetModulePathAndProductVersion(&module_path_, &product_version_);
@@ -355,10 +360,6 @@
programdata_override_ = std::make_unique<base::ScopedPathOverride>(
base::DIR_COMMON_APP_DATA, scoped_temp_progdata_dir_.GetPath());
- if (!::IsUserAnAdmin()) {
- GTEST_SKIP() << "Test requires administrative privileges.";
- }
-
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
// In non-component builds, base::FILE_MODULE will always return the path
@@ -705,6 +706,32 @@
.empty());
}
+TEST_F(GcpSetupTest, RelaunchUninstallerStagesUnderSystemTemp) {
+#if defined(COMPONENT_BUILD)
+ GTEST_SKIP() << "RelaunchUninstaller is not supported in component builds.";
+#else
+ base::ScopedTempDir system_temp;
+ EXPECT_TRUE(system_temp.CreateUniqueTempDir());
+ base::ScopedPathOverride system_temp_override(base::DIR_SYSTEM_TEMP,
+ system_temp.GetPath());
+
+ base::ScopedTempDir source_dir;
+ EXPECT_TRUE(source_dir.CreateUniqueTempDir());
+ base::FilePath installer_path =
+ source_dir.GetPath().Append(FILE_PATH_LITERAL("gcp_setup.exe"));
+ EXPECT_TRUE(base::WriteFile(installer_path, "stub"));
+
+ RelaunchUninstaller(installer_path);
+
+ base::FileEnumerator enumerator(system_temp.GetPath(), /*recursive=*/false,
+ base::FileEnumerator::DIRECTORIES,
+ FILE_PATH_LITERAL("gcp*"));
+ base::FilePath staged_dir = enumerator.Next();
+ EXPECT_FALSE(staged_dir.empty());
+ EXPECT_TRUE(base::PathExists(staged_dir.Append(installer_path.BaseName())));
+#endif // defined(COMPONENT_BUILD)
+}
+
TEST_F(GcpSetupTest, ValidLsaWithNoExistingUser) {
logging::ResetEventSourceForTesting();
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page