From 8edb248647dd2cda71cc85b3d21e465353e79e0c Mon Sep 17 00:00:00 2001 From: James Graham Date: Sun, 8 Feb 2026 18:21:12 +0000 Subject: [PATCH] Fix use after free in message delegate. We can't delete the incubator in the completed callback because it then returns to the incubator we just deleted. --- src/timeline/messagedelegate.cpp | 37 ++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/src/timeline/messagedelegate.cpp b/src/timeline/messagedelegate.cpp index 676e3f5fb..a1c4c5f69 100644 --- a/src/timeline/messagedelegate.cpp +++ b/src/timeline/messagedelegate.cpp @@ -155,10 +155,10 @@ void MessageDelegateBase::cleanupIncubator(MessageObjectIncubator *incubator) incubator->clear(); const auto it = std::find(m_activeIncubators.begin(), m_activeIncubators.end(), incubator); + delete incubator; if (it != m_activeIncubators.end()) { m_activeIncubators.erase(it); } - delete incubator; } void MessageDelegateBase::cleanupItem(QQuickItem *item) @@ -249,7 +249,12 @@ void MessageDelegateBase::updateAvatar() markAsDirty(); } m_avatarIncubating = false; - cleanupIncubator(incubator); + // We can't cleanup the incubator in the completedCallback otherwise + // we use after free when we return to the status changed function + // of that incubator + QTimer::singleShot(0, this, [this, incubator]() { + cleanupIncubator(incubator); + }); }, m_errorCallback); m_activeIncubators.push_back(avatarIncubator); @@ -313,7 +318,12 @@ void MessageDelegateBase::updateSection() markAsDirty(); } m_sectionIncubating = false; - cleanupIncubator(incubator); + // We can't cleanup the incubator in the completedCallback otherwise + // we use after free when we return to the status changed function + // of that incubator + QTimer::singleShot(0, this, [this, incubator]() { + cleanupIncubator(incubator); + }); }, m_errorCallback); m_activeIncubators.push_back(sectionIncubator); @@ -377,7 +387,12 @@ void MessageDelegateBase::updateReadMarker() markAsDirty(); } m_readMarkerIncubating = false; - cleanupIncubator(incubator); + // We can't cleanup the incubator in the completedCallback otherwise + // we use after free when we return to the status changed function + // of that incubator + QTimer::singleShot(0, this, [this, incubator]() { + cleanupIncubator(incubator); + }); }, m_errorCallback); m_activeIncubators.push_back(readMarkerIncubator); @@ -448,7 +463,12 @@ void MessageDelegateBase::updateBackground() markAsDirty(); } m_compactBackgroundIncubating = false; - cleanupIncubator(incubator); + // We can't cleanup the incubator in the completedCallback otherwise + // we use after free when we return to the status changed function + // of that incubator + QTimer::singleShot(0, this, [this, incubator]() { + cleanupIncubator(incubator); + }); }, m_errorCallback); m_activeIncubators.push_back(compactBackgroundIncubator); @@ -506,8 +526,13 @@ void MessageDelegateBase::updateQuickAction() } markAsDirty(); } - cleanupIncubator(incubator); m_quickActionIncubating = false; + // We can't cleanup the incubator in the completedCallback otherwise + // we use after free when we return to the status changed function + // of that incubator + QTimer::singleShot(0, this, [this, incubator]() { + cleanupIncubator(incubator); + }); }, m_errorCallback); m_activeIncubators.push_back(quickActionIncubator);