Package: release.debian.org
Severity: normal
Tags: bookworm
X-Debbugs-Cc: qtbase-opensource-src@packages.debian.org
Control: affects -1 + src:qtbase-opensource-src
User: release.debian.org@packages.debian.org
Usertags: pu
Dear Release team,
I would like to request a stable update of qtbase package for Bookworm.
[ Reason ]
It will fix two bugs:
- #1081682: Segfault in QAccessibleTableInterface::cellAt
- #1076293: CVE-2024-39936
[ Impact ]
The first bug is an important issue for screen reader users, which
makes Qt applications with tables unusable.
The second bug is minor security issue.
See https://security-tracker.debian.org/tracker/CVE-2024-39936 for details.
[ Tests ]
The reporter of #1081682 has tested the patch and it works for them.
See https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1081682#17.
The patch for CVE-2024-39936 was not tested explicitly, but it was applied
in sid since 2024-07-14 and no issues were reported.
[ Risks ]
The patch for #1081682 is quite trivial, it just adds NULL pointer checks.
The patch for CVE-2024-39936 is a bit more complex, but it has a verbose
explanation of what it does (I have included it in the Debian patch too).
[ Checklist ]
[x] *all* changes are documented in the d/changelog
[x] I reviewed all changes and I approve them
[x] attach debdiff against the package in (old)stable
[x] the issue is verified as fixed in unstable
[ Changes ]
* Backport upstream patch to add null checks in table iface methods in
linuxaccessibility/atspiadaptor.cpp (closes: #1081682).
* Backport upstream patch to delay any communication until encrypted() can
be responded to (CVE-2024-39936, closes: #1076293).
[ Other info ]
Links to upstream bugs, commits, patches for the first issue:
- https://bugreports.qt.io/browse/QTBUG-125954
- https://codereview.qt-project.org/c/qt/qtbase/+/518991
- https://invent.kde.org/qt/qt/qtbase/-/commit/076da096464a5d3f (KDE branch)
For the second issue:
- https://codereview.qt-project.org/c/qt/qtbase/+/571601
- https://download.qt.io/official_releases/qt/5.15/CVE-2024-39936-qtbase-5.15.patch
- https://invent.kde.org/qt/qt/qtbase/-/commit/0581ace6d4b8c0c0 (KDE branch)
--
Dmitry Shachnev
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,12 @@
+qtbase-opensource-src (5.15.8+dfsg-11+deb12u3) bookworm; urgency=medium
+
+ * Backport upstream patch to add null checks in table iface methods in
+ linuxaccessibility/atspiadaptor.cpp (closes: #1081682).
+ * Backport upstream patch to delay any communication until encrypted() can
+ be responded to (CVE-2024-39936, closes: #1076293).
+
+ -- Dmitry Shachnev <mitya57@debian.org> Mon, 24 Mar 2025 11:41:15 +0300
+
qtbase-opensource-src (5.15.8+dfsg-11+deb12u2) bookworm; urgency=medium
* Non-maintainer upload by the LTS Team.
--- /dev/null
+++ b/debian/patches/CVE-2024-39936.diff
@@ -0,0 +1,153 @@
+Description: HTTP2: delay any communication until encrypted() can be responded to
+ We have the encrypted() signal that lets users do extra checks on the
+ established connection. It is emitted as BlockingQueued, so the HTTP
+ thread stalls until it is done emitting. Users can potentially call
+ abort() on the QNetworkReply at that point, which is passed as a Queued
+ call back to the HTTP thread. That means that any currently queued
+ signal emission will be processed before the abort() call is processed.
+ .
+ In the case of HTTP2 it is a little special since it is multiplexed and
+ the code is built to start requests as they are available. This means
+ that, while the code worked fine for HTTP1, since one connection only
+ has one request, it is not working for HTTP2, since we try to send more
+ requests in-between the encrypted() signal and the abort() call.
+ .
+ This patch changes the code to delay any communication until the
+ encrypted() signal has been emitted and processed, for HTTP2 only.
+ It's done by adding a few booleans, both to know that we have to return
+ early and so we can keep track of what events arose and what we need to
+ resume once enough time has passed that any abort() call must have been
+ processed.
+Origin: upstream, https://code.qt.io/cgit/qt/qtbase.git/commit/?id=b1e75376cc3adfc7
+Last-Update: 2025-03-25
+
+--- a/src/network/access/qhttp2protocolhandler.cpp
++++ b/src/network/access/qhttp2protocolhandler.cpp
+@@ -371,12 +371,12 @@ bool QHttp2ProtocolHandler::sendRequest(
+ }
+ }
+
+- if (!prefaceSent && !sendClientPreface())
+- return false;
+-
+ if (!requests.size())
+ return true;
+
++ if (!prefaceSent && !sendClientPreface())
++ return false;
++
+ m_channel->state = QHttpNetworkConnectionChannel::WritingState;
+ // Check what was promised/pushed, maybe we do not have to send a request
+ // and have a response already?
+--- a/src/network/access/qhttpnetworkconnectionchannel.cpp
++++ b/src/network/access/qhttpnetworkconnectionchannel.cpp
+@@ -255,6 +255,10 @@ void QHttpNetworkConnectionChannel::abor
+ bool QHttpNetworkConnectionChannel::sendRequest()
+ {
+ Q_ASSERT(!protocolHandler.isNull());
++ if (waitingForPotentialAbort) {
++ needInvokeSendRequest = true;
++ return false; // this return value is unused
++ }
+ return protocolHandler->sendRequest();
+ }
+
+@@ -267,21 +271,28 @@ bool QHttpNetworkConnectionChannel::send
+ void QHttpNetworkConnectionChannel::sendRequestDelayed()
+ {
+ QMetaObject::invokeMethod(this, [this] {
+- Q_ASSERT(!protocolHandler.isNull());
+ if (reply)
+- protocolHandler->sendRequest();
++ sendRequest();
+ }, Qt::ConnectionType::QueuedConnection);
+ }
+
+ void QHttpNetworkConnectionChannel::_q_receiveReply()
+ {
+ Q_ASSERT(!protocolHandler.isNull());
++ if (waitingForPotentialAbort) {
++ needInvokeReceiveReply = true;
++ return;
++ }
+ protocolHandler->_q_receiveReply();
+ }
+
+ void QHttpNetworkConnectionChannel::_q_readyRead()
+ {
+ Q_ASSERT(!protocolHandler.isNull());
++ if (waitingForPotentialAbort) {
++ needInvokeReadyRead = true;
++ return;
++ }
+ protocolHandler->_q_readyRead();
+ }
+
+@@ -1289,7 +1300,18 @@ void QHttpNetworkConnectionChannel::_q_e
+ // Similar to HTTP/1.1 counterpart below:
+ const auto &pairs = spdyRequestsToSend.values(); // (request, reply)
+ const auto &pair = pairs.first();
++ waitingForPotentialAbort = true;
+ emit pair.second->encrypted();
++
++ // We don't send or handle any received data until any effects from
++ // emitting encrypted() have been processed. This is necessary
++ // because the user may have called abort(). We may also abort the
++ // whole connection if the request has been aborted and there is
++ // no more requests to send.
++ QMetaObject::invokeMethod(this,
++ &QHttpNetworkConnectionChannel::checkAndResumeCommunication,
++ Qt::QueuedConnection);
++
+ // In case our peer has sent us its settings (window size, max concurrent streams etc.)
+ // let's give _q_receiveReply a chance to read them first ('invokeMethod', QueuedConnection).
+ QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
+@@ -1307,6 +1329,26 @@ void QHttpNetworkConnectionChannel::_q_e
+ }
+ }
+
++void QHttpNetworkConnectionChannel::checkAndResumeCommunication()
++{
++ Q_ASSERT(connection->connectionType() > QHttpNetworkConnection::ConnectionTypeHTTP);
++
++ // Because HTTP/2 requires that we send a SETTINGS frame as the first thing we do, and respond
++ // to a SETTINGS frame with an ACK, we need to delay any handling until we can ensure that any
++ // effects from emitting encrypted() have been processed.
++ // This function is called after encrypted() was emitted, so check for changes.
++
++ if (!reply && spdyRequestsToSend.isEmpty())
++ abort();
++ waitingForPotentialAbort = false;
++ if (needInvokeReadyRead)
++ _q_readyRead();
++ if (needInvokeReceiveReply)
++ _q_receiveReply();
++ if (needInvokeSendRequest)
++ sendRequest();
++}
++
+ void QHttpNetworkConnectionChannel::requeueSpdyRequests()
+ {
+ QList<HttpMessagePair> spdyPairs = spdyRequestsToSend.values();
+--- a/src/network/access/qhttpnetworkconnectionchannel_p.h
++++ b/src/network/access/qhttpnetworkconnectionchannel_p.h
+@@ -107,6 +107,10 @@ public:
+ QAbstractSocket *socket;
+ bool ssl;
+ bool isInitialized;
++ bool waitingForPotentialAbort = false;
++ bool needInvokeReceiveReply = false;
++ bool needInvokeReadyRead = false;
++ bool needInvokeSendRequest = false;
+ ChannelState state;
+ QHttpNetworkRequest request; // current request, only used for HTTP
+ QHttpNetworkReply *reply; // current reply for this request, only used for HTTP
+@@ -187,6 +191,8 @@ public:
+ void closeAndResendCurrentRequest();
+ void resendCurrentRequest();
+
++ void checkAndResumeCommunication();
++
+ bool isSocketBusy() const;
+ bool isSocketWriting() const;
+ bool isSocketWaiting() const;
--- /dev/null
+++ b/debian/patches/a11y_null_checks.diff
@@ -0,0 +1,76 @@
+Description: a11y atspi: add null checks in table iface methods
+ Add null checks to cover the cases where QAccessibleTableInterface::cellAt
+ returns nullptr (which happens e.g. when called with invalid indices via
+ AT-SPI) or where the cell object doesn't implement the
+ QAccessibleTableCellInterface, which would previously result in crashes.
+ .
+ Cherry-picked into 5.15 as it fixes a crash in popular accessibility client
+ software. Conflict resolution: remove C++17'isms (`if` with initializer).
+Origin: upstream, https://invent.kde.org/qt/qt/qtbase/-/commit/076da096464a5d3f
+Last-Update: 2025-03-24
+Bug: https://bugs.debian.org/1081682
+
+--- a/src/platformsupport/linuxaccessibility/atspiadaptor.cpp
++++ b/src/platformsupport/linuxaccessibility/atspiadaptor.cpp
+@@ -2393,13 +2393,14 @@ bool AtSpiAdaptor::tableInterface(QAcces
+ if (cols > 0) {
+ row = index / cols;
+ col = index % cols;
+- QAccessibleTableCellInterface *cell = interface->tableInterface()->cellAt(row, col)->tableCellInterface();
+- if (cell) {
+- row = cell->rowIndex();
+- col = cell->columnIndex();
+- rowExtents = cell->rowExtent();
+- colExtents = cell->columnExtent();
+- isSelected = cell->isSelected();
++ QAccessibleInterface *cell = interface->tableInterface()->cellAt(row, col);
++ QAccessibleTableCellInterface *cellIface = cell ? cell->tableCellInterface() : nullptr;
++ if (cellIface) {
++ row = cellIface->rowIndex();
++ col = cellIface->columnIndex();
++ rowExtents = cellIface->rowExtent();
++ colExtents = cellIface->columnExtent();
++ isSelected = cellIface->isSelected();
+ success = true;
+ }
+ }
+@@ -2410,12 +2411,22 @@ bool AtSpiAdaptor::tableInterface(QAcces
+ } else if (function == QLatin1String("GetColumnExtentAt")) {
+ int row = message.arguments().at(0).toInt();
+ int column = message.arguments().at(1).toInt();
+- connection.send(message.createReply(interface->tableInterface()->cellAt(row, column)->tableCellInterface()->columnExtent()));
++ int columnExtent = 0;
++ QAccessibleInterface *cell = interface->tableInterface()->cellAt(row, column);
++ QAccessibleTableCellInterface *cellIface = cell ? cell->tableCellInterface() : nullptr;
++ if (cellIface)
++ columnExtent = cellIface->columnExtent();
++ connection.send(message.createReply(columnExtent));
+
+ } else if (function == QLatin1String("GetRowExtentAt")) {
+ int row = message.arguments().at(0).toInt();
+ int column = message.arguments().at(1).toInt();
+- connection.send(message.createReply(interface->tableInterface()->cellAt(row, column)->tableCellInterface()->rowExtent()));
++ int rowExtent = 0;
++ QAccessibleInterface *cell = interface->tableInterface()->cellAt(row, column);
++ QAccessibleTableCellInterface *cellIface = cell ? cell->tableCellInterface() : nullptr;
++ if (cellIface)
++ rowExtent = cellIface->rowExtent();
++ connection.send(message.createReply(rowExtent));
+
+ } else if (function == QLatin1String("GetColumnHeader")) {
+ int column = message.arguments().at(0).toInt();
+@@ -2455,8 +2466,12 @@ bool AtSpiAdaptor::tableInterface(QAcces
+ } else if (function == QLatin1String("IsSelected")) {
+ int row = message.arguments().at(0).toInt();
+ int column = message.arguments().at(1).toInt();
+- QAccessibleTableCellInterface* cell = interface->tableInterface()->cellAt(row, column)->tableCellInterface();
+- connection.send(message.createReply(cell->isSelected()));
++ bool isSelected = false;
++ QAccessibleInterface *cell = interface->tableInterface()->cellAt(row, column);
++ QAccessibleTableCellInterface *cellIface = cell ? cell->tableCellInterface() : nullptr;
++ if (cellIface)
++ isSelected = cellIface->isSelected();
++ connection.send(message.createReply(isSelected));
+ } else if (function == QLatin1String("AddColumnSelection")) {
+ int column = message.arguments().at(0).toInt();
+ connection.send(message.createReply(interface->tableInterface()->selectColumn(column)));
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -23,9 +23,10 @@ sql_odbc_fix_unicode_check.diff
CVE-2023-34410.diff
CVE-2023-37369.diff
CVE-2023-38197.diff
-
CVE-2023-51714.diff
CVE-2024-25580.diff
+a11y_null_checks.diff
+CVE-2024-39936.diff
# Debian specific.
gnukfreebsd.diff
Attachment:
signature.asc
Description: PGP signature