[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#1055802: bookworm-pu: package qtbase-opensource-src/5.15.8+dfsg-11+deb12u1



Package: release.debian.org
Severity: normal
Tags: bookworm
User: release.debian.org@packages.debian.org
Usertags: pu
X-Debbugs-Cc: qtbase-opensource-src@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-kde@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 "&#x222a;">
++
++   <!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 "&hArr;">
++
++   <!ATTLIST CASE CLASS CDATA #REQUIRED>
++]>
++<!-- invalid second DTD follows -->
++<!DOCTYPE SECOND [
++   <!ELEMENT SECONDATTRIBUTE (#PCDATA)>
++   <!ENTITY on "&#8728;">
++]>
++<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 "&#x222a;">
++
++   <!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) {

Attachment: signature.asc
Description: PGP signature


Reply to: