From 4aed85e617e48c7601330c88012c7afd0fb3d4c84d15cb4fe2a1a8a9d17331cd Mon Sep 17 00:00:00 2001 From: Christophe Marin Date: Sat, 22 Jul 2023 06:50:09 +0000 Subject: [PATCH 1/2] Accepting request 1099849 from home:krop:Qt6:Release Qt 6.5.2 OBS-URL: https://build.opensuse.org/request/show/1099849 OBS-URL: https://build.opensuse.org/package/show/KDE:Qt6/qt6-base?expand=0&rev=63 --- ...certificate-not-signed-by-a-configur.patch | 291 ------------- ...demand-cert-loading-bool-from-defaul.patch | 112 ----- 0001-tabbar-fix.patch | 99 ----- CVE-2023-38197-qtbase-6.5.diff | 388 ++++++++++++++++++ qt6-base.changes | 14 + qt6-base.spec | 10 +- qtbase-everywhere-src-6.5.1.tar.xz | 3 - qtbase-everywhere-src-6.5.2.tar.xz | 3 + 8 files changed, 409 insertions(+), 511 deletions(-) delete mode 100644 0001-Schannel-Reject-certificate-not-signed-by-a-configur.patch delete mode 100644 0001-Ssl-Copy-the-on-demand-cert-loading-bool-from-defaul.patch delete mode 100644 0001-tabbar-fix.patch create mode 100644 CVE-2023-38197-qtbase-6.5.diff delete mode 100644 qtbase-everywhere-src-6.5.1.tar.xz create mode 100644 qtbase-everywhere-src-6.5.2.tar.xz diff --git a/0001-Schannel-Reject-certificate-not-signed-by-a-configur.patch b/0001-Schannel-Reject-certificate-not-signed-by-a-configur.patch deleted file mode 100644 index c7a25b3..0000000 --- a/0001-Schannel-Reject-certificate-not-signed-by-a-configur.patch +++ /dev/null @@ -1,291 +0,0 @@ -From ada2c573c1a25f8d96577734968fe317ddfa292a Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?M=C3=A5rten=20Nordheim?= -Date: Wed, 10 May 2023 16:43:41 +0200 -Subject: [PATCH] Schannel: Reject certificate not signed by a configured CA - certificate - -Not entirely clear why, but when building the certificate chain for a -peer the system certificate store is searched for root certificates. -General expectation is that after calling -`sslConfiguration.setCaCertificates()` the system certificates will -not be taken into consideration. - -To work around this behavior, we do a manual check that the root of the -chain is part of the configured CA certificates. - -Pick-to: 6.5 6.2 5.15 -Change-Id: I03666a4d9b0eac39ae97e150b4743120611a11b3 -Reviewed-by: Edward Welbourne -Reviewed-by: Volker Hilsheimer ---- - src/plugins/tls/schannel/qtls_schannel.cpp | 21 ++++ - .../network/ssl/client-auth/CMakeLists.txt | 24 ++++ - .../network/ssl/client-auth/certs/.gitignore | 4 + - .../client-auth/certs/accepted-client.conf | 14 +++ - .../network/ssl/client-auth/certs/generate.sh | 33 +++++ - .../tst_manual_ssl_client_auth.cpp | 118 ++++++++++++++++++ - 6 files changed, 214 insertions(+) - create mode 100644 tests/manual/network/ssl/client-auth/CMakeLists.txt - create mode 100644 tests/manual/network/ssl/client-auth/certs/.gitignore - create mode 100644 tests/manual/network/ssl/client-auth/certs/accepted-client.conf - create mode 100755 tests/manual/network/ssl/client-auth/certs/generate.sh - create mode 100644 tests/manual/network/ssl/client-auth/tst_manual_ssl_client_auth.cpp - -diff --git a/src/plugins/tls/schannel/qtls_schannel.cpp b/src/plugins/tls/schannel/qtls_schannel.cpp -index d72aaa5a36..ae9ff06eae 100644 ---- a/src/plugins/tls/schannel/qtls_schannel.cpp -+++ b/src/plugins/tls/schannel/qtls_schannel.cpp -@@ -2066,6 +2066,27 @@ bool TlsCryptographSchannel::verifyCertContext(CERT_CONTEXT *certContext) - verifyDepth = DWORD(q->peerVerifyDepth()); - - const auto &caCertificates = q->sslConfiguration().caCertificates(); -+ -+ if (!rootCertOnDemandLoadingAllowed() -+ && !(chain->TrustStatus.dwErrorStatus & CERT_TRUST_IS_PARTIAL_CHAIN) -+ && (q->peerVerifyMode() == QSslSocket::VerifyPeer -+ || (isClient && q->peerVerifyMode() == QSslSocket::AutoVerifyPeer))) { -+ // When verifying a peer Windows "helpfully" builds a chain that -+ // may include roots from the system store. But we don't want that if -+ // the user has set their own CA certificates. -+ // Since Windows claims this is not a partial chain the root is included -+ // and we have to check that it is one of our configured CAs. -+ CERT_CHAIN_ELEMENT *element = chain->rgpElement[chain->cElement - 1]; -+ QSslCertificate certificate = getCertificateFromChainElement(element); -+ if (!caCertificates.contains(certificate)) { -+ auto error = QSslError(QSslError::CertificateUntrusted, certificate); -+ sslErrors += error; -+ emit q->peerVerifyError(error); -+ if (q->state() != QAbstractSocket::ConnectedState) -+ return false; -+ } -+ } -+ - QList peerCertificateChain; - for (DWORD i = 0; i < verifyDepth; i++) { - CERT_CHAIN_ELEMENT *element = chain->rgpElement[i]; -diff --git a/tests/manual/network/ssl/client-auth/CMakeLists.txt b/tests/manual/network/ssl/client-auth/CMakeLists.txt -new file mode 100644 -index 0000000000..67ecc20bf4 ---- /dev/null -+++ b/tests/manual/network/ssl/client-auth/CMakeLists.txt -@@ -0,0 +1,24 @@ -+# Copyright (C) 2023 The Qt Company Ltd. -+# SPDX-License-Identifier: BSD-3-Clause -+ -+qt_internal_add_manual_test(tst_manual_ssl_client_auth -+ SOURCES -+ tst_manual_ssl_client_auth.cpp -+ LIBRARIES -+ Qt::Network -+) -+ -+qt_internal_add_resource(tst_manual_ssl_client_auth "tst_manual_ssl_client_auth" -+ PREFIX -+ "/" -+ FILES -+ "certs/127.0.0.1.pem" -+ "certs/127.0.0.1-key.pem" -+ "certs/127.0.0.1-client.pem" -+ "certs/127.0.0.1-client-key.pem" -+ "certs/accepted-client.pem" -+ "certs/accepted-client-key.pem" -+ "certs/rootCA.pem" -+ BASE -+ "certs" -+) -diff --git a/tests/manual/network/ssl/client-auth/certs/.gitignore b/tests/manual/network/ssl/client-auth/certs/.gitignore -new file mode 100644 -index 0000000000..5866f7b609 ---- /dev/null -+++ b/tests/manual/network/ssl/client-auth/certs/.gitignore -@@ -0,0 +1,4 @@ -+* -+!/.gitignore -+!/generate.sh -+!/accepted-client.conf -diff --git a/tests/manual/network/ssl/client-auth/certs/accepted-client.conf b/tests/manual/network/ssl/client-auth/certs/accepted-client.conf -new file mode 100644 -index 0000000000..a88b276efe ---- /dev/null -+++ b/tests/manual/network/ssl/client-auth/certs/accepted-client.conf -@@ -0,0 +1,14 @@ -+[req] -+default_md = sha512 -+basicConstraints = CA:FALSE -+extendedKeyUsage = clientAuth -+[req] -+distinguished_name = client_distinguished_name -+prompt = no -+[client_distinguished_name] -+C = NO -+ST = Oslo -+L = Oslo -+O = The Qt Project -+OU = The Qt Project -+CN = Fake Qt Project Client Certificate -diff --git a/tests/manual/network/ssl/client-auth/certs/generate.sh b/tests/manual/network/ssl/client-auth/certs/generate.sh -new file mode 100755 -index 0000000000..5dbe3b3712 ---- /dev/null -+++ b/tests/manual/network/ssl/client-auth/certs/generate.sh -@@ -0,0 +1,33 @@ -+#!/bin/bash -+# Copyright (C) 2023 The Qt Company Ltd. -+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -+ -+# Requires mkcert and openssl -+ -+warn () { echo "$@" >&2; } -+die () { warn "$@"; exit 1; } -+ -+ -+command -v mkcert 1>/dev/null 2>&1 || die "Failed to find mkcert" -+command -v openssl 1>/dev/null 2>&1 || die "Failed to find openssl" -+ -+SCRIPT=$(realpath "$0") -+SCRIPTPATH=$(dirname "$SCRIPT") -+ -+pushd "$SCRIPTPATH" || die "Unable to pushd to $SCRIPTPATH" -+mkcert 127.0.0.1 -+mkcert -client 127.0.0.1 -+warn "Remember to run mkcert -install if you haven't already" -+ -+# Generate CA -+openssl genrsa -out ca-key.pem 2048 -+openssl req -new -x509 -noenc -days 365 -key ca-key.pem -out rootCA.pem -+ -+# Generate accepted client certificate -+openssl genrsa -out accepted-client-key.pem 2048 -+openssl req -new -sha512 -nodes -key accepted-client-key.pem -out accepted-client.csr -config accepted-client.conf -+openssl x509 -req -sha512 -days 45 -in accepted-client.csr -CA rootCA.pem -CAkey ca-key.pem -CAcreateserial -out accepted-client.pem -+rm accepted-client.csr -+rm rootCA.srl -+ -+popd || die "Unable to popd" -diff --git a/tests/manual/network/ssl/client-auth/tst_manual_ssl_client_auth.cpp b/tests/manual/network/ssl/client-auth/tst_manual_ssl_client_auth.cpp -new file mode 100644 -index 0000000000..2307cbb191 ---- /dev/null -+++ b/tests/manual/network/ssl/client-auth/tst_manual_ssl_client_auth.cpp -@@ -0,0 +1,118 @@ -+// Copyright (C) 2023 The Qt Company Ltd. -+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -+ -+#include -+ -+#include -+#include -+#include -+ -+#include -+#include -+#include -+#include -+ -+// Client and/or server presents a certificate signed by a system-trusted CA -+// but the other side presents a certificate signed by a different CA. -+constexpr bool TestServerPresentsIncorrectCa = false; -+constexpr bool TestClientPresentsIncorrectCa = true; -+ -+class ServerThread : public QThread -+{ -+ Q_OBJECT -+public: -+ void run() override -+ { -+ QSslServer server; -+ -+ QSslConfiguration config = server.sslConfiguration(); -+ QList certs = QSslCertificate::fromPath(QStringLiteral(":/rootCA.pem")); -+ config.setCaCertificates(certs); -+ config.setLocalCertificate(QSslCertificate::fromPath(QStringLiteral(":/127.0.0.1.pem")) -+ .first()); -+ QFile keyFile(QStringLiteral(":/127.0.0.1-key.pem")); -+ if (!keyFile.open(QIODevice::ReadOnly)) -+ qFatal("Failed to open key file"); -+ config.setPrivateKey(QSslKey(&keyFile, QSsl::Rsa)); -+ config.setPeerVerifyMode(QSslSocket::VerifyPeer); -+ server.setSslConfiguration(config); -+ -+ connect(&server, &QSslServer::pendingConnectionAvailable, [&server]() { -+ QSslSocket *socket = static_cast(server.nextPendingConnection()); -+ qDebug() << "[s] newConnection" << socket->peerAddress() << socket->peerPort(); -+ socket->disconnectFromHost(); -+ qApp->quit(); -+ }); -+ connect(&server, &QSslServer::startedEncryptionHandshake, [](QSslSocket *socket) { -+ qDebug() << "[s] new handshake" << socket->peerAddress() << socket->peerPort(); -+ }); -+ connect(&server, &QSslServer::errorOccurred, -+ [](QSslSocket *socket, QAbstractSocket::SocketError error) { -+ qDebug() << "[s] errorOccurred" << socket->peerAddress() << socket->peerPort() -+ << error << socket->errorString(); -+ }); -+ connect(&server, &QSslServer::peerVerifyError, -+ [](QSslSocket *socket, const QSslError &error) { -+ qDebug() << "[s] peerVerifyError" << socket->peerAddress() << socket->peerPort() -+ << error; -+ }); -+ server.listen(QHostAddress::LocalHost, 24242); -+ -+ exec(); -+ -+ server.close(); -+ } -+}; -+ -+int main(int argc, char **argv) -+{ -+ QCoreApplication app(argc, argv); -+ -+ using namespace Qt::StringLiterals; -+ -+ if (!QFileInfo(u":/rootCA.pem"_s).exists()) -+ qFatal("rootCA.pem not found. Did you run generate.sh in the certs directory?"); -+ -+ ServerThread serverThread; -+ serverThread.start(); -+ -+ QSslSocket socket; -+ QSslConfiguration config = socket.sslConfiguration(); -+ QString certificatePath; -+ QString keyFileName; -+ if constexpr (TestClientPresentsIncorrectCa) { // true: Present cert signed with incorrect CA: should fail -+ certificatePath = u":/127.0.0.1-client.pem"_s; -+ keyFileName = u":/127.0.0.1-client-key.pem"_s; -+ } else { // false: Use correct CA: should succeed -+ certificatePath = u":/accepted-client.pem"_s; -+ keyFileName = u":/accepted-client-key.pem"_s; -+ } -+ config.setLocalCertificate(QSslCertificate::fromPath(certificatePath).first()); -+ if (TestServerPresentsIncorrectCa) // true: Verify server using incorrect CA: should fail -+ config.setCaCertificates(QSslCertificate::fromPath(u":/rootCA.pem"_s)); -+ QFile keyFile(keyFileName); -+ if (!keyFile.open(QIODevice::ReadOnly)) -+ qFatal("Failed to open key file"); -+ config.setPrivateKey(QSslKey(&keyFile, QSsl::Rsa)); -+ socket.setSslConfiguration(config); -+ -+ QObject::connect(&socket, &QSslSocket::encrypted, []() { qDebug() << "[c] encrypted"; }); -+ QObject::connect(&socket, &QSslSocket::errorOccurred, -+ [&socket](QAbstractSocket::SocketError error) { -+ qDebug() << "[c] errorOccurred" << error << socket.errorString(); -+ qApp->quit(); -+ }); -+ QObject::connect(&socket, &QSslSocket::sslErrors, [](const QList &errors) { -+ qDebug() << "[c] sslErrors" << errors; -+ }); -+ QObject::connect(&socket, &QSslSocket::connected, []() { qDebug() << "[c] connected"; }); -+ -+ socket.connectToHostEncrypted(QStringLiteral("127.0.0.1"), 24242); -+ -+ const int res = app.exec(); -+ serverThread.quit(); -+ serverThread.wait(); -+ return res; -+} -+ -+#include "tst_manual_ssl_client_auth.moc" --- -2.40.1 - diff --git a/0001-Ssl-Copy-the-on-demand-cert-loading-bool-from-defaul.patch b/0001-Ssl-Copy-the-on-demand-cert-loading-bool-from-defaul.patch deleted file mode 100644 index 85f0ff8..0000000 --- a/0001-Ssl-Copy-the-on-demand-cert-loading-bool-from-defaul.patch +++ /dev/null @@ -1,112 +0,0 @@ -From 57ba6260c0801055b7188fdaa1818b940590f5f1 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?M=C3=A5rten=20Nordheim?= -Date: Thu, 25 May 2023 14:40:29 +0200 -Subject: [PATCH] Ssl: Copy the on-demand cert loading bool from default config - -Otherwise individual sockets will still load system certificates when -a chain doesn't match against the configured CA certificates. -That's not intended behavior, since specifically setting the CA -certificates means you don't want the system certificates to be used. - -Follow-up to/amends ada2c573c1a25f8d96577734968fe317ddfa292a - -This is potentially a breaking change because now, if you ever add a -CA to the default config, it will disable loading system certificates -on demand for all sockets. And the only way to re-enable it is to -create a null-QSslConfiguration and set it as the new default. - -Pick-to: 6.5 6.2 5.15 -Change-Id: Ic3b2ab125c0cdd58ad654af1cb36173960ce2d1e -Reviewed-by: Timur Pocheptsov ---- - src/network/ssl/qsslsocket.cpp | 5 ++++ - .../tst_manual_ssl_client_auth.cpp | 24 ++++++++++++++++--- - 2 files changed, 26 insertions(+), 3 deletions(-) - -diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp -index 4eefe43929..0563fd0663 100644 ---- a/src/network/ssl/qsslsocket.cpp -+++ b/src/network/ssl/qsslsocket.cpp -@@ -1973,6 +1973,10 @@ QSslSocketPrivate::QSslSocketPrivate() - , flushTriggered(false) - { - QSslConfigurationPrivate::deepCopyDefaultConfiguration(&configuration); -+ // If the global configuration doesn't allow root certificates to be loaded -+ // on demand then we have to disable it for this socket as well. -+ if (!configuration.allowRootCertOnDemandLoading) -+ allowRootCertOnDemandLoading = false; - - const auto *tlsBackend = tlsBackendInUse(); - if (!tlsBackend) { -@@ -2281,6 +2285,7 @@ void QSslConfigurationPrivate::deepCopyDefaultConfiguration(QSslConfigurationPri - ptr->sessionProtocol = global->sessionProtocol; - ptr->ciphers = global->ciphers; - ptr->caCertificates = global->caCertificates; -+ ptr->allowRootCertOnDemandLoading = global->allowRootCertOnDemandLoading; - ptr->protocol = global->protocol; - ptr->peerVerifyMode = global->peerVerifyMode; - ptr->peerVerifyDepth = global->peerVerifyDepth; -diff --git a/tests/manual/network/ssl/client-auth/tst_manual_ssl_client_auth.cpp b/tests/manual/network/ssl/client-auth/tst_manual_ssl_client_auth.cpp -index 2307cbb191..4d4aaca7e3 100644 ---- a/tests/manual/network/ssl/client-auth/tst_manual_ssl_client_auth.cpp -+++ b/tests/manual/network/ssl/client-auth/tst_manual_ssl_client_auth.cpp -@@ -16,6 +16,9 @@ - // but the other side presents a certificate signed by a different CA. - constexpr bool TestServerPresentsIncorrectCa = false; - constexpr bool TestClientPresentsIncorrectCa = true; -+// Decides whether or not to put the root CA into the global ssl configuration -+// or into the socket's specific ssl configuration. -+constexpr bool UseGlobalConfiguration = true; - - class ServerThread : public QThread - { -@@ -26,8 +29,10 @@ public: - QSslServer server; - - QSslConfiguration config = server.sslConfiguration(); -- QList certs = QSslCertificate::fromPath(QStringLiteral(":/rootCA.pem")); -- config.setCaCertificates(certs); -+ if (!UseGlobalConfiguration) { -+ QList certs = QSslCertificate::fromPath(QStringLiteral(":/rootCA.pem")); -+ config.setCaCertificates(certs); -+ } - config.setLocalCertificate(QSslCertificate::fromPath(QStringLiteral(":/127.0.0.1.pem")) - .first()); - QFile keyFile(QStringLiteral(":/127.0.0.1-key.pem")); -@@ -73,6 +78,12 @@ int main(int argc, char **argv) - if (!QFileInfo(u":/rootCA.pem"_s).exists()) - qFatal("rootCA.pem not found. Did you run generate.sh in the certs directory?"); - -+ if (UseGlobalConfiguration) { -+ QSslConfiguration config = QSslConfiguration::defaultConfiguration(); -+ config.setCaCertificates(QSslCertificate::fromPath(u":/rootCA.pem"_s)); -+ QSslConfiguration::setDefaultConfiguration(config); -+ } -+ - ServerThread serverThread; - serverThread.start(); - -@@ -88,12 +99,19 @@ int main(int argc, char **argv) - keyFileName = u":/accepted-client-key.pem"_s; - } - config.setLocalCertificate(QSslCertificate::fromPath(certificatePath).first()); -- if (TestServerPresentsIncorrectCa) // true: Verify server using incorrect CA: should fail -+ if (!UseGlobalConfiguration && TestServerPresentsIncorrectCa) { -+ // Verify server using incorrect CA: should fail - config.setCaCertificates(QSslCertificate::fromPath(u":/rootCA.pem"_s)); -+ } else if (UseGlobalConfiguration && !TestServerPresentsIncorrectCa) { -+ // Verify server using correct CA, we need to explicitly set the -+ // system CAs when the global config is overridden. -+ config.setCaCertificates(QSslConfiguration::systemCaCertificates()); -+ } - QFile keyFile(keyFileName); - if (!keyFile.open(QIODevice::ReadOnly)) - qFatal("Failed to open key file"); - config.setPrivateKey(QSslKey(&keyFile, QSsl::Rsa)); -+ - socket.setSslConfiguration(config); - - QObject::connect(&socket, &QSslSocket::encrypted, []() { qDebug() << "[c] encrypted"; }); --- -2.40.1 - diff --git a/0001-tabbar-fix.patch b/0001-tabbar-fix.patch deleted file mode 100644 index f08ae31..0000000 --- a/0001-tabbar-fix.patch +++ /dev/null @@ -1,99 +0,0 @@ -From 2a7da1b3c8c4096d7c2b09f3fcc58e9cf47867cd Mon Sep 17 00:00:00 2001 -From: Volker Hilsheimer -Date: Mon, 5 Jun 2023 17:10:00 +0200 -Subject: QTabBar: recalculate scroll offset when showing - -If an application sets the current index and resizes the tab widget -before showing it, then the scroll offset might be calculated based on -an old size. Since after ca15f650a1a914bb9a41131109c46c4e52c5ebb1, -resizing explicitly avoids scrolling, this could result in tabs ending -up scrolled outside of the tab bar when showing the tab widget. - -Fix that by explicitly making the current tab visible in the tab bar's -showEvent handler, which recalculates the scroll offset based on the -actual size. - -This is only reproducible with a tab widget, which lays out the tab bar -for each change and resets the tab bar's layoutDirty flag. Add a test -case there. - -Pick-to: 6.5 6.6 -Fixes: QTBUG-114204 -Change-Id: I1e9506b9dde1dd892291d108dd2c7b675ef99509 -Reviewed-by: Axel Spoerl -Reviewed-by: Jonas Kvinge ---- - src/widgets/widgets/qtabbar.cpp | 2 ++ - .../widgets/widgets/qtabwidget/tst_qtabwidget.cpp | 37 ++++++++++++++++++++++ - 2 files changed, 39 insertions(+) - -diff --git a/src/widgets/widgets/qtabbar.cpp b/src/widgets/widgets/qtabbar.cpp -index 75cb6dd4ea..ea408ec7f6 100644 ---- a/src/widgets/widgets/qtabbar.cpp -+++ b/src/widgets/widgets/qtabbar.cpp -@@ -1656,6 +1656,8 @@ void QTabBar::showEvent(QShowEvent *) - d->refresh(); - if (!d->validIndex(d->currentIndex)) - setCurrentIndex(0); -+ else -+ d->makeVisible(d->currentIndex); - d->updateMacBorderMetrics(); - } - -diff --git a/tests/auto/widgets/widgets/qtabwidget/tst_qtabwidget.cpp b/tests/auto/widgets/widgets/qtabwidget/tst_qtabwidget.cpp -index 00cb26c2d3..eb29933fd1 100644 ---- a/tests/auto/widgets/widgets/qtabwidget/tst_qtabwidget.cpp -+++ b/tests/auto/widgets/widgets/qtabwidget/tst_qtabwidget.cpp -@@ -79,6 +79,9 @@ private slots: - void moveCurrentTab(); - void autoHide(); - -+ void setCurrentBeforeShow_data(); -+ void setCurrentBeforeShow(); -+ - private: - int addPage(); - void removePage(int index); -@@ -750,5 +753,39 @@ void tst_QTabWidget::autoHide() - QVERIFY(heightForWidth1 > tabWidget.heightForWidth(20)); - } - -+void tst_QTabWidget::setCurrentBeforeShow_data() -+{ -+ QTest::addColumn("tabPosition"); -+ QTest::newRow("West") << QTabWidget::West; -+ QTest::newRow("North") << QTabWidget::North; -+ QTest::newRow("East") << QTabWidget::East; -+ QTest::newRow("South") << QTabWidget::South; -+} -+ -+void tst_QTabWidget::setCurrentBeforeShow() -+{ -+ QFETCH(QTabWidget::TabPosition, tabPosition); -+ -+ QTabWidget tabWidget; -+ tabWidget.setTabPosition(tabPosition); -+ -+ QPixmap pm(50, 50); -+ pm.fill(Qt::red); -+ const QIcon icon(pm); -+ for (int i = 0; i < 4; ++i) -+ tabWidget.addTab(new QWidget, icon, QString("Tab %1").arg(i)); -+ -+ // the tab widget has space for the entire tab bar -+ tabWidget.resize(tabWidget.tabBar()->sizeHint() + QSize(50, 50)); -+ tabWidget.setCurrentIndex(2); -+ tabWidget.show(); -+ QVERIFY(QTest::qWaitForWindowExposed(&tabWidget)); -+ -+ QCOMPARE_GE(tabWidget.tabBar()->tabRect(0).x(), 0); -+ QCOMPARE_GE(tabWidget.tabBar()->tabRect(0).y(), 0); -+ -+ QTest::qWait(2000); -+} -+ - QTEST_MAIN(tst_QTabWidget) - #include "tst_qtabwidget.moc" --- -cgit v1.2.3 - diff --git a/CVE-2023-38197-qtbase-6.5.diff b/CVE-2023-38197-qtbase-6.5.diff new file mode 100644 index 0000000..bf92a20 --- /dev/null +++ b/CVE-2023-38197-qtbase-6.5.diff @@ -0,0 +1,388 @@ +From c216c3d9859a20b3aeec985512e89316423fc3a8 Mon Sep 17 00:00:00 2001 +From: Axel Spoerl +Date: Fri, 30 Jun 2023 12:43:59 +0200 +Subject: [PATCH] QXmlStreamReader: Raise error on unexpected tokens + +QXmlStreamReader accepted multiple DOCTYPE elements, containing DTD +fragments in the XML prolog, and in the XML body. +Well-formed but invalid XML files - with multiple DTD fragments in +prolog and body, combined with recursive entity expansions - have +caused infinite loops in QXmlStreamReader. + +This patch implements a token check in QXmlStreamReader. +A stream is allowed to start with an XML prolog. StartDocument +and DOCTYPE elements are only allowed in this prolog, which +may also contain ProcessingInstruction and Comment elements. +As soon as anything else is seen, the prolog ends. +After that, the prolog-specific elements are treated as unexpected. +Furthermore, the prolog can contain at most one DOCTYPE element. + +Update the documentation to reflect the new behavior. +Add an autotest that checks the new error cases are correctly detected, +and no error is raised for legitimate input. + +The original OSS-Fuzz files (see bug reports) are not included in this +patch for file size reasons. They have been tested manually. Each of +them has more than one DOCTYPE element, causing infinite loops in +recursive entity expansions. The newly implemented functionality +detects those invalid DTD fragments. By raising an error, it aborts +stream reading before an infinite loop occurs. + +Thanks to OSS-Fuzz for finding this. + +Fixes: QTBUG-92113 +Fixes: QTBUG-95188 +Change-Id: I0a082b9188b2eee50b396c4d5b1c9e1fd237bbdd +Reviewed-by: Volker Hilsheimer +(cherry picked from commit c4301be7d5f94852e1b17f2c2989d5ca807855d4) +--- + +diff --git a/src/corelib/serialization/qxmlstream.cpp b/src/corelib/serialization/qxmlstream.cpp +index 6e34d4d..cf46d69 100644 +--- a/src/corelib/serialization/qxmlstream.cpp ++++ b/src/corelib/serialization/qxmlstream.cpp +@@ -185,7 +185,7 @@ + addData() or by waiting for it to arrive on the device(). + + \value UnexpectedElementError The parser encountered an element +- that was different to those it expected. ++ or token that was different to those it expected. + + */ + +@@ -322,13 +322,34 @@ + + QXmlStreamReader is a well-formed XML 1.0 parser that does \e not + include external parsed entities. As long as no error occurs, the +- application code can thus be assured that the data provided by the +- stream reader satisfies the W3C's criteria for well-formed XML. For +- example, you can be certain that all tags are indeed nested and +- closed properly, that references to internal entities have been +- replaced with the correct replacement text, and that attributes have +- been normalized or added according to the internal subset of the +- DTD. ++ application code can thus be assured, that ++ \list ++ \li the data provided by the stream reader satisfies the W3C's ++ criteria for well-formed XML, ++ \li tokens are provided in a valid order. ++ \endlist ++ ++ Unless QXmlStreamReader raises an error, it guarantees the following: ++ \list ++ \li All tags are nested and closed properly. ++ \li References to internal entities have been replaced with the ++ correct replacement text. ++ \li Attributes have been normalized or added according to the ++ internal subset of the \l DTD. ++ \li Tokens of type \l StartDocument happen before all others, ++ aside from comments and processing instructions. ++ \li At most one DOCTYPE element (a token of type \l DTD) is present. ++ \li If present, the DOCTYPE appears before all other elements, ++ aside from StartDocument, comments and processing instructions. ++ \endlist ++ ++ In particular, once any token of type \l StartElement, \l EndElement, ++ \l Characters, \l EntityReference or \l EndDocument is seen, no ++ tokens of type StartDocument or DTD will be seen. If one is present in ++ the input stream, out of order, an error is raised. ++ ++ \note The token types \l Comment and \l ProcessingInstruction may appear ++ anywhere in the stream. + + If an error occurs while parsing, atEnd() and hasError() return + true, and error() returns the error that occurred. The functions +@@ -659,6 +680,7 @@ + d->token = -1; + return readNext(); + } ++ d->checkToken(); + return d->type; + } + +@@ -743,6 +765,11 @@ + "ProcessingInstruction" + ); + ++static constexpr auto QXmlStreamReader_XmlContextString = qOffsetStringArray( ++ "Prolog", ++ "Body" ++); ++ + /*! + \property QXmlStreamReader::namespaceProcessing + \brief the namespace-processing flag of the stream reader. +@@ -777,6 +804,15 @@ + return QLatin1StringView(QXmlStreamReader_tokenTypeString.at(d->type)); + } + ++/*! ++ \internal ++ \return \param loc (Prolog/Body) as a string. ++ */ ++static constexpr QLatin1StringView contextString(QXmlStreamReaderPrivate::XmlContext ctxt) ++{ ++ return QLatin1StringView(QXmlStreamReader_XmlContextString.at(static_cast(ctxt))); ++} ++ + #endif // QT_NO_XMLSTREAMREADER + + QXmlStreamPrivateTagStack::QXmlStreamPrivateTagStack() +@@ -864,6 +900,8 @@ + + type = QXmlStreamReader::NoToken; + error = QXmlStreamReader::NoError; ++ currentContext = XmlContext::Prolog; ++ foundDTD = false; + } + + /* +@@ -3838,6 +3876,97 @@ + } + } + ++static constexpr bool isTokenAllowedInContext(QXmlStreamReader::TokenType type, ++ QXmlStreamReaderPrivate::XmlContext loc) ++{ ++ switch (type) { ++ case QXmlStreamReader::StartDocument: ++ case QXmlStreamReader::DTD: ++ return loc == QXmlStreamReaderPrivate::XmlContext::Prolog; ++ ++ case QXmlStreamReader::StartElement: ++ case QXmlStreamReader::EndElement: ++ case QXmlStreamReader::Characters: ++ case QXmlStreamReader::EntityReference: ++ case QXmlStreamReader::EndDocument: ++ return loc == QXmlStreamReaderPrivate::XmlContext::Body; ++ ++ case QXmlStreamReader::Comment: ++ case QXmlStreamReader::ProcessingInstruction: ++ return true; ++ ++ case QXmlStreamReader::NoToken: ++ case QXmlStreamReader::Invalid: ++ return false; ++ } ++ ++ // GCC 8.x does not treat __builtin_unreachable() as constexpr ++#if !defined(Q_CC_GNU_ONLY) || (Q_CC_GNU >= 900) ++ Q_UNREACHABLE_RETURN(false); ++#else ++ return false; ++#endif ++} ++ ++/*! ++ \internal ++ \brief QXmlStreamReader::isValidToken ++ \return \c true if \param type is a valid token type. ++ \return \c false if \param type is an unexpected token, ++ which indicates a non-well-formed or invalid XML stream. ++ */ ++bool QXmlStreamReaderPrivate::isValidToken(QXmlStreamReader::TokenType type) ++{ ++ // Don't change currentContext, if Invalid or NoToken occur in the prolog ++ if (type == QXmlStreamReader::Invalid || type == QXmlStreamReader::NoToken) ++ return false; ++ ++ // If a token type gets rejected in the body, there is no recovery ++ const bool result = isTokenAllowedInContext(type, currentContext); ++ if (result || currentContext == XmlContext::Body) ++ return result; ++ ++ // First non-Prolog token observed => switch context to body and check again. ++ currentContext = XmlContext::Body; ++ return isTokenAllowedInContext(type, currentContext); ++} ++ ++/*! ++ \internal ++ Checks token type and raises an error, if it is invalid ++ in the current context (prolog/body). ++ */ ++void QXmlStreamReaderPrivate::checkToken() ++{ ++ Q_Q(QXmlStreamReader); ++ ++ // The token type must be consumed, to keep track if the body has been reached. ++ const XmlContext context = currentContext; ++ const bool ok = isValidToken(type); ++ ++ // Do nothing if an error has been raised already (going along with an unexpected token) ++ if (error != QXmlStreamReader::Error::NoError) ++ return; ++ ++ if (!ok) { ++ raiseError(QXmlStreamReader::UnexpectedElementError, ++ QObject::tr("Unexpected token type %1 in %2.") ++ .arg(q->tokenString(), contextString(context))); ++ return; ++ } ++ ++ if (type != QXmlStreamReader::DTD) ++ return; ++ ++ // Raise error on multiple DTD tokens ++ if (foundDTD) { ++ raiseError(QXmlStreamReader::UnexpectedElementError, ++ QObject::tr("Found second DTD token in %1.").arg(contextString(context))); ++ } else { ++ foundDTD = true; ++ } ++} ++ + /*! + \fn bool QXmlStreamAttributes::hasAttribute(QAnyStringView qualifiedName) const + +diff --git a/src/corelib/serialization/qxmlstream_p.h b/src/corelib/serialization/qxmlstream_p.h +index 070424a..f09adaa 100644 +--- a/src/corelib/serialization/qxmlstream_p.h ++++ b/src/corelib/serialization/qxmlstream_p.h +@@ -297,6 +297,17 @@ + QStringDecoder decoder; + bool atEnd; + ++ enum class XmlContext ++ { ++ Prolog, ++ Body, ++ }; ++ ++ XmlContext currentContext = XmlContext::Prolog; ++ bool foundDTD = false; ++ bool isValidToken(QXmlStreamReader::TokenType type); ++ void checkToken(); ++ + /*! + \sa setType() + */ +diff --git a/tests/auto/corelib/serialization/qxmlstream/tokenError/dtdInBody.xml b/tests/auto/corelib/serialization/qxmlstream/tokenError/dtdInBody.xml +new file mode 100644 +index 0000000..1c3ca4e +--- /dev/null ++++ b/tests/auto/corelib/serialization/qxmlstream/tokenError/dtdInBody.xml +@@ -0,0 +1,20 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++]> ++ ++ ++ tst_QXmlStream ++ ++ ++ ++ ++ ]> ++ +diff --git a/tests/auto/corelib/serialization/qxmlstream/tokenError/multipleDtd.xml b/tests/auto/corelib/serialization/qxmlstream/tokenError/multipleDtd.xml +new file mode 100644 +index 0000000..cd398c0 +--- /dev/null ++++ b/tests/auto/corelib/serialization/qxmlstream/tokenError/multipleDtd.xml +@@ -0,0 +1,20 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++]> ++ ++ ++ ++]> ++ ++ ++ tst_QXmlStream ++ ++ +diff --git a/tests/auto/corelib/serialization/qxmlstream/tokenError/wellFormed.xml b/tests/auto/corelib/serialization/qxmlstream/tokenError/wellFormed.xml +new file mode 100644 +index 0000000..1b61a3f +--- /dev/null ++++ b/tests/auto/corelib/serialization/qxmlstream/tokenError/wellFormed.xml +@@ -0,0 +1,15 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++]> ++ ++ ++ tst_QXmlStream ++ ++ +diff --git a/tests/auto/corelib/serialization/qxmlstream/tst_qxmlstream.cpp b/tests/auto/corelib/serialization/qxmlstream/tst_qxmlstream.cpp +index 2a340e1..30f5499 100644 +--- a/tests/auto/corelib/serialization/qxmlstream/tst_qxmlstream.cpp ++++ b/tests/auto/corelib/serialization/qxmlstream/tst_qxmlstream.cpp +@@ -590,6 +590,9 @@ + + void entityExpansionLimit() const; + ++ void tokenErrorHandling_data() const; ++ void tokenErrorHandling() const; ++ + private: + static QByteArray readFile(const QString &filename); + +@@ -1855,5 +1858,41 @@ + QCOMPARE(reader.error(), errorType); + } + ++void tst_QXmlStream::tokenErrorHandling_data() const ++{ ++ QTest::addColumn("fileName"); ++ QTest::addColumn("expectedError"); ++ QTest::addColumn("errorKeyWord"); ++ ++ constexpr auto invalid = QXmlStreamReader::Error::UnexpectedElementError; ++ constexpr auto valid = QXmlStreamReader::Error::NoError; ++ QTest::newRow("DtdInBody") << "dtdInBody.xml" << invalid << "DTD"; ++ QTest::newRow("multipleDTD") << "multipleDtd.xml" << invalid << "second DTD"; ++ QTest::newRow("wellFormed") << "wellFormed.xml" << valid << ""; ++} ++ ++void tst_QXmlStream::tokenErrorHandling() const ++{ ++ QFETCH(const QString, fileName); ++ QFETCH(const QXmlStreamReader::Error, expectedError); ++ QFETCH(const QString, errorKeyWord); ++ ++ const QDir dir(QFINDTESTDATA("tokenError")); ++ QFile file(dir.absoluteFilePath(fileName)); ++ ++ // Cross-compiling: File will be on host only ++ if (!file.exists()) ++ QSKIP("Testfile not found."); ++ ++ file.open(QIODevice::ReadOnly); ++ QXmlStreamReader reader(&file); ++ while (!reader.atEnd()) ++ reader.readNext(); ++ ++ QCOMPARE(reader.error(), expectedError); ++ if (expectedError != QXmlStreamReader::Error::NoError) ++ QVERIFY(reader.errorString().contains(errorKeyWord)); ++} ++ + #include "tst_qxmlstream.moc" + // vim: et:ts=4:sw=4:sts=4 diff --git a/qt6-base.changes b/qt6-base.changes index ab9a6d6..e85aa58 100644 --- a/qt6-base.changes +++ b/qt6-base.changes @@ -1,3 +1,17 @@ +------------------------------------------------------------------- +Thu Jul 20 08:34:44 UTC 2023 - Christophe Marin + +- Use a mirror to download sources for all Qt packages. Upstream + servers are very slow since a couple weeks. +- Update to 6.5.2 + * https://www.qt.io/blog/qt-6.5.2-released-1 +- Drop patches, merged upstream: + * 0001-Schannel-Reject-certificate-not-signed-by-a-configur.patch + * 0001-Ssl-Copy-the-on-demand-cert-loading-bool-from-defaul.patch + * 0001-tabbar-fix.patch +- Add patch: + * CVE-2023-38197-qtbase-6.5.diff (boo#38197, CVE-2023-38197) + ------------------------------------------------------------------- Fri Jun 30 15:13:07 UTC 2023 - Christophe Marin diff --git a/qt6-base.spec b/qt6-base.spec index 65b7039..8daa5a3 100644 --- a/qt6-base.spec +++ b/qt6-base.spec @@ -16,7 +16,7 @@ # -%define real_version 6.5.1 +%define real_version 6.5.2 %define short_version 6.5 %define tar_name qtbase-everywhere-src %define tar_suffix %{nil} @@ -30,18 +30,16 @@ %global with_gles 1 %endif Name: qt6-base%{?pkg_suffix} -Version: 6.5.1 +Version: 6.5.2 Release: 0 Summary: Qt 6 core components (Core, Gui, Widgets, Network...) # Legal: qtpaths is BSD-3-Clause License: LGPL-2.1-with-Qt-Company-Qt-exception-1.1 OR LGPL-3.0-only URL: https://www.qt.io -Source: https://download.qt.io/official_releases/qt/%{short_version}/%{real_version}%{tar_suffix}/submodules/%{tar_name}-%{real_version}%{tar_suffix}.tar.xz +Source: https://www.nic.funet.fi/pub/mirrors/download.qt-project.org/official_releases/qt/%{short_version}/%{real_version}%{tar_suffix}/submodules/%{tar_name}-%{real_version}%{tar_suffix}.tar.xz Source99: qt6-base-rpmlintrc # Patches 0-100 are upstream patches # -Patch0: 0001-Schannel-Reject-certificate-not-signed-by-a-configur.patch -Patch1: 0001-Ssl-Copy-the-on-demand-cert-loading-bool-from-defaul.patch -Patch2: 0001-tabbar-fix.patch +Patch0: CVE-2023-38197-qtbase-6.5.diff # Patches 100-200 are openSUSE and/or non-upstream(able) patches # Patch100: 0001-Tell-the-truth-about-private-API.patch # No need to pollute the library dir with object files, install them in the qt6 subfolder diff --git a/qtbase-everywhere-src-6.5.1.tar.xz b/qtbase-everywhere-src-6.5.1.tar.xz deleted file mode 100644 index e962271..0000000 --- a/qtbase-everywhere-src-6.5.1.tar.xz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:db56fa1f4303a1189fe33418d25d1924931c7aef237f89eea9de58e858eebfed -size 48287392 diff --git a/qtbase-everywhere-src-6.5.2.tar.xz b/qtbase-everywhere-src-6.5.2.tar.xz new file mode 100644 index 0000000..1ad10e5 --- /dev/null +++ b/qtbase-everywhere-src-6.5.2.tar.xz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3db4c729b4d80a9d8fda8dd77128406353baff4755ca619177eda4cddae71269 +size 48410716 From 8f0373f8b1358d635a4bc16707f2d2c34e2bde7b71fb97ee605429c05841a93d Mon Sep 17 00:00:00 2001 From: Christophe Marin Date: Sat, 22 Jul 2023 06:59:12 +0000 Subject: [PATCH 2/2] OBS-URL: https://build.opensuse.org/package/show/KDE:Qt6/qt6-base?expand=0&rev=64 --- qt6-base.changes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt6-base.changes b/qt6-base.changes index e85aa58..bde94d7 100644 --- a/qt6-base.changes +++ b/qt6-base.changes @@ -10,7 +10,7 @@ Thu Jul 20 08:34:44 UTC 2023 - Christophe Marin * 0001-Ssl-Copy-the-on-demand-cert-loading-bool-from-defaul.patch * 0001-tabbar-fix.patch - Add patch: - * CVE-2023-38197-qtbase-6.5.diff (boo#38197, CVE-2023-38197) + * CVE-2023-38197-qtbase-6.5.diff (boo#1213326, CVE-2023-38197) ------------------------------------------------------------------- Fri Jun 30 15:13:07 UTC 2023 - Christophe Marin