--- Begin Message ---
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
--- End Message ---