Bug 516937 - plasmashell crashes at startup: dangling pointer in m_waitingPanels causes SIGSEGV in isScreenUiReady()
Summary: plasmashell crashes at startup: dangling pointer in m_waitingPanels causes SI...
Status: RESOLVED FIXED
Alias: None
Product: plasmashell
Classification: Plasma
Component: Startup process (other bugs)
Version First Reported In: 6.6.1
Platform: NixOS Linux
: NOR crash
Target Milestone: 1.0
Assignee: Plasma Bugs List
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2026-03-01 21:53 UTC by Dobry
Modified: 2026-03-03 20:29 UTC (History)
2 users (show)

See Also:
Latest Commit:
Version Fixed/Implemented In: 6.6.3
Sentry Crash Report: https://crash-reports.kde.org/organizations/kde/issues/113276/


Attachments
Patch attached. (2.08 KB, patch)
2026-03-01 21:53 UTC, Dobry
Details
v2: add checkAllDesktopsUiReady() hunk and fix panelContainmentDestroyed() guard (2.18 KB, patch)
2026-03-01 22:16 UTC, Dobry
Details
v2.1: fix hunk headers — correct panelContainmentDestroyed() line counts and add missing checkAllDesktopsUiReady() hunk (2.11 KB, patch)
2026-03-01 22:28 UTC, Dobry
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Dobry 2026-03-01 21:53:45 UTC
Created attachment 190251 [details]
Patch attached.

plasmashell crashes at startup on multi-monitor setups. The crash is
100% reproducible on cold login and affects at least 6.6.0 and 6.6.1.

== Crash trace (Thread 1) ==

#4  Plasma::Containment::lastScreen() const  [libPlasma.so.7]
#5  ShellCorona::isScreenUiReady(int)
#6  ShellCorona::checkAllDesktopsUiReady()
#7  void doActivate<false>(QObject*, int, void**)  [libQt6Core]
#8  Plasma::Containment::uiReadyChanged(bool)
#9  Plasma::AppletPrivate::setUiReady()
#10 Plasma::Applet::flushPendingConstraintsEvents()
#11 QObject::event(QEvent*)
#12 ... QTimerInfoList::activateTimers() ...

== Root cause ==

m_waitingPanels holds raw Plasma::Containment* pointers. Between
load() populating the list and createWaitingPanels() running (after a
250 ms timer), those pointers have no lifetime guard.

checkAllDesktopsUiReady() can fire before the timer via uiReadyChanged
from a desktop applet. It calls isScreenUiReady(), which iterates
m_waitingPanels and calls cont->lastScreen() on each entry. If any
containment was freed in the meantime, the qobject_cast<Containment*>
(parent()) inside lastScreen() dereferences a corrupted vtable → SIGSEGV
(observed PC = 0x7).

The old early guard (connecting QObject::destroyed in the constructor for
panel-type containments) was removed in commit 086ff5d8, which created
this regression. That commit reasoned that createWaitingPanels() already
connects destroyed — but it does so only after successfully creating a
PanelView, which is too late.

Commit 00bd19ec introduced the vulnerable window; 086ff5d8 removed the
guard that covered it.

== Fix ==

Change m_waitingPanels from QList<Plasma::Containment*> to
QList<QPointer<Plasma::Containment>>. QPointer auto-nulls when the
object is destroyed, eliminating the dangling-pointer window entirely:

- createWaitingPanels() calls removeAll(nullptr) at entry to purge any
  freed containments, and no longer needs to connect destroyed.
- panelContainmentDestroyed() no longer needs a m_waitingPanels branch;
  it returns early if the containment has no panel view.
- isScreenUiReady() null-checks each QPointer before dereferencing.

Patch attached.

== Environment ==
- KDE Plasma 6.6.1, NixOS, dual-monitor (Wayland)
- Reproducible on every cold login
- GPU: irrelevant (crash is in Qt signal dispatch, not rendering)
Comment 1 Dobry 2026-03-01 22:16:21 UTC
Created attachment 190252 [details]
v2: add checkAllDesktopsUiReady() hunk and fix panelContainmentDestroyed() guard
Comment 2 Dobry 2026-03-01 22:17:08 UTC
Updated patch attached (v2).
After further review, the fix has one additional change vs. the original
description:
- checkAllDesktopsUiReady() also calls m_waitingPanels.removeAll(nullptr)
  before the isEmpty() check, preventing a spurious timer start if all
  remaining entries were already freed.
- panelContainmentDestroyed() replaces the old m_waitingPanels branch with
  if (!m_panelViews.contains(cont)) return; — keeping the early-return
  structure but guarding on m_panelViews instead. This correctly handles
  both the startup case (no PanelView yet → QPointer handles cleanup) and
  the runtime case (PanelView exists → fall through to cleanup it).
The original description's wording of the second bullet was slightly
misleading. The "destroyed" connect in createWaitingPanels() is also kept
(not removed) — it is still needed for runtime m_panelViews cleanup.
All four changed sites: shellcorona.h, createWaitingPanels(),
checkAllDesktopsUiReady(), panelContainmentDestroyed(), isScreenUiReady().
Comment 3 Bug Janitor Service 2026-03-01 22:25:59 UTC
A possibly relevant merge request was started @ https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/6361
Comment 4 Bug Janitor Service 2026-03-01 22:26:01 UTC
A possibly relevant merge request was started @ https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/6361
Comment 5 Dobry 2026-03-01 22:28:38 UTC
Created attachment 190253 [details]
v2.1: fix hunk headers — correct panelContainmentDestroyed() line counts and add missing checkAllDesktopsUiReady() hunk
Comment 6 Nate Graham 2026-03-02 18:00:46 UTC
Nice, is this the same as Bug 477941?
Comment 7 Dobry 2026-03-02 18:25:00 UTC
(In reply to Nate Graham from comment #6)
> Nice, is this the same as Bug 477941?

I'm not sure, but by the description and backtrace of Bug 477941 it looks like a different issue. 
The backtrace shows a Qt assert failure on qobject_cast<PanelView*>(panel) inside a lambda in shellcorona.cpp, triggered when a PanelView is destroyed while that lambda is still pending.
This bug (516937) crashes in isScreenUiReady() on a freed Plasma::Containment* in m_waitingPanels, before any PanelView is created.
Comment 8 Dobry 2026-03-03 20:02:57 UTC
Git commit 0cc554a33cd496569f0fb1c9ba3a3f9261e627b7 by Dobry Nikolov.
Committed on 03/03/2026 at 15:05.
Pushed by ngraham into branch 'master'.

shell: restore early destroyed guard for panel containments

Commit 086ff5d8139eed36c25b3a7417f0eed8ec223739 removed a
containmentAdded lambda from the constructor that connected
QObject::destroyed -> panelContainmentDestroyed for every panel
containment as soon as it was created. It reasoned that
createWaitingPanels() already connects this signal, making it
redundant.

However, createWaitingPanels() only connects destroyed *after*
successfully creating a PanelView for the containment. If a containment
is freed before that point (e.g. its plugin is missing and libplasma
issues deleteLater()), panelContainmentDestroyed is never connected,
m_waitingPanels retains a dangling pointer, and the next dereference
(e.g. in isScreenUiReady()) crashes with SIGSEGV.

Restore the constructor connect so the guard is in place from the
moment any panel containment is added, with no window of exposure.
The connect inside createWaitingPanels() is now truly redundant
and is removed.

M  +6    -2    shell/shellcorona.cpp

https://invent.kde.org/plasma/plasma-workspace/-/commit/0cc554a33cd496569f0fb1c9ba3a3f9261e627b7
Comment 9 Dobry 2026-03-03 20:03:05 UTC
Git commit 7308d3153f5e9ee8607f89005aa05c2fd4f07dd6 by Dobry Nikolov.
Committed on 03/03/2026 at 15:05.
Pushed by ngraham into branch 'master'.

shellcorona: use QPointer for m_waitingPanels to futureproof against dangling pointers

Change m_waitingPanels to QList<QPointer<Plasma::Containment>> so that
any freed containment pointer auto-nulls in place rather than dangling.

- createWaitingPanels(): call removeAll(nullptr) at entry to discard
  any auto-nulled entries before iterating.
- checkAllDesktopsUiReady(): same removeAll(nullptr) before isEmpty()
  to avoid spuriously starting the timer for nulled entries.
- panelContainmentDestroyed(): the m_waitingPanels branch is now
  redundant since QPointer auto-nulls it; guard on m_panelViews
  instead to keep the PanelView cleanup path intact.
- isScreenUiReady(): null-check each QPointer before dereferencing.

M  +7    -6    shell/shellcorona.cpp
M  +1    -1    shell/shellcorona.h

https://invent.kde.org/plasma/plasma-workspace/-/commit/7308d3153f5e9ee8607f89005aa05c2fd4f07dd6
Comment 10 Nate Graham 2026-03-03 20:29:20 UTC
Git commit 8b4500d7acc699ec72bbac4aeed30db3e168a76d by Nate Graham, on behalf of Dobry Nikolov.
Committed on 03/03/2026 at 20:04.
Pushed by ngraham into branch 'Plasma/6.6'.

shellcorona: use QPointer for m_waitingPanels to futureproof against dangling pointers

Change m_waitingPanels to QList<QPointer<Plasma::Containment>> so that
any freed containment pointer auto-nulls in place rather than dangling.

- createWaitingPanels(): call removeAll(nullptr) at entry to discard
  any auto-nulled entries before iterating.
- checkAllDesktopsUiReady(): same removeAll(nullptr) before isEmpty()
  to avoid spuriously starting the timer for nulled entries.
- panelContainmentDestroyed(): the m_waitingPanels branch is now
  redundant since QPointer auto-nulls it; guard on m_panelViews
  instead to keep the PanelView cleanup path intact.
- isScreenUiReady(): null-check each QPointer before dereferencing.
(cherry picked from commit 7308d3153f5e9ee8607f89005aa05c2fd4f07dd6)

M  +7    -6    shell/shellcorona.cpp
M  +1    -1    shell/shellcorona.h

https://invent.kde.org/plasma/plasma-workspace/-/commit/8b4500d7acc699ec72bbac4aeed30db3e168a76d
Comment 11 Nate Graham 2026-03-03 20:29:28 UTC
Git commit db08d2f00951ad7a0ad604fb03cf5d0b6e2c057b by Nate Graham, on behalf of Dobry Nikolov.
Committed on 03/03/2026 at 20:03.
Pushed by ngraham into branch 'Plasma/6.6'.

shell: restore early destroyed guard for panel containments

Commit 086ff5d8139eed36c25b3a7417f0eed8ec223739 removed a
containmentAdded lambda from the constructor that connected
QObject::destroyed -> panelContainmentDestroyed for every panel
containment as soon as it was created. It reasoned that
createWaitingPanels() already connects this signal, making it
redundant.

However, createWaitingPanels() only connects destroyed *after*
successfully creating a PanelView for the containment. If a containment
is freed before that point (e.g. its plugin is missing and libplasma
issues deleteLater()), panelContainmentDestroyed is never connected,
m_waitingPanels retains a dangling pointer, and the next dereference
(e.g. in isScreenUiReady()) crashes with SIGSEGV.

Restore the constructor connect so the guard is in place from the
moment any panel containment is added, with no window of exposure.
The connect inside createWaitingPanels() is now truly redundant
and is removed.
(cherry picked from commit 0cc554a33cd496569f0fb1c9ba3a3f9261e627b7)

M  +6    -2    shell/shellcorona.cpp

https://invent.kde.org/plasma/plasma-workspace/-/commit/db08d2f00951ad7a0ad604fb03cf5d0b6e2c057b