Chrome · Chromoting › Remoting
CVE-2026-106195
Authorization Error in Chromoting (Remoting)
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
LocalKeyboardInputMonitorMacremoting/host/input_monitor/local_keyboard_input_monitor_mac.mm |
modified | |
EventHandlerremoting/host/input_monitor/local_keyboard_input_monitor_mac.mm |
modified | |
Coreremoting/host/input_monitor/local_keyboard_input_monitor_mac.mm |
modified | |
ifremoting/host/input_monitor/local_keyboard_input_monitor_mac.mm |
modified |
Files Changed
remoting/host/input_monitor/local_keyboard_input_monitor_mac.mm
Patch
From ab0cd1a251b81bc89cac0417e8c54843adc571ff Mon Sep 17 00:00:00 2001
From: Joe Downing <joedow@google.com>
Date: Thu, 17 Sep 2026 15:04:14 -0700
Subject: [PATCH] [remoting] Implement local keyboard input monitor on macOS
LocalKeyboardInputMonitor::Create previously returned nullptr on
macOS. Because of this, local physical keystrokes and modifier key
presses were never delivered to ClientSessionControl::OnLocalKeyPressed
or RemoteInputFilter::LocalKeyPressed, preventing local keystrokes from
triggering the 2-second remote input suppression window or un-hiding
the disconnect window.
This change:
- Implements LocalKeyboardInputMonitorMac and
LocalKeyboardInputMonitorManager using CGEventTapCreate.
- Listens to kCGEventKeyDown, kCGEventKeyUp, and kCGEventFlagsChanged
to capture both key presses/releases and modifier key transitions.
- Converts macOS native keycodes to USB keycodes via
ui::KeycodeConverter::NativeKeycodeToUsbKeycode.
- Filters out CRD's own injected key events by checking
kCGEventSourceUnixProcessID != getpid().
- Re-enables the event tap when kCGEventTapDisabledByTimeout is received.
Bug: 553930843
Change-Id: I9d7b7cabe3fdc1ff3d0b8372945c09f004bfd57c
Fixed: 553930843
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8391909
Reviewed-by: Yuwei Huang <yuweih@chromium.org>
Commit-Queue: Joe Downing <joedow@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1700154}
---
diff --git a/remoting/host/input_monitor/local_keyboard_input_monitor_mac.mm b/remoting/host/input_monitor/local_keyboard_input_monitor_mac.mm
index be37d37..d1caae1 100644
--- a/remoting/host/input_monitor/local_keyboard_input_monitor_mac.mm
+++ b/remoting/host/input_monitor/local_keyboard_input_monitor_mac.mm
@@ -4,21 +4,280 @@
#include "remoting/host/input_monitor/local_keyboard_input_monitor.h"
+#import <AppKit/AppKit.h>
+#include <unistd.h>
+
+#include <memory>
#include <utility>
+#include "base/apple/scoped_cftyperef.h"
+#include "base/compiler_specific.h"
+#include "base/functional/bind.h"
#include "base/functional/callback.h"
-#import "base/task/single_thread_task_runner.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/sequence_checker.h"
+#include "base/task/bind_post_task.h"
#include "base/task/single_thread_task_runner.h"
+#include "ui/events/keycodes/dom/keycode_converter.h"
namespace remoting {
+namespace {
+
+class LocalKeyboardInputMonitorMac : public LocalKeyboardInputMonitor {
+ public:
+ // Invoked by LocalKeyboardInputMonitorManager.
+ class EventHandler {
+ public:
+ virtual ~EventHandler() = default;
+
+ virtual void OnLocalKeyPressed(uint32_t usb_keycode) = 0;
+ };
+
+ LocalKeyboardInputMonitorMac(
+ scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
+ scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
+ LocalInputMonitor::KeyPressedCallback on_key_event_callback,
+ base::OnceClosure disconnect_callback);
+
+ LocalKeyboardInputMonitorMac(const LocalKeyboardInputMonitorMac&) = delete;
+ LocalKeyboardInputMonitorMac& operator=(const LocalKeyboardInputMonitorMac&) =
+ delete;
+
+ ~LocalKeyboardInputMonitorMac() override;
+
+ private:
+ // The actual implementation resides in LocalKeyboardInputMonitorMac::Core.
+ class Core;
+ scoped_refptr<Core> core_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+};
+
+} // namespace
+} // namespace remoting
+
+@interface LocalKeyboardInputMonitorManager : NSObject {
+ @private
+ base::apple::ScopedCFTypeRef<CFRunLoopSourceRef> _keyboardRunLoopSource;
+ base::apple::ScopedCFTypeRef<CFMachPortRef> _keyboardMachPort;
+ raw_ptr<remoting::LocalKeyboardInputMonitorMac::EventHandler> _monitor;
+}
+
+- (instancetype)initWithMonitor:
+ (remoting::LocalKeyboardInputMonitorMac::EventHandler*)monitor;
+
+// Called when a local key event is detected.
+- (void)localKeyPressed:(uint32_t)usb_keycode;
+
+// Re-enables the event tap if disabled by timeout.
+- (void)reEnableEventTap;
+
+// Must be called when the LocalKeyboardInputMonitorManager is no longer needed.
+- (void)invalidate;
+
+@end
+
+static CGEventRef LocalKeyboardEvent([[maybe_unused]] CGEventTapProxy proxy,
+ CGEventType type,
+ CGEventRef event,
+ void* context) {
+ LocalKeyboardInputMonitorManager* manager =
+ (__bridge LocalKeyboardInputMonitorManager*)context;
+ if (type == kCGEventTapDisabledByTimeout) {
+ [manager reEnableEventTap];
+ return event;
+ }
+ if (type == kCGEventTapDisabledByUserInput) {
+ return event;
+ }
+
+ // Filter out events injected by the CRD host process, while capturing
+ // hardware events (pid == 0) and synthetic events from other local software
+ // (e.g. accessibility and assistive tools).
+ int64_t pid = CGEventGetIntegerValueField(event, kCGEventSourceUnixProcessID);
+ if (pid != getpid()) {
+ int mac_keycode = static_cast<int>(
+ CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode));
+ uint32_t usb_keycode =
+ ui::KeycodeConverter::NativeKeycodeToUsbKeycode(mac_keycode);
+ [manager localKeyPressed:usb_keycode];
+ }
+ return event;
+}
+
+@implementation LocalKeyboardInputMonitorManager
+
+- (instancetype)initWithMonitor:
+ (remoting::LocalKeyboardInputMonitorMac::EventHandler*)monitor {
+ if ((self = [super init])) {
+ _monitor = monitor;
+
+ const CGEventMask keyboard_mask = CGEventMaskBit(kCGEventKeyDown) |
+ CGEventMaskBit(kCGEventKeyUp) |
+ CGEventMaskBit(kCGEventFlagsChanged);
+
+ _keyboardMachPort.reset(CGEventTapCreate(
+ kCGSessionEventTap, kCGHeadInsertEventTap, kCGEventTapOptionListenOnly,
+ keyboard_mask, LocalKeyboardEvent, (__bridge void*)self));
+ if (_keyboardMachPort) {
+ _keyboardRunLoopSource.reset(
+ CFMachPortCreateRunLoopSource(nullptr, _keyboardMachPort.get(), 0));
+ CFRunLoopAddSource(CFRunLoopGetMain(), _keyboardRunLoopSource.get(),
+ kCFRunLoopCommonModes);
+ } else {
+ LOG(ERROR) << "CGEventTapCreate failed for keyboard.";
+ self = nil;
+ return nil;
+ }
+ }
+ return self;
+}
+
+- (void)localKeyPressed:(uint32_t)usb_keycode {
+ _monitor->OnLocalKeyPressed(usb_keycode);
+}
+
+- (void)reEnableEventTap {
+ if (_keyboardMachPort) {
+ CGEventTapEnable(_keyboardMachPort.get(), true);
+ }
+}
+
+- (void)invalidate {
+ if (_keyboardRunLoopSource) {
+ CFMachPortInvalidate(_keyboardMachPort.get());
+ CFRunLoopRemoveSource(CFRunLoopGetMain(), _keyboardRunLoopSource.get(),
+ kCFRunLoopCommonModes);
+ _keyboardMachPort.reset();
+ _keyboardRunLoopSource.reset();
+ }
+}
+
+- (void)dealloc {
+ [self invalidate];
+}
+
+@end
+
+namespace remoting {
+namespace {
+
+class LocalKeyboardInputMonitorMac::Core
+ : public base::RefCountedThreadSafe<Core>,
+ public EventHandler {
+ public:
+ Core(scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
+ scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
+ LocalInputMonitor::KeyPressedCallback on_key_event_callback,
+ base::OnceClosure disconnect_callback);
+
+ Core(const Core&) = delete;
+ Core& operator=(const Core&) = delete;
+
+ void Start();
+ void Stop();
+
+ private:
+ friend class base::RefCountedThreadSafe<Core>;
+ ~Core() override;
+
+ void StartOnUiThread();
+ void StopOnUiThread();
+
+ // EventHandler interface.
+ void OnLocalKeyPressed(uint32_t usb_keycode) override;
+
+ scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_;
+
+ LocalKeyboardInputMonitorManager* __strong manager_ = nil;
+
+ LocalInputMonitor::KeyPressedCallback on_key_event_callback_;
+ base::OnceClosure disconnect_callback_;
+};
+
+LocalKeyboardInputMonitorMac::LocalKeyboardInputMonitorMac(
+ scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
+ scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
+ LocalInputMonitor::KeyPressedCallback on_key_event_callback,
+ base::OnceClosure disconnect_callback)
+ : core_(base::MakeRefCounted<Core>(std::move(caller_task_runner),
+ std::move(ui_task_runner),
+ std::move(on_key_event_callback),
+ std::move(disconnect_callback))) {
+ core_->Start();
+}
+
+LocalKeyboardInputMonitorMac::~LocalKeyboardInputMonitorMac() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ core_->Stop();
+}
+
+LocalKeyboardInputMonitorMac::Core::Core(
+ scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
+ scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
+ LocalInputMonitor::KeyPressedCallback on_key_event_callback,
+ base::OnceClosure disconnect_callback)
+ : ui_task_runner_(std::move(ui_task_runner)),
+ on_key_event_callback_(
+ base::BindPostTask(caller_task_runner,
+ std::move(on_key_event_callback))),
+ disconnect_callback_(
+ disconnect_callback
+ ? base::BindPostTask(caller_task_runner,
+ std::move(disconnect_callback))
+ : base::OnceClosure()) {}
+
+void LocalKeyboardInputMonitorMac::Core::Start() {
+ ui_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&Core::StartOnUiThread, base::WrapRefCounted(this)));
+}
+
+void LocalKeyboardInputMonitorMac::Core::Stop() {
+ ui_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&Core::StopOnUiThread, base::WrapRefCounted(this)));
+}
+
+LocalKeyboardInputMonitorMac::Core::~Core() = default;
+
+void LocalKeyboardInputMonitorMac::Core::StartOnUiThread() {
+ DCHECK(ui_task_runner_->BelongsToCurrentThread());
+
+ manager_ = [[LocalKeyboardInputMonitorManager alloc] initWithMonitor:this];
+ if (!manager_ && disconnect_callback_) {
+ std::move(disconnect_callback_).Run();
+ }
+}
+
+void LocalKeyboardInputMonitorMac::Core::StopOnUiThread() {
+ DCHECK(ui_task_runner_->BelongsToCurrentThread());
+
+ [manager_ invalidate];
+ manager_ = nil;
+ disconnect_callback_.Reset();
+}
+
+void LocalKeyboardInputMonitorMac::Core::OnLocalKeyPressed(
+ uint32_t usb_keycode) {
+ on_key_event_callback_.Run(usb_keycode);
+}
+
+} // namespace
std::unique_ptr<LocalKeyboardInputMonitor> LocalKeyboardInputMonitor::Create(
scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
- scoped_refptr<base::SingleThreadTaskRunner> input_task_runner,
+ scoped_refptr<base::SingleThreadTaskRunner> /* input_task_runner */,
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
LocalInputMonitor::KeyPressedCallback on_key_event_callback,
base::OnceClosure disconnect_callback) {
- return nullptr;
+ return std::make_unique<LocalKeyboardInputMonitorMac>(
+ std::move(caller_task_runner), std::move(ui_task_runner),
+ std::move(on_key_event_callback), std::move(disconnect_callback));
}
} // 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