Package: release.debian.org Severity: normal Tags: bookworm User: release.debian....@packages.debian.org Usertags: pu X-Debbugs-Cc: qtbase-opensource-...@packages.debian.org Control: affects -1 + src:qtbase-opensource-src
[ Reason ] The main goal of the proposed update is to fix bug #1055280: broken Unicode support in libqt5sql5-odbc because of patch for CVE-2023-24607. Additionally, I backported fixes for three more CVEs which were discovered in the meantime: CVE-2023-34410, CVE-2023-37369 and CVE-2023-38197. [ Impact ] The ODBC backend of Qt SQL is broken without this fix. In particular, it's known to be broken when using it with Microsoft SQL server. [ Tests ] Unfortunately we do not run upstream testsuite in qtbase, due to known issues with it. But all patches come from Qt upstream, where they have been tested of course. [ Risks ] - The patches to fix Unicode regressions are quite trivial. - The patch to fix CVE-2023-34410 is quite trivial too. - This cannot be said about the remaining two patches (for CVE-2023-37369 and CVE-2023-38197), however they are present in Debian unstable since version 5.15.10+dfsg-3 from 2023-07-27, and nobody has complained since then. Also, all these CVE patches are present in KDE's Qt 5.15 branch. In fact, our CVE-2023-37369.diff is based on the KDE's patch. If you consider those last two patches risky, I will be happy to upload without them, that is, with the regression fixes and CVE-2023-34410 only. This way the upload will be equivalent to 5.15.8+dfsg-13 from 2023-07-05. [ 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 patches to fix regression caused by CVE-2023-24607.diff (closes: #1055280). * Backport fixes for three CVEs from Debian unstable: - CVE-2023-34410: use of system CA certificates when not wanted (closes: #1037210). - CVE-2023-37369: potential buffer overflow in QXmlStreamReader. - CVE-2023-38197: infinite loop in XML recursive entity expansion (closes: #1041105). [ Other info ] See also the Security Tracker: https://security-tracker.debian.org/tracker/source-package/qtbase-opensource-src -- Dmitry Shachnev
diff --git a/debian/changelog b/debian/changelog index 7215917..526e1c3 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,19 @@ +qtbase-opensource-src (5.15.8+dfsg-11+deb12u1) UNRELEASED; urgency=medium + + [ Alexander Volkov ] + * Backport upstream patches to fix regression caused by CVE-2023-24607.diff + (closes: #1055280). + + [ Dmitry Shachnev ] + * Backport fixes for three CVEs from Debian unstable: + - CVE-2023-34410: use of system CA certificates when not wanted + (closes: #1037210). + - CVE-2023-37369: potential buffer overflow in QXmlStreamReader. + - CVE-2023-38197: infinite loop in XML recursive entity expansion + (closes: #1041105). + + -- Debian Qt/KDE Maintainers <debian-qt-...@lists.debian.org> Sun, 05 Nov 2023 18:18:43 +0300 + qtbase-opensource-src (5.15.8+dfsg-11) unstable; urgency=medium * Rename the patches for consistency and add DEP-3 headers. diff --git a/debian/patches/CVE-2023-34410.diff b/debian/patches/CVE-2023-34410.diff new file mode 100644 index 0000000..bc5e30d --- /dev/null +++ b/debian/patches/CVE-2023-34410.diff @@ -0,0 +1,34 @@ +Description: 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. + . + 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. +Origin: upstream, https://code.qt.io/cgit/qt/qtbase.git/commit/?id=57ba6260c0801055 +Last-Update: 2023-06-08 + +--- a/src/network/ssl/qsslsocket.cpp ++++ b/src/network/ssl/qsslsocket.cpp +@@ -2221,6 +2221,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; + } + + /*! +@@ -2470,6 +2474,7 @@ void QSslConfigurationPrivate::deepCopyD + 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/debian/patches/CVE-2023-37369.diff b/debian/patches/CVE-2023-37369.diff new file mode 100644 index 0000000..8b0af91 --- /dev/null +++ b/debian/patches/CVE-2023-37369.diff @@ -0,0 +1,289 @@ +Description: QXmlStreamReader: make fastScanName() indicate parsing status to callers + This fixes a crash while parsing an XML file with garbage data, the file + starts with '<' then garbage data: + - The loop in the parse() keeps iterating until it hits "case 262:", + which calls fastScanName() + - fastScanName() iterates over the text buffer scanning for the + attribute name (e.g. "xml:lang"), until it finds ':' + - Consider a Value val, fastScanName() is called on it, it would set + val.prefix to a number > val.len, then it would hit the 4096 condition + and return (returned 0, now it returns the equivalent of + std::null_opt), which means that val.len doesn't get modified, making + it smaller than val.prefix + - The code would try constructing an XmlStringRef with negative length, + which would hit an assert in one of QStringView's constructors + . + Add an assert to the XmlStringRef constructor. + . + Add unittest based on the file from the bug report. + . + Credit to OSS-Fuzz. +Origin: upstream, commits + https://code.qt.io/cgit/qt/qtbase.git/commit/?id=1a423ce4372d18a7 + https://code.qt.io/cgit/qt/qtbase.git/commit/?id=6326bec46a618c72 + https://code.qt.io/cgit/qt/qtbase.git/commit/?id=bdc8dc51380d2ce4 + https://code.qt.io/cgit/qt/qtbase.git/commit/?id=3bc3b8d69a291aa5 + . + Based on KDE's backport: + https://invent.kde.org/qt/qt/qtbase/-/merge_requests/263 +Last-Update: 2023-07-15 + +--- a/src/corelib/serialization/qxmlstream.cpp ++++ b/src/corelib/serialization/qxmlstream.cpp +@@ -1302,15 +1302,18 @@ inline int QXmlStreamReaderPrivate::fast + return n; + } + +-inline int QXmlStreamReaderPrivate::fastScanName(int *prefix) ++// Fast scan an XML attribute name (e.g. "xml:lang"). ++inline QXmlStreamReaderPrivate::FastScanNameResult ++QXmlStreamReaderPrivate::fastScanName(Value *val) + { + int n = 0; + uint c; + while ((c = getChar()) != StreamEOF) { + if (n >= 4096) { + // This is too long to be a sensible name, and +- // can exhaust memory +- return 0; ++ // can exhaust memory, or the range of decltype(*prefix) ++ raiseNamePrefixTooLongError(); ++ return {}; + } + switch (c) { + case '\n': +@@ -1339,23 +1342,23 @@ inline int QXmlStreamReaderPrivate::fast + case '+': + case '*': + putChar(c); +- if (prefix && *prefix == n+1) { +- *prefix = 0; ++ if (val && val->prefix == n + 1) { ++ val->prefix = 0; + putChar(':'); + --n; + } +- return n; ++ return FastScanNameResult(n); + case ':': +- if (prefix) { +- if (*prefix == 0) { +- *prefix = n+2; ++ if (val) { ++ if (val->prefix == 0) { ++ val->prefix = n + 2; + } else { // only one colon allowed according to the namespace spec. + putChar(c); +- return n; ++ return FastScanNameResult(n); + } + } else { + putChar(c); +- return n; ++ return FastScanNameResult(n); + } + Q_FALLTHROUGH(); + default: +@@ -1364,12 +1367,12 @@ inline int QXmlStreamReaderPrivate::fast + } + } + +- if (prefix) +- *prefix = 0; ++ if (val) ++ val->prefix = 0; + int pos = textBuffer.size() - n; + putString(textBuffer, pos); + textBuffer.resize(pos); +- return 0; ++ return FastScanNameResult(0); + } + + enum NameChar { NameBeginning, NameNotBeginning, NotName }; +@@ -1878,6 +1881,14 @@ void QXmlStreamReaderPrivate::raiseWellF + raiseError(QXmlStreamReader::NotWellFormedError, message); + } + ++void QXmlStreamReaderPrivate::raiseNamePrefixTooLongError() ++{ ++ // TODO: add a ImplementationLimitsExceededError and use it instead ++ raiseError(QXmlStreamReader::NotWellFormedError, ++ QXmlStream::tr("Length of XML attribute name exceeds implementation limits (4KiB " ++ "characters).")); ++} ++ + void QXmlStreamReaderPrivate::parseError() + { + +--- a/src/corelib/serialization/qxmlstream.g ++++ b/src/corelib/serialization/qxmlstream.g +@@ -516,7 +516,16 @@ public: + int fastScanLiteralContent(); + int fastScanSpace(); + int fastScanContentCharList(); +- int fastScanName(int *prefix = nullptr); ++ ++ struct FastScanNameResult { ++ FastScanNameResult() : ok(false) {} ++ explicit FastScanNameResult(int len) : addToLen(len), ok(true) { } ++ operator bool() { return ok; } ++ int operator*() { Q_ASSERT(ok); return addToLen; } ++ int addToLen; ++ bool ok; ++ }; ++ FastScanNameResult fastScanName(Value *val = nullptr); + inline int fastScanNMTOKEN(); + + +@@ -525,6 +534,7 @@ public: + + void raiseError(QXmlStreamReader::Error error, const QString& message = QString()); + void raiseWellFormedError(const QString &message); ++ void raiseNamePrefixTooLongError(); + + QXmlStreamEntityResolver *entityResolver; + +@@ -1809,7 +1819,12 @@ space_opt ::= space; + qname ::= LETTER; + /. + case $rule_number: { +- sym(1).len += fastScanName(&sym(1).prefix); ++ Value &val = sym(1); ++ if (auto res = fastScanName(&val)) ++ val.len += *res; ++ else ++ return false; ++ + if (atEnd) { + resume($rule_number); + return false; +@@ -1820,7 +1835,11 @@ qname ::= LETTER; + name ::= LETTER; + /. + case $rule_number: +- sym(1).len += fastScanName(); ++ if (auto res = fastScanName()) ++ sym(1).len += *res; ++ else ++ return false; ++ + if (atEnd) { + resume($rule_number); + return false; +--- a/src/corelib/serialization/qxmlstream_p.h ++++ b/src/corelib/serialization/qxmlstream_p.h +@@ -1005,7 +1005,16 @@ public: + int fastScanLiteralContent(); + int fastScanSpace(); + int fastScanContentCharList(); +- int fastScanName(int *prefix = nullptr); ++ ++ struct FastScanNameResult { ++ FastScanNameResult() : ok(false) {} ++ explicit FastScanNameResult(int len) : addToLen(len), ok(true) { } ++ operator bool() { return ok; } ++ int operator*() { Q_ASSERT(ok); return addToLen; } ++ int addToLen; ++ bool ok; ++ }; ++ FastScanNameResult fastScanName(Value *val = nullptr); + inline int fastScanNMTOKEN(); + + +@@ -1014,6 +1023,7 @@ public: + + void raiseError(QXmlStreamReader::Error error, const QString& message = QString()); + void raiseWellFormedError(const QString &message); ++ void raiseNamePrefixTooLongError(); + + QXmlStreamEntityResolver *entityResolver; + +@@ -1937,7 +1947,12 @@ bool QXmlStreamReaderPrivate::parse() + break; + + case 262: { +- sym(1).len += fastScanName(&sym(1).prefix); ++ Value &val = sym(1); ++ if (auto res = fastScanName(&val)) ++ val.len += *res; ++ else ++ return false; ++ + if (atEnd) { + resume(262); + return false; +@@ -1945,7 +1960,11 @@ bool QXmlStreamReaderPrivate::parse() + } break; + + case 263: +- sym(1).len += fastScanName(); ++ if (auto res = fastScanName()) ++ sym(1).len += *res; ++ else ++ return false; ++ + if (atEnd) { + resume(263); + return false; +--- a/tests/auto/corelib/serialization/qxmlstream/tst_qxmlstream.cpp ++++ b/tests/auto/corelib/serialization/qxmlstream/tst_qxmlstream.cpp +@@ -39,6 +39,7 @@ + + #include "qc14n.h" + ++Q_DECLARE_METATYPE(QXmlStreamReader::Error) + Q_DECLARE_METATYPE(QXmlStreamReader::ReadElementTextBehaviour) + + static const char *const catalogFile = "XML-Test-Suite/xmlconf/finalCatalog.xml"; +@@ -580,6 +581,8 @@ private slots: + void readBack() const; + void roundTrip() const; + void roundTrip_data() const; ++ void test_fastScanName_data() const; ++ void test_fastScanName() const; + + void entityExpansionLimit() const; + +@@ -1812,5 +1815,42 @@ void tst_QXmlStream::roundTrip() const + QCOMPARE(out, in); + } + ++void tst_QXmlStream::test_fastScanName_data() const ++{ ++ QTest::addColumn<QByteArray>("data"); ++ QTest::addColumn<QXmlStreamReader::Error>("errorType"); ++ ++ // 4096 is the limit in QXmlStreamReaderPrivate::fastScanName() ++ ++ QByteArray arr = "<a:" + QByteArray("b").repeated(4096 - 1); ++ QTest::newRow("data1") << arr << QXmlStreamReader::PrematureEndOfDocumentError; ++ ++ arr = "<a:" + QByteArray("b").repeated(4096); ++ QTest::newRow("data2") << arr << QXmlStreamReader::NotWellFormedError; ++ ++ arr = "<" + QByteArray("a").repeated(4000) + ":" + QByteArray("b").repeated(96); ++ QTest::newRow("data3") << arr << QXmlStreamReader::PrematureEndOfDocumentError; ++ ++ arr = "<" + QByteArray("a").repeated(4000) + ":" + QByteArray("b").repeated(96 + 1); ++ QTest::newRow("data4") << arr << QXmlStreamReader::NotWellFormedError; ++ ++ arr = "<" + QByteArray("a").repeated(4000 + 1) + ":" + QByteArray("b").repeated(96); ++ QTest::newRow("data5") << arr << QXmlStreamReader::NotWellFormedError; ++} ++ ++void tst_QXmlStream::test_fastScanName() const ++{ ++ QFETCH(QByteArray, data); ++ QFETCH(QXmlStreamReader::Error, errorType); ++ ++ QXmlStreamReader reader(data); ++ QXmlStreamReader::TokenType tokenType; ++ while (!reader.atEnd()) ++ tokenType = reader.readNext(); ++ ++ QCOMPARE(tokenType, QXmlStreamReader::Invalid); ++ QCOMPARE(reader.error(), errorType); ++} ++ + #include "tst_qxmlstream.moc" + // vim: et:ts=4:sw=4:sts=4 diff --git a/debian/patches/CVE-2023-38197.diff b/debian/patches/CVE-2023-38197.diff new file mode 100644 index 0000000..99acf0c --- /dev/null +++ b/debian/patches/CVE-2023-38197.diff @@ -0,0 +1,364 @@ +Description: 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. +Origin: upstream, https://download.qt.io/official_releases/qt/5.15/CVE-2023-38197-qtbase-5.15.diff +Last-Update: 2023-07-15 + +--- a/src/corelib/serialization/qxmlstream.cpp ++++ b/src/corelib/serialization/qxmlstream.cpp +@@ -160,7 +160,7 @@ enum { StreamEOF = ~0U }; + 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. + + */ + +@@ -295,13 +295,34 @@ QXmlStreamEntityResolver *QXmlStreamRead + + 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 +@@ -620,6 +641,7 @@ QXmlStreamReader::TokenType QXmlStreamRe + d->token = -1; + return readNext(); + } ++ d->checkToken(); + return d->type; + } + +@@ -740,6 +762,14 @@ static const short QXmlStreamReader_toke + }; + + ++static const char QXmlStreamReader_XmlContextString[] = ++ "Prolog\0" ++ "Body\0"; ++ ++static const short QXmlStreamReader_XmlContextString_indices[] = { ++ 0, 7 ++}; ++ + /*! + \property QXmlStreamReader::namespaceProcessing + The namespace-processing flag of the stream reader +@@ -775,6 +805,16 @@ QString QXmlStreamReader::tokenString() + QXmlStreamReader_tokenTypeString_indices[d->type]); + } + ++/*! ++ \internal ++ \return \param ctxt (Prolog/Body) as a string. ++ */ ++QString contextString(QXmlStreamReaderPrivate::XmlContext ctxt) ++{ ++ return QLatin1String(QXmlStreamReader_XmlContextString + ++ QXmlStreamReader_XmlContextString_indices[static_cast<int>(ctxt)]); ++} ++ + #endif // QT_NO_XMLSTREAMREADER + + QXmlStreamPrivateTagStack::QXmlStreamPrivateTagStack() +@@ -866,6 +906,8 @@ void QXmlStreamReaderPrivate::init() + + type = QXmlStreamReader::NoToken; + error = QXmlStreamReader::NoError; ++ currentContext = XmlContext::Prolog; ++ foundDTD = false; + } + + /* +@@ -4061,6 +4103,92 @@ void QXmlStreamWriter::writeCurrentToken + } + } + ++static 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; ++ default: ++ return false; ++ } ++} ++ ++/*! ++ \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, ++ QLatin1String("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, ++ QLatin1String("Found second DTD token in %1.").arg(contextString(context))); ++ } else { ++ foundDTD = true; ++ } ++} ++ + /*! + \fn bool QXmlStreamAttributes::hasAttribute(const QString &qualifiedName) const + \since 4.5 +--- a/src/corelib/serialization/qxmlstream_p.h ++++ b/src/corelib/serialization/qxmlstream_p.h +@@ -804,6 +804,17 @@ public: + #endif + bool atEnd; + ++ enum class XmlContext ++ { ++ Prolog, ++ Body, ++ }; ++ ++ XmlContext currentContext = XmlContext::Prolog; ++ bool foundDTD = false; ++ bool isValidToken(QXmlStreamReader::TokenType type); ++ void checkToken(); ++ + /*! + \sa setType() + */ +--- /dev/null ++++ b/tests/auto/corelib/serialization/qxmlstream/tokenError/dtdInBody.xml +@@ -0,0 +1,20 @@ ++<!DOCTYPE TEST [ ++ <!ELEMENT TESTATTRIBUTE (CASE+)> ++ <!ELEMENT CASE (CLASS, FUNCTION)> ++ <!ELEMENT CLASS (#PCDATA)> ++ ++ <!-- adding random ENTITY statement, as this is typical DTD content --> ++ <!ENTITY unite "∪"> ++ ++ <!ATTLIST CASE CLASS CDATA #REQUIRED> ++]> ++<TEST> ++ <CASE> ++ <CLASS>tst_QXmlStream</CLASS> ++ </CASE> ++ <!-- invalid DTD in XML body follows --> ++ <!DOCTYPE DTDTEST [ ++ <!ELEMENT RESULT (CASE+)> ++ <!ATTLIST RESULT OUTPUT CDATA #REQUIRED> ++ ]> ++</TEST> +--- /dev/null ++++ b/tests/auto/corelib/serialization/qxmlstream/tokenError/multipleDtd.xml +@@ -0,0 +1,20 @@ ++<!DOCTYPE TEST [ ++ <!ELEMENT TESTATTRIBUTE (CASE+)> ++ <!ELEMENT CASE (CLASS, FUNCTION, DATASET, COMMENTS)> ++ <!ELEMENT CLASS (#PCDATA)> ++ ++ <!-- adding random ENTITY statements, as this is typical DTD content --> ++ <!ENTITY iff "⇔"> ++ ++ <!ATTLIST CASE CLASS CDATA #REQUIRED> ++]> ++<!-- invalid second DTD follows --> ++<!DOCTYPE SECOND [ ++ <!ELEMENT SECONDATTRIBUTE (#PCDATA)> ++ <!ENTITY on "∘"> ++]> ++<TEST> ++ <CASE> ++ <CLASS>tst_QXmlStream</CLASS> ++ </CASE> ++</TEST> +--- /dev/null ++++ b/tests/auto/corelib/serialization/qxmlstream/tokenError/wellFormed.xml +@@ -0,0 +1,15 @@ ++<!DOCTYPE TEST [ ++ <!ELEMENT TESTATTRIBUTE (CASE+)> ++ <!ELEMENT CASE (CLASS, FUNCTION, DATASET, COMMENTS)> ++ <!ELEMENT CLASS (#PCDATA)> ++ ++ <!-- adding random ENTITY statements, as this is typical DTD content --> ++ <!ENTITY unite "∪"> ++ ++ <!ATTLIST CASE CLASS CDATA #REQUIRED> ++]> ++<TEST> ++ <CASE> ++ <CLASS>tst_QXmlStream</CLASS> ++ </CASE> ++</TEST> +--- a/tests/auto/corelib/serialization/qxmlstream/tst_qxmlstream.cpp ++++ b/tests/auto/corelib/serialization/qxmlstream/tst_qxmlstream.cpp +@@ -586,6 +586,9 @@ private slots: + + void entityExpansionLimit() const; + ++ void tokenErrorHandling_data() const; ++ void tokenErrorHandling() const; ++ + private: + static QByteArray readFile(const QString &filename); + +@@ -1852,5 +1855,42 @@ void tst_QXmlStream::test_fastScanName() + QCOMPARE(reader.error(), errorType); + } + ++void tst_QXmlStream::tokenErrorHandling_data() const ++{ ++ qRegisterMetaType<QXmlStreamReader::Error>(); ++ QTest::addColumn<QString>("fileName"); ++ QTest::addColumn<QXmlStreamReader::Error>("expectedError"); ++ QTest::addColumn<QString>("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/debian/patches/series b/debian/patches/series index b3c675f..52659ab 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -18,6 +18,11 @@ qshapedpixmapwindow_no_tooltip.diff CVE-2023-32763.diff CVE-2023-32762.diff CVE-2023-33285.diff +sql_odbc_more_unicode_checks.diff +sql_odbc_fix_unicode_check.diff +CVE-2023-34410.diff +CVE-2023-37369.diff +CVE-2023-38197.diff # Debian specific. gnukfreebsd.diff diff --git a/debian/patches/sql_odbc_fix_unicode_check.diff b/debian/patches/sql_odbc_fix_unicode_check.diff new file mode 100644 index 0000000..dd27b32 --- /dev/null +++ b/debian/patches/sql_odbc_fix_unicode_check.diff @@ -0,0 +1,32 @@ +Description: QSQL/ODBC: fix regression (trailing NUL) + When we fixed the callers of toSQLTCHAR() to use the result's size() + instead of the input's (which differ, if sizeof(SQLTCHAR) != 2), we + exposed callers to the append(0), which changes the size() of the + result QVLA. Callers that don't rely on NUL-termination (all?) now saw + an additional training NUL. + . + Fix by not NUL-terminating, and changing the only user of SQL_NTS to + use an explicit length. +Origin: upstream, https://code.qt.io/cgit/qt/qtbase.git/commit/?id=9020034b3b6a3a81 +Last-Update: 2023-06-30 + +--- a/src/plugins/sqldrivers/odbc/qsql_odbc.cpp ++++ b/src/plugins/sqldrivers/odbc/qsql_odbc.cpp +@@ -125,7 +125,6 @@ inline static QVarLengthArray<SQLTCHAR> + { + QVarLengthArray<SQLTCHAR> result; + toSQLTCHARImpl(result, input); +- result.append(0); // make sure it's null terminated, doesn't matter if it already is, it does if it isn't. + return result; + } + +@@ -2119,7 +2118,8 @@ void QODBCDriverPrivate::checkUnicode() + QLatin1String("select 'test' from dual"), + }; + for (const auto &statement : statements) { +- r = SQLExecDirect(hStmt, toSQLTCHAR(statement).data(), SQL_NTS); ++ auto encoded = toSQLTCHAR(statement); ++ r = SQLExecDirect(hStmt, encoded.data(), SQLINTEGER(encoded.size())); + if (r == SQL_SUCCESS) + break; + } diff --git a/debian/patches/sql_odbc_more_unicode_checks.diff b/debian/patches/sql_odbc_more_unicode_checks.diff new file mode 100644 index 0000000..de7d2d9 --- /dev/null +++ b/debian/patches/sql_odbc_more_unicode_checks.diff @@ -0,0 +1,35 @@ +Description: SQL/ODBC: add another check to detect unicode availability in driver + Since ODBC does not have a direct way finding out if unicode is + supported by the underlying driver the ODBC plugin does some checks. As + a last resort a sql statement is executed which returns a string. But + even this may fail because the select statement has no FROM part which + is rejected by at least Oracle does not allow. Therefore add another + query which is correct for Oracle & DB2 as a workaround. The question + why the first three statements to check for unicode availability fail + is still open but can't be checked since I've no access to an oracle + database. +Origin: upstream, https://code.qt.io/cgit/qt/qtbase.git/commit/?id=f19320748d282b1e +Last-Update: 2023-06-30 + +--- a/src/plugins/sqldrivers/odbc/qsql_odbc.cpp ++++ b/src/plugins/sqldrivers/odbc/qsql_odbc.cpp +@@ -2111,7 +2111,18 @@ void QODBCDriverPrivate::checkUnicode() + hDbc, + &hStmt); + +- r = SQLExecDirect(hStmt, toSQLTCHAR(QLatin1String("select 'test'")).data(), SQL_NTS); ++ // for databases which do not return something useful in SQLGetInfo and are picky about a ++ // 'SELECT' statement without 'FROM' but support VALUE(foo) statement like e.g. DB2 or Oracle ++ const auto statements = { ++ QLatin1String("select 'test'"), ++ QLatin1String("values('test')"), ++ QLatin1String("select 'test' from dual"), ++ }; ++ for (const auto &statement : statements) { ++ r = SQLExecDirect(hStmt, toSQLTCHAR(statement).data(), SQL_NTS); ++ if (r == SQL_SUCCESS) ++ break; ++ } + if(r == SQL_SUCCESS) { + r = SQLFetch(hStmt); + if(r == SQL_SUCCESS) {
signature.asc
Description: PGP signature