Overview

High
Severity
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
Bug ClassLogic Error
Tracker519731111
Fix commit8ac610d5dd55 (chromium/src) +824/-5
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-14

Changed Functions

FunctionChangeNotes
if
BUILD.gn
modified

Files Changed

  • BUILD.gn
  • infra/config/generated/builders/ci/Linux ASan LSan Builder/targets/chromium.memory.json
  • infra/config/generated/builders/ci/Linux Builder/targets/chromium.linux.json
From 8ac610d5dd5540b3c58d5cf2927a35faa6a9afa3 Mon Sep 17 00:00:00 2001
From: Tom Anderson <thomasanderson@chromium.org>
Date: Fri, 26 Jun 2026 10:29:44 -0700
Subject: [PATCH] gtk: Add gtk_unittests target and runner

This adds the testing infrastructure for UI GTK code. Currently, there
is no unit test runner for the ui/gtk component, making it difficult to
write and verify GTK integrations.

This CL:
* Adds a gtk_unittests test target under //ui/gtk:gtk_unittests.
* Registers //ui/gtk:gtk_unittests in top-level targets and chromium
  testing configurations (LUCICFG/star files).
* Adds a standard test runner main that initializes Gtk.
* Adds a placeholder/dummy unit test to verify compilation.

NOTRY=true
R=bpastene

Bug: 519731111
Change-Id: I799f3db6423e383e8f8fa9e03df86d683bb389d1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7988034
Reviewed-by: Ben Pastene <bpastene@chromium.org>
Commit-Queue: Thomas Anderson <thomasanderson@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1653263}
---

diff --git a/BUILD.gn b/BUILD.gn
index 7ff9671..33d5e9f 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -15,6 +15,7 @@
 import("//build/config/cronet/config.gni")
 import("//build/config/dcheck_always_on.gni")
 import("//build/config/features.gni")
+import("//build/config/linux/gtk/gtk.gni")
 import("//build/config/rust.gni")
 import("//build/config/sanitizers/sanitizers.gni")
 import("//build/config/ui.gni")
@@ -321,6 +322,10 @@
       ]
     }
 
+    if (use_gtk) {
+      deps += [ "//ui/gtk:gtk_unittests" ]
+    }
+
     if (enterprise_watermark) {
       deps += [ "//chrome/browser/enterprise/watermark:watermark_app" ]
     }
diff --git a/infra/config/generated/builders/ci/Linux ASan LSan Builder/targets/chromium.memory.json b/infra/config/generated/builders/ci/Linux ASan LSan Builder/targets/chromium.memory.json
index 60aa76ef..18f5ccc 100644
--- a/infra/config/generated/builders/ci/Linux ASan LSan Builder/targets/chromium.memory.json
+++ b/infra/config/generated/builders/ci/Linux ASan LSan Builder/targets/chromium.memory.json
@@ -772,6 +772,26 @@
         "args": [
           "--test-launcher-print-test-stdio=always"
         ],
+        "experiment_percentage": 100,
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "module_name": "//ui/gtk:gtk_unittests",
+        "module_scheme": "gtest",
+        "name": "gtk_unittests",
+        "swarming": {
+          "dimensions": {
+            "os": "Ubuntu-22.04"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gtk_unittests",
+        "test_id_prefix": "ninja://ui/gtk:gtk_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-print-test-stdio=always"
+        ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
diff --git "a/infra/config/generated/builders/ci/Linux ASan LSan Tests \0501\051/targets/chromium.memory.json" "b/infra/config/generated/builders/ci/Linux ASan LSan Tests \0501\051/targets/chromium.memory.json"
index 60aa76ef..18f5ccc 100644
--- "a/infra/config/generated/builders/ci/Linux ASan LSan Tests \0501\051/targets/chromium.memory.json"
+++ "b/infra/config/generated/builders/ci/Linux ASan LSan Tests \0501\051/targets/chromium.memory.json"
@@ -772,6 +772,26 @@
         "args": [
           "--test-launcher-print-test-stdio=always"
         ],
+        "experiment_percentage": 100,
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "module_name": "//ui/gtk:gtk_unittests",
+        "module_scheme": "gtest",
+        "name": "gtk_unittests",
+        "swarming": {
+          "dimensions": {
+            "os": "Ubuntu-22.04"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gtk_unittests",
+        "test_id_prefix": "ninja://ui/gtk:gtk_unittests/"
+      },
+      {
+        "args": [
+          "--test-launcher-print-test-stdio=always"
+        ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
diff --git "a/infra/config/generated/builders/ci/Linux Builder \050Wayland\051/targets/chromium.linux.json" "b/infra/config/generated/builders/ci/Linux Builder \050Wayland\051/targets/chromium.linux.json"
index 89bee1b..c94c689 100644
--- "a/infra/config/generated/builders/ci/Linux Builder \050Wayland\051/targets/chromium.linux.json"
+++ "b/infra/config/generated/builders/ci/Linux Builder \050Wayland\051/targets/chromium.linux.json"
@@ -944,6 +944,29 @@
           "--use-weston",
           "--ozone-platform=wayland"
         ],
+        "experiment_percentage": 100,
+        "isolate_profile_data": true,
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "module_name": "//ui/gtk:gtk_unittests",
+        "module_scheme": "gtest",
+        "name": "gtk_unittests",
+        "swarming": {
+          "dimensions": {
+            "os": "Ubuntu-22.04"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gtk_unittests",
+        "test_id_prefix": "ninja://ui/gtk:gtk_unittests/"
+      },
+      {
+        "args": [
+          "--no-xvfb",
+          "--use-weston",
+          "--ozone-platform=wayland"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
diff --git "a/infra/config/generated/builders/ci/Linux Builder \050dbg\051/targets/chromium.linux.json" "b/infra/config/generated/builders/ci/Linux Builder \050dbg\051/targets/chromium.linux.json"
index 44b4373..90faf44 100644
--- "a/infra/config/generated/builders/ci/Linux Builder \050dbg\051/targets/chromium.linux.json"
+++ "b/infra/config/generated/builders/ci/Linux Builder \050dbg\051/targets/chromium.linux.json"
@@ -632,6 +632,23 @@
         "test_id_prefix": "ninja://gpu:gpu_unittests/"
       },
       {
+        "experiment_percentage": 100,
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "module_name": "//ui/gtk:gtk_unittests",
+        "module_scheme": "gtest",
+        "name": "gtk_unittests",
+        "swarming": {
+          "dimensions": {
+            "os": "Ubuntu-22.04"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gtk_unittests",
+        "test_id_prefix": "ninja://ui/gtk:gtk_unittests/"
+      },
+      {
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
diff --git a/infra/config/generated/builders/ci/Linux Builder/targets/chromium.linux.json b/infra/config/generated/builders/ci/Linux Builder/targets/chromium.linux.json
index 18142a8d..437e1701 100644
--- a/infra/config/generated/builders/ci/Linux Builder/targets/chromium.linux.json
+++ b/infra/config/generated/builders/ci/Linux Builder/targets/chromium.linux.json
@@ -708,6 +708,24 @@
         "test_id_prefix": "ninja://gpu:gpu_unittests/"
       },
       {
+        "experiment_percentage": 100,
+        "isolate_profile_data": true,
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "module_name": "//ui/gtk:gtk_unittests",
+        "module_scheme": "gtest",
+        "name": "gtk_unittests",
+        "swarming": {
+          "dimensions": {
+            "os": "Ubuntu-22.04"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gtk_unittests",
+        "test_id_prefix": "ninja://ui/gtk:gtk_unittests/"
+      },
+      {
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/infra/config/generated/testing/gn_isolate_map.pyl b/infra/config/generated/testing/gn_isolate_map.pyl
index e0d3a3d..d489f5e 100644
--- a/infra/config/generated/testing/gn_isolate_map.pyl
+++ b/infra/config/generated/testing/gn_isolate_map.pyl
@@ -1208,6 +1208,12 @@
       "../../tools/grit/grit/test_suite_all.py",
     ],
   },
+  "gtk_unittests": {
+    "label": "//ui/gtk:gtk_unittests",
+    "type": "windowed_test_launcher",
+    "module_scheme": "gtest",
+    "skip_usage_check": True,
+  },
   "gwp_asan_unittests": {
     "label": "//components/gwp_asan:gwp_asan_unittests",
     "type": "console_test_launcher",
diff --git a/ui/gtk/gtk_util_unittest.cc b/ui/gtk/gtk_util_unittest.cc
new file mode 100644
index 0000000..a0f0dc5
--- /dev/null
+++ b/ui/gtk/gtk_util_unittest.cc
@@ -0,0 +1,13 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gtk {
+
+TEST(GtkUtilTest, Dummy) {
+  EXPECT_TRUE(true);
+}
+
+}  // namespace gtk
diff --git a/ui/gtk/run_all_unittests.cc b/ui/gtk/run_all_unittests.cc
new file mode 100644
index 0000000..b2929ec0
--- /dev/null
+++ b/ui/gtk/run_all_unittests.cc
@@ -0,0 +1,27 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/functional/bind.h"
+#include "base/test/launcher/unit_test_launcher.h"
+#include "base/test/test_suite.h"
+#include "ui/gtk/gtk_compat.h"
+#include "ui/linux/linux_ui.h"
+
+int main(int argc, char** argv) {
+  base::TestSuite test_suite(argc, argv);
+
+  // Load GTK libraries. Since we run tests in xvfb, load X11 backend.
+  if (!gtk::LoadGtk(ui::LinuxUiBackend::kX11)) {
+    return 1;
+  }
+
+  // Initialize GTK
+  if (!gtk::GtkInitCheck(&argc, argv)) {
+    return 1;
+  }
+
+  return base::LaunchUnitTests(
+      argc, argv,
+      base::BindOnce(&base::TestSuite::Run, base::Unretained(&test_suite)));
+}
Loading diff…

Original Bug Report

reported by vm...@google.com

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