diff --git a/changelog/unreleased/12600.md b/changelog/unreleased/12600.md new file mode 100644 index 00000000000..e123057c4da --- /dev/null +++ b/changelog/unreleased/12600.md @@ -0,0 +1,9 @@ +Bugfix: Fix crash after waking the computer from sleep + +We've fixed a crash that could occur about a second after the computer resumed +from sleep or hibernation. A network reply's internal transfer-timeout timer +could deliver a queued call to a reply that had already been deleted during the +reconnect handling, causing a use-after-free. Replies are now drained of any +pending events before they are deleted. + +https://github.com/owncloud/client/issues/12600 diff --git a/src/libsync/abstractnetworkjob.cpp b/src/libsync/abstractnetworkjob.cpp index 151ae2ab594..d3e25d866f9 100644 --- a/src/libsync/abstractnetworkjob.cpp +++ b/src/libsync/abstractnetworkjob.cpp @@ -14,6 +14,7 @@ */ #include +#include #include #include "common/asserts.h" @@ -30,6 +31,25 @@ using namespace std::chrono_literals; namespace { constexpr int MaxRetryCount = 5; + +// Tear down a reply we are abandoning. +// +// reply->abort() also stops the reply's internal transfer-timeout QTimer, but a +// _q_transferTimedOut metacall that this timer already posted (it is connected +// via a Qt::QueuedConnection with the reply as the *receiver*, a role the plain +// reply->disconnect() does not cover) would survive deleteLater() and be +// delivered to freed memory - the wake-from-sleep use-after-free (#12600). +// removePostedEvents() drains any such pending call before we schedule deletion. +void severAndDeleteReply(QNetworkReply *reply) +{ + if (!reply) { + return; + } + reply->disconnect(); + reply->abort(); + QCoreApplication::removePostedEvents(reply); + reply->deleteLater(); +} } @@ -172,9 +192,7 @@ void AbstractNetworkJob::adoptRequest(QPointer reply) { std::swap(_reply, reply); if (reply) { - reply->disconnect(); - reply->abort(); - reply->deleteLater(); + severAndDeleteReply(reply.data()); } _request = _reply->request(); @@ -289,9 +307,7 @@ AbstractNetworkJob::~AbstractNetworkJob() qCCritical(lcNetworkJob) << "Deleting running job" << this; } if (_reply) { - _reply->disconnect(); - _reply->abort(); - _reply->deleteLater(); + severAndDeleteReply(_reply.data()); _reply.clear(); } }