SIGSEGV in WindowsetManager::onServerCommit() / endPropertyUpdateGroup() on ext-workspace tree rebuild during output disconnect (niri, multi-monitor)
Summary
Quickshell reliably SIGSEGVs when the compositor (niri) rebuilds its entire ext-workspace-v1 tree in a single done batch — which happens on output disconnect/reconnect, e.g. every time the session locks and the displays blank. The crash is a use-after-free: a workspace is moved to a new group while its old group is destroyed in the same done batch, and when ext_workspace_manager_v1_done() emits serverCommit(), WindowsetManager::onServerCommit() dereferences state tied to the freed group while flushing the property update group.
This is distinct from the known lock/output-disconnect crashes (#540, #503, #287): those crash in QV4::QObjectWrapper::wrap() or wl_proxy_get_listener(). This one crashes in Qt::endPropertyUpdateGroup() reached from the windowset / ext-workspace path, and reproduces on a build that includes the recent wayland/lock fixes (b93d613, d1760ed).
Version
Quickshell 0.3.0 (revision 4b4fca3224ab977dc515ac0bb78d00b3dfa71e00, distributed by sam local)
Built from main @ 4b4fca3 (latest commit), Qt 6.10.2, gcc 15.2.0, RelWithDebInfo, jemalloc ON, crash handler ON.
Environment
- Compositor: niri (Ubuntu 26.04 package)
- Multi-monitor: HDMI-A-1 (ultrawide 2560x1080) + DP-3 (4K). Crash fires when one output drops/reconnects.
- GPU: AMD (amdgpu, 1002:67df)
- Shell config: DankMaterialShell (uses Quickshell workspace/windowset APIs)
- Trigger: session lock (display blanks → niri reports output disconnect → workspace tree rebuilt). Also reproduces on raw monitor power-cycle.
- Frequency: every lock; 40 crash dumps accumulated in
~/.cache/quickshell/crashes/ over two days.
Stacktrace
#0 libc.so.6
#1 Qt::endPropertyUpdateGroup() libQt6Core.so.6
#2 qs::wm::wayland::WindowsetManager::onServerCommit() src/wayland/windowmanager/windowset.cpp:122
#3 libQt6Core.so.6
#4 qs::wayland::workspace::WorkspaceManager::ext_workspace_manager_v1_done() src/wayland/windowmanager/ext_workspace.cpp:56
#5-7 libffi.so.8 (ffi_call)
#8-10 libwayland-client.so.0 (wl_display_dispatch_queue_pending)
#11 QtWaylandClient::QWaylandDisplay::flushRequests() libQt6WaylandClient.so.6
#12-15 libQt6Core.so.6 (QCoreApplication::exec)
#16 qs::launch::launch(...) src/launch/launch.cpp:320
Log tail (the smoking gun)
The compositor rebuilds the workspace tree in one done batch — note workspace 0x...838339e0 is removed from group 0x...4a03f4e0, that group is destroyed, then the same workspace is re-added to a new group 0x...4a03f420, all before Workspace changes done:
INFO qml: [DMSShell] Screens changed: 1 'HDMI-A-1'
DEBUG quickshell.wm.wayland.workspace: Workspace 0x...552650 removed from group 0x...4a03f4e0
DEBUG quickshell.wm.wayland.workspace: Destroyed workspace 0x...552650
DEBUG quickshell.wm.wayland.workspace: Workspace 0x...838339e0 removed from group 0x...4a03f4e0
DEBUG quickshell.wm.wayland.workspace: Destroyed group 0x...4a03f4e0
DEBUG quickshell.wm.wayland.workspace: Workspace 0x...838339e0 added to group 0x...4a03f420
DEBUG quickshell.wm.wayland.workspace: Updated name for workspace 0x...838339e0 to "2"
DEBUG quickshell.wm.wayland.workspace: Updated coordinates for workspace 0x...838339e0 to QList(0, 1)
DEBUG quickshell.wm.wayland.workspace: Workspace changes done <-- triggers ext_workspace_manager_v1_done()
<-- SIGSEGV
Suspected cause
In WorkspaceManager::ext_workspace_manager_v1_done() (ext_workspace.cpp:54):
void WorkspaceManager::ext_workspace_manager_v1_done() {
emit this->serverCommit(); // <-- onServerCommit runs HERE
for (auto* workspace: this->destroyedWorkspaces) delete workspace; // <-- freed AFTER
for (auto* group: this->destroyedGroups) delete group;
...
}
serverCommit() is emitted (running WindowsetManager::onServerCommit() and flushing a property update group at windowset.cpp:122) while destroyedGroups/destroyedWorkspaces still hold pointers slated for deletion. When the compositor moves a workspace to a new group and destroys the old group in the same batch, a windowset/projection bound to the destroyed group appears to be dereferenced during the property flush. On a single-output steady state this never fires; it only triggers when an output drop forces a full group teardown+rebuild in one done.
Reproducer
- niri, two outputs.
- A shell using the workspace/windowset APIs (DankMaterialShell here).
- Lock the session (or power-cycle a monitor) so niri drops an output and rebuilds the ext-workspace tree.
- Quickshell SIGSEGVs; crash dump under
~/.cache/quickshell/crashes/.
Full report.txt and log.qslog.log available on request.
SIGSEGV in WindowsetManager::onServerCommit() / endPropertyUpdateGroup() on ext-workspace tree rebuild during output disconnect (niri, multi-monitor)
Summary
Quickshell reliably SIGSEGVs when the compositor (niri) rebuilds its entire
ext-workspace-v1tree in a singledonebatch — which happens on output disconnect/reconnect, e.g. every time the session locks and the displays blank. The crash is a use-after-free: a workspace is moved to a new group while its old group is destroyed in the samedonebatch, and whenext_workspace_manager_v1_done()emitsserverCommit(),WindowsetManager::onServerCommit()dereferences state tied to the freed group while flushing the property update group.This is distinct from the known lock/output-disconnect crashes (#540, #503, #287): those crash in
QV4::QObjectWrapper::wrap()orwl_proxy_get_listener(). This one crashes inQt::endPropertyUpdateGroup()reached from the windowset / ext-workspace path, and reproduces on a build that includes the recentwayland/lockfixes (b93d613, d1760ed).Version
Built from
main@ 4b4fca3 (latest commit), Qt 6.10.2, gcc 15.2.0, RelWithDebInfo, jemalloc ON, crash handler ON.Environment
~/.cache/quickshell/crashes/over two days.Stacktrace
Log tail (the smoking gun)
The compositor rebuilds the workspace tree in one
donebatch — note workspace0x...838339e0is removed from group0x...4a03f4e0, that group is destroyed, then the same workspace is re-added to a new group0x...4a03f420, all beforeWorkspace changes done:Suspected cause
In
WorkspaceManager::ext_workspace_manager_v1_done()(ext_workspace.cpp:54):serverCommit()is emitted (runningWindowsetManager::onServerCommit()and flushing a property update group atwindowset.cpp:122) whiledestroyedGroups/destroyedWorkspacesstill hold pointers slated for deletion. When the compositor moves a workspace to a new group and destroys the old group in the same batch, a windowset/projection bound to the destroyed group appears to be dereferenced during the property flush. On a single-output steady state this never fires; it only triggers when an output drop forces a full group teardown+rebuild in onedone.Reproducer
~/.cache/quickshell/crashes/.Full
report.txtandlog.qslog.logavailable on request.