Chrome · Chromoting › Remoting host
CVE-2026-113067
UAF in Chromoting (Remoting host)
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifremoting/host/disconnect_window_linux.cc |
modified | |
TEST_Fremoting/host/disconnect_window_linux_unittest.cc |
modified | |
ifremoting/host/disconnect_window_linux_unittest.cc |
modified |
Files Changed
remoting/host/disconnect_window_linux.ccremoting/host/disconnect_window_linux_unittest.cc
Patch
From e99a5890a1a441d3b1dae1af3ec464f2efdb8ad5 Mon Sep 17 00:00:00 2001
From: Lambros Lambrou <lambroslambrou@chromium.org>
Date: Thu, 24 Sep 2026 15:55:34 -0700
Subject: [PATCH] [remoting host] Position Linux disconnect window using monitor workarea
Update DisconnectWindowGtk::SetDialogPosition() to query gdk_monitor_get_workarea() instead of gdk_monitor_get_geometry() and remove the hardcoded kTopMargin and kBottomMargin offsets. Also check for workarea changes during the periodic Raise() timer callback so the dialog is repositioned when desktop panels or docks are resized.
Bug: 562999766
Change-Id: I5b424ad3c7af2333d3b42a4faa8f39b0299de106
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8453671
Auto-Submit: Lambros Lambrou <lambroslambrou@chromium.org>
Commit-Queue: Lambros Lambrou <lambroslambrou@chromium.org>
Reviewed-by: Jamie Walch <jamiewalch@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1704542}
---
diff --git a/remoting/host/disconnect_window_linux.cc b/remoting/host/disconnect_window_linux.cc
index 57d3ecf8..e9461ae 100644
--- a/remoting/host/disconnect_window_linux.cc
+++ b/remoting/host/disconnect_window_linux.cc
@@ -31,11 +31,6 @@
namespace {
-// Margins from screen edges to ensure the dialog is not obscured by the top bar
-// or an auto-hiding dock/panel at the bottom.
-constexpr int kTopMargin = 40;
-constexpr int kBottomMargin = 60;
-
// Padding and spacing for the window contents.
constexpr int kButtonRowSpacing = 12;
constexpr int kHorizontalPadding = 12;
@@ -87,6 +82,7 @@
gboolean OnConfigure(GtkWidget* widget, GdkEventConfigure* event);
gboolean OnDraw(GtkWidget* widget, cairo_t* cr);
#if !GTK_CHECK_VERSION(3, 90, 0)
+ GdkMonitor* GetCurrentMonitor() const;
void OnMonitorsChanged(GdkScreen* screen);
gboolean OnWindowState(GtkWidget* window, GdkEventWindowState* event);
void Raise();
@@ -110,6 +106,9 @@
base::RepeatingTimer raise_timer_;
+#if !GTK_CHECK_VERSION(3, 90, 0)
+ GdkRectangle current_workarea_ = {0, 0, 0, 0};
+#endif
std::vector<ScopedGSignal> signals_;
};
@@ -375,32 +374,14 @@
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
#if !GTK_CHECK_VERSION(3, 90, 0)
- if (!disconnect_window_) {
- return;
- }
-
- GdkDisplay* display = gtk_widget_get_display(disconnect_window_.get());
- if (!display) {
- return;
- }
-
- GdkMonitor* monitor = nullptr;
- GdkWindow* gdk_window = gtk_widget_get_window(disconnect_window_.get());
- if (gdk_window) {
- monitor = gdk_display_get_monitor_at_window(display, gdk_window);
- }
- if (!monitor) {
- monitor = gdk_display_get_primary_monitor(display);
- }
- if (!monitor) {
- monitor = gdk_display_get_monitor(display, 0);
- }
+ GdkMonitor* monitor = GetCurrentMonitor();
if (!monitor) {
return;
}
- GdkRectangle geometry;
- gdk_monitor_get_geometry(monitor, &geometry);
+ GdkRectangle workarea;
+ gdk_monitor_get_workarea(monitor, &workarea);
+ current_workarea_ = workarea;
int width = current_width_;
int height = current_height_;
@@ -414,10 +395,10 @@
current_height_ = height;
}
- int left = geometry.x + std::max(0, (geometry.width - width) / 2);
+ int left = workarea.x + std::max(0, (workarea.width - width) / 2);
int top = (current_anchor() == WindowAnchor::kTop)
- ? (geometry.y + kTopMargin)
- : (geometry.y + geometry.height - height - kBottomMargin);
+ ? workarea.y
+ : (workarea.y + workarea.height - height);
SetExpectedPosition(left, top);
gtk_window_move(GTK_WINDOW(disconnect_window_.get()), left, top);
@@ -439,6 +420,28 @@
}
#if !GTK_CHECK_VERSION(3, 90, 0)
+GdkMonitor* DisconnectWindowGtk::GetCurrentMonitor() const {
+ if (!disconnect_window_) {
+ return nullptr;
+ }
+ GdkDisplay* display = gtk_widget_get_display(disconnect_window_.get());
+ if (!display) {
+ return nullptr;
+ }
+ GdkMonitor* monitor = nullptr;
+ GdkWindow* gdk_window = gtk_widget_get_window(disconnect_window_.get());
+ if (gdk_window) {
+ monitor = gdk_display_get_monitor_at_window(display, gdk_window);
+ }
+ if (!monitor) {
+ monitor = gdk_display_get_primary_monitor(display);
+ }
+ if (!monitor) {
+ monitor = gdk_display_get_monitor(display, 0);
+ }
+ return monitor;
+}
+
void DisconnectWindowGtk::OnMonitorsChanged(GdkScreen* screen) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ResetRepositionAttempts();
@@ -473,6 +476,14 @@
if (!disconnect_window_) {
return;
}
+ if (GdkMonitor* monitor = GetCurrentMonitor()) {
+ GdkRectangle workarea;
+ gdk_monitor_get_workarea(monitor, &workarea);
+ if (!gdk_rectangle_equal(&workarea, ¤t_workarea_)) {
+ ResetRepositionAttempts();
+ SetDialogPosition();
+ }
+ }
GdkWindow* gdk_window = gtk_widget_get_window(disconnect_window_.get());
if (gdk_window) {
gdk_window_raise(gdk_window);
diff --git a/remoting/host/disconnect_window_linux_unittest.cc b/remoting/host/disconnect_window_linux_unittest.cc
index d7b9837..5c4379f 100644
--- a/remoting/host/disconnect_window_linux_unittest.cc
+++ b/remoting/host/disconnect_window_linux_unittest.cc
@@ -4,6 +4,7 @@
#include <gtk/gtk.h>
+#include <algorithm>
#include <memory>
#include <string>
#include <string_view>
@@ -14,6 +15,7 @@
#include "base/test/task_environment.h"
#include "remoting/base/string_resources.h"
#include "remoting/host/client_session_control.h"
+#include "remoting/host/disconnect_window_base.h"
#include "remoting/host/host_mock_objects.h"
#include "remoting/host/host_window.h"
#include "testing/gmock/include/gmock/gmock.h"
@@ -272,6 +274,7 @@
~DisconnectWindowLinuxTest() override = default;
void TearDown() override {
+ DisconnectWindowBase::ResetCurrentAnchorForTesting();
// Pump the event loop to ensure GTK widget destruction and cleanup is
// processed.
task_environment_.RunUntilIdle();
@@ -693,6 +696,64 @@
window.reset();
}
+
+TEST_F(DisconnectWindowLinuxTest, PositionsAtWorkareaEdges) {
+ 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);
+
+ GdkDisplay* display = gtk_widget_get_display(GTK_WIDGET(gtk_window));
+ ASSERT_NE(display, nullptr);
+ GdkMonitor* monitor = nullptr;
+ GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(gtk_window));
+ if (gdk_window) {
+ monitor = gdk_display_get_monitor_at_window(display, gdk_window);
+ }
+ if (!monitor) {
+ monitor = gdk_display_get_primary_monitor(display);
+ }
+ if (!monitor) {
+ monitor = gdk_display_get_monitor(display, 0);
+ }
+ ASSERT_NE(monitor, nullptr);
+
+ GdkRectangle workarea;
+ gdk_monitor_get_workarea(monitor, &workarea);
+
+ int width = GPOINTER_TO_INT(
+ g_object_get_data(G_OBJECT(gtk_window), kCurrentWidthKey));
+ int height = GPOINTER_TO_INT(
+ g_object_get_data(G_OBJECT(gtk_window), kCurrentHeightKey));
+ int expected_x =
+ GPOINTER_TO_INT(g_object_get_data(G_OBJECT(gtk_window), kExpectedXKey));
+ int bottom_expected_y =
+ GPOINTER_TO_INT(g_object_get_data(G_OBJECT(gtk_window), kExpectedYKey));
+
+ EXPECT_EQ(expected_x, workarea.x + std::max(0, (workarea.width - width) / 2));
+ EXPECT_EQ(bottom_expected_y, workarea.y + workarea.height - height);
+
+ // Toggle to top anchor and verify it aligns directly with workarea.y.
+ GtkWidget* toggle_button =
+ FindButtonByLabel(GTK_WIDGET(gtk_window), kUpArrow);
+ ASSERT_NE(toggle_button, nullptr);
+ g_signal_emit_by_name(toggle_button, "clicked");
+
+ int top_expected_y =
+ GPOINTER_TO_INT(g_object_get_data(G_OBJECT(gtk_window), kExpectedYKey));
+ EXPECT_EQ(top_expected_y, workarea.y);
+
+ // Restore bottom anchor.
+ g_signal_emit_by_name(toggle_button, "clicked");
+ 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 d7b9837..5c4379f 100644
--- a/remoting/host/disconnect_window_linux_unittest.cc
+++ b/remoting/host/disconnect_window_linux_unittest.cc
@@ -4,6 +4,7 @@
#include <gtk/gtk.h>
+#include <algorithm>
#include <memory>
#include <string>
#include <string_view>
@@ -14,6 +15,7 @@
#include "base/test/task_environment.h"
#include "remoting/base/string_resources.h"
#include "remoting/host/client_session_control.h"
+#include "remoting/host/disconnect_window_base.h"
#include "remoting/host/host_mock_objects.h"
#include "remoting/host/host_window.h"
#include "testing/gmock/include/gmock/gmock.h"
@@ -272,6 +274,7 @@
~DisconnectWindowLinuxTest() override = default;
void TearDown() override {
+ DisconnectWindowBase::ResetCurrentAnchorForTesting();
// Pump the event loop to ensure GTK widget destruction and cleanup is
// processed.
task_environment_.RunUntilIdle();
@@ -693,6 +696,64 @@
window.reset();
}
+
+TEST_F(DisconnectWindowLinuxTest, PositionsAtWorkareaEdges) {
+ 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);
+
+ GdkDisplay* display = gtk_widget_get_display(GTK_WIDGET(gtk_window));
+ ASSERT_NE(display, nullptr);
+ GdkMonitor* monitor = nullptr;
+ GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(gtk_window));
+ if (gdk_window) {
+ monitor = gdk_display_get_monitor_at_window(display, gdk_window);
+ }
+ if (!monitor) {
+ monitor = gdk_display_get_primary_monitor(display);
+ }
+ if (!monitor) {
+ monitor = gdk_display_get_monitor(display, 0);
+ }
+ ASSERT_NE(monitor, nullptr);
+
+ GdkRectangle workarea;
+ gdk_monitor_get_workarea(monitor, &workarea);
+
+ int width = GPOINTER_TO_INT(
+ g_object_get_data(G_OBJECT(gtk_window), kCurrentWidthKey));
+ int height = GPOINTER_TO_INT(
+ g_object_get_data(G_OBJECT(gtk_window), kCurrentHeightKey));
+ int expected_x =
+ GPOINTER_TO_INT(g_object_get_data(G_OBJECT(gtk_window), kExpectedXKey));
+ int bottom_expected_y =
+ GPOINTER_TO_INT(g_object_get_data(G_OBJECT(gtk_window), kExpectedYKey));
+
+ EXPECT_EQ(expected_x, workarea.x + std::max(0, (workarea.width - width) / 2));
+ EXPECT_EQ(bottom_expected_y, workarea.y + workarea.height - height);
+
+ // Toggle to top anchor and verify it aligns directly with workarea.y.
+ GtkWidget* toggle_button =
+ FindButtonByLabel(GTK_WIDGET(gtk_window), kUpArrow);
+ ASSERT_NE(toggle_button, nullptr);
+ g_signal_emit_by_name(toggle_button, "clicked");
+
+ int top_expected_y =
+ GPOINTER_TO_INT(g_object_get_data(G_OBJECT(gtk_window), kExpectedYKey));
+ EXPECT_EQ(top_expected_y, workarea.y);
+
+ // Restore bottom anchor.
+ g_signal_emit_by_name(toggle_button, "clicked");
+ 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.
References
On This Page