From 99c87fe4314d392ae8d3e4cf9c05e3f014322608 Mon Sep 17 00:00:00 2001 From: Méven Car Date: Sat, 13 Jun 2026 13:14:39 +0000 Subject: versioncontrol: fix use-after-free of the current plugin searchPlugin returns one of the observer-owned m_plugins, so m_currentPlugin always aliases an entry in that list. Deleting m_currentPlugin in setModel freed that object while leaving a dangling pointer in m_plugins, which the next searchPlugin or actions call would dereference. setModel(nullptr) runs on every directory change, so this was reachable when leaving a versioned directory. Make the observer the real owner: parent the plugins to it so QObject deletes them, stop deleting the shared plugin in setModel (just clear the current selection), and drop the matching delete in the destructor. --- src/views/versioncontrol/versioncontrolobserver.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/views/versioncontrol/versioncontrolobserver.cpp b/src/views/versioncontrol/versioncontrolobserver.cpp index 008a9a4cf..de81156c5 100644 --- a/src/views/versioncontrol/versioncontrolobserver.cpp +++ b/src/views/versioncontrol/versioncontrolobserver.cpp @@ -44,6 +44,7 @@ VersionControlObserver::~VersionControlObserver() { if (m_currentPlugin) { m_currentPlugin->disconnect(this); + m_currentPlugin = nullptr; } if (m_updateItemStatesThread) { m_updateItemStatesThread->requestInterruption(); @@ -51,20 +52,16 @@ VersionControlObserver::~VersionControlObserver() m_updateItemStatesThread->deleteLater(); } - if (m_currentPlugin) { - delete m_currentPlugin; - m_currentPlugin = nullptr; - } - m_plugins.clear(); + // m_plugins are parented to this observer and destroyed with it by QObject. + // m_currentPlugin is one of them, so it must not be deleted on its own. } void VersionControlObserver::setModel(KFileItemModel *model) { if (m_model) { - if (m_currentPlugin) { - delete m_currentPlugin; - m_currentPlugin = nullptr; - } + // m_currentPlugin points to one of the reused, observer-owned m_plugins. + // Forget the current detection without destroying the shared plugin. + m_currentPlugin = nullptr; if (m_updateItemStatesThread) { m_updateItemStatesThread->requestInterruption(); } @@ -302,7 +299,7 @@ void VersionControlObserver::initPlugins() for (const auto &p : plugins) { if (enabledPlugins.contains(p.name())) { - auto plugin = KPluginFactory::instantiatePlugin(p, parent()).plugin; + auto plugin = KPluginFactory::instantiatePlugin(p, this).plugin; if (plugin) { m_plugins.append(plugin); } -- cgit v1.3.1