Chrome · Chromoting › Remoting
CVE-2026-113061
Memory Corruption in Chromoting (Remoting)
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifremoting/host/input_monitor/local_hotkey_input_monitor_x11.cc |
modified | |
forremoting/host/input_monitor/local_hotkey_input_monitor_x11.cc |
modified | |
LocalHotkeyInputMonitorX11Testremoting/host/input_monitor/local_hotkey_input_monitor_x11_unittest.cc |
modified | |
forremoting/host/input_monitor/local_hotkey_input_monitor_x11_unittest.cc |
modified | |
ifremoting/host/input_monitor/local_hotkey_input_monitor_x11_unittest.cc |
modified | |
TEST_Fremoting/host/input_monitor/local_hotkey_input_monitor_x11_unittest.cc |
modified | |
ifremoting/host/BUILD.gn |
modified |
Files Changed
remoting/host/BUILD.gnremoting/host/input_monitor/local_hotkey_input_monitor_x11.ccremoting/host/input_monitor/local_hotkey_input_monitor_x11.hremoting/host/input_monitor/local_hotkey_input_monitor_x11_unittest.cc
Patch
From 9af78877f4641a7830d4a01b1cb57248c393e9d1 Mon Sep 17 00:00:00 2001
From: Lambros Lambrou <lambroslambrou@chromium.org>
Date: Fri, 18 Sep 2026 10:43:05 -0700
Subject: [PATCH] [remoting] Ignore XTEST events in LocalHotkeyInputMonitorX11
Filter out synthetic XTEST device events in
LocalHotkeyInputMonitorX11::Core::OnEvent() by checking raw->sourceid
against XTEST slave device IDs queried via XIQueryDevice().
Track pressed Left/Right Control and Alt keys in flat_sets
(pressed_ctrl_keys_ and pressed_alt_keys_) so releasing one side
(e.g. Control_R) does not clear modifier state while the other side
(e.g. Control_L) remains physically held.
Bug: 554557866
Change-Id: I3b2c5b049714b50b35ed657cbbbe8910a92d568f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8428380
Reviewed-by: Yuwei Huang <yuweih@chromium.org>
Commit-Queue: Lambros Lambrou <lambroslambrou@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1700934}
---
diff --git a/remoting/host/input_monitor/local_hotkey_input_monitor_x11.cc b/remoting/host/input_monitor/local_hotkey_input_monitor_x11.cc
index 12a3a39..b72fc0f 100644
--- a/remoting/host/input_monitor/local_hotkey_input_monitor_x11.cc
+++ b/remoting/host/input_monitor/local_hotkey_input_monitor_x11.cc
@@ -4,17 +4,14 @@
#include "remoting/host/input_monitor/local_hotkey_input_monitor_x11.h"
-#include <sys/select.h>
-#include <unistd.h>
-
#include <utility>
-#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/logging.h"
#include "remoting/host/input_monitor/local_input_monitor_x11_common.h"
+#include "ui/gfx/x/future.h"
#include "ui/gfx/x/keysyms/keysyms.h"
#include "ui/gfx/x/xinput.h"
@@ -73,6 +70,8 @@
return;
}
+ UpdateXTestDeviceIds();
+
auto mask = CommonXIEventMaskForRootWindow();
connection_->xinput().XISelectEvents(
{connection_->default_root(),
@@ -85,6 +84,30 @@
connection_->RemoveEventObserver(this);
}
+void LocalHotkeyInputMonitorX11::Core::UpdateXTestDeviceIds() {
+ DCHECK(input_task_runner_->BelongsToCurrentThread());
+ xtest_device_ids_.clear();
+
+ auto reply =
+ connection_->xinput().XIQueryDevice({x11::Input::DeviceId::All}).Sync();
+ if (!reply) {
+ LOG(ERROR) << "XIQueryDevice failed.";
+ return;
+ }
+ for (const auto& info : reply->infos) {
+ if (info.name.ends_with("XTEST keyboard") ||
+ info.name.ends_with("XTEST pointer")) {
+ xtest_device_ids_.insert(info.deviceid);
+ }
+ }
+}
+
+bool LocalHotkeyInputMonitorX11::Core::IsXTestDevice(
+ x11::Input::DeviceId device_id) const {
+ DCHECK(input_task_runner_->BelongsToCurrentThread());
+ return xtest_device_ids_.contains(device_id);
+}
+
void LocalHotkeyInputMonitorX11::Core::OnEvent(const x11::Event& event) {
DCHECK(input_task_runner_->BelongsToCurrentThread());
@@ -104,15 +127,30 @@
return;
}
+ // Ignore events injected via XTEST so remote input cannot manipulate local
+ // modifier state or trigger local hotkeys.
+ if (IsXTestDevice(raw->sourceid)) {
+ return;
+ }
+
const bool down = raw->opcode == x11::Input::RawDeviceEvent::RawKeyPress;
const auto key_sym =
connection_->KeycodeToKeysym(static_cast<x11::KeyCode>(raw->detail), 0);
if (key_sym == XK_Control_L || key_sym == XK_Control_R) {
- ctrl_pressed_ = down;
+ if (down) {
+ pressed_ctrl_keys_.insert(key_sym);
+ } else {
+ pressed_ctrl_keys_.erase(key_sym);
+ }
} else if (key_sym == XK_Alt_L || key_sym == XK_Alt_R) {
- alt_pressed_ = down;
- } else if (key_sym == XK_Escape && down && alt_pressed_ && ctrl_pressed_) {
+ if (down) {
+ pressed_alt_keys_.insert(key_sym);
+ } else {
+ pressed_alt_keys_.erase(key_sym);
+ }
+ } else if (key_sym == XK_Escape && down && !pressed_alt_keys_.empty() &&
+ !pressed_ctrl_keys_.empty()) {
caller_task_runner_->PostTask(FROM_HERE, std::move(disconnect_callback_));
}
}
diff --git a/remoting/host/input_monitor/local_hotkey_input_monitor_x11.h b/remoting/host/input_monitor/local_hotkey_input_monitor_x11.h
index fb1c4dd..22b1487 100644
--- a/remoting/host/input_monitor/local_hotkey_input_monitor_x11.h
+++ b/remoting/host/input_monitor/local_hotkey_input_monitor_x11.h
@@ -5,6 +5,9 @@
#ifndef REMOTING_HOST_INPUT_MONITOR_LOCAL_HOTKEY_INPUT_MONITOR_X11_H_
#define REMOTING_HOST_INPUT_MONITOR_LOCAL_HOTKEY_INPUT_MONITOR_X11_H_
+#include <cstdint>
+
+#include "base/containers/flat_set.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/sequence_checker.h"
@@ -12,7 +15,7 @@
#include "remoting/host/input_monitor/local_hotkey_input_monitor.h"
#include "ui/gfx/x/connection.h"
#include "ui/gfx/x/event.h"
-#include "ui/gfx/x/future.h"
+#include "ui/gfx/x/xinput.h"
namespace remoting {
@@ -45,6 +48,8 @@
~Core() override;
void StartOnInputThread();
void StopOnInputThread();
+ void UpdateXTestDeviceIds();
+ bool IsXTestDevice(x11::Input::DeviceId device_id) const;
// x11::EventObserver:
void OnEvent(const x11::Event& event) override;
// Task runner on which public methods of this class must be called.
@@ -53,10 +58,11 @@
scoped_refptr<base::SingleThreadTaskRunner> input_task_runner_;
// Used to send session disconnect requests.
base::OnceClosure disconnect_callback_;
- // True when Alt is pressed.
- bool alt_pressed_ = false;
- // True when Ctrl is pressed.
- bool ctrl_pressed_ = false;
+ // Local Control and Alt keysyms currently held down.
+ base::flat_set<uint32_t> pressed_ctrl_keys_;
+ base::flat_set<uint32_t> pressed_alt_keys_;
+ // Cached XTEST slave device IDs to ignore synthetic events.
+ base::flat_set<x11::Input::DeviceId> xtest_device_ids_;
raw_ptr<x11::Connection> connection_ = nullptr;
};
scoped_refptr<Core> core_;
diff --git a/remoting/host/input_monitor/local_hotkey_input_monitor_x11_unittest.cc b/remoting/host/input_monitor/local_hotkey_input_monitor_x11_unittest.cc
new file mode 100644
index 0000000..a31d2e9
--- /dev/null
+++ b/remoting/host/input_monitor/local_hotkey_input_monitor_x11_unittest.cc
@@ -0,0 +1,187 @@
+// 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 <cstdint>
+#include <memory>
+#include <utility>
+
+#include "base/run_loop.h"
+#include "base/test/task_environment.h"
+#include "base/test/test_future.h"
+#include "remoting/host/input_monitor/local_hotkey_input_monitor.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/x/connection.h"
+#include "ui/gfx/x/event.h"
+#include "ui/gfx/x/future.h"
+#include "ui/gfx/x/keysyms/keysyms.h"
+#include "ui/gfx/x/xinput.h"
+#include "ui/gfx/x/xproto.h"
+#include "ui/gfx/x/xtest.h"
+
+namespace remoting {
+
+class LocalHotkeyInputMonitorX11Test : public testing::Test {
+ public:
+ LocalHotkeyInputMonitorX11Test()
+ : task_environment_(base::test::TaskEnvironment::MainThreadType::UI) {}
+
+ void SetUp() override {
+ connection_ = x11::Connection::Get();
+ ASSERT_NE(connection_, nullptr);
+ ASSERT_TRUE(connection_->Ready());
+ ASSERT_TRUE(connection_->xinput().present());
+
+ auto reply =
+ connection_->xinput().XIQueryDevice({x11::Input::DeviceId::All}).Sync();
+ ASSERT_TRUE(reply);
+ for (const auto& info : reply->infos) {
+ if (info.type == x11::Input::DeviceType::MasterKeyboard) {
+ master_keyboard_id_ = info.deviceid;
+ } else if (info.name.ends_with("XTEST keyboard")) {
+ xtest_keyboard_id_ = info.deviceid;
+ }
+ }
+ ASSERT_NE(master_keyboard_id_, x11::Input::DeviceId{});
+ ASSERT_NE(xtest_keyboard_id_, x11::Input::DeviceId{});
+ ASSERT_NE(xtest_keyboard_id_, kLocalKeyboardId);
+
+ auto task_runner = task_environment_.GetMainThreadTaskRunner();
+ monitor_ =
+ LocalHotkeyInputMonitor::Create(task_runner, task_runner, task_runner,
+ disconnect_future_.GetCallback());
+ FlushTaskRunner();
+ connection_->Sync();
+ connection_->DispatchAll();
+ }
+
+ void TearDown() override {
+ monitor_.reset();
+ FlushTaskRunner();
+ }
+
+ void DispatchRawKeyEvent(uint32_t keysym,
+ bool down,
+ x11::Input::DeviceId source_id) {
+ x11::KeyCode keycode = connection_->KeysymToKeycode(keysym);
+ ASSERT_NE(keycode, x11::KeyCode{});
+
+ x11::Input::RawDeviceEvent raw_event;
+ raw_event.opcode = down ? x11::Input::RawDeviceEvent::RawKeyPress
+ : x11::Input::RawDeviceEvent::RawKeyRelease;
+ raw_event.deviceid = master_keyboard_id_;
+ raw_event.sourceid = source_id;
+ raw_event.detail = static_cast<uint32_t>(keycode);
+
+ x11::Event event(false, std::move(raw_event));
+ connection_->DispatchEvent(event);
+ FlushTaskRunner();
+ }
+
+ void InjectXTestKeyEvent(uint32_t keysym, bool down) {
+ ASSERT_TRUE(connection_->xtest().present());
+ x11::KeyCode keycode = connection_->KeysymToKeycode(keysym);
+ ASSERT_NE(keycode, x11::KeyCode{});
+
+ uint8_t opcode = down ? x11::KeyEvent::Press : x11::KeyEvent::Release;
+ connection_->xtest().FakeInput({opcode, static_cast<uint8_t>(keycode)});
+ connection_->Sync();
+ connection_->DispatchAll();
+ FlushTaskRunner();
+ }
+
+ void FlushTaskRunner() {
+ base::RunLoop run_loop;
+ task_environment_.GetMainThreadTaskRunner()->PostTask(
+ FROM_HERE, run_loop.QuitClosure());
+ run_loop.Run();
+ }
+
+ protected:
+ // Headless Xorg (used by testing/xvfb.py on CQ bots) only creates the master
+ // devices and the virtual XTEST slave devices, with no physical slave
+ // keyboard attached. Use a distinct synthetic DeviceId to represent a
+ // physical slave keyboard when dispatching RawDeviceEvents.
+ static constexpr x11::Input::DeviceId kLocalKeyboardId{100};
+
+ base::test::SingleThreadTaskEnvironment task_environment_;
+ raw_ptr<x11::Connection> connection_ = nullptr;
+ x11::Input::DeviceId master_keyboard_id_{};
+ x11::Input::DeviceId xtest_keyboard_id_{};
+ x11::Input::DeviceId local_keyboard_id_{kLocalKeyboardId};
+ base::test::TestFuture<void> disconnect_future_;
+ std::unique_ptr<LocalHotkeyInputMonitor> monitor_;
+};
+
+TEST_F(LocalHotkeyInputMonitorX11Test, LocalCtrlAltEscTriggersDisconnect) {
+ DispatchRawKeyEvent(XK_Control_L, /*down=*/true, local_keyboard_id_);
+ DispatchRawKeyEvent(XK_Alt_L, /*down=*/true, local_keyboard_id_);
+ DispatchRawKeyEvent(XK_Escape, /*down=*/true, local_keyboard_id_);
+
+ EXPECT_TRUE(disconnect_future_.IsReady());
+}
+
+TEST_F(LocalHotkeyInputMonitorX11Test,
+ RemoteModifierReleaseDoesNotSuppressDisconnect) {
+ // Local user physically holds Control and Alt.
+ DispatchRawKeyEvent(XK_Control_L, /*down=*/true, local_keyboard_id_);
+ DispatchRawKeyEvent(XK_Alt_L, /*down=*/true, local_keyboard_id_);
+
+ // Remote client streams modifier release events via the XTEST device.
+ DispatchRawKeyEvent(XK_Control_L, /*down=*/false, xtest_keyboard_id_);
+ DispatchRawKeyEvent(XK_Alt_L, /*down=*/false, xtest_keyboard_id_);
+
+ // Local user presses Escape while still physically holding Control and Alt.
+ DispatchRawKeyEvent(XK_Escape, /*down=*/true, local_keyboard_id_);
+
+ EXPECT_TRUE(disconnect_future_.IsReady());
+}
+
+TEST_F(LocalHotkeyInputMonitorX11Test,
+ LiveXTestModifierChurnDoesNotSuppressDisconnect) {
+ // Local user physically holds Control and Alt.
+ DispatchRawKeyEvent(XK_Control_L, /*down=*/true, local_keyboard_id_);
+ DispatchRawKeyEvent(XK_Alt_L, /*down=*/true, local_keyboard_id_);
+
+ // Remote client injects modifier press/release churn through live XTest.
+ InjectXTestKeyEvent(XK_Control_L, /*down=*/true);
+ InjectXTestKeyEvent(XK_Control_L, /*down=*/false);
+ InjectXTestKeyEvent(XK_Alt_L, /*down=*/true);
+ InjectXTestKeyEvent(XK_Alt_L, /*down=*/false);
+
+ // Local user presses Escape.
+ DispatchRawKeyEvent(XK_Escape, /*down=*/true, local_keyboard_id_);
+
+ EXPECT_TRUE(disconnect_future_.IsReady());
+}
+
+TEST_F(LocalHotkeyInputMonitorX11Test,
+ RemoteInjectedHotkeysDoNotTriggerDisconnect) {
+ DispatchRawKeyEvent(XK_Control_L, /*down=*/true, xtest_keyboard_id_);
+ DispatchRawKeyEvent(XK_Alt_L, /*down=*/true, xtest_keyboard_id_);
+ DispatchRawKeyEvent(XK_Escape, /*down=*/true, xtest_keyboard_id_);
+
+ EXPECT_FALSE(disconnect_future_.IsReady());
+}
+
+TEST_F(LocalHotkeyInputMonitorX11Test, LocalModifierReleasePreventsDisconnect) {
+ DispatchRawKeyEvent(XK_Control_L, /*down=*/true, local_keyboard_id_);
+ DispatchRawKeyEvent(XK_Control_L, /*down=*/false, local_keyboard_id_);
+ DispatchRawKeyEvent(XK_Alt_L, /*down=*/true, local_keyboard_id_);
+ DispatchRawKeyEvent(XK_Escape, /*down=*/true, local_keyboard_id_);
+
+ EXPECT_FALSE(disconnect_future_.IsReady());
+}
+
+TEST_F(LocalHotkeyInputMonitorX11Test,
+ ReleasingOneCtrlWhileOtherCtrlHeldStillTriggersDisconnect) {
+ DispatchRawKeyEvent(XK_Control_L, /*down=*/true, local_keyboard_id_);
+ DispatchRawKeyEvent(XK_Control_R, /*down=*/true, local_keyboard_id_);
+ DispatchRawKeyEvent(XK_Control_R, /*down=*/false, local_keyboard_id_);
+ DispatchRawKeyEvent(XK_Alt_L, /*down=*/true, local_keyboard_id_);
+ DispatchRawKeyEvent(XK_Escape, /*down=*/true, local_keyboard_id_);
+
+ EXPECT_TRUE(disconnect_future_.IsReady());
+}
+
+} // namespace remoting
diff --git a/remoting/host/BUILD.gn b/remoting/host/BUILD.gn
index 482ff1ef..dadad5e 100644
--- a/remoting/host/BUILD.gn
+++ b/remoting/host/BUILD.gn
@@ -1049,7 +1049,11 @@
}
if (remoting_use_x11) {
- deps += [ "//remoting/host/linux:platform_impls" ]
+ sources += [ "input_monitor/local_hotkey_input_monitor_x11_unittest.cc" ]
+ deps += [
+ "//remoting/host/linux:platform_impls",
+ "//ui/gfx/x",
+ ]
}
if (is_chromeos) {
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/remoting/host/input_monitor/local_hotkey_input_monitor_x11_unittest.cc b/remoting/host/input_monitor/local_hotkey_input_monitor_x11_unittest.cc
new file mode 100644
index 0000000..a31d2e9
--- /dev/null
+++ b/remoting/host/input_monitor/local_hotkey_input_monitor_x11_unittest.cc
@@ -0,0 +1,187 @@
+// 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 <cstdint>
+#include <memory>
+#include <utility>
+
+#include "base/run_loop.h"
+#include "base/test/task_environment.h"
+#include "base/test/test_future.h"
+#include "remoting/host/input_monitor/local_hotkey_input_monitor.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/x/connection.h"
+#include "ui/gfx/x/event.h"
+#include "ui/gfx/x/future.h"
+#include "ui/gfx/x/keysyms/keysyms.h"
+#include "ui/gfx/x/xinput.h"
+#include "ui/gfx/x/xproto.h"
+#include "ui/gfx/x/xtest.h"
+
+namespace remoting {
+
+class LocalHotkeyInputMonitorX11Test : public testing::Test {
+ public:
+ LocalHotkeyInputMonitorX11Test()
+ : task_environment_(base::test::TaskEnvironment::MainThreadType::UI) {}
+
+ void SetUp() override {
+ connection_ = x11::Connection::Get();
+ ASSERT_NE(connection_, nullptr);
+ ASSERT_TRUE(connection_->Ready());
+ ASSERT_TRUE(connection_->xinput().present());
+
+ auto reply =
+ connection_->xinput().XIQueryDevice({x11::Input::DeviceId::All}).Sync();
+ ASSERT_TRUE(reply);
+ for (const auto& info : reply->infos) {
+ if (info.type == x11::Input::DeviceType::MasterKeyboard) {
+ master_keyboard_id_ = info.deviceid;
+ } else if (info.name.ends_with("XTEST keyboard")) {
+ xtest_keyboard_id_ = info.deviceid;
+ }
+ }
+ ASSERT_NE(master_keyboard_id_, x11::Input::DeviceId{});
+ ASSERT_NE(xtest_keyboard_id_, x11::Input::DeviceId{});
+ ASSERT_NE(xtest_keyboard_id_, kLocalKeyboardId);
+
+ auto task_runner = task_environment_.GetMainThreadTaskRunner();
+ monitor_ =
+ LocalHotkeyInputMonitor::Create(task_runner, task_runner, task_runner,
+ disconnect_future_.GetCallback());
+ FlushTaskRunner();
+ connection_->Sync();
+ connection_->DispatchAll();
+ }
+
+ void TearDown() override {
+ monitor_.reset();
+ FlushTaskRunner();
+ }
+
+ void DispatchRawKeyEvent(uint32_t keysym,
+ bool down,
+ x11::Input::DeviceId source_id) {
+ x11::KeyCode keycode = connection_->KeysymToKeycode(keysym);
+ ASSERT_NE(keycode, x11::KeyCode{});
+
+ x11::Input::RawDeviceEvent raw_event;
+ raw_event.opcode = down ? x11::Input::RawDeviceEvent::RawKeyPress
+ : x11::Input::RawDeviceEvent::RawKeyRelease;
+ raw_event.deviceid = master_keyboard_id_;
+ raw_event.sourceid = source_id;
+ raw_event.detail = static_cast<uint32_t>(keycode);
+
+ x11::Event event(false, std::move(raw_event));
+ connection_->DispatchEvent(event);
+ FlushTaskRunner();
+ }
+
+ void InjectXTestKeyEvent(uint32_t keysym, bool down) {
+ ASSERT_TRUE(connection_->xtest().present());
+ x11::KeyCode keycode = connection_->KeysymToKeycode(keysym);
+ ASSERT_NE(keycode, x11::KeyCode{});
+
+ uint8_t opcode = down ? x11::KeyEvent::Press : x11::KeyEvent::Release;
+ connection_->xtest().FakeInput({opcode, static_cast<uint8_t>(keycode)});
+ connection_->Sync();
+ connection_->DispatchAll();
+ FlushTaskRunner();
+ }
+
+ void FlushTaskRunner() {
+ base::RunLoop run_loop;
+ task_environment_.GetMainThreadTaskRunner()->PostTask(
+ FROM_HERE, run_loop.QuitClosure());
+ run_loop.Run();
+ }
+
+ protected:
+ // Headless Xorg (used by testing/xvfb.py on CQ bots) only creates the master
+ // devices and the virtual XTEST slave devices, with no physical slave
+ // keyboard attached. Use a distinct synthetic DeviceId to represent a
+ // physical slave keyboard when dispatching RawDeviceEvents.
+ static constexpr x11::Input::DeviceId kLocalKeyboardId{100};
+
+ base::test::SingleThreadTaskEnvironment task_environment_;
+ raw_ptr<x11::Connection> connection_ = nullptr;
+ x11::Input::DeviceId master_keyboard_id_{};
+ x11::Input::DeviceId xtest_keyboard_id_{};
+ x11::Input::DeviceId local_keyboard_id_{kLocalKeyboardId};
+ base::test::TestFuture<void> disconnect_future_;
+ std::unique_ptr<LocalHotkeyInputMonitor> monitor_;
+};
+
+TEST_F(LocalHotkeyInputMonitorX11Test, LocalCtrlAltEscTriggersDisconnect) {
+ DispatchRawKeyEvent(XK_Control_L, /*down=*/true, local_keyboard_id_);
+ DispatchRawKeyEvent(XK_Alt_L, /*down=*/true, local_keyboard_id_);
+ DispatchRawKeyEvent(XK_Escape, /*down=*/true, local_keyboard_id_);
+
+ EXPECT_TRUE(disconnect_future_.IsReady());
+}
+
+TEST_F(LocalHotkeyInputMonitorX11Test,
+ RemoteModifierReleaseDoesNotSuppressDisconnect) {
+ // Local user physically holds Control and Alt.
+ DispatchRawKeyEvent(XK_Control_L, /*down=*/true, local_keyboard_id_);
+ DispatchRawKeyEvent(XK_Alt_L, /*down=*/true, local_keyboard_id_);
+
+ // Remote client streams modifier release events via the XTEST device.
+ DispatchRawKeyEvent(XK_Control_L, /*down=*/false, xtest_keyboard_id_);
+ DispatchRawKeyEvent(XK_Alt_L, /*down=*/false, xtest_keyboard_id_);
+
+ // Local user presses Escape while still physically holding Control and Alt.
+ DispatchRawKeyEvent(XK_Escape, /*down=*/true, local_keyboard_id_);
+
+ EXPECT_TRUE(disconnect_future_.IsReady());
+}
+
+TEST_F(LocalHotkeyInputMonitorX11Test,
+ LiveXTestModifierChurnDoesNotSuppressDisconnect) {
+ // Local user physically holds Control and Alt.
+ DispatchRawKeyEvent(XK_Control_L, /*down=*/true, local_keyboard_id_);
+ DispatchRawKeyEvent(XK_Alt_L, /*down=*/true, local_keyboard_id_);
+
+ // Remote client injects modifier press/release churn through live XTest.
+ InjectXTestKeyEvent(XK_Control_L, /*down=*/true);
+ InjectXTestKeyEvent(XK_Control_L, /*down=*/false);
+ InjectXTestKeyEvent(XK_Alt_L, /*down=*/true);
+ InjectXTestKeyEvent(XK_Alt_L, /*down=*/false);
+
+ // Local user presses Escape.
+ DispatchRawKeyEvent(XK_Escape, /*down=*/true, local_keyboard_id_);
+
+ EXPECT_TRUE(disconnect_future_.IsReady());
+}
+
+TEST_F(LocalHotkeyInputMonitorX11Test,
+ RemoteInjectedHotkeysDoNotTriggerDisconnect) {
+ DispatchRawKeyEvent(XK_Control_L, /*down=*/true, xtest_keyboard_id_);
+ DispatchRawKeyEvent(XK_Alt_L, /*down=*/true, xtest_keyboard_id_);
+ DispatchRawKeyEvent(XK_Escape, /*down=*/true, xtest_keyboard_id_);
+
+ EXPECT_FALSE(disconnect_future_.IsReady());
+}
+
+TEST_F(LocalHotkeyInputMonitorX11Test, LocalModifierReleasePreventsDisconnect) {
+ DispatchRawKeyEvent(XK_Control_L, /*down=*/true, local_keyboard_id_);
+ DispatchRawKeyEvent(XK_Control_L, /*down=*/false, local_keyboard_id_);
+ DispatchRawKeyEvent(XK_Alt_L, /*down=*/true, local_keyboard_id_);
+ DispatchRawKeyEvent(XK_Escape, /*down=*/true, local_keyboard_id_);
+
+ EXPECT_FALSE(disconnect_future_.IsReady());
+}
+
+TEST_F(LocalHotkeyInputMonitorX11Test,
+ ReleasingOneCtrlWhileOtherCtrlHeldStillTriggersDisconnect) {
+ DispatchRawKeyEvent(XK_Control_L, /*down=*/true, local_keyboard_id_);
+ DispatchRawKeyEvent(XK_Control_R, /*down=*/true, local_keyboard_id_);
+ DispatchRawKeyEvent(XK_Control_R, /*down=*/false, local_keyboard_id_);
+ DispatchRawKeyEvent(XK_Alt_L, /*down=*/true, local_keyboard_id_);
+ DispatchRawKeyEvent(XK_Escape, /*down=*/true, local_keyboard_id_);
+
+ EXPECT_TRUE(disconnect_future_.IsReady());
+}
+
+} // 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