From bef4f370b6097c8bdddaa493b11a38e3ea9a6e35 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. (cherry picked from commit 8edb248647dd2cda71cc85b3d21e465353e79e0c) --- 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);