0016-HTTP2-Delay-any-communication-until-encrypted-can-be.patch 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. From 6d2a5ad09074bd77b2de09adf7147107ee0db026 Mon Sep 17 00:00:00 2001
  2. From: =?UTF-8?q?M=C3=A5rten=20Nordheim?= <marten.nordheim@qt.io>
  3. Date: Tue, 25 Jun 2024 17:09:35 +0200
  4. Subject: [PATCH] HTTP2: Delay any communication until encrypted() can be
  5. responded to
  6. We have the encrypted() signal that lets users do extra checks on the
  7. established connection. It is emitted as BlockingQueued, so the HTTP
  8. thread stalls until it is done emitting. Users can potentially call
  9. abort() on the QNetworkReply at that point, which is passed as a Queued
  10. call back to the HTTP thread. That means that any currently queued
  11. signal emission will be processed before the abort() call is processed.
  12. In the case of HTTP2 it is a little special since it is multiplexed and
  13. the code is built to start requests as they are available. This means
  14. that, while the code worked fine for HTTP1, since one connection only
  15. has one request, it is not working for HTTP2, since we try to send more
  16. requests in-between the encrypted() signal and the abort() call.
  17. This patch changes the code to delay any communication until the
  18. encrypted() signal has been emitted and processed, for HTTP2 only.
  19. It's done by adding a few booleans, both to know that we have to return
  20. early and so we can keep track of what events arose and what we need to
  21. resume once enough time has passed that any abort() call must have been
  22. processed.
  23. Fixes: QTBUG-126610
  24. Pick-to: 6.5 6.2 5.15 5.12
  25. Change-Id: Ic25a600c278203256e35f541026f34a8783235ae
  26. Reviewed-by: Marc Mutz <marc.mutz@qt.io>
  27. Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
  28. (cherry picked from commit b1e75376cc3adfc7da5502a277dfe9711f3e0536)
  29. Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
  30. (cherry picked from commit 0fb43e4395da34d561814242a0186999e4956e28)
  31. Fixes: https://security-tracker.debian.org/tracker/CVE-2024-39936
  32. Upstream: https://github.com/qt/qtbase/commit/2b1e36e183ce75c224305c7a94457b92f7a5cf58
  33. Signed-off-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
  34. ---
  35. src/network/access/qhttp2protocolhandler.cpp | 6 +--
  36. .../access/qhttpnetworkconnectionchannel.cpp | 48 ++++++++++++++++++-
  37. .../access/qhttpnetworkconnectionchannel_p.h | 6 +++
  38. tests/auto/network/access/http2/tst_http2.cpp | 44 +++++++++++++++++
  39. 4 files changed, 99 insertions(+), 5 deletions(-)
  40. diff --git a/src/network/access/qhttp2protocolhandler.cpp b/src/network/access/qhttp2protocolhandler.cpp
  41. index 562b883242f..8c543efc742 100644
  42. --- a/src/network/access/qhttp2protocolhandler.cpp
  43. +++ b/src/network/access/qhttp2protocolhandler.cpp
  44. @@ -335,12 +335,12 @@ bool QHttp2ProtocolHandler::sendRequest()
  45. }
  46. }
  47. - if (!prefaceSent && !sendClientPreface())
  48. - return false;
  49. -
  50. if (!requests.size())
  51. return true;
  52. + if (!prefaceSent && !sendClientPreface())
  53. + return false;
  54. +
  55. m_channel->state = QHttpNetworkConnectionChannel::WritingState;
  56. // Check what was promised/pushed, maybe we do not have to send a request
  57. // and have a response already?
  58. diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp
  59. index 1e036bdc450..a64d1a25898 100644
  60. --- a/src/network/access/qhttpnetworkconnectionchannel.cpp
  61. +++ b/src/network/access/qhttpnetworkconnectionchannel.cpp
  62. @@ -209,6 +209,10 @@ void QHttpNetworkConnectionChannel::abort()
  63. bool QHttpNetworkConnectionChannel::sendRequest()
  64. {
  65. Q_ASSERT(protocolHandler);
  66. + if (waitingForPotentialAbort) {
  67. + needInvokeSendRequest = true;
  68. + return false; // this return value is unused
  69. + }
  70. return protocolHandler->sendRequest();
  71. }
  72. @@ -221,21 +225,28 @@ bool QHttpNetworkConnectionChannel::sendRequest()
  73. void QHttpNetworkConnectionChannel::sendRequestDelayed()
  74. {
  75. QMetaObject::invokeMethod(this, [this] {
  76. - Q_ASSERT(protocolHandler);
  77. if (reply)
  78. - protocolHandler->sendRequest();
  79. + sendRequest();
  80. }, Qt::ConnectionType::QueuedConnection);
  81. }
  82. void QHttpNetworkConnectionChannel::_q_receiveReply()
  83. {
  84. Q_ASSERT(protocolHandler);
  85. + if (waitingForPotentialAbort) {
  86. + needInvokeReceiveReply = true;
  87. + return;
  88. + }
  89. protocolHandler->_q_receiveReply();
  90. }
  91. void QHttpNetworkConnectionChannel::_q_readyRead()
  92. {
  93. Q_ASSERT(protocolHandler);
  94. + if (waitingForPotentialAbort) {
  95. + needInvokeReadyRead = true;
  96. + return;
  97. + }
  98. protocolHandler->_q_readyRead();
  99. }
  100. @@ -1232,7 +1243,18 @@ void QHttpNetworkConnectionChannel::_q_encrypted()
  101. // Similar to HTTP/1.1 counterpart below:
  102. const auto &h2Pairs = h2RequestsToSend.values(); // (request, reply)
  103. const auto &pair = h2Pairs.first();
  104. + waitingForPotentialAbort = true;
  105. emit pair.second->encrypted();
  106. +
  107. + // We don't send or handle any received data until any effects from
  108. + // emitting encrypted() have been processed. This is necessary
  109. + // because the user may have called abort(). We may also abort the
  110. + // whole connection if the request has been aborted and there is
  111. + // no more requests to send.
  112. + QMetaObject::invokeMethod(this,
  113. + &QHttpNetworkConnectionChannel::checkAndResumeCommunication,
  114. + Qt::QueuedConnection);
  115. +
  116. // In case our peer has sent us its settings (window size, max concurrent streams etc.)
  117. // let's give _q_receiveReply a chance to read them first ('invokeMethod', QueuedConnection).
  118. QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
  119. @@ -1250,6 +1272,28 @@ void QHttpNetworkConnectionChannel::_q_encrypted()
  120. }
  121. }
  122. +
  123. +void QHttpNetworkConnectionChannel::checkAndResumeCommunication()
  124. +{
  125. + Q_ASSERT(connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2
  126. + || connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2Direct);
  127. +
  128. + // Because HTTP/2 requires that we send a SETTINGS frame as the first thing we do, and respond
  129. + // to a SETTINGS frame with an ACK, we need to delay any handling until we can ensure that any
  130. + // effects from emitting encrypted() have been processed.
  131. + // This function is called after encrypted() was emitted, so check for changes.
  132. +
  133. + if (!reply && h2RequestsToSend.isEmpty())
  134. + abort();
  135. + waitingForPotentialAbort = false;
  136. + if (needInvokeReadyRead)
  137. + _q_readyRead();
  138. + if (needInvokeReceiveReply)
  139. + _q_receiveReply();
  140. + if (needInvokeSendRequest)
  141. + sendRequest();
  142. +}
  143. +
  144. void QHttpNetworkConnectionChannel::requeueHttp2Requests()
  145. {
  146. QList<HttpMessagePair> h2Pairs = h2RequestsToSend.values();
  147. diff --git a/src/network/access/qhttpnetworkconnectionchannel_p.h b/src/network/access/qhttpnetworkconnectionchannel_p.h
  148. index 0772a4452b3..2a5336ca8a4 100644
  149. --- a/src/network/access/qhttpnetworkconnectionchannel_p.h
  150. +++ b/src/network/access/qhttpnetworkconnectionchannel_p.h
  151. @@ -73,6 +73,10 @@ public:
  152. QAbstractSocket *socket;
  153. bool ssl;
  154. bool isInitialized;
  155. + bool waitingForPotentialAbort = false;
  156. + bool needInvokeReceiveReply = false;
  157. + bool needInvokeReadyRead = false;
  158. + bool needInvokeSendRequest = false;
  159. ChannelState state;
  160. QHttpNetworkRequest request; // current request, only used for HTTP
  161. QHttpNetworkReply *reply; // current reply for this request, only used for HTTP
  162. @@ -145,6 +149,8 @@ public:
  163. void closeAndResendCurrentRequest();
  164. void resendCurrentRequest();
  165. + void checkAndResumeCommunication();
  166. +
  167. bool isSocketBusy() const;
  168. bool isSocketWriting() const;
  169. bool isSocketWaiting() const;
  170. diff --git a/tests/auto/network/access/http2/tst_http2.cpp b/tests/auto/network/access/http2/tst_http2.cpp
  171. index 9c1a63bae1e..30abbb0d3ed 100644
  172. --- a/tests/auto/network/access/http2/tst_http2.cpp
  173. +++ b/tests/auto/network/access/http2/tst_http2.cpp
  174. @@ -99,6 +99,8 @@ private slots:
  175. void redirect_data();
  176. void redirect();
  177. + void abortOnEncrypted();
  178. +
  179. protected slots:
  180. // Slots to listen to our in-process server:
  181. void serverStarted(quint16 port);
  182. @@ -1280,6 +1282,48 @@ void tst_Http2::redirect()
  183. QTRY_VERIFY(serverGotSettingsACK);
  184. }
  185. +void tst_Http2::abortOnEncrypted()
  186. +{
  187. +#if !QT_CONFIG(ssl)
  188. + QSKIP("TLS support is needed for this test");
  189. +#else
  190. + clearHTTP2State();
  191. + serverPort = 0;
  192. +
  193. + ServerPtr targetServer(newServer(defaultServerSettings, H2Type::h2Direct));
  194. +
  195. + QMetaObject::invokeMethod(targetServer.data(), "startServer", Qt::QueuedConnection);
  196. + runEventLoop();
  197. +
  198. + nRequests = 1;
  199. + nSentRequests = 0;
  200. +
  201. + const auto url = requestUrl(H2Type::h2Direct);
  202. + QNetworkRequest request(url);
  203. + request.setAttribute(QNetworkRequest::Http2DirectAttribute, true);
  204. +
  205. + std::unique_ptr<QNetworkReply> reply{manager->get(request)};
  206. + reply->ignoreSslErrors();
  207. + connect(reply.get(), &QNetworkReply::encrypted, reply.get(), [reply = reply.get()](){
  208. + reply->abort();
  209. + });
  210. + connect(reply.get(), &QNetworkReply::errorOccurred, this, &tst_Http2::replyFinishedWithError);
  211. +
  212. + runEventLoop();
  213. + STOP_ON_FAILURE
  214. +
  215. + QCOMPARE(nRequests, 0);
  216. + QCOMPARE(reply->error(), QNetworkReply::OperationCanceledError);
  217. +
  218. + const bool res = QTest::qWaitFor(
  219. + [this, server = targetServer.get()]() {
  220. + return serverGotSettingsACK || prefaceOK || nSentRequests > 0;
  221. + },
  222. + 500);
  223. + QVERIFY(!res);
  224. +#endif // QT_CONFIG(ssl)
  225. +}
  226. +
  227. void tst_Http2::serverStarted(quint16 port)
  228. {
  229. serverPort = port;
  230. --
  231. 2.46.0