https://bugs.kde.org/show_bug.cgi?id=524918
Bug ID: 524918
Summary: Crash (use-after-free) when the device list request
completes after the Devices settings page is destroyed
Classification: Applications
Product: NeoChat
Version First git master
Reported In:
Platform: Flatpak
OS: Linux
Status: REPORTED
Severity: crash
Priority: NOR
Component: General
Assignee: [email protected]
Reporter: [email protected]
CC: [email protected], [email protected]
Target Milestone: ---
SUMMARY
DevicesModel::fetchDevices() attaches its continuation to the GetDevicesJob
future using the overload of onResult() that takes no context object, and
captures a raw "this":
// src/settings/models/devicesmodel.cpp:31
m_connection->callApi<GetDevicesJob>().onResult([this](const auto &job) {
beginResetModel();
m_devices = job->devices();
endResetModel();
Q_EMIT countChanged();
});
DevicesModel is instantiated inside DevicesPage.qml:
// src/settings/DevicesPage.qml:26
property DevicesModel devicesModel: DevicesModel {
connection: root.connection
}
so it is owned by the page and destroyed when the page is closed. Nothing
cancels or disconnects the continuation, so when the request completes after
that point the lambda runs and calls beginResetModel() on freed memory.
fetchDevices() is also called from the Connection::sessionVerified and
Connection::finishedQueryingKeys handlers, so on a busy account there can be
several requests in flight over the life of the page, and the window in which
closing it is fatal is not small.
Crashed twice in two days on two different nightly builds (281d7340 on
2026-08-28, 763d91e3 on 2026-08-29). Not deliberately reproduced yet.
STEPS TO REPRODUCE
1. Open Settings -> Security & Safety (or any page hosting DevicesPage).
2. Close it immediately, before the device list has rendered.
3. Repeat, ideally on an account where finishedQueryingKeys fires often, so
extra GetDevicesJobs are in flight.
Slow network or a large account widens the window considerably.
OBSERVED RESULT
SIGSEGV. The application exits; nothing is written to the log first.
EXPECTED RESULT
The stale reply is discarded and the application keeps running.
BACKTRACE
#0 operator<<(QDebug, QObject const*)+81 libQt6Core.so.6.11.1
#1 (QAbstractItemModel::beginResetModel) libQt6Core.so.6.11.1
#2 neochat+0x718ab7
DevicesModel::fetchDevices()::<lambda>
#3 neochat+0x719191
#4 QFutureInterfaceBase::runContinuation() libQt6Core.so.6.11.1
#5 neochat+0x71bd18
#6 neochat+0x71bfb0
#7 QFutureInterfaceBase::runContinuation() libQt6Core.so.6.11.1
#8 neochat+0xa1102c
#9 (QMetaCallEvent dispatch) libQt6Core.so.6.11.1
#10 QObject::event(QEvent*) libQt6Core.so.6.11.1
#11 QApplicationPrivate::notify_helper libQt6Widgets.so.6.11.1
#12 QCoreApplication::notifyInternal2 libQt6Core.so.6.11.1
#13 QCoreApplicationPrivate::sendPostedEvents libQt6Core.so.6.11.1
... QEventDispatcherGlib::processEvents / QEventLoop::exec /
QCoreApplication::exec / main
The nightly build is stripped, so frames #2-#8 are given as offsets. They are
identified from the disassembly of the shipped binary rather than guessed:
718aab: mov %r15,-0x128(%rbp)
718ab2: call QAbstractItemModel::beginResetModel@plt <-- return address
is frame #2, 0x718ab7
718ab7: mov %r15,%rsi
718abd: call <QString ctor>
718ac2: mov $0x7,%edx
718acd: lea 0x315f79c(%rip),%rcx # "devices"
718ad4: call QJsonObject::value(QLatin1String)@plt
...
718c20: call QAbstractItemModel::endResetModel@plt
718c37: call QMetaObject::activate(...)@plt
beginResetModel() ... parse the "devices" key ... endResetModel() ... emit -
that is the body of the fetchDevices() lambda, reached through two nested
QFuture continuations.
WHY IT DIES INSIDE qWarning
The crashing instruction is a virtual dispatch on a garbage vtable pointer:
=> 0x...18481 <operator<<(QDebug, QObject const*)+81>: call *0x18(%rax)
rax = 0x0020002000410049
The object's first eight bytes are UTF-16 text (I, A, U+2000, U+2000), i.e. the
freed block has already been recycled into string data. beginResetModel() reads
a garbage d->resetting, takes its "called twice without endResetModel()"
warning branch, and streams "this" to qWarning(), which dereferences the
recycled vtable pointer.
The day before, on nightly 281d7340, the same path crashed with frame #0 at
address 0x0 - the same use-after-free landing on a zeroed slot instead of
recycled text. So the operator<< frame is where it lands, not where the bug is:
any use of the destroyed model would be equally fatal.
SUGGESTED FIX
JobHandle::onResult() has a context-taking overload, documented in
Quotient/jobs/jobhandle.h as usable "as the object for a slot-like member
function in QObject::connect() fashion":
m_connection->callApi<GetDevicesJob>().onResult(this, [this](const auto
&job) { ... });
The same file already uses the guarded form elsewhere (devicesmodel.cpp:109
passes "this" to then()), so this looks like an oversight rather than a
deliberate choice.
More broadly, master currently has about 24 onResult(/then(/onFailure(
continuations that take a bare lambda capturing "this", against about 10 that
pass a context object. Most of the unguarded ones sit on long-lived objects
(NeoChatConnection, NeoChatRoom) where it rarely matters, but a few are on
short-lived QML-owned objects and have the same shape as this one, e.g.:
src/settings/models/devicesmodel.cpp:31 (this crash)
src/login/registration.cpp:283
src/libneochat/pollblock.cpp:46
src/app/models/commonroomsmodel.cpp:120
Passing the context object in all of them would close the class of bug rather
than this instance.
SOFTWARE/OS VERSIONS
NeoChat 26.08.0 master, KDE nightly flatpak (org.kde.neochat//master, commit
763d91e3, built 2026-08-28)
libQuotient dev (0.10, matrix-rust-sdk-crypto) statically linked
Qt 6.11.1, org.kde.Platform 6.11
Fedora 44, KDE Plasma 6.7.4, Wayland, kernel 7.1.10-200
Homeserver: Synapse, ~826 joined rooms
ADDITIONAL INFORMATION
The nightly is stripped and the nightly repository keeps only the newest
org.kde.neochat.Debug commit, which no longer matches the binary that crashed,
so the analysis above was done against the shipped binary and the runtime's
exported symbols rather than with DWARF. Happy to provide the raw core dumps or
further detail on request.
--
You are receiving this mail because:
You are watching all bug changes.