https://bugs.kde.org/show_bug.cgi?id=524908

            Bug ID: 524908
           Summary: kdeconnectd crashes in Notification::show() when a
                    notification icon download finishes after the
                    KNotification has already been closed (null QPointer
                    dereference in KNotification::sendEvent)
    Classification: Applications
           Product: kdeconnect
      Version First 26.08.0
       Reported In:
          Platform: Arch Linux
                OS: Linux
            Status: REPORTED
          Severity: crash
          Priority: NOR
         Component: common
          Assignee: [email protected]
          Reporter: [email protected]
                CC: [email protected]
  Target Milestone: ---

Created attachment 195585
  --> https://bugs.kde.org/attachment.cgi?id=195585&action=edit
Core

## Description

### SUMMARY

kdeconnectd segfaults with `KNotification::sendEvent(this=0x0)` when a
`FileTransferJob` that is downloading a phone notification's icon/image
completes after the corresponding `KNotification` has already been closed.

`Notification::m_notification` is a `QPointer<KNotification>` and the
`KNotification` is created with `KNotification::CloseOnTimeout`, so it deletes
itself when the notification server closes it, which nulls the `QPointer`.
`Notification::show()` then dereferences it without a check:

```cpp
// plugins/notifications/notification.cpp (v26.08.0, unchanged on master)
void Notification::show()
{
    m_ready = true;
    Q_EMIT ready();
    if (!m_silent) {
        m_notification->sendEvent();   // <-- m_notification can be null here
    }
}
```

`show()` is reached from the `FileTransferJob::result` lambda in
`Notification::loadIcon()`. `applyIcon()`, called on the same path, does guard
against `m_notification` being null, but `show()` does not.

### STEPS TO REPRODUCE

1. Pair an Android phone (here: Pixel 10 Pro, KDE Connect Android, protocol v8)
   with the "Receive notifications" plugin enabled on the desktop.
2. Have an app on the phone post a notification and then update it with a
   large image payload (Google Home "Person seen" camera alerts do this: the
   notification appears first, the camera snapshot arrives as an update a few
   seconds later).
3. Let the desktop copy of the notification close (timeout or dismiss) while
   the image is still being transferred.

Timing-dependent; I have hit it once in the first day of use. An 8-second
window between the notification being shown and the crash was observed.

### OBSERVED RESULT

kdeconnectd dies with SIGSEGV and, because it is started as an XDG autostart
unit, is not restarted — all KDE Connect functionality is silently gone until
the next login.

Kernel: `kdeconnectd[1852]: segfault at 10 ip 00007f77c99adde0 ... error 4 in
libKF6Notifications.so.6.29.0`

### EXPECTED RESULT

The stale icon download is ignored (or the notification is re-sent) instead of
crashing the daemon. A null check on `m_notification` in `Notification::show()`
would avoid the crash.

### SOFTWARE/OS VERSIONS

- KDE Connect: 26.08.0 (Arch package kdeconnect 26.08.0-1)
- KDE Frameworks: 6.29.0 (kcoreaddons 6.29.0-1, knotifications 6.29.0-1)
- Qt: 6.11.2 (qt6-base 6.11.2-2)
- glib2: 2.88.3-1
- Kernel: 7.1.9-arch1-2
- Session: Hyprland (Wayland), Omarchy 4.0.0.alpha
- Notification server: quickshell (org.freedesktop.Notifications), not Plasma
- Phone: Pixel 10 Pro, KDE Connect Android (protocol version 8)

### ADDITIONAL INFORMATION

Backtrace of the crashing (main) thread, symbolized with gdb against the Arch
debuginfod server. Frame 3 shows `this=0x0`. All other threads were idle in
their event loops.

```
#0  std::__uniq_ptr_impl<KNotification::Private, ...>::_M_ptr (this=0x10) at
/usr/include/c++/16/bits/unique_ptr.h:192
#1  std::unique_ptr<KNotification::Private, ...>::get (this=0x10) at
/usr/include/c++/16/bits/unique_ptr.h:491
#2  std::unique_ptr<KNotification::Private, ...>::operator-> (this=0x10) at
/usr/include/c++/16/bits/unique_ptr.h:484
#3  KNotification::sendEvent (this=0x0) at
knotifications-6.29.0/src/knotification.cpp:511
#4  QtPrivate::QSlotObjectBase::call (...) at
qtbase/src/corelib/kernel/qobjectdefs_impl.h:462
#5  doActivate<false> (sender=0x561955c9a950 [FileTransferJob], signal_index=6,
...) at qtbase/src/corelib/kernel/qobject.cpp:4393
#6  QMetaObject::activate<void, KJob*, KJob::QPrivateSignal>
(sender=0x561955c9a950, ..., local_signal_index=3, ...) at qobjectdefs.h:320
#7  KJob::result (this=0x561955c9a950, ...) at
kcoreaddons/build/src/lib/KF6CoreAddons_autogen/include/moc_kjob.cpp:475
#8  KJob::finishJob (this=0x561955c9a950, emitResult=true) at
kcoreaddons-6.29.0/src/lib/jobs/kjob.cpp:115
#9  QtPrivate::QSlotObjectBase::call (...) at qobjectdefs_impl.h:462
#10 doActivate<false> (sender=0x561955a86c50 [QNetworkReplyImpl],
signal_index=12, ...) at qobject.cpp:4393
#11 QNetworkReply::finished (this=<optimized out>) at
Network_autogen/include/moc_qnetworkreply.cpp:330
#12 QNetworkReplyImplPrivate::finished (this=0x561955f01930) at
qtbase/src/network/access/qnetworkreplyimpl.cpp:629
#13 QNetworkAccessBackend::finished (this=0x561955e51e60) at
qtbase/src/network/access/qnetworkaccessbackend.cpp:746
#14 QNetworkAccessFileBackend::uploadReadyReadSlot (this=0x561955e51e60) at
qtbase/src/network/access/qnetworkaccessfilebackend.cpp:169
#15 doActivate<false> (sender=0x56195598c220, signal_index=3, ...) at
qobject.cpp:4405
#16 QtPrivate::QSlotObjectBase::call (...) at qobjectdefs_impl.h:462
#17 doActivate<false> (sender=0x5619556ad700, signal_index=3, ...) at
qobject.cpp:4393
#18 QtPrivate::QSlotObjectBase::call (...) at qobjectdefs_impl.h:462
#19 doActivate<false> (sender=0x561955d8ca70, signal_index=8, ...) at
qobject.cpp:4393
#20 doActivate<false> (sender=0x5619559497f0, signal_index=8, ...) at
qobject.cpp:4405
#21 QAbstractSocket::disconnectFromHost (this=0x5619559497f0) at
qtbase/src/network/socket/qabstractsocket.cpp:2737
#22 QAbstractSocketPrivate::canReadNotification (this=<optimized out>) at
qabstractsocket.cpp:647
#23 QAbstractSocketEngine::readNotification (this=<optimized out>) at
qabstractsocketengine.cpp:123
#24 QReadNotifier::event (this=<optimized out>, e=<optimized out>) at
qnativesocketengine.cpp:1247
#25 QApplicationPrivate::notify_helper(QObject*, QEvent*) () from
/usr/lib/libQt6Widgets.so.6
#26 QCoreApplication::notifyInternal2 (receiver=0x5619558ee370, ...) at
qcoreapplication.cpp:1126
#27 QCoreApplication::sendEvent (...) at qcoreapplication.cpp:1571
#28 socketNotifierSourceDispatch (source=0x56195527d620) at
qeventdispatcher_glib.cpp:78
#29 g_main_dispatch (context=0x7f77bc000f00) at ../glib/glib/gmain.c:3591
#30 g_main_context_iteration () from /usr/lib/libglib-2.0.so.0
#31 QEventDispatcherGlib::processEvents(...) from /usr/lib/libQt6Core.so.6
#32 QEventLoop::exec(...) from /usr/lib/libQt6Core.so.6
#33 QCoreApplication::exec() from /usr/lib/libQt6Core.so.6
#34 main (kdeconnectd)
```

The sender in frame 5 was confirmed to be a `FileTransferJob` by reading its
vtable pointer in the core (`vtable for FileTransferJob + 16` in
libkdeconnectcore.so.26); the sender in frame 10 is a `QNetworkReplyImpl`
(the `file://` PUT that FileTransferJob uses to write the payload to disk).
Frames 21-22 are the payload socket from the phone reaching end-of-stream,
which is what completed the transfer.

Registers at the fault: `rdi = 0x0` (the `this` pointer), faulting instruction
`mov 0x10(%rdi),%rax` (loading `d` from a null KNotification).

Sequence observed on this machine:

- 14:31:32 — the notification from the phone ("Home" app, camera alert) was
  displayed on the desktop without an image.
- 14:31:40 — kdeconnectd crashed while finishing a payload transfer from the
  phone, in the `FileTransferJob::result` handler for that notification's icon.

Not related to earlier reports 447059 (crash in Device::reloadPlugins) or
410397 (fixed in 1.3.5); the stack is different.

Proposed fix (one line):

```cpp
void Notification::show()
{
    m_ready = true;
    Q_EMIT ready();
    if (!m_silent && m_notification) {
        m_notification->sendEvent();
    }
}```

-- 
You are receiving this mail because:
You are watching all bug changes.

Reply via email to