Skip to content

hyprland/ipc: use deleteLater() for workspace and toplevel deletion#499

Open
simaotwx wants to merge 1 commit intoquickshell-mirror:masterfrom
simaotwx:fix-hyprland-workspace-uaf
Open

hyprland/ipc: use deleteLater() for workspace and toplevel deletion#499
simaotwx wants to merge 1 commit intoquickshell-mirror:masterfrom
simaotwx:fix-hyprland-workspace-uaf

Conversation

@simaotwx
Copy link

Problem

Quickshell crashes with SIGSEGV when rapidly switching workspaces in Hyprland. The crash occurs because workspace and toplevel objects are deleted immediately while QML bindings may still reference them during the same event loop iteration.

Root Cause

In src/wayland/hyprland/ipc/connection.cpp, workspace and toplevel objects are deleted with delete immediately after being removed from their containers. However, QML property bindings that depend on these objects may not have been re-evaluated yet, causing them to access freed memory when the Qt event loop continues processing.

Solution

Replace delete workspace and delete toplevel with deleteLater() to defer destruction until the next event loop iteration. This ensures all QML bindings have completed their updates before the objects are destroyed.

Stack trace

                Stack trace of thread 207400:
                #0  0x00007fa1ab613fe6 _ZN3QV414QObjectWrapper4wrapEPNS_15ExecutionEngineEP7QObject (libQt6Qml.so.6 + 0x213fe6)
                #1  0x00007fa1ab690ae8 _ZN3QV4L12loadPropertyEPNS_15ExecutionEngineEPNS_4Heap6ObjectEP7QObjectRK16QQmlPropertyData (libQt6Qml.so.6 + 0x290ae8)
                #2  0x00007fa1ab8cbf76 _ZN3QV415QQmlTypeWrapper23lookupSingletonPropertyEPNS_6LookupEPNS_15ExecutionEngineERKNS_5ValueE (libQt6Qml.so.6 + 0x4cbf76)
                #3  0x00007fa1ab70b46f _ZN3QV44Moth3VME9interpretEPNS_17JSTypesStackFrameEPNS_15ExecutionEngineEPKc (libQt6Qml.so.6 + 0x30b46f)
                #4  0x00007fa1ab70f65f _ZN3QV44Moth3VME4execEPNS_17JSTypesStackFrameEPNS_15ExecutionEngineE (libQt6Qml.so.6 + 0x30f65f)
                #5  0x00007fa1ab63756f _ZL9qfoDoCallPKN3QV424JavaScriptFunctionObjectEPKNS_5ValueES5_i (libQt6Qml.so.6 + 0x23756f)
                #6  0x00007fa1ab70a7b6 _ZN3QV44Moth3VME9interpretEPNS_17JSTypesStackFrameEPNS_15ExecutionEngineEPKc (libQt6Qml.so.6 + 0x30a7b6)
                #7  0x00007fa1ab70f65f _ZN3QV44Moth3VME4execEPNS_17JSTypesStackFrameEPNS_15ExecutionEngineE (libQt6Qml.so.6 + 0x30f65f)
                #8  0x00007fa1ab630e64 _ZN3QV4L6doCallEPNS_8FunctionEPKNS_5ValueES4_iPNS_16ExecutionContextE (libQt6Qml.so.6 + 0x230e64)
                #9  0x00007fa1ab63134f _ZN3QV48Function4callEP7QObjectPPvPK9QMetaTypeiPNS_16ExecutionContextE (libQt6Qml.so.6 + 0x23134f)
                #10 0x00007fa1ab7c679c _ZN24QQmlJavaScriptExpression8evaluateEPPvPK9QMetaTypei (libQt6Qml.so.6 + 0x3c679c)
                #11 0x00007fa1ab758f22 _ZN25QQmlBoundSignalExpression8evaluateEPPv (libQt6Qml.so.6 + 0x358f22)
                #12 0x00007fa1ab759970 _Z24QQmlBoundSignal_callbackP20QQmlNotifierEndpointPPv (libQt6Qml.so.6 + 0x359970)
                #13 0x00007fa1ab7f9056 _ZN12QQmlNotifier10emitNotifyEP20QQmlNotifierEndpointPPv (libQt6Qml.so.6 + 0x3f9056)
                #14 0x00007fa1aaa2a890 _Z10doActivateILb0EEvP7QObjectiPPv (libQt6Core.so.6 + 0x22a890)
                #15 0x00007fa1ad068827 _ZN9QQmlTimer5eventEP6QEvent (libQt6QmlMeta.so.6 + 0x25827)
                #16 0x00007fa1aa9bcd68 _ZN16QCoreApplication15notifyInternal2EP7QObjectP6QEvent (libQt6Core.so.6 + 0x1bcd68)
                #17 0x00007fa1aa9c04fd _ZN23QCoreApplicationPrivate16sendPostedEventsEP7QObjectiP11QThreadData (libQt6Core.so.6 + 0x1c04fd)
                #18 0x00007fa1aacfa17f _ZL23postEventSourceDispatchP8_GSourcePFiPvES1_ (libQt6Core.so.6 + 0x4fa17f)
                #19 0x00007fa1ad101a2d g_main_context_dispatch_unlocked (libglib-2.0.so.0 + 0x62a2d)
                #20 0x00007fa1ad104eb8 g_main_context_iterate_unlocked.isra.0 (libglib-2.0.so.0 + 0x65eb8)
                #21 0x00007fa1ad10574f g_main_context_iteration (libglib-2.0.so.0 + 0x6674f)
                #22 0x00007fa1aacf9833 _ZN20QEventDispatcherGlib13processEventsE6QFlagsIN10QEventLoop17ProcessEventsFlagEE (libQt6Core.so.6 + 0x4f9833)
                #23 0x00007fa1aa9caaf3 _ZN10QEventLoop4execE6QFlagsINS_17ProcessEventsFlagEE (libQt6Core.so.6 + 0x1caaf3)
                #24 0x00007fa1aa9c59e1 _ZN16QCoreApplication4execEv (libQt6Core.so.6 + 0x1c59e1)
                #25 0x00005634c9b4bd03 _ZN2qs6launch6launchERKNS0_10LaunchArgsEPPcP16QCoreApplication (/nix/store/g08lnqwm1zacfcpg9pm9gnndk6mblb0s-quickshell-wrapped-0.2.1/bin/.quickshell-wrapped + 0x119d03)
                #26 0x00005634c9b45e29 _ZN2qs6launch10runCommandEiPPcP16QCoreApplication (/nix/store/g08lnqwm1zacfcpg9pm9gnndk6mblb0s-quickshell-wrapped-0.2.1/bin/.quickshell-wrapped + 0x113e29)
                #27 0x00005634c9b342e0 _ZN2qs6launch4mainEiPPc (/nix/store/g08lnqwm1zacfcpg9pm9gnndk6mblb0s-quickshell-wrapped-0.2.1/bin/.quickshell-wrapped + 0x1022e0)
                #28 0x00007fa1aa02a4d8 __libc_start_call_main (libc.so.6 + 0x2a4d8)
                #29 0x00007fa1aa02a59b __libc_start_main@@GLIBC_2.34 (libc.so.6 + 0x2a59b)
                #30 0x00005634c9b35ff5 _start (/nix/store/g08lnqwm1zacfcpg9pm9gnndk6mblb0s-quickshell-wrapped-0.2.1/bin/.quickshell-wrapped + 0x103ff5)
                ELF object binary architecture: AMD x86-64

Testing

Rapidly switch between workspaces using keybindings (e.g., Mod+1, Mod+2 repeatedly). Before this fix, quickshell would crash within a few switches. After the fix, no crashes occur.

Here are some snippets to demonstrate where this issue was coming from:

// Connections listening to raw events
Connections {
    target: Hyprland
    function onRawEvent(event) {
        if (event.name === "openwindow" || event.name === "closewindow" || event.name === "movewindow" || event.name === "movewindowv2" || event.name === "workspace" || event.name === "workspacev2" || event.name === "focusedmon" || event.name === "focusedmonv2" || event.name === "activewindow" || event.name ===
"activewindowv2" || event.name === "changefloatingmode" || event.name === "fullscreen" || event.name === "moveintogroup" || event.name === "moveoutofgroup") {
            try {
                Hyprland.refreshToplevels()
            } catch (e) {}
            shell.scheduleUpdate()
        }
    }
}
// Bindings that access workspace objects
readonly property var monitorWorkspace: monitor?.activeWorkspace ?? null

readonly property int windowCount: {
    if (!monitorWorkspace) return 0
    const toplevels = Hyprland.toplevels
    if (!toplevels) return 0
    const list = toplevels.values ?? []
    return list.filter(t => t.workspace?.id === monitorWorkspace.id).length
}
function tiledWindowCountForWorkspace(workspace) {
    if (!workspace) return 0
    const toplevels = Hyprland.toplevels
    if (!toplevels) return 0
    const list = toplevels.values ?? []
    return list.filter(t =>
        t.workspace?.id === workspace.id &&
        !(t.lastIpcObject?.floating ?? false)
    ).length
}

Fix use-after-free crash when rapidly switching workspaces. The crash
occurs because QML bindings may still reference workspace/toplevel
objects during the same event loop iteration after immediate deletion.

Crash stack trace:
  #0 QV4::QObjectWrapper::wrap (libQt6Qml.so.6)
  quickshell-mirror#1 QV4::loadProperty (libQt6Qml.so.6)
  quickshell-mirror#2 QV4::QQmlTypeWrapper::lookupSingletonProperty (libQt6Qml.so.6)

Using deleteLater() defers deletion to the next event loop iteration,
ensuring all QML bindings have completed their updates first.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant