High CVSS 8.3 chrome Input Validation 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
8.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in Linux Toolkit Theming
DescriptionInsufficient validation of untrusted input in Linux Toolkit Theming
ComponentLinux Toolkit Theming
Sub-componentGtk
Bug ClassInput Validation
Tracker519731111
Fix commit89fe26fe4c58 (chromium/src) +235/-53
Related commits8ac610d5dd55 previously mapped (not the fix) · chromium/src
CWECWE-20 (Improper input validation)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-06-03 (per vendor advisory)
Fix landed2026-06-26 (public git)
Disclosed2026-07-14
Patch gap18 days — fix public in source before the advisory
Re-mapped on 2026-10-03: our pipeline had picked the earliest commit citing this bug (8ac610d5dd55), which an audit found was a preparatory/helper change, not the fix. This page now points to the commit whose diff removes the bug. No root-cause analysis is published for this CVE.

Changed Functions

FunctionChangeNotes
if
ui/gtk/gtk_ui.cc
modified
if
ui/gtk/gtk_util.cc
modified
switch
ui/gtk/gtk_util.cc
modified
TEST
ui/gtk/gtk_util_unittest.cc
modified
GtkUtilInterceptorTest
ui/gtk/gtk_util_unittest.cc
modified
TEST_F
ui/gtk/gtk_util_unittest.cc
modified
if
ui/gtk/gtk_util_unittest.cc
modified

Files Changed

  • ui/gtk/gtk_ui.cc
  • ui/gtk/gtk_ui.h
  • ui/gtk/gtk_util.cc
  • ui/gtk/gtk_util.h
  • ui/gtk/gtk_util_unittest.cc
diff --git a/ui/gtk/gtk_ui.cc b/ui/gtk/gtk_ui.cc
index 60d64938..441a77d 100644
--- a/ui/gtk/gtk_ui.cc
+++ b/ui/gtk/gtk_ui.cc
@@ -289,49 +289,23 @@
   return true;
 }
 
-bool IsValidIconThemeName(std::string_view theme) {
-  base::FilePath theme_path(theme);
-  return !theme.empty() && theme != "." && !theme_path.IsAbsolute() &&
-         !theme_path.ReferencesParent() && theme_path.BaseName() == theme_path;
+std::string GetSettingsStringProperty(const char* property_name) {
+  gchar* prop_value = nullptr;
+  g_object_get(gtk_settings_get_default(), property_name, &prop_value, nullptr);
+  std::string prop_string;
+  if (prop_value) {
+    prop_string = prop_value;
+    g_free(prop_value);
+  }
+  return prop_string;
 }
 
 std::string GetIconThemeName() {
-  gchar* theme = nullptr;
-  g_object_get(gtk_settings_get_default(), "gtk-icon-theme-name", &theme,
-               nullptr);
-  std::string theme_string;
-  if (theme) {
-    theme_string = theme;
-    g_free(theme);
-  }
-  return theme_string;
+  return GetSettingsStringProperty("gtk-icon-theme-name");
 }
 
-void (*g_orig_set_property)(GObject* object,
-                            guint property_id,
-                            const GValue* value,
-                            GParamSpec* pspec) = nullptr;
-
-DISABLE_CFI_ICALL
-void GtkSettingsSetProperty(GObject* object,
-                            guint property_id,
-                            const GValue* value,
-                            GParamSpec* pspec) {
-  if (pspec && pspec->name &&
-      std::string_view(pspec->name) == "gtk-key-theme-name") {
-    const gchar* name = g_value_get_string(value);
-    if (name && *name) {
-      if (!IsValidIconThemeName(name)) {
-        GValue sanitized_value = G_VALUE_INIT;
-        g_value_init(&sanitized_value, G_TYPE_STRING);
-        g_value_set_string(&sanitized_value, nullptr);
-        g_orig_set_property(object, property_id, &sanitized_value, pspec);
-        g_value_unset(&sanitized_value);
-        return;
-      }
-    }
-  }
-  g_orig_set_property(object, property_id, value, pspec);
+std::string GetThemeName() {
+  return GetSettingsStringProperty("gtk-theme-name");
 }
 
 }  // namespace
@@ -392,15 +366,10 @@
 
   GtkSettings* settings = gtk_settings_get_default();
   SanitizeIconThemeName();
-  if (!GtkCheckVersion(4)) {
-    if (!g_orig_set_property) {
-      GObjectClass* gobject_class =
-          G_OBJECT_CLASS(g_type_class_ref(GTK_TYPE_SETTINGS));
-      g_orig_set_property = gobject_class->set_property;
-      gobject_class->set_property = GtkSettingsSetProperty;
-      g_type_class_unref(gobject_class);
-    }
+  SanitizeThemeName();
+  InstallGtkSettingsInterceptor();
 
+  if (!GtkCheckVersion(4)) {
     SanitizeKeyThemeName();
     connect(settings, "notify::gtk-key-theme-name",
             &GtkUi::OnKeyThemeNameChanged);
@@ -555,7 +524,8 @@
 gfx::Image GtkUi::GetIconForContentType(const std::string& content_type,
                                         int dip_size,
                                         float scale) const {
-  if (!IsValidIconThemeName(GetIconThemeName())) {
+  if (!IsValidThemeName(ThemeProperty::kIconThemeName,
+                        GetIconThemeName().c_str())) {
     return gfx::Image();
   }
 
@@ -838,7 +808,7 @@
 
 bool GtkUi::SanitizeIconThemeName() {
   std::string theme = GetIconThemeName();
-  if (!IsValidIconThemeName(theme)) {
+  if (!IsValidThemeName(ThemeProperty::kIconThemeName, theme.c_str())) {
     g_object_set(gtk_settings_get_default(), "gtk-icon-theme-name", "hicolor",
                  nullptr);
     return true;
@@ -846,6 +816,16 @@
   return false;
 }
 
+bool GtkUi::SanitizeThemeName() {
+  std::string theme = GetThemeName();
+  if (!IsValidThemeName(ThemeProperty::kThemeName, theme.c_str())) {
+    g_object_set(gtk_settings_get_default(), "gtk-theme-name", "Adwaita",
+                 nullptr);
+    return true;
+  }
+  return false;
+}
+
 bool GtkUi::SanitizeKeyThemeName() {
   gchar* name = nullptr;
   g_object_get(gtk_settings_get_default(), "gtk-key-theme-name", &name,
@@ -855,8 +835,7 @@
     name_str = name;
     g_free(name);
   }
-  // Unlike the icon theme, an empty key-theme name is normal (no key theme).
-  if (!name_str.empty() && !IsValidIconThemeName(name_str)) {
+  if (!IsValidThemeName(ThemeProperty::kKeyThemeName, name_str.c_str())) {
     g_object_set(gtk_settings_get_default(), "gtk-key-theme-name", nullptr,
                  nullptr);
     return true;
@@ -918,7 +897,7 @@
 #endif
 
 void GtkUi::OnThemeChanged(GtkSettings* settings, GtkParamSpec* param) {
-  if (SanitizeIconThemeName()) {
+  if (SanitizeIconThemeName() || SanitizeThemeName()) {
     return;  // Exit early; modifying the setting re-triggered this function
   }
   colors_.clear();
diff --git a/ui/gtk/gtk_ui.h b/ui/gtk/gtk_ui.h
index 4db8bdd..3c7ef27 100644
--- a/ui/gtk/gtk_ui.h
+++ b/ui/gtk/gtk_ui.h
@@ -121,6 +121,10 @@
   // Returns true if the setting was modified.
   bool SanitizeIconThemeName();
 
+  // Sanitizes the "gtk-theme-name" setting in GtkSettings if it is unsafe.
+  // Returns true if the setting was modified.
+  bool SanitizeThemeName();
+
   // Sanitizes the "gtk-key-theme-name" setting in GtkSettings if it is unsafe.
   // Returns true if the setting was modified.
   bool SanitizeKeyThemeName();
diff --git a/ui/gtk/gtk_util.cc b/ui/gtk/gtk_util.cc
index 8002e0b3c..8f35e5ad 100644
--- a/ui/gtk/gtk_util.cc
+++ b/ui/gtk/gtk_util.cc
@@ -8,11 +8,14 @@
 #include <stddef.h>
 
 #include <memory>
+#include <optional>
 #include <string_view>
 
 #include "base/compiler_specific.h"
 #include "base/environment.h"
+#include "base/files/file_path.h"
 #include "base/functional/callback.h"
+#include "base/notreached.h"
 #include "base/strings/string_split.h"
 #include "base/strings/string_tokenizer.h"
 #include "base/strings/string_util.h"
@@ -757,4 +760,93 @@
   return opacity;
 }
 
+bool IsValidThemeName(ThemeProperty property, const char* theme) {
+  const bool is_key_theme = property == ThemeProperty::kKeyThemeName;
+  if (!theme) {
+    return is_key_theme;
+  }
+  std::string_view theme_str(theme);
+  if (theme_str.empty()) {
+    return is_key_theme;
+  }
+  base::FilePath theme_path(theme_str);
+  return theme_str != "." && !theme_path.IsAbsolute() &&
+         !theme_path.ReferencesParent() && theme_path.BaseName() == theme_path;
+}
+
+const char* GetThemeFallback(ThemeProperty property) {
+  switch (property) {
+    case ThemeProperty::kIconThemeName:
+      return "hicolor";
+    case ThemeProperty::kThemeName:
+      return "Adwaita";
+    case ThemeProperty::kKeyThemeName:
+      return nullptr;
+  }
+  NOTREACHED();
+}
+
+namespace {
+
+void (*g_orig_set_property)(GObject* object,
+                            guint property_id,
+                            const GValue* value,
+                            GParamSpec* pspec) = nullptr;
+
+DISABLE_CFI_ICALL
+void GtkSettingsSetProperty(GObject* object,
+                            guint property_id,
+                            const GValue* value,
+                            GParamSpec* pspec) {
+  if (pspec && pspec->name) {
+    std::string_view prop_name(pspec->name);
+    std::optional<ThemeProperty> property;
+    if (prop_name == "gtk-theme-name") {
+      property = ThemeProperty::kThemeName;
+    } else if (prop_name == "gtk-icon-theme-name") {
+      property = ThemeProperty::kIconThemeName;
+    } else if (prop_name == "gtk-key-theme-name") {
+      property = ThemeProperty::kKeyThemeName;
+    }
+    if (property) {
+      const gchar* name = g_value_get_string(value);
+      if (!IsValidThemeName(*property, name)) {
+        GValue sanitized_value = G_VALUE_INIT;
+        g_value_init(&sanitized_value, G_TYPE_STRING);
+        g_value_set_string(&sanitized_value, GetThemeFallback(*property));
+        g_orig_set_property(object, property_id, &sanitized_value, pspec);
+        g_value_unset(&sanitized_value);
+        return;
+      }
+    }
+  }
+  g_orig_set_property(object, property_id, value, pspec);
+}
+
+}  // namespace
+
+void InstallGtkSettingsInterceptor() {
+  if (!g_orig_set_property) {
+    GObjectClass* gobject_class =
+        G_OBJECT_CLASS(g_type_class_ref(GTK_TYPE_SETTINGS));
+    g_orig_set_property = gobject_class->set_property;
+    gobject_class->set_property = GtkSettingsSetProperty;
+    g_type_class_unref(gobject_class);
+  }
+}
+
+void UninstallGtkSettingsInterceptor() {
+  if (g_orig_set_property) {
+    GObjectClass* gobject_class =
+        G_OBJECT_CLASS(g_type_class_ref(GTK_TYPE_SETTINGS));
+    gobject_class->set_property = g_orig_set_property;
+    g_orig_set_property = nullptr;
+    g_type_class_unref(gobject_class);
+  }
+}
+
+GtkSettings* GetDefaultGtkSettings() {
+  return gtk_settings_get_default();
+}
+
 }  // namespace gtk
diff --git a/ui/gtk/gtk_util.h b/ui/gtk/gtk_util.h
index 5f10b39..c4c3830 100644
--- a/ui/gtk/gtk_util.h
+++ b/ui/gtk/gtk_util.h
@@ -200,6 +200,33 @@
 
 double GetOpacityFromContext(GtkStyleContext* context);
 
+enum class ThemeProperty {
+  kThemeName,
+  kIconThemeName,
+  kKeyThemeName,
+};
+
+// Returns true if `theme` is a safe, valid theme name for `property`.
+// If `theme` is null, returns true only for kKeyThemeName.
+COMPONENT_EXPORT(GTK)
+bool IsValidThemeName(ThemeProperty property, const char* theme);
+
+// Returns the safe fallback value for the given theme-related property.
+COMPONENT_EXPORT(GTK)
+const char* GetThemeFallback(ThemeProperty property);
+
+// Hook the `GtkSettings` `set_property` method to sanitize theme names.
+COMPONENT_EXPORT(GTK) void InstallGtkSettingsInterceptor();
+
+// Unhook the `GtkSettings` `set_property` method.
+COMPONENT_EXPORT(GTK) void UninstallGtkSettingsInterceptor();
+
+// Returns the default `GtkSettings` instance. This wrapper is required because
+// in component builds, raw GTK symbols loaded via stubs (including
+// `gtk_settings_get_default`) are not exported, preventing direct usage in
+// non-component targets like tests.
+COMPONENT_EXPORT(GTK) GtkSettings* GetDefaultGtkSettings();
+
 }  // namespace gtk
 
 #endif  // UI_GTK_GTK_UTIL_H_
diff --git a/ui/gtk/gtk_util_unittest.cc b/ui/gtk/gtk_util_unittest.cc
index a0f0dc5..ba116960 100644
--- a/ui/gtk/gtk_util_unittest.cc
+++ b/ui/gtk/gtk_util_unittest.cc
@@ -2,12 +2,92 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "ui/gtk/gtk_util.h"
+
+#include <string>
+
+#include "base/functional/bind.h"
+#include "base/functional/callback.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/glib/scoped_gsignal.h"
+#include "ui/gtk/gtk_compat.h"
 
 namespace gtk {
 
-TEST(GtkUtilTest, Dummy) {
-  EXPECT_TRUE(true);
+TEST(GtkUtilTest, IsValidThemeName) {
+  EXPECT_TRUE(IsValidThemeName(ThemeProperty::kThemeName, "Adwaita"));
+  EXPECT_TRUE(IsValidThemeName(ThemeProperty::kIconThemeName, "hicolor"));
+  EXPECT_TRUE(IsValidThemeName(ThemeProperty::kKeyThemeName, ""));
+  EXPECT_FALSE(IsValidThemeName(ThemeProperty::kThemeName, ""));
+  EXPECT_FALSE(IsValidThemeName(ThemeProperty::kThemeName, "../invalid"));
+  EXPECT_FALSE(
+      IsValidThemeName(ThemeProperty::kThemeName, "/absolute/invalid"));
+  EXPECT_FALSE(IsValidThemeName(ThemeProperty::kThemeName, "."));
+}
+
+TEST(GtkUtilTest, GetThemeFallback) {
+  EXPECT_STREQ(GetThemeFallback(ThemeProperty::kIconThemeName), "hicolor");
+  EXPECT_STREQ(GetThemeFallback(ThemeProperty::kThemeName), "Adwaita");
+  EXPECT_EQ(GetThemeFallback(ThemeProperty::kKeyThemeName), nullptr);
+}
+
+class GtkUtilInterceptorTest : public testing::Test {
+ protected:
+  void SetUp() override { InstallGtkSettingsInterceptor(); }
+  void TearDown() override { UninstallGtkSettingsInterceptor(); }
+};
+
+TEST_F(GtkUtilInterceptorTest, ThemeNamesSanitizedAtWriteTime) {
+  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-theme-name", &name, nullptr);
+        if (name) {
+          *out_str = name;
+          g_free(name);
+        }
+      },
+      base::Unretained(&observed_theme_name));
+
+  ScopedGSignal signal(settings, "notify::gtk-theme-name", callback);
+
+  // Set to an invalid value (path traversal)
+  g_object_set(settings, "gtk-theme-name", "../../../invalid-theme", nullptr);
+
+  // The interceptor should have triggered and sanitized the theme name to
+  // "Adwaita" before the notify callback ran!
+  EXPECT_EQ(observed_theme_name, "Adwaita");
+}
+
+TEST_F(GtkUtilInterceptorTest, IconThemeNamesSanitizedAtWriteTime) {
+  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-icon-theme-name", &name, nullptr);
+        if (name) {
+          *out_str = name;
+          g_free(name);
+        }
+      },
+      base::Unretained(&observed_theme_name));
+
+  ScopedGSignal signal(settings, "notify::gtk-icon-theme-name", callback);
+
+  // Set to an invalid value (path traversal)
+  g_object_set(settings, "gtk-icon-theme-name", "../../../invalid-theme",
+               nullptr);
+
+  // The interceptor should have triggered and sanitized the theme name to
... (file diff truncated)
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/ui/gtk/gtk_util_unittest.cc b/ui/gtk/gtk_util_unittest.cc
index a0f0dc5..ba116960 100644
--- a/ui/gtk/gtk_util_unittest.cc
+++ b/ui/gtk/gtk_util_unittest.cc
@@ -2,12 +2,92 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "ui/gtk/gtk_util.h"
+
+#include <string>
+
+#include "base/functional/bind.h"
+#include "base/functional/callback.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/glib/scoped_gsignal.h"
+#include "ui/gtk/gtk_compat.h"
 
 namespace gtk {
 
-TEST(GtkUtilTest, Dummy) {
-  EXPECT_TRUE(true);
+TEST(GtkUtilTest, IsValidThemeName) {
+  EXPECT_TRUE(IsValidThemeName(ThemeProperty::kThemeName, "Adwaita"));
+  EXPECT_TRUE(IsValidThemeName(ThemeProperty::kIconThemeName, "hicolor"));
+  EXPECT_TRUE(IsValidThemeName(ThemeProperty::kKeyThemeName, ""));
+  EXPECT_FALSE(IsValidThemeName(ThemeProperty::kThemeName, ""));
+  EXPECT_FALSE(IsValidThemeName(ThemeProperty::kThemeName, "../invalid"));
+  EXPECT_FALSE(
+      IsValidThemeName(ThemeProperty::kThemeName, "/absolute/invalid"));
+  EXPECT_FALSE(IsValidThemeName(ThemeProperty::kThemeName, "."));
+}
+
+TEST(GtkUtilTest, GetThemeFallback) {
+  EXPECT_STREQ(GetThemeFallback(ThemeProperty::kIconThemeName), "hicolor");
+  EXPECT_STREQ(GetThemeFallback(ThemeProperty::kThemeName), "Adwaita");
+  EXPECT_EQ(GetThemeFallback(ThemeProperty::kKeyThemeName), nullptr);
+}
+
+class GtkUtilInterceptorTest : public testing::Test {
+ protected:
+  void SetUp() override { InstallGtkSettingsInterceptor(); }
+  void TearDown() override { UninstallGtkSettingsInterceptor(); }
+};
+
+TEST_F(GtkUtilInterceptorTest, ThemeNamesSanitizedAtWriteTime) {
+  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-theme-name", &name, nullptr);
+        if (name) {
+          *out_str = name;
+          g_free(name);
+        }
+      },
+      base::Unretained(&observed_theme_name));
+
+  ScopedGSignal signal(settings, "notify::gtk-theme-name", callback);
+
+  // Set to an invalid value (path traversal)
+  g_object_set(settings, "gtk-theme-name", "../../../invalid-theme", nullptr);
+
+  // The interceptor should have triggered and sanitized the theme name to
+  // "Adwaita" before the notify callback ran!
+  EXPECT_EQ(observed_theme_name, "Adwaita");
+}
+
+TEST_F(GtkUtilInterceptorTest, IconThemeNamesSanitizedAtWriteTime) {
+  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-icon-theme-name", &name, nullptr);
+        if (name) {
+          *out_str = name;
+          g_free(name);
+        }
+      },
+      base::Unretained(&observed_theme_name));
+
+  ScopedGSignal signal(settings, "notify::gtk-icon-theme-name", callback);
+
+  // Set to an invalid value (path traversal)
+  g_object_set(settings, "gtk-icon-theme-name", "../../../invalid-theme",
+               nullptr);
+
+  // The interceptor should have triggered and sanitized the theme name to
+  // "hicolor" before the notify callback ran!
+  EXPECT_EQ(observed_theme_name, "hicolor");
 }
 
 }  // namespace gtk
Loading diff…

Original Bug Report

reported by [email protected]

Path traversal in GtkIconTheme allows compromised GPU to trigger SVG parsing in Browser

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 potential path-traversal vulnerability exists in Chromium’s GTK theme integration layer on Linux/X11 platforms. A compromised GPU process could leverage its retained pre-sandbox X11 connection to modify the Net/IconThemeName setting via XSETTINGS. This could force the unsandboxed browser process to load and parse an attacker-controlled SVG icon from the local downloads directory during window frame rendering.

Affected files:

  • ui/gtk/nav_button_provider_gtk.cc
  • ui/gtk/gtk_ui.cc
  • ui/gtk/gtk_util.cc

Estimated timestamp from git blame: 2017-08-21

Summary

A potential path-traversal vulnerability in Chromium’s GTK theme integration layer on Linux/X11 platforms could allow a compromised GPU process to force the unsandboxed browser process to load and parse an attacker-controlled SVG file from the local filesystem (such as ~/Downloads). If combined with a memory safety issue in the underlying SVG rendering library (like librsvg or gdk-pixbuf), this represents a potential GPU-to-browser sandbox escape vector.

Root Cause Analysis

In ui/gtk/gtk_ui.cc, the browser process subscribes to GTK configuration changes via GtkSettings:

GtkSettings* settings = gtk_settings_get_default();
connect(settings, "notify::gtk-icon-theme-name", &GtkUi::OnThemeChanged);

When GDK’s XSETTINGS client processes setting changes, it updates the gtk-icon-theme-name property. Currently, Chromium performs no validation or sanitization on this property.

When window frame buttons are drawn or updated (for example, in ui/gtk/nav_button_provider_gtk.cc or when MaybeUpdateCachedFrameButtonImages() triggers a redraw), the browser invokes GTK’s icon lookup routines:

auto icon_info = TakeGObject(gtk_icon_theme_lookup_icon_for_scale(
    GetDefaultIconTheme(), icon_name, kNavButtonIconSize, scale, ...));

Because gtk-icon-theme-name can contain path-traversal sequences (e.g., ../../../Downloads), GTK’s internal path construction (g_build_filename) resolves the theme path to a relative location on the disk, such as the user’s ~/Downloads directory. If a valid index.theme file is present in that directory, GTK will register it as a theme and subsequently resolve navigation icons (e.g., window-close-symbolic.svg) directly from that directory.

Once resolved, the icon is loaded and parsed via librsvg / gdk-pixbuf inside the unsandboxed browser process:

auto icon_pixbuf = TakeGObject(gtk_icon_info_load_symbolic_for_context(
    icon_info, button_context, nullptr, nullptr));

Potential Attack Vector & Steps to Trigger

Note: These are suggested/potential steps to trigger the bug. Our tooling does not currently have the capability to execute code or verify this with a running exploit.

  1. Local File Placement: An attacker-controlled webpage triggers a drive-by download of two files into the user’s default ~/Downloads directory:
    • index.theme (declaring a custom theme structure).
    • window-close-symbolic.svg (containing an SVG payload targeting a parser vulnerability).
  2. GPU Settings Hijack: The attacker leverages an existing compromise in the sandboxed GPU process to access the cloned pre-sandbox X11 connection:
    • Reference: ui/ozone/platform/x11/ozone_platform_x11.cc
  3. Claim Selection: The GPU process issues a SetSelectionOwner request to own _XSETTINGS_S0.
  4. Publish Malicious Setting: The GPU process writes a _XSETTINGS_SETTINGS property setting Net/IconThemeName = "../../../Downloads".
  5. Trigger Parse: The browser process processes the setting change and updates gtk-icon-theme-name. When a new window is laid out or opened (e.g., via window.open() in the renderer), the cache is missed, and NavButtonProviderGtk::LoadNavButtonIcon is called. The browser resolves and parses ~/Downloads/window-close-symbolic.svg using GTK/librsvg, potentially executing code in the unsandboxed browser context.

Suggested Fix

To mitigate this issue, Chromium should sanitize or validate incoming GTK icon theme name updates to ensure they do not contain path traversal components or absolute paths. This is similar to the existing cursor theme validation implemented in ui/base/x/x11_cursor_loader.cc:

bool IsValidIconThemeName(const std::string& theme) {
  base::FilePath theme_path(theme);
  return !theme.empty() && theme != "." && !theme_path.IsAbsolute() &&
         !theme_path.ReferencesParent() && theme_path.BaseName() == theme_path;
}

If the received theme name fails validation, Chromium should reject the configuration update and revert to a safe default.

Evaluated with Chrome root at commit: 9ebf4302210513a012c901d87a2668b3aadf8cc1


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.

View on issue tracker