Low chrome Input Validation 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper input validation in Chromoting
DescriptionImproper input validation in Chromoting
ComponentChromoting
Sub-componentRemoting
Bug ClassInput Validation
Tracker562960821
Fix commit06ba3f5e4033 (chromium/src) +164/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-20

Changed Functions

FunctionChangeNotes
ContinueWindowGtk
remoting/host/continue_window_linux.cc
modified
if
remoting/host/continue_window_linux.cc
modified
if
remoting/host/disconnect_window_linux.cc
modified
TEST_F
remoting/host/disconnect_window_linux_unittest.cc
modified

Files Changed

  • remoting/host/disconnect_window_linux.cc
  • remoting/host/disconnect_window_linux_unittest.cc
  • remoting/host/continue_window_linux.cc
From 06ba3f5e40335c1913890b3d8c1e7cf98cb4ca31 Mon Sep 17 00:00:00 2001
From: Jamie Walch <jamiewalch@chromium.org>
Date: Wed, 23 Sep 2026 13:27:36 -0700
Subject: [PATCH] [remoting] Keep disconnect and continue windows visible over fullscreen apps on Linux X11

On Linux X11, DisconnectWindowGtk and ContinueWindowGtk previously
relied only on gtk_window_stick() and gtk_window_set_keep_above()
(_NET_WM_STATE_ABOVE). Under EWMH window manager stacking rules, focused
fullscreen windows (_NET_WM_STATE_FULLSCREEN) stack above windows in the
ABOVE layer. An authenticated remote client could inject an F11
keystroke into the victim's application, hiding the consent indicator
and Stop Sharing button while screen capture and input injection
continued.

This CL:

1. Sets override_redirect on both DisconnectWindowGtk and
   ContinueWindowGtk before mapping them so that window managers do not
   constrain them below fullscreen windows.
2. Centers ContinueWindowGtk via gtk_window_set_position. 3. Adds a
   periodic 1-second re-raise timer to keep the windows raised above
   any newly created or restacked windows.
4. Adds tests verifying override-redirect and periodic re-raising.

Fixed: 562960821
Change-Id: I8311aecd19c40e118473fc0d1e4fd11cc7cbe7f9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8449130
Auto-Submit: Jamie Walch <jamiewalch@chromium.org>
Reviewed-by: Lambros Lambrou <lambroslambrou@chromium.org>
Commit-Queue: Jamie Walch <jamiewalch@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1703716}
---
diff --git a/remoting/host/continue_window_linux.cc b/remoting/host/continue_window_linux.cc
index 0bbe311..f5fbd9e0 100644
--- a/remoting/host/continue_window_linux.cc
+++ b/remoting/host/continue_window_linux.cc
@@ -13,12 +13,22 @@
 #include "base/logging.h"
 #include "base/memory/raw_ptr_exclusion.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
 #include "remoting/base/string_resources.h"
 #include "ui/base/glib/scoped_gsignal.h"
 #include "ui/base/l10n/l10n_util.h"
 
 namespace remoting {
 
+namespace {
+
+// Interval for periodic re-raising to ensure the dialog stays above fullscreen
+// applications.
+constexpr base::TimeDelta kRaiseInterval = base::Seconds(1);
+
+}  // namespace
+
 class ContinueWindowGtk : public ContinueWindow {
  public:
   ContinueWindowGtk();
@@ -39,11 +49,17 @@
 
   void OnResponse(GtkDialog*, int);
 
+#if !GTK_CHECK_VERSION(3, 90, 0)
+  void Raise();
+#endif
+
   raw_ptr<GtkWidget> continue_window_;
 
   bool buttons_enabled_ = false;
 
   ScopedGSignal signal_;
+
+  base::RepeatingTimer raise_timer_;
 };
 
 ContinueWindowGtk::ContinueWindowGtk() : continue_window_(nullptr) {}
@@ -59,11 +75,18 @@
   CreateWindow();
   gtk_window_set_urgency_hint(GTK_WINDOW(continue_window_.get()), TRUE);
   gtk_window_present(GTK_WINDOW(continue_window_.get()));
+#if !GTK_CHECK_VERSION(3, 90, 0)
+  Raise();
+  raise_timer_.Start(
+      FROM_HERE, kRaiseInterval,
+      base::BindRepeating(&ContinueWindowGtk::Raise, base::Unretained(this)));
+#endif
 }
 
 void ContinueWindowGtk::HideUi() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
+  raise_timer_.Stop();
   if (continue_window_) {
     signal_.Reset();
     gtk_widget_destroy(continue_window_.ExtractAsDangling());
@@ -105,6 +128,18 @@
   // Set always-on-top, otherwise this window tends to be obscured by the
   // DisconnectWindow.
   gtk_window_set_keep_above(GTK_WINDOW(continue_window_.get()), TRUE);
+#if !GTK_CHECK_VERSION(3, 90, 0)
+  gtk_window_set_position(GTK_WINDOW(continue_window_.get()),
+                          GTK_WIN_POS_CENTER);
+
+  // Override-redirect prevents the window manager from stacking fullscreen
+  // windows above the dialog.
+  gtk_widget_realize(continue_window_.get());
+  GdkWindow* gdk_window = gtk_widget_get_window(continue_window_.get());
+  if (gdk_window) {
+    gdk_window_set_override_redirect(gdk_window, TRUE);
+  }
+#endif
 
   signal_ = ScopedGSignal(GTK_DIALOG(continue_window_.get()), "response",
                           base::BindRepeating(&ContinueWindowGtk::OnResponse,
@@ -150,6 +185,19 @@
   HideUi();
 }
 
+#if !GTK_CHECK_VERSION(3, 90, 0)
+void ContinueWindowGtk::Raise() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!continue_window_) {
+    return;
+  }
+  GdkWindow* gdk_window = gtk_widget_get_window(continue_window_.get());
+  if (gdk_window) {
+    gdk_window_raise(gdk_window);
+  }
+}
+#endif
+
 // static
 std::unique_ptr<HostWindow> HostWindow::CreateContinueWindow() {
   return std::make_unique<ContinueWindowGtk>();
diff --git a/remoting/host/disconnect_window_linux.cc b/remoting/host/disconnect_window_linux.cc
index 4b33c3c..57d3ecf8 100644
--- a/remoting/host/disconnect_window_linux.cc
+++ b/remoting/host/disconnect_window_linux.cc
@@ -53,6 +53,10 @@
 constexpr char kUpArrow[] = "▲";
 constexpr char kDownArrow[] = "▼";
 
+// Interval for periodic re-raising to ensure the window stays above fullscreen
+// applications.
+constexpr base::TimeDelta kRaiseInterval = base::Seconds(1);
+
 // GObject data keys for testing and position tracking.
 constexpr char kCurrentWidthKey[] = "current_width";
 constexpr char kCurrentHeightKey[] = "current_height";
@@ -85,6 +89,7 @@
 #if !GTK_CHECK_VERSION(3, 90, 0)
   void OnMonitorsChanged(GdkScreen* screen);
   gboolean OnWindowState(GtkWidget* window, GdkEventWindowState* event);
+  void Raise();
 #endif
 
   // Positions the dialog window based on the current anchor.
@@ -103,6 +108,8 @@
   int current_width_ = 0;
   int current_height_ = 0;
 
+  base::RepeatingTimer raise_timer_;
+
   std::vector<ScopedGSignal> signals_;
 };
 
@@ -292,6 +299,14 @@
     }
   }
 
+  // Override-redirect prevents the window manager from stacking fullscreen
+  // windows above the indicator.
+  gtk_widget_realize(disconnect_window_.get());
+  GdkWindow* gdk_window = gtk_widget_get_window(disconnect_window_.get());
+  if (gdk_window) {
+    gdk_window_set_override_redirect(gdk_window, TRUE);
+  }
+
   // GTK4 shows windows by default.
   gtk_widget_show_all(disconnect_window_.get());
 #endif
@@ -301,6 +316,13 @@
   gtk_label_set_text(GTK_LABEL(message_.get()), message_text.c_str());
   SetDialogPosition();
   gtk_window_present(window);
+
+#if !GTK_CHECK_VERSION(3, 90, 0)
+  Raise();
+  raise_timer_.Start(
+      FROM_HERE, kRaiseInterval,
+      base::BindRepeating(&DisconnectWindowGtk::Raise, base::Unretained(this)));
+#endif
 }
 
 void DisconnectWindowGtk::OnClicked(GtkButton* button) {
@@ -445,6 +467,17 @@
   }
   return FALSE;
 }
+
+void DisconnectWindowGtk::Raise() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!disconnect_window_) {
+    return;
+  }
+  GdkWindow* gdk_window = gtk_widget_get_window(disconnect_window_.get());
+  if (gdk_window) {
+    gdk_window_raise(gdk_window);
+  }
+}
 #endif
 
 gboolean DisconnectWindowGtk::OnDelete(GtkWidget* window, GdkEvent* event) {
diff --git a/remoting/host/disconnect_window_linux_unittest.cc b/remoting/host/disconnect_window_linux_unittest.cc
index ec6ca81..d7b9837 100644
--- a/remoting/host/disconnect_window_linux_unittest.cc
+++ b/remoting/host/disconnect_window_linux_unittest.cc
@@ -20,6 +20,11 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/base/resource/resource_scale_factor.h"
+#include "ui/gfx/x/connection.h"
+
+#if defined(GDK_WINDOWING_X11)
+#include <gdk/gdkx.h>
+#endif
 
 namespace remoting {
 
@@ -274,7 +279,8 @@
 
  protected:
   base::test::TaskEnvironment task_environment_{
-      base::test::TaskEnvironment::MainThreadType::UI};
+      base::test::TaskEnvironment::MainThreadType::UI,
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
   TestResourceBundleDelegate resource_delegate_;
   ui::ResourceBundle resource_bundle_{&resource_delegate_};
   ui::ResourceBundle::SharedInstanceSwapperForTesting resource_swapper_{
@@ -611,6 +617,82 @@
 
   window.reset();
 }
+
+TEST_F(DisconnectWindowLinuxTest, WindowIsOverrideRedirectOnX11) {
+  if (!InitializeGtk()) {
+    GTEST_SKIP() << "No display available for GTK.";
+  }
+
+  FakeClientSessionControl session_control(kTestUserJid);
+  std::unique_ptr<HostWindow> window = HostWindow::CreateDisconnectWindow();
+  ASSERT_TRUE(window);
+  window->Start(session_control.GetWeakPtr());
+
+  GtkWindow* gtk_window = FindDisconnectWindow();
+  ASSERT_NE(gtk_window, nullptr);
+
+  GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(gtk_window));
+  ASSERT_NE(gdk_window, nullptr);
+
+#if defined(GDK_WINDOWING_X11)
+  if (GDK_IS_X11_WINDOW(gdk_window)) {
+    auto* connection = x11::Connection::Get();
+    auto xid = static_cast<x11::Window>(gdk_x11_window_get_xid(gdk_window));
+    auto attrs = connection->GetWindowAttributes({xid}).Sync();
+    ASSERT_TRUE(attrs);
+    EXPECT_TRUE(attrs->override_redirect);
+  }
+#endif
+
+  // Verify the periodic re-raise timer can fire without issue.
+  task_environment_.FastForwardBy(base::Seconds(5));
+
+  window.reset();
+}
+
+TEST_F(DisconnectWindowLinuxTest, ContinueWindowIsOverrideRedirectOnX11) {
+  if (!InitializeGtk()) {
+    GTEST_SKIP() << "No display available for GTK.";
+  }
+
+  FakeClientSessionControl session_control(kTestUserJid);
+  std::unique_ptr<HostWindow> window = HostWindow::CreateContinueWindow();
+  ASSERT_TRUE(window);
+  window->Start(session_control.GetWeakPtr());
+
+  // Fast forward by 30 minutes to trigger ShowUi().
+  task_environment_.FastForwardBy(base::Minutes(30));
+
+  // Find the dialog in toplevels.
+  GList* toplevels = gtk_window_list_toplevels();
+  GtkWindow* continue_dialog = nullptr;
+  for (GList* iter = toplevels; iter != nullptr; iter = g_list_next(iter)) {
+    if (GTK_IS_DIALOG(iter->data)) {
+      continue_dialog = GTK_WINDOW(iter->data);
+      break;
+    }
+  }
+  g_list_free(toplevels);
+  ASSERT_NE(continue_dialog, nullptr);
+
+  GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(continue_dialog));
+  ASSERT_NE(gdk_window, nullptr);
+
+#if defined(GDK_WINDOWING_X11)
+  if (GDK_IS_X11_WINDOW(gdk_window)) {
+    auto* connection = x11::Connection::Get();
+    auto xid = static_cast<x11::Window>(gdk_x11_window_get_xid(gdk_window));
+    auto attrs = connection->GetWindowAttributes({xid}).Sync();
+    ASSERT_TRUE(attrs);
+    EXPECT_TRUE(attrs->override_redirect);
+  }
+#endif
+
+  // Verify the periodic re-raise timer can fire without issue.
+  task_environment_.FastForwardBy(base::Seconds(5));
+
+  window.reset();
+}
 #endif
 
 }  // namespace remoting
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/remoting/host/disconnect_window_linux_unittest.cc b/remoting/host/disconnect_window_linux_unittest.cc
index ec6ca81..d7b9837 100644
--- a/remoting/host/disconnect_window_linux_unittest.cc
+++ b/remoting/host/disconnect_window_linux_unittest.cc
@@ -20,6 +20,11 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/base/resource/resource_scale_factor.h"
+#include "ui/gfx/x/connection.h"
+
+#if defined(GDK_WINDOWING_X11)
+#include <gdk/gdkx.h>
+#endif
 
 namespace remoting {
 
@@ -274,7 +279,8 @@
 
  protected:
   base::test::TaskEnvironment task_environment_{
-      base::test::TaskEnvironment::MainThreadType::UI};
+      base::test::TaskEnvironment::MainThreadType::UI,
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
   TestResourceBundleDelegate resource_delegate_;
   ui::ResourceBundle resource_bundle_{&resource_delegate_};
   ui::ResourceBundle::SharedInstanceSwapperForTesting resource_swapper_{
@@ -611,6 +617,82 @@
 
   window.reset();
 }
+
+TEST_F(DisconnectWindowLinuxTest, WindowIsOverrideRedirectOnX11) {
+  if (!InitializeGtk()) {
+    GTEST_SKIP() << "No display available for GTK.";
+  }
+
+  FakeClientSessionControl session_control(kTestUserJid);
+  std::unique_ptr<HostWindow> window = HostWindow::CreateDisconnectWindow();
+  ASSERT_TRUE(window);
+  window->Start(session_control.GetWeakPtr());
+
+  GtkWindow* gtk_window = FindDisconnectWindow();
+  ASSERT_NE(gtk_window, nullptr);
+
+  GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(gtk_window));
+  ASSERT_NE(gdk_window, nullptr);
+
+#if defined(GDK_WINDOWING_X11)
+  if (GDK_IS_X11_WINDOW(gdk_window)) {
+    auto* connection = x11::Connection::Get();
+    auto xid = static_cast<x11::Window>(gdk_x11_window_get_xid(gdk_window));
+    auto attrs = connection->GetWindowAttributes({xid}).Sync();
+    ASSERT_TRUE(attrs);
+    EXPECT_TRUE(attrs->override_redirect);
+  }
+#endif
+
+  // Verify the periodic re-raise timer can fire without issue.
+  task_environment_.FastForwardBy(base::Seconds(5));
+
+  window.reset();
+}
+
+TEST_F(DisconnectWindowLinuxTest, ContinueWindowIsOverrideRedirectOnX11) {
+  if (!InitializeGtk()) {
+    GTEST_SKIP() << "No display available for GTK.";
+  }
+
+  FakeClientSessionControl session_control(kTestUserJid);
+  std::unique_ptr<HostWindow> window = HostWindow::CreateContinueWindow();
+  ASSERT_TRUE(window);
+  window->Start(session_control.GetWeakPtr());
+
+  // Fast forward by 30 minutes to trigger ShowUi().
+  task_environment_.FastForwardBy(base::Minutes(30));
+
+  // Find the dialog in toplevels.
+  GList* toplevels = gtk_window_list_toplevels();
+  GtkWindow* continue_dialog = nullptr;
+  for (GList* iter = toplevels; iter != nullptr; iter = g_list_next(iter)) {
+    if (GTK_IS_DIALOG(iter->data)) {
+      continue_dialog = GTK_WINDOW(iter->data);
+      break;
+    }
+  }
+  g_list_free(toplevels);
+  ASSERT_NE(continue_dialog, nullptr);
+
+  GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(continue_dialog));
+  ASSERT_NE(gdk_window, nullptr);
+
+#if defined(GDK_WINDOWING_X11)
+  if (GDK_IS_X11_WINDOW(gdk_window)) {
+    auto* connection = x11::Connection::Get();
+    auto xid = static_cast<x11::Window>(gdk_x11_window_get_xid(gdk_window));
+    auto attrs = connection->GetWindowAttributes({xid}).Sync();
+    ASSERT_TRUE(attrs);
+    EXPECT_TRUE(attrs->override_redirect);
+  }
+#endif
+
+  // Verify the periodic re-raise timer can fire without issue.
+  task_environment_.FastForwardBy(base::Seconds(5));
+
+  window.reset();
+}
 #endif
 
 }  // namespace remoting
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.