Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package kf6-kservice for openSUSE:Factory checked in at 2026-03-04 21:02:53 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/kf6-kservice (Old) and /work/SRC/openSUSE:Factory/.kf6-kservice.new.561 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "kf6-kservice" Wed Mar 4 21:02:53 2026 rev:25 rq:1335919 version:6.23.1 Changes: -------- --- /work/SRC/openSUSE:Factory/kf6-kservice/kf6-kservice.changes 2026-02-16 13:06:37.019301171 +0100 +++ /work/SRC/openSUSE:Factory/.kf6-kservice.new.561/kf6-kservice.changes 2026-03-04 21:03:27.432068429 +0100 @@ -1,0 +2,8 @@ +Mon Mar 2 21:21:54 UTC 2026 - Christophe Marin <[email protected]> + +- Update to 6.23.1: + * ksycocafactory: guard against integer underflow + * ksycocafactory: do not crash when failing to find a factory stream + * ksycoca: do not allow for recursive repairs (kde#516426) + +------------------------------------------------------------------- Old: ---- kservice-6.23.0.tar.xz kservice-6.23.0.tar.xz.sig New: ---- kservice-6.23.1.tar.xz kservice-6.23.1.tar.xz.sig ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ kf6-kservice.spec ++++++ --- /var/tmp/diff_new_pack.K2T9lX/_old 2026-03-04 21:03:31.768247659 +0100 +++ /var/tmp/diff_new_pack.K2T9lX/_new 2026-03-04 21:03:31.784248321 +0100 @@ -20,10 +20,11 @@ %define rname kservice # Full KF6 version (e.g. 6.23.0) -%{!?_kf6_version: %global _kf6_version %{version}} +# %%{!?_kf6_version: %%global _kf6_version %%{version}} +%global _kf6_version 6.23.0 %bcond_without released Name: kf6-kservice -Version: 6.23.0 +Version: 6.23.1 Release: 0 Summary: Plugin framework for desktop services License: LGPL-2.1-or-later ++++++ kservice-6.23.0.tar.xz -> kservice-6.23.1.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kservice-6.23.0/CMakeLists.txt new/kservice-6.23.1/CMakeLists.txt --- old/kservice-6.23.0/CMakeLists.txt 2026-02-06 13:16:56.000000000 +0100 +++ new/kservice-6.23.1/CMakeLists.txt 2026-03-02 16:30:35.000000000 +0100 @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.27) -set(KF_VERSION "6.23.0") # handled by release scripts +set(KF_VERSION "6.23.1") # handled by release scripts set(KF_DEP_VERSION "6.23.0") # handled by release scripts project(KService VERSION ${KF_VERSION}) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kservice-6.23.0/autotests/kservicetest.cpp new/kservice-6.23.1/autotests/kservicetest.cpp --- old/kservice-6.23.0/autotests/kservicetest.cpp 2026-02-06 13:16:56.000000000 +0100 +++ new/kservice-6.23.1/autotests/kservicetest.cpp 2026-03-02 16:30:35.000000000 +0100 @@ -1,6 +1,6 @@ /* SPDX-FileCopyrightText: 2006 David Faure <[email protected]> - SPDX-FileCopyrightText: 2022 Harald Sitter <[email protected]> + SPDX-FileCopyrightText: 2022-2026 Harald Sitter <[email protected]> SPDX-License-Identifier: LGPL-2.0-only */ @@ -25,6 +25,7 @@ #include <kservicegroup.h> #include <QFile> +#include <QScopedValueRollback> #include <QSignalSpy> #include <QStandardPaths> #include <QThread> @@ -33,6 +34,9 @@ #include <QLoggingCategory> #include <QMimeDatabase> +using namespace std::chrono_literals; +using namespace Qt::StringLiterals; + QTEST_MAIN(KServiceTest) extern KSERVICE_EXPORT int ksycoca_ms_between_checks; @@ -572,4 +576,31 @@ } } +void KServiceTest::testRecursiveUpdate() +{ + if (!KSycoca::isAvailable()) { + QSKIP("ksycoca not available"); + } + + // Make sure updates trigger immediately. + QScopedValueRollback rollback(ksycoca_ms_between_checks, 0); + + const QString filePath = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + "/org.kde.testRecursiveUpdate.desktop"_L1; + { + QFile f(filePath); + QVERIFY(f.open(QIODevice::WriteOnly | QIODevice::Truncate)); + } + QVERIFY(QFile::exists(filePath)); + + QSignalSpy spy(KSycoca::self(), &KSycoca::databaseChanged); + + auto service = KService::serviceByDesktopPath(filePath); + // The signal must not have fired already. If it had we'd be able to recursively update and break the singleton. + QCOMPARE(spy.count(), 0); + + // If we event loop it should fire though. + spy.wait(4s); + QCOMPARE(spy.count(), 1); +} + #include "moc_kservicetest.cpp" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kservice-6.23.0/autotests/kservicetest.h new/kservice-6.23.1/autotests/kservicetest.h --- old/kservice-6.23.0/autotests/kservicetest.h 2026-02-06 13:16:56.000000000 +0100 +++ new/kservice-6.23.1/autotests/kservicetest.h 2026-03-02 16:30:35.000000000 +0100 @@ -1,6 +1,6 @@ /* SPDX-FileCopyrightText: 2006 David Faure <[email protected]> - SPDX-FileCopyrightText: 2022 Harald Sitter <[email protected]> + SPDX-FileCopyrightText: 2022-2026 Harald Sitter <[email protected]> SPDX-License-Identifier: LGPL-2.0-only */ @@ -46,6 +46,7 @@ void testAliasFor(); void testServiceActionService(); void testStartupNotify(); + void testRecursiveUpdate(); private: void runKBuildSycoca(bool noincremental = false); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kservice-6.23.0/src/services/kservicefactory.cpp new/kservice-6.23.1/src/services/kservicefactory.cpp --- old/kservice-6.23.0/src/services/kservicefactory.cpp 2026-02-06 13:16:56.000000000 +0100 +++ new/kservice-6.23.1/src/services/kservicefactory.cpp 2026-03-02 16:30:35.000000000 +0100 @@ -13,6 +13,7 @@ #include "servicesdebug.h" #include <QDir> #include <QFile> +#include <QScopedValueRollback> extern int servicesDebugArea(); @@ -22,6 +23,18 @@ , m_relNameDict(nullptr) , m_menuIdDict(nullptr) { + // Safety net against recursive calls. Because KSycocaFactory calls findFactory and that issues a checkDatabase + // and that may end up repairing the database and thus closing and reopening it, we must absolutely never find + // ourselves in a recursive call chain. + // The inner call would thrash the state of the outer call causing impossibly difficult to debug state corruption. + thread_local bool inside = false; + if (inside) { + qFatal( + "Recursive call detected in KServiceFactory. This is not allowed and indicates a bug in KSycoca/KServiceFactory. Please report this on " + "bugs.kde.org."); + } + QScopedValueRollback guard(inside, true); + m_offerListOffset = 0; m_nameDictOffset = 0; m_relNameDictOffset = 0; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kservice-6.23.0/src/sycoca/ksycoca.cpp new/kservice-6.23.1/src/sycoca/ksycoca.cpp --- old/kservice-6.23.0/src/sycoca/ksycoca.cpp 2026-02-06 13:16:56.000000000 +0100 +++ new/kservice-6.23.1/src/sycoca/ksycoca.cpp 2026-03-02 16:30:35.000000000 +0100 @@ -320,7 +320,12 @@ m_databasePath = findDatabase(); // Now notify applications - Q_EMIT q->databaseChanged(); + QMetaObject::invokeMethod(q, + &KSycoca::databaseChanged, + // Guard against recursive call chains caused by database updates. + // Because of the self-healing nature of KSycoca we must absolutely never find ourselves in a recursive call chain + // where one database change causes another database change (for example because there was an intermediate file change). + Qt::QueuedConnection); } } @@ -676,7 +681,12 @@ qCDebug(SYCOCA) << "Still no database..."; return false; } - Q_EMIT q->databaseChanged(); + QMetaObject::invokeMethod(q, + &KSycoca::databaseChanged, + // Guard against recursive call chains caused by database updates. + // Because of the self-healing nature of KSycoca we must absolutely never find ourselves in a recursive call chain + // where one database change causes another database change (for example because there was an intermediate file change). + Qt::QueuedConnection); return true; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kservice-6.23.0/src/sycoca/ksycocafactory.cpp new/kservice-6.23.1/src/sycoca/ksycocafactory.cpp --- old/kservice-6.23.0/src/sycoca/ksycocafactory.cpp 2026-02-06 13:16:56.000000000 +0100 +++ new/kservice-6.23.1/src/sycoca/ksycocafactory.cpp 2026-03-02 16:30:35.000000000 +0100 @@ -34,27 +34,42 @@ int m_beginEntryOffset = 0; int m_endEntryOffset = 0; KSycocaDict *m_sycocaDict = nullptr; + // Used to avoid crashes when the factory failed to locate an actual data stream. + // Mind that we need a backing buffer since callers also tap into the stream's QIODevice. + QByteArray m_fallbackBuffer; + QDataStream m_fallbackStream{m_fallbackBuffer}; }; KSycocaFactory::KSycocaFactory(KSycocaFactoryId factory_id, KSycoca *sycoca) : m_sycoca(sycoca) , d(new KSycocaFactoryPrivate) { - if (!m_sycoca->isBuilding() && (m_str = m_sycoca->findFactory(factory_id))) { - // Read position of index tables.... - qint32 i; - (*m_str) >> i; - d->m_sycocaDictOffset = i; - (*m_str) >> i; - d->m_beginEntryOffset = i; - (*m_str) >> i; - d->m_endEntryOffset = i; - - QDataStream *str = stream(); - qint64 saveOffset = str->device()->pos(); - // Init index tables - d->m_sycocaDict = new KSycocaDict(str, d->m_sycocaDictOffset); - saveOffset = str->device()->seek(saveOffset); + if (!m_sycoca->isBuilding()) { + m_str = m_sycoca->findFactory(factory_id); + if (m_str) { + // Read position of index tables.... + qint32 i; + (*m_str) >> i; + d->m_sycocaDictOffset = i; + (*m_str) >> i; + d->m_beginEntryOffset = i; + (*m_str) >> i; + d->m_endEntryOffset = i; + + QDataStream *str = stream(); + qint64 saveOffset = str->device()->pos(); + // Init index tables + d->m_sycocaDict = new KSycocaDict(str, d->m_sycocaDictOffset); + saveOffset = str->device()->seek(saveOffset); + } else { + qWarning() << "Could not find factory with id" << int(factory_id) + << "in sycoca database, you must run kbuildsycoca first! Creating a fake stream to not crash."; + m_str = &d->m_fallbackStream; + m_entryDict = new KSycocaEntryDict; + d->m_sycocaDict = new KSycocaDict; + d->m_beginEntryOffset = 0; + d->m_endEntryOffset = 0; + } } else { // We are in kbuildsycoca -- build new database! m_entryDict = new KSycocaEntryDict; @@ -186,8 +201,8 @@ qint32 entryCount; (*str) >> entryCount; - if (entryCount > 8192) { - qCWarning(SYCOCA) << QThread::currentThread() << "error detected in factory" << this; + if (entryCount < 0 || entryCount > 8192) { // mind that new accepts a size_t (unsigned) but we are dealing with an int here + qCWarning(SYCOCA) << QThread::currentThread() << "error detected in factory" << this << entryCount; KSycoca::flagError(); return list; }
