CVE-2026-11697
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
TESTui/base/x/x11_cursor_loader_unittest.cc |
modified | |
forui/base/x/x11_cursor_loader_unittest.cc |
modified |
Files Changed
ui/base/x/x11_cursor_loader.ccui/base/x/x11_cursor_loader.hui/base/x/x11_cursor_loader_unittest.cc
Patch
From 633de441a4c8d326e1575eef15167d9e38e4cdb1 Mon Sep 17 00:00:00 2001 From: Tom Anderson <[email protected]> Date: Mon, 01 Jun 2026 14:50:58 -0700 Subject: [PATCH] Add validation to cursor theme name to prevent path traversal. On Linux Ozone/X11 platforms, a compromised GPU process could potentially exploit its cloned X11 connection to hijack the XSETTINGS selection, supplying a malicious cursor theme name. This CL remediates the issue by sanitizing and validating any cursor theme name retrieved from GetCursorThemeName() or recursive inherits parsing during the cursor loading process, rejecting absolute paths, relative parent references, and paths with multiple components. Fixed: 518105731 Change-Id: Ia0f9eb7b433dc1d7a200485dfb70e1ec67e68910 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7890525 Reviewed-by: Lei Zhang <[email protected]> Commit-Queue: Thomas Anderson <[email protected]> Cr-Commit-Position: refs/heads/main@{#1639694} --- diff --git a/ui/base/x/x11_cursor_loader.cc b/ui/base/x/x11_cursor_loader.cc index 7188541..ad5feb08 100644 --- a/ui/base/x/x11_cursor_loader.cc +++ b/ui/base/x/x11_cursor_loader.cc @@ -152,12 +152,22 @@ return path; } +bool IsValidCursorThemeName(const std::string& theme) { + base::FilePath theme_path(theme); + return !theme.empty() && theme != "." && !theme_path.IsAbsolute() && + !theme_path.ReferencesParent() && theme_path.BaseName() == theme_path; +} + scoped_refptr<base::RefCountedMemory> ReadCursorFromThemeImpl( const std::string& theme, const std::string& cursor_name, base::flat_set<ThemeAndCursorName>* parent_theme_and_cursor_names, base::flat_map<ThemeAndCursorName, scoped_refptr<base::RefCountedMemory>>* cache) { + if (!IsValidCursorThemeName(theme)) { + return nullptr; + } + constexpr const char kCursorDir[] = "cursors"; constexpr const char kThemeInfo[] = "index.theme"; @@ -267,6 +277,10 @@ } // namespace +bool IsValidCursorThemeNameForTesting(const std::string& theme) { + return IsValidCursorThemeName(theme); +} + XCursorLoader::XCursorLoader(x11::Connection* connection, base::RepeatingClosure on_cursor_config_changed) : connection_(connection), diff --git a/ui/base/x/x11_cursor_loader.h b/ui/base/x/x11_cursor_loader.h index 94d0872..570a70ce 100644 --- a/ui/base/x/x11_cursor_loader.h +++ b/ui/base/x/x11_cursor_loader.h @@ -90,6 +90,9 @@ }; COMPONENT_EXPORT(UI_BASE_X) +bool IsValidCursorThemeNameForTesting(const std::string& theme); + +COMPONENT_EXPORT(UI_BASE_X) std::vector<XCursorLoader::Image> ParseCursorFile( scoped_refptr<base::RefCountedMemory> file, uint32_t preferred_size); diff --git a/ui/base/x/x11_cursor_loader_unittest.cc b/ui/base/x/x11_cursor_loader_unittest.cc index d72f51d..3cdf6db 100644 --- a/ui/base/x/x11_cursor_loader_unittest.cc +++ b/ui/base/x/x11_cursor_loader_unittest.cc @@ -262,4 +262,31 @@ EXPECT_EQ(images[1].frame_delay.InMilliseconds(), 500); } +TEST(XCursorLoaderTest, ThemeNameValidation) { + const char* const kInvalidThemes[] = { + "", + ".", + "..", + "/foo", + "/tmp/evil", + "../foo", + "foo/..", + "../../../../tmp/poc-cursor-evil", + "foo/bar", + }; + for (const char* theme : kInvalidThemes) { + EXPECT_FALSE(IsValidCursorThemeNameForTesting(theme)); + } + + const char* const kValidThemes[] = { + "default", + "Adwaita", + "DMZ-White", + "my_theme-123", + }; + for (const char* theme : kValidThemes) { + EXPECT_TRUE(IsValidCursorThemeNameForTesting(theme)); + } +} + } // namespace ui
Regression Test / PoC
diff --git a/ui/base/x/x11_cursor_loader_unittest.cc b/ui/base/x/x11_cursor_loader_unittest.cc
index d72f51d..3cdf6db 100644
--- a/ui/base/x/x11_cursor_loader_unittest.cc
+++ b/ui/base/x/x11_cursor_loader_unittest.cc
@@ -262,4 +262,31 @@
EXPECT_EQ(images[1].frame_delay.InMilliseconds(), 500);
}
+TEST(XCursorLoaderTest, ThemeNameValidation) {
+ const char* const kInvalidThemes[] = {
+ "",
+ ".",
+ "..",
+ "/foo",
+ "/tmp/evil",
+ "../foo",
+ "foo/..",
+ "../../../../tmp/poc-cursor-evil",
+ "foo/bar",
+ };
+ for (const char* theme : kInvalidThemes) {
+ EXPECT_FALSE(IsValidCursorThemeNameForTesting(theme));
+ }
+
+ const char* const kValidThemes[] = {
+ "default",
+ "Adwaita",
+ "DMZ-White",
+ "my_theme-123",
+ };
+ for (const char* theme : kValidThemes) {
+ EXPECT_TRUE(IsValidCursorThemeNameForTesting(theme));
+ }
+}
+
} // namespace ui
Original Bug Report
Potential GPU-to-Browser Sandbox Escape via GTK XSETTINGS and Cursor Theme Path Traversal
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: A compromised GPU process can potentially exploit its cloned X11 connection to hijack the XSETTINGS selection and supply a malicious cursor theme name. Because the browser process does not sanitize this retrieved cursor theme name, it can lead to a directory traversal in the cursor loader. This allows the browser to traverse directories and parse arbitrary cursor files outside of the intended directories.
Affected files:
ui/base/x/x11_cursor_loader.ccui/gtk/gtk_ui.cc
Estimated timestamp from git blame: 2020-07-17
Description and Root Cause
On Linux Ozone/X11 platforms, the sandboxed GPU process retains a cloned, live X11 connection before sandbox lockdown to facilitate initialization. Specifically, in ui/ozone/platform/x11/ozone_platform_x11.cc:
// Set up the X11 connection before the sandbox gets set up.
auto connection = x11::Connection::Get()->Clone();
connection->DetachFromSequence();
surface_factory_ozone_ = std::make_unique<X11SurfaceFactory>(std::move(connection));
Meanwhile, the browser process runs the GDK/GTK backend in-process, which monitors XSETTINGS properties (specifically the _XSETTINGS_SETTINGS property on the selection owner of _XSETTINGS_S0) to propagate system-wide settings. GtkUi registers for notifications on the gtk-cursor-theme-name setting (ui/gtk/gtk_ui.cc):
connect(settings, "notify::gtk-cursor-theme-name", &GtkUi::OnCursorThemeNameChanged);
When a change is signaled, GtkUi::GetCursorThemeName retrieves the property string verbatim and propagates it without sanitization:
std::string GtkUi::GetCursorThemeName() {
gchar* theme = nullptr;
g_object_get(gtk_settings_get_default(), "gtk-cursor-theme-name", &theme, nullptr);
...
return theme_string;
}
Downstream in ui/base/x/x11_cursor_loader.cc, XCursorLoader reads and loads the cursor files on a background ThreadPool thread. In ReadCursorFromThemeImpl, it traverses search directories and appends the theme name directly using base::FilePath::Append:
for (const auto& path : paths) {
auto dir = CanonicalizePath(base::FilePath(path));
if (dir.empty())
continue;
base::FilePath theme_dir = dir.Append(theme); // Path traversal vulnerability
base::FilePath cursor_dir = theme_dir.Append(kCursorDir);
std::string contents;
if (base::ReadFileToString(cursor_dir.Append(cursor_name), &contents)) {
...
}
}
Because base::FilePath::Append does not validate path components in release builds (it only contains a DCHECK(!IsPathAbsolute(appended)) which is stripped on release and does not block .. parent directory segments), a theme name containing .. sequences can successfully escape the standard icon directories. The browser process will then attempt to read and parse files at the resolved path via ParseCursorFile.
Potential Exploitation Scenario
Note: These are suggested/potential steps; we do not currently have a fully running proof of concept.
- An attacker gains code execution in the sandboxed GPU process.
- The attacker uses the cloned X11 connection to create an X11 window.
- The attacker writes a malicious XSETTINGS payload containing a path traversal string (e.g.,
../../../../tmp/evil_theme) to the_XSETTINGS_SETTINGSproperty on the window. - The attacker claims ownership of the
_XSETTINGS_S0selection and broadcasts aMANAGERClientMessageto notify the root window. - GDK in the browser process intercepts this, parses the payload in-process, and updates
GtkSettings. - The
notify::gtk-cursor-theme-namesignal fires, clearing the cached cursors inX11CursorFactory::ClearThemeCursors. - Upon the next cursor state update, the browser process requests a cursor reload, invoking
ReadCursorFromThemeImplwhich appends the traversal path, resolves the absolute target location, reads the file, and parses it inside the browser process.
Suggested Remediation
To remediate this issue, the browser process should validate and sanitize any cursor theme name retrieved from GetCursorThemeName() or during the cursor loading process.
Specifically, you can reject theme names that contain references to parent directories or absolute paths, or enforce that the theme string consists of only alphanumeric characters, dashes, and underscores:
// Verify that the theme name does not attempt directory traversal
base::FilePath theme_path(theme);
if (theme_path.ReferencesParent() || theme_path.IsAbsolute()) {
return nullptr; // or fallback to "default"
}
Evaluated with Chrome root at commit: fb72408a8493c46bc75fae1c70d03daec96b3040
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.