Skip to content

[Bug]: macOS host: crash in IOHIDDeviceCopyMatchingElements when a HID keyboard disappears while a VM window has focus #875

Description

@SzymonPaczos

Version

7.2.6 (r172322). DarwinKeyboard.cpp is byte-identical in 7.2.20; main differs only in $Id/copyright.

Host OS Type

macOS

Host OS name + version

macOS 27.0 (build 26A428)

Host Architecture

arm64

Guest OS Type

Windows

Guest Architecture

arm64

Guest OS name + version

Windows 11 (irrelevant: host-side crash in the GUI process)

Component

GUI

What happened?

VirtualBoxVM crashes with SIGSEGV when the VM window resigns key (click outside it, or switch application) after a HID keyboard was removed from the host while the window had focus. VM state goes to aborted; VBox.log just stops, with no guru meditation and no shutdown sequence.

Three crashes in two days here, identical frames and offsets, fault address 0x0, x0 == 0:

EXC_BAD_ACCESS (SIGSEGV), KERN_INVALID_ADDRESS at 0x0
0  IOKit           IOHIDDeviceCopyMatchingElements + 44
1  UICommon.dylib  +0x3e6ebc  (static darwinSetDeviceLedsState)
2  UICommon.dylib  DarwinHidDevicesApplyAndReleaseLedsState + 512
…  QApplicationPrivate::setActiveWindow <- QCocoaWindow::windowDidResignKey

Root cause: dangling IOHIDDeviceRef in the LED-sync cache

darwinHidAddDevice() stores the device without retaining it (DarwinKeyboard.cpp:1867). Neither source transfers ownership: the IOHIDManagerCopyDevices() set is released right after the loop (:2041, :2056), the matching callback passes a borrowed reference, and pDeviceCollection has NULL callbacks (:2035). Only the manager's own references remain, and the manager drops them through its own interest notification, independently of the removal callback VirtualBox registers at :1897 (IOKitUser hid.subproj, IOKitUser-100231.100.18.0.1):

IOHIDManager.c:460  IOServiceAddInterestNotification(__IOHIDManagerDeviceRemoved) on manager->notifyPort
IOHIDManager.c:480  device kept as a retained key in removalNotifiers
IOHIDManager.c:671  CFDictionaryRemoveValue(removalNotifiers, sender)
IOHIDManager.c:704  CFSetRemoveValue(manager->devices, sender)   -> last reference
HIDDeviceBase.m:46  -dealloc -> _IOHIDDeviceReleasePrivate()     (IOHIDDevice.c:292)
IOHIDDevice.c:321      device->deviceInterface = NULL             (only place it is zeroed)
IOHIDDevice.c:341      IONotificationPortDestroy(device->notificationPort)

After that, the port darwinHidRemovalCallback() (:1817) would arrive on is gone and the stale entry stays cached. On focus loss DarwinHidDevicesApplyAndReleaseLedsState() -> darwinSetDeviceLedsState(pKbd->pDevice, …) -> IOHIDDeviceCopyMatchingElements(), which does (*device->deviceInterface)->copyMatchingElements(…) (IOHIDDevice.c:676). The shipping IOKit disassembles to +40: ldr x0, [x0, #0x18] (load deviceInterface, struct offset 24) then +44: ldr x8, [x0]; all three reports fault there with x0 == 0. The field sits past the 16-byte free-list header of a freed block, so it still holds the NULL the finalizer wrote, hence 0x0 rather than a random address.

Secondary defect

idxPosition is set at insertion (:1869) and never updated, but CFArrayRemoveValueAtIndex() (:1832) shifts later entries down. With two or more keyboards the callback removes the wrong entry and frees its own. Reachable as soon as the callback actually fires, i.e. once the reference above is taken.

Workaround

VBoxManage setextradata global GUI/HidLedsSync 0 (or per VM). Disables the code path; Caps Lock / Num Lock stop syncing.

Prior reports

Same crash, on Trac since 2019, no root-cause analysis so far:

Ticket Opened Status
#19153 2019-12-13 new, last activity 2021-09-20; logs in the comments (6.1.26) show this exact stack at 0x0; comment:5 gives the workaround
#19190 2019-12-30 closed worksforme
#21017 2022-07-08 closed as duplicate of #19153

Same stack on the forums: t=86559 (5.2.6), t=102320 (6.1.18), t=107794 (7.0.2).

Fine with closing as a duplicate of #19153; filed here because the fix will come as a GitHub pull request (in preparation). VBox.log and the three .ips reports are available on request (they contain host paths and VM names).

How can we reproduce this?

Any Mac (this one is Apple Silicon; the Trac reports above are x86-64), VirtualBox 7.2.x, HID LEDs sync at its default (enabled). Observed here with the built-in keyboard plus a Bluetooth mouse (VID 0x373E) that also publishes a HID keyboard collection and drops off when idle. GamePolicyAgent lists it as <GCMouse 'R6 Mouse'> next to a <GCKeyboard 'Generic Keyboard'>, loginwindow logs Keyboard Service added on every reconnect (83 and 161 times on the two days in question), and one such reconnect precedes the second crash by 4 s -- which is why no user action was needed. Any terminated kHIDUsage_GD_Keyboard device should do (Bluetooth keyboard going to sleep, USB keyboard unplugged, keyboard captured by a USB filter); the mechanism does not depend on the number of keyboards, but only the Bluetooth-mouse case was exercised here.

  1. Start a VM and give its window keyboard focus.
  2. While it has focus, let such a device disappear from the host.
  3. Click outside the VM window or switch application, so it resigns key.
  4. VirtualBoxVM crashes with the stack above; VM state becomes aborted.

Check for the missing callback: with GUI release logging at level 2, no Forget KBD line (printed by darwinHidRemovalCallback()) appears after step 2.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions