Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in UI
DescriptionInsufficient validation of untrusted input in UI
ComponentUI
Bug ClassLogic Error
Tracker540289900
Fix commit128dd53d2480 (chromium/src) +139/-7
CISA KEVNot listed
CreditedFabian Wahle (Hap Security)
Disclosed2026-08-06

Changed Functions

FunctionChangeNotes
TEST
ui/base/cursor/cursor_unittest.cc
modified

Files Changed

  • ui/base/cursor/cursor_factory.cc
  • ui/base/cursor/cursor_factory.h
  • ui/base/cursor/cursor_unittest.cc
  • ui/base/x/x11_cursor_loader.cc
  • ui/gtk/gtk_ui.cc
From 128dd53d2480f175488af33fdf13d16e878f5748 Mon Sep 17 00:00:00 2001
From: Tom Anderson <[email protected]>
Date: Fri, 14 Aug 2026 20:54:45 -0700
Subject: [PATCH] Reland "Reland "Sanitize cursor theme name and size in GTK and Wayland""

This is a reland of commit 91a2334cbf479f688cc421aa7f7eb1802e8e2bfb

Original change's description:
> Reland "Sanitize cursor theme name and size in GTK and Wayland"
>
> This is a reland of commit 283ac1ef12df16eb0256a4d5ce83477b5afba061
>
> Original change's description:
> > Sanitize cursor theme name and size in GTK and Wayland
> >
> > This change extends GtkSettingsSetProperty interceptor and GtkUi to
> > sanitize gtk-cursor-theme-name and gtk-cursor-theme-size at write-time,
> > preventing unsanitized cursor theme names from being set in GTK
> > settings.
> >
> > It also introduces unified ui::IsValidCursorThemeName and
> > ui::IsValidCursorThemeSize validation helpers in //ui/base/cursor, and
> > adds validation to WaylandCursorFactory to ensure invalid or
> > path-traversing cursor theme names are rejected before calling
> > wl_cursor_theme_load.
> >
> > Change-Id: Ibe8a3ce208d76d2e724902424491a6116d4352f6
> > Fixed: 540289900
> > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8164559
> > Reviewed-by: Yichen Zhou <[email protected]>
> > Commit-Queue: Thomas Anderson <[email protected]>
> > Cr-Commit-Position: refs/heads/main@{#1670761}
>
> Bug: 542165218
> Fixed: 540289900
> Change-Id: I29b295e3296eb82a1304d9884d547cf939528f5a
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8180111
> Reviewed-by: Yichen Zhou <[email protected]>
> Commit-Queue: Thomas Anderson <[email protected]>
> Cr-Commit-Position: refs/heads/main@{#1673803}

Bug: 542165218
Fixed: 540289900
Change-Id: I9fff8f89924244a924d2daa292b98c9f1e6985e2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8225184
Auto-Submit: Thomas Anderson <[email protected]>
Reviewed-by: Yichen Zhou <[email protected]>
Commit-Queue: Thomas Anderson <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1680121}
---

diff --git a/ui/base/cursor/cursor_factory.cc b/ui/base/cursor/cursor_factory.cc
index 70fc2653..ddba1a97 100644
--- a/ui/base/cursor/cursor_factory.cc
+++ b/ui/base/cursor/cursor_factory.cc
@@ -8,6 +8,7 @@
 
 #include "base/check.h"
 #include "base/check_op.h"
+#include "base/files/file_path.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/notimplemented.h"
 #include "base/notreached.h"
@@ -99,6 +100,19 @@
 
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
 
+bool IsValidCursorThemeName(std::string_view name) {
+  if (name.empty() || name == ".") {
+    return false;
+  }
+  base::FilePath theme_path(name);
+  return !theme_path.IsAbsolute() && !theme_path.ReferencesParent() &&
+         theme_path.BaseName() == theme_path;
+}
+
+bool IsValidCursorThemeSize(int size) {
+  return size >= 0 && size <= 512;
+}
+
 // Returns a cursor name compatible with either X11 or the FreeDesktop.org
 // cursor spec ([1] and [2]), followed by fallbacks that can work as
 // replacements in some environments where the original may not be available
diff --git a/ui/base/cursor/cursor_factory.h b/ui/base/cursor/cursor_factory.h
index abcc292..3fcfcdf 100644
--- a/ui/base/cursor/cursor_factory.h
+++ b/ui/base/cursor/cursor_factory.h
@@ -7,6 +7,7 @@
 
 #include <optional>
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include "base/component_export.h"
@@ -96,6 +97,12 @@
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
 COMPONENT_EXPORT(UI_BASE_CURSOR)
 std::vector<std::string> CursorNamesFromType(mojom::CursorType type);
+
+COMPONENT_EXPORT(UI_BASE_CURSOR)
+bool IsValidCursorThemeName(std::string_view name);
+
+COMPONENT_EXPORT(UI_BASE_CURSOR)
+bool IsValidCursorThemeSize(int size);
 #endif
 
 }  // namespace ui
diff --git a/ui/base/cursor/cursor_unittest.cc b/ui/base/cursor/cursor_unittest.cc
index 446a689..83834f8b 100644
--- a/ui/base/cursor/cursor_unittest.cc
+++ b/ui/base/cursor/cursor_unittest.cc
@@ -4,8 +4,10 @@
 
 #include "ui/base/cursor/cursor.h"
 
+#include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/base/cursor/cursor_factory.h"
 #include "ui/base/cursor/mojom/cursor_type.mojom-shared.h"
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/image/image_unittest_util.h"
@@ -70,5 +72,27 @@
   EXPECT_EQ(gfx::Point(4, 6), cursor.custom_hotspot());
 }
 
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+TEST(CursorTest, IsValidCursorThemeName) {
+  EXPECT_TRUE(IsValidCursorThemeName("Adwaita"));
+  EXPECT_TRUE(IsValidCursorThemeName("DMZ-White"));
+  EXPECT_FALSE(IsValidCursorThemeName(""));
+  EXPECT_FALSE(IsValidCursorThemeName("."));
+  EXPECT_FALSE(IsValidCursorThemeName("../invalid"));
+  EXPECT_FALSE(IsValidCursorThemeName("/absolute/invalid"));
+  EXPECT_FALSE(IsValidCursorThemeName("sub/dir"));
+  EXPECT_FALSE(IsValidCursorThemeName("../../../../tmp/evil"));
+}
+
+TEST(CursorTest, IsValidCursorThemeSize) {
+  EXPECT_TRUE(IsValidCursorThemeSize(0));
+  EXPECT_TRUE(IsValidCursorThemeSize(16));
+  EXPECT_TRUE(IsValidCursorThemeSize(24));
+  EXPECT_TRUE(IsValidCursorThemeSize(512));
+  EXPECT_FALSE(IsValidCursorThemeSize(-1));
+  EXPECT_FALSE(IsValidCursorThemeSize(513));
+}
+#endif
+
 }  // namespace
 }  // namespace ui
diff --git a/ui/base/x/x11_cursor_loader.cc b/ui/base/x/x11_cursor_loader.cc
index ad5feb08..9a88782 100644
--- a/ui/base/x/x11_cursor_loader.cc
+++ b/ui/base/x/x11_cursor_loader.cc
@@ -33,6 +33,7 @@
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
 #include "base/time/time.h"
+#include "ui/base/cursor/cursor_factory.h"
 #include "ui/base/x/x11_util.h"
 #include "ui/gfx/x/atom_cache.h"
 #include "ui/gfx/x/connection.h"
@@ -153,9 +154,7 @@
 }
 
 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;
+  return ui::IsValidCursorThemeName(theme);
 }
 
 scoped_refptr<base::RefCountedMemory> ReadCursorFromThemeImpl(
diff --git a/ui/gtk/gtk_ui.cc b/ui/gtk/gtk_ui.cc
index a688787..f414351 100644
--- a/ui/gtk/gtk_ui.cc
+++ b/ui/gtk/gtk_ui.cc
@@ -35,6 +35,7 @@
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "third_party/skia/include/core/SkShader.h"
+#include "ui/base/cursor/cursor_factory.h"
 #include "ui/base/glib/glib_cast.h"
 #include "ui/base/ime/input_method.h"
 #include "ui/base/ime/linux/fake_input_method_context.h"
@@ -370,6 +371,8 @@
   g_object_set(settings, "gtk-modules", "", nullptr);
   SanitizeIconThemeName();
   SanitizeThemeName();
+  SanitizeCursorThemeName();
+  SanitizeCursorThemeSize();
   InstallGtkSettingsInterceptor();
 
   if (!GtkCheckVersion(4)) {
@@ -857,6 +860,26 @@
   return false;
 }
 
+bool GtkUi::SanitizeCursorThemeName() {
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/ui/base/cursor/cursor_unittest.cc b/ui/base/cursor/cursor_unittest.cc
index 446a689..83834f8b 100644
--- a/ui/base/cursor/cursor_unittest.cc
+++ b/ui/base/cursor/cursor_unittest.cc
@@ -4,8 +4,10 @@
 
 #include "ui/base/cursor/cursor.h"
 
+#include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/base/cursor/cursor_factory.h"
 #include "ui/base/cursor/mojom/cursor_type.mojom-shared.h"
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/image/image_unittest_util.h"
@@ -70,5 +72,27 @@
   EXPECT_EQ(gfx::Point(4, 6), cursor.custom_hotspot());
 }
 
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+TEST(CursorTest, IsValidCursorThemeName) {
+  EXPECT_TRUE(IsValidCursorThemeName("Adwaita"));
+  EXPECT_TRUE(IsValidCursorThemeName("DMZ-White"));
+  EXPECT_FALSE(IsValidCursorThemeName(""));
+  EXPECT_FALSE(IsValidCursorThemeName("."));
+  EXPECT_FALSE(IsValidCursorThemeName("../invalid"));
+  EXPECT_FALSE(IsValidCursorThemeName("/absolute/invalid"));
+  EXPECT_FALSE(IsValidCursorThemeName("sub/dir"));
+  EXPECT_FALSE(IsValidCursorThemeName("../../../../tmp/evil"));
+}
+
+TEST(CursorTest, IsValidCursorThemeSize) {
+  EXPECT_TRUE(IsValidCursorThemeSize(0));
+  EXPECT_TRUE(IsValidCursorThemeSize(16));
+  EXPECT_TRUE(IsValidCursorThemeSize(24));
+  EXPECT_TRUE(IsValidCursorThemeSize(512));
+  EXPECT_FALSE(IsValidCursorThemeSize(-1));
+  EXPECT_FALSE(IsValidCursorThemeSize(513));
+}
+#endif
+
 }  // namespace
 }  // namespace ui
diff --git a/ui/gtk/gtk_util_unittest.cc b/ui/gtk/gtk_util_unittest.cc
index ba116960..933b634f9 100644
--- a/ui/gtk/gtk_util_unittest.cc
+++ b/ui/gtk/gtk_util_unittest.cc
@@ -17,8 +17,10 @@
 TEST(GtkUtilTest, IsValidThemeName) {
   EXPECT_TRUE(IsValidThemeName(ThemeProperty::kThemeName, "Adwaita"));
   EXPECT_TRUE(IsValidThemeName(ThemeProperty::kIconThemeName, "hicolor"));
+  EXPECT_TRUE(IsValidThemeName(ThemeProperty::kCursorThemeName, "Adwaita"));
   EXPECT_TRUE(IsValidThemeName(ThemeProperty::kKeyThemeName, ""));
   EXPECT_FALSE(IsValidThemeName(ThemeProperty::kThemeName, ""));
+  EXPECT_FALSE(IsValidThemeName(ThemeProperty::kCursorThemeName, ""));
   EXPECT_FALSE(IsValidThemeName(ThemeProperty::kThemeName, "../invalid"));
   EXPECT_FALSE(
       IsValidThemeName(ThemeProperty::kThemeName, "/absolute/invalid"));
@@ -28,6 +30,7 @@
 TEST(GtkUtilTest, GetThemeFallback) {
   EXPECT_STREQ(GetThemeFallback(ThemeProperty::kIconThemeName), "hicolor");
   EXPECT_STREQ(GetThemeFallback(ThemeProperty::kThemeName), "Adwaita");
+  EXPECT_STREQ(GetThemeFallback(ThemeProperty::kCursorThemeName), "Adwaita");
   EXPECT_EQ(GetThemeFallback(ThemeProperty::kKeyThemeName), nullptr);
 }
 
@@ -90,4 +93,31 @@
   EXPECT_EQ(observed_theme_name, "hicolor");
 }
 
+TEST_F(GtkUtilInterceptorTest, CursorThemeNamesSanitizedAtWriteTime) {
+  GtkSettings* settings = GetDefaultGtkSettings();
+  ASSERT_TRUE(settings);
+
+  std::string observed_theme_name;
+  auto callback = base::BindRepeating(
+      [](std::string* out_str, GtkSettings* settings, GParamSpec* pspec) {
+        gchar* name = nullptr;
+        g_object_get(settings, "gtk-cursor-theme-name", &name, nullptr);
+        if (name) {
+          *out_str = name;
+          g_free(name);
+        }
+      },
+      base::Unretained(&observed_theme_name));
+
+  ScopedGSignal signal(settings, "notify::gtk-cursor-theme-name", callback);
+
+  // Set to an invalid value (path traversal)
+  g_object_set(settings, "gtk-cursor-theme-name",
+               "../../../../tmp/w8_evil_cursor", nullptr);
+
+  // The interceptor should have triggered and sanitized the cursor theme name
+  // to "Adwaita" before the notify callback ran!
+  EXPECT_EQ(observed_theme_name, "Adwaita");
+}
+
 }  // namespace gtk
Loading diff…

Original Bug Report

reported by [email protected]

Incomplete fix of CVE-2026-15769: gtk-cursor-theme-name/size unsanitized and Wayland cursor load has no theme-name validation


Report description

Incomplete fix of CVE-2026-15769: gtk-cursor-theme-name/size unsanitized and Wayland cursor load has no theme-name validation


Bug location

Where do you want to report your vulnerability?

Chrome VRP – Report security issues affecting the Chrome browser. See program rules

Which URL (or repository) have you found the vulnerability in?

https://chromium.googlesource.com/chromium/src/+/refs/heads/main/ui/gtk/gtk_util.cc


The problem

Please describe the technical details of the vulnerability

VULNERABILITY

Incomplete fix of CVE-2026-15769. The M150 fix installs a write-time interceptor on GTK settings (GtkSettingsSetProperty in ui/gtk/gtk_util.cc) that sanitizes theme names, but it covers only three properties (gtk-theme-name, gtk-icon-theme-name, gtk-key-theme-name). gtk-cursor-theme-name and gtk-cursor-theme-size are not on the list, so writes to them pass through raw.

On the load side, the X11 cursor loader validates the theme name with IsValidCursorThemeName (ui/base/x/x11_cursor_loader.cc), which rejects absolute paths and parent references. The Wayland cursor load (ui/ozone/platform/wayland/host/wayland_cursor_factory.cc) stores the raw string and passes it to wl_cursor_theme_load with no validation. libwayland-cursor concatenates the name into the XCURSOR search path (<dir>/<theme>/cursors/<shape>) with no ../ rejection, so a name like ../../../../tmp/evil resolves outside the icons root.

ROOT CAUSE

Two gaps that line up on the Wayland path:

  1. Write time: GtkSettingsSetProperty special-cases only the three properties; gtk-cursor-theme-name and gtk-cursor-theme-size fall through to the raw g_orig_set_property.

  2. Load time: the Wayland sink does not call IsValidCursorThemeName (the X11 sink does). The raw name reaches wl_cursor_theme_load, and libwayland-cursor resolves it into a filesystem path with no traversal rejection.

The result is a dual-path residual: the same spoofed-settings hop that CVE-2026-15769 was about still forces the browser process to open and parse an attacker-controlled XCursor theme tree on Wayland. X11 is hardened (verified negative control below); Wayland is not.

WHY THIS IS A NEW BUG (and not the fixed CVE)

CVE-2026-15769’s fix rewrote the three listed properties and hardened the X11 cursor sink. It did not touch gtk-cursor-theme-name or gtk-cursor-theme-size, and it did not add validation to the Wayland load. The cursor row has neither half of the fix. Incomplete fixes are treated as new bugs on their own merits; this is the omitted cursor half of that fix. Do not re-file the three fixed properties.

AFFECTED VERSIONS

Chromium main (source verified 2026-07-26): the interceptor omission and the Wayland sink are both present. Chrome for Testing 150.0.7871.186 (post-15769): X11 sink confirmed hardened, confirming the cursor property is the residual. Linux Wayland is the affected configuration; on X11 the traversal is blocked at the load sink.

REPRODUCTION

Two independent measurements, both in the lab and both reproducible.

  1. The write-time gap (pure GtkSettings, no Chrome needed): set gtk-cursor-theme-name to ../../../../tmp/w8_evil_cursor with g_object_set and read it back. It is retained verbatim, while the interceptor-rewritten properties are forced to Adwaita. From evidence_gtk_property.txt:

BEFORE gtk-cursor-theme-name=(null) AFTER gtk-cursor-theme-name=../../../../tmp/w8_evil_cursor AFTER gtk-theme-name (pure GTK)=../../../invalid-theme NOTE: Chromium interceptor rewrites theme-name to Adwaita; cursor-theme-name has NO interceptor entry

  1. The load-time path resolution (libwayland-cursor semantics): a name of ../../../../tmp/w8_evil_cursor under /usr/share/icons resolves and opens outside the icons root. From the path-sink harness (evidence_path_sink.txt):

openat(…, “/usr/share/icons/../../../../tmp/w8_evil_cursor/cursors/left_ptr”, O_RDONLY) = 3 (followed by a read of the planted content)

The X11 negative control: on Chrome for Testing 150 (post-fix), the X11 sink rejects the same traversal name (IsValidCursorThemeName), so the gap is specific to the Wayland load.

EVIDENCE

evidence_gtk_property.txt: the interceptor gap (cursor property retained raw, theme-name rewritten).

evidence_path_sink.txt: the libwayland-cursor path concatenation opening a traversal path outside the icons root.

The companion consequence: this gap is the reachability for the out-of-bounds write in Chromium issue 540280769 (the libwayland-cursor pool integer overflow), because the unsanitized theme name and size feed an attacker-chosen theme to the vulnerable allocator. Reported there as the P1 chain.

ATTACHED FILES

gtk_property_gap.c - sets gtk-cursor-theme-name to a traversal value and reads it back wayland_path_sink.c - demonstrates libwayland-cursor resolving a traversal name to an open evidence_gtk_property.txt - the interceptor gap output evidence_path_sink.txt - the path resolution openat proof

Impact analysis

WHO CAN EXPLOIT IT

An attacker who can write to the desktop cursor-theme settings that Chrome reads. In the Chrome threat model this is a compromised renderer or GPU/display process, which acts as the display client and can act as a hostile settings source (the CVE-2026-15769 hop class). The theme name and size are fully attacker-controlled.

WHAT THEY GAIN

The browser process, which is outside the renderer sandbox, opens and parses an attacker-controlled XCursor theme tree on Wayland. On X11 this is blocked at the load sink; on Wayland it is not, and libwayland-cursor resolves traversal names outside the icons root. This is a browser-process filesystem open and parse of attacker bytes, in the same impact class CVE-2026-15769 was rated High for.

The direct impact is an incomplete fix of a shipped High CVE that leaves a browser-process attack surface open through the same settings-spoof hop the CVE addressed. It is also the reachability step for the memory-corruption chain reported in Chromium issue 540280769 (the libwayland-cursor pool integer overflow), where the unsanitized theme name and size feed an attacker-chosen theme to a vulnerable allocator that then performs an out-of-bounds write in the browser process.

This is a logic and missing-validation bug, not memory corruption in its own right; its severity comes from the incomplete fix of a High-rated issue and from being the entry point to the demonstrated pool-overflow chain.

SUGGESTED FIX

Extend GtkSettingsSetProperty to handle gtk-cursor-theme-name with the same path rules as IsValidThemeName / IsValidCursorThemeName (reject empty, absolute, parent-referencing, and multi-component names) and rewrite to a safe fallback, and apply equivalent bounds validation to gtk-cursor-theme-size. Also validate the name in the Wayland cursor load before wl_cursor_theme_load, matching the X11 sink as defense in depth. Unify the validators in a shared helper so X11, Wayland, and the GTK interceptor cannot drift apart again. This is the second drift in this series.

REFERENCE

Chromium issue 540280769: the pool integer overflow this gap reaches. CVE-2026-15769: the incomplete parent fix (Chrome 150.0.7871.125).


The cause

What version of Chrome have you found the security issue in?

150.0.7871.186 [stable]

No, it is not related to a crash.

Choose the type of vulnerability

Permissions Bypass

How would you like to be publicly acknowledged for your report?

Fabian Wahle (Hap Security)

View on issue tracker