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 <gab@chromium.org>
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.
> >
> > R=grt@chromium.org
> >
> > 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 <grt@chromium.org>
> > Auto-Submit: Gabriel Charette <gab@chromium.org>
> > Commit-Queue: Gabriel Charette <gab@chromium.org>
> > 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 <bashi@chromium.org>
> Owners-Override: Kenichi Ishibashi <bashi@chromium.org>
> Commit-Queue: rubber-stamper@appspot.gserviceaccount.com <rubber-stamper@appspot.gserviceaccount.com>
> Bot-Commit: rubber-stamper@appspot.gserviceaccount.com <rubber-stamper@appspot.gserviceaccount.com>
> 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 <gab@chromium.org>
Commit-Queue: Greg Thompson <grt@chromium.org>
Reviewed-by: Greg Thompson <grt@chromium.org>
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