Bug 519302 - plasma-login-greeter: SIGSEGV in QWindow::screen() via dangling QWindowPrivate pointer when Wayland compositor replaces pending screen output during startup
Summary: plasma-login-greeter: SIGSEGV in QWindow::screen() via dangling QWindowPrivat...
Status: RESOLVED FIXED
Alias: None
Product: plasma-login-manager
Classification: Plasma
Component: general (other bugs)
Version First Reported In: 6.6.4
Platform: Fedora RPMs Linux
: VHI grave
Target Milestone: ---
Assignee: Plasma Bugs List
URL: https://discuss.kde.org/t/plasmalogin...
Keywords: qt6, wayland-only
Depends on:
Blocks:
 
Reported: 2026-04-23 11:27 UTC by Lendy
Modified: 2026-04-24 20:20 UTC (History)
6 users (show)

See Also:
Latest Commit:
Version Fixed/Implemented In: 6.6.5
Sentry Crash Report:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Lendy 2026-04-23 11:27:02 UTC
reference:
https://discuss.kde.org/t/plasmaloginmanager-blank-screen-only-on-first-cold-boot/46087/2

```
TITLE
plasma-login-greeter: SIGSEGV in QWindow::screen() via dangling QWindowPrivate
pointer when Wayland compositor replaces pending screen output during startup

COMPONENT
plasma-login-manager

VERSION
6.6.4-1.fc43

SEVERITY
Critical -- process terminates on every cold boot

---

ENVIRONMENT

  OS:         Fedora 43, x86_64 (kernel 6.19.11-200.fc43.x86_64)
  Package:    plasma-login-manager-6.6.4-1.fc43.x86_64
  Binary:     /usr/libexec/plasma-login-greeter
  Build ID:   ec6b236da37d731156d601f1f4d19fae54a14ffb
  Qt:         6.10.3 (libQt6Gui.so.6, libQt6WaylandClient.so.6)
  Session:    Wayland
  Display:    Single monitor
  User:       plasmalogin (UID 967, GID 967)
  Unit:       user@967.service -> user unit plasma-login.service

---

OBSERVABLE BEHAVIOR

On every cold boot, plasma-login-greeter terminates with SIGSEGV approximately
140 seconds into the boot sequence. The greeter does not display. The crash
is visible in dmesg:

  [140.093515] plasma-login-gr[2275]: segfault at 8e ip 00007ff359d532e4
               sp 00007fff0cb6e7a8 error 4 in
               libQt6Gui.so.6.10.3[1532e4,7ff359c00000+795000]
               likely on CPU 1 (core 1, socket 0)

  fault address: 0x8e (error 4 = user-mode read from unmapped address)
  faulting IP:   libQt6Gui.so.6 + 0x1532e4 = QWindow::screen()

---

CRASH DETAILS

  Signal:       SIGSEGV
  Fault addr:   0x8e (0xe == 14 == likely vtable offset dereference on
                freed/null object, classic use-after-free fingerprint)
  Crash site:   QWindow::screen() -- qwindow.cpp:2261

Source at crash point (qt6-qtbase-6.10.3):

  // qwindow.cpp:2261
  QScreen *QWindow::screen() const
  {
      Q_D(const QWindow);
      return d->parentWindow ? d->parentWindow->screen()   // <-- CRASH
                             : d->topLevelScreen.data();
  }

  d = 0xe

The d_func() macro expands to reinterpret_cast<const QWindowPrivate*>(d_ptr.d).
A value of 0xe for d_ptr.d means the QWindow object's d-pointer is corrupt or
freed. Dereferencing 0xe + offset of parentWindow (0x80 in QWindowPrivate)
yields 0x8e, which matches the fault address exactly.

---

FULL BACKTRACE -- THREAD 1 (crashing thread, LWP 2275)

  #0  QWindow::screen (this=<optimized out>)
          at qwindow.cpp:2261
          d = 0xe                              <-- QWindowPrivate* is freed/corrupt

  #1  LoginGreeter::createWindowForScreen(QScreen*)::{lambda(QScreen*)#1}
          ::operator()(QScreen*) const
          at src/frontend/greeter/main.cpp:56
          window = 0x55b1c5a67200              <-- captured QQuickWindow* (destroyed)
          screenRemoved = 0x7ff2f46dab80       <-- the removed QScreen*

  #2-#6  Qt slot dispatch machinery
          (QtPrivate::FunctorCall, QCallableObject::impl, QSlotObjectBase::call)

  #7  QtPrivate::QSlotObjectBase::call
          at qobjectdefs_impl.h:461
          this = 0x55b1c59fe540

  #8  doActivate<false>
          at qobject.cpp:4273
          sender    = 0x7fff0cb6f300
          receiver  = 0x7fff0cb6f340
          signal_index (screenRemoved) = 10

  #9  QMetaObject::activate
          at qobject.cpp:4333

  #10 QGuiApplication::screenRemoved
          at moc_qguiapplication.cpp:313
          _t1 = 0x7ff2f46dab80               <-- the QScreen being removed

  #11 QWindowSystemInterface::handleScreenRemoved
          at qcoreapplication.h:97
          platformScreen = 0x7ff2f45ec660
          screen         = 0x7ff2f46dab80
          newPrimaryScreen = 0x55b1c5993680

  #12 QtWaylandClient::QWaylandDisplay::handleScreenInitialized
          at qwaylanddisplay.cpp:617
          (note: screen initialization triggering screen *removal* -- see analysis)

  #13 QtWaylandClient::QWaylandScreen::maybeInitialize
          at qwaylandscreen.cpp:82

  #14-#22  Wayland dispatch chain:
          ffi_call_unix64 / ffi_call_int / ffi_call
          wl_closure_invoke (libwayland-client.so.0)
          dispatch_event / wl_display_dispatch_queue_pending
          QtWaylandClient::EventThread::readAndDispatchEvents
          QObject::event / QCoreApplication::notifyInternal2
          QCoreApplicationPrivate::sendPostedEvents
          postEventSourceDispatch / GLib main context dispatch

  #23 main
          at src/frontend/greeter/main.cpp:136

---

ROOT CAUSE ANALYSIS

1. WAYLAND OUTPUT NEGOTIATION DURING STARTUP

   The Wayland protocol requires multiple round-trips before a wl_output
   (screen) is considered fully initialized. Qt's Wayland platform plugin
   tracks this with a "pending screens" list and a set of required events
   (e.g., wl_output.geometry, wl_output.mode, wl_output.done).

   QWaylandScreen::maybeInitialize() (qwaylandscreen.cpp:73-82) checks
   whether all required events have been received. Once they have,
   QWaylandDisplay::handleScreenInitialized() is called.

   In handleScreenInitialized() (qwaylanddisplay.cpp:617), Qt may determine
   that the newly initialized output duplicates or replaces an existing
   screen (e.g., a placeholder created before geometry was known). In that
   case it calls QWindowSystemInterface::handleScreenRemoved() to remove the
   superseded screen before promoting the new one.

   This sequence fires the QGuiApplication::screenRemoved signal during the
   compositor's initial output negotiation phase -- even on single-monitor
   systems. It is not a multi-monitor scenario; it is a normal part of
   single-monitor Wayland startup on this hardware/compositor combination.

2. THE GREETER'S screenRemoved HANDLER

   In main.cpp, createWindowForScreen() creates a QQuickWindow per screen
   and connects a lambda to QGuiApplication::screenRemoved:

     // main.cpp:56 (reconstructed from backtrace symbols)
     connect(app, &QGuiApplication::screenRemoved, greeter,
             [window](QScreen *screenRemoved) {
                 if (window->screen() == screenRemoved) {   // <-- CRASH
                     // presumably: close or move the window
                 }
             });

   The lambda captures window (a raw QQuickWindow*) by value. When
   screenRemoved fires for the placeholder screen during output negotiation,
   Qt may have already destroyed the QQuickWindow associated with that
   placeholder (QWindowSystemInterface::handleScreenRemoved triggers
   QWindow destruction for windows on the removed screen). By the time the
   lambda executes, window is a dangling pointer. Calling window->screen()
   dereferences d_ptr.d = 0xe and crashes.

3. WHY d = 0xe

   QObject stores its private data at d_ptr.d. When a QObject subclass is
   destroyed, the d-pointer is not explicitly zeroed. If the allocation is
   reclaimed and partially reused before the dangling pointer is accessed,
   d_ptr.d will contain whatever the allocator wrote there -- in this case
   0xe, producing a fault at 0xe + offsetof(QWindowPrivate, parentWindow)
   = 0x8e, which matches the kernel's reported fault address exactly.

---

SUGGESTED FIX

Use QPointer<QQuickWindow> to safely detect window destruction before
dereferencing:

  // main.cpp -- createWindowForScreen()
  QPointer<QQuickWindow> safeWindow = window;

  connect(app, &QGuiApplication::screenRemoved, greeter,
          [safeWindow](QScreen *screenRemoved) {
              if (!safeWindow)           // window was already destroyed
                  return;
              if (safeWindow->screen() == screenRemoved) {
                  // handle screen removal
              }
          });

QPointer is automatically zeroed when the pointed-to QObject is destroyed,
making the null check safe without requiring manual lifecycle management.

Additionally, the connection should be disconnected or the lambda should
become a no-op once the greeter has finished initializing, to avoid acting
on screen topology changes that arrive after startup is complete.

---

WORKAROUND

None currently known that does not involve modifying the binary or compositor
configuration. The crash occurs on every cold boot; warm reboots may or may
not reproduce depending on compositor state at startup.

---

REPRODUCTION

  1. Cold boot on affected hardware (confirmed single-monitor Wayland system)
  2. Observe greeter fail to display
  3. Confirm via: journalctl -b | grep plasma-login-greeter
                  dmesg | grep plasma-login-gr
                  coredumpctl list

  Approximate time to crash from boot: 140 seconds (varies by hardware).

---

CORE DUMP

  Captured via systemd-coredump.
  coredumpctl info 2275

  Storage: /var/lib/systemd/coredump/
           core.plasma-login-gr.967.<bootid>.2275.<timestamp>.zst
  Size:    8.4M (compressed)
```
Comment 1 Lendy 2026-04-23 12:40:06 UTC
cc: jgrulich@redhat.com at the Fedora KDE sig
Comment 2 David Edmundson 2026-04-23 13:40:49 UTC
Urgh, I see it:

You would need to

create screen A
create screen B
delete screen B (this is fine, but the connect is not bound and now the captured window is dangling)
delete screen A (this now crashes evaluating the lambda set up for screen B)

Using QPointer is a bad solution, we should scope the connect.

*or* just pivot to letting layer shell dismiss the window and deleting on that.
Comment 3 Bug Janitor Service 2026-04-23 14:02:01 UTC
A possibly relevant merge request was started @ https://invent.kde.org/plasma/plasma-login-manager/-/merge_requests/128
Comment 4 Oliver Beard 2026-04-23 15:13:19 UTC
Git commit d4da6b79377756796081a19c1f628263b6e3a442 by Oliver Beard, on behalf of David Edmundson.
Committed on 23/04/2026 at 14:40.
Pushed by davidedmundson into branch 'master'.

greeter: Avoid leaking connections for window destruction

When a screen is removed we delete the window, because the connect was scoped
to the greeter this connect lingered with the captured 'window' pointer dangling.

We also dropped the geometry connection as this is done implicitly
via layer-shell anyway.

M  +1    -4    src/frontend/greeter/main.cpp
M  +0    -4    src/frontend/wallpaper/wallpaperapp.cpp

https://invent.kde.org/plasma/plasma-login-manager/-/commit/d4da6b79377756796081a19c1f628263b6e3a442
Comment 5 Lendy 2026-04-23 19:03:28 UTC
Hi.
Thanks for the quick turnaround on this!
I see it's committed to `master`.
Looking at 
  https://community.kde.org/Schedules/Plasma_6#Future_releases
I see 
  6.6.5 	Bugfix Release 	Tue 2026-05-12
I'm not sure where to look to check when this fix gets into Distro -- Fedora in my case; v43 right now, v44 any day now.
Does this project push from master>6.6.5 , and then the Distro(s) pick that up on regular update schedule?
Or does the Distro need to drive it?  Maybe with a ping from me?
Comment 6 Oliver Beard 2026-04-23 19:10:23 UTC
As I understand it Fedora are quick to pick up Plasma bugfix releases. Distros can cherry-pick commits before they land in a release.

I'll recommend that we ask distros to cherry-pick this sooner after also looking at the wallpaper, as reports that users are left with a black screen implies that both are deficient.
Comment 7 Oliver Beard 2026-04-23 19:34:42 UTC
Git commit b967468299bd28bffcfaad04cc4ee0485a80616e by Oliver Beard.
Committed on 23/04/2026 at 19:34.
Pushed by olib into branch 'Plasma/6.6'.

greeter: Avoid leaking connections for window destruction

When a screen is removed we delete the window, because the connect was scoped
to the greeter this connect lingered with the captured 'window' pointer dangling.

We also dropped the geometry connection as this is done implicitly
via layer-shell anyway.


(cherry picked from commit d4da6b79377756796081a19c1f628263b6e3a442)

Co-authored-by: David Edmundson <kde@davidedmundson.co.uk>

M  +1    -4    src/frontend/greeter/main.cpp
M  +0    -4    src/frontend/wallpaper/wallpaperapp.cpp

https://invent.kde.org/plasma/plasma-login-manager/-/commit/b967468299bd28bffcfaad04cc4ee0485a80616e
Comment 8 Lendy 2026-04-23 22:38:32 UTC
(In reply to Oliver Beard from comment #6)

Hi.
Ok, Thanks. It sounds like it gets done pretty much on auto-pilot.
Just in case, I see in the Fedora pkg listing
https://src.fedoraproject.org/rpms/plasma-login-manager
it's mainted by 'ngompa'.
I don't see them on the list here.
I guess cc: for info won't hurt.