Compare commits

...

2 Commits

Author SHA1 Message Date
Jan Grulich 649a85826e Fix incorrect integer overflow check in HTTP2 implementation 2024-01-05 04:45:04 +00:00
Jan Grulich f21267bde2 Fix infinite loops in QXmlStreamReader (CVE-2023-38197)
Resolves: bz#2222771
2023-07-21 11:17:14 +02:00
6 changed files with 538 additions and 1 deletions

2
.qt5-qtbase.metadata Normal file
View File

@ -0,0 +1,2 @@
a5bbeafa6319cd3e666b12ccc722a357de7230be qtbase-everywhere-opensource-src-5.15.9.tar.xz
677b605bf6033bdfa84a676096ec6e77da6e844d kde-5.15-rollup-20230411.patch.gz

View File

@ -0,0 +1,38 @@
From ea63c28efc1d2ecb467b83a34923d12462efa96f Mon Sep 17 00:00:00 2001
From: Marc Mutz <marc.mutz@qt.io>
Date: Tue, 12 Dec 2023 20:51:56 +0100
Subject: [PATCH] HPack: fix a Yoda Condition
Putting the variable on the LHS of a relational operation makes the
expression easier to read. In this case, we find that the whole
expression is nonsensical as an overflow protection, because if
name.size() + value.size() overflows, the result will exactly _not_
be > max() - 32, because UB will have happened.
To be fixed in a follow-up commit.
As a drive-by, add parentheses around the RHS.
Change-Id: I35ce598884c37c51b74756b3bd2734b9aad63c09
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
(cherry picked from commit 658607a34ead214fbacbc2cca44915655c318ea9)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
(cherry picked from commit 4f7efd41740107f90960116700e3134f5e433867)
(cherry picked from commit 13c16b756900fe524f6d9534e8a07aa003c05e0c)
(cherry picked from commit 1d4788a39668fb2dc5912a8d9c4272dc40e99f92)
(cherry picked from commit 87de75b5cc946d196decaa6aef4792a6cac0b6db)
---
diff --git a/src/network/access/http2/hpacktable.cpp b/src/network/access/http2/hpacktable.cpp
index 834214f..ab166a6 100644
--- a/src/network/access/http2/hpacktable.cpp
+++ b/src/network/access/http2/hpacktable.cpp
@@ -63,7 +63,7 @@
// 32 octets of overhead."
const unsigned sum = unsigned(name.size() + value.size());
- if (std::numeric_limits<unsigned>::max() - 32 < sum)
+ if (sum > (std::numeric_limits<unsigned>::max() - 32))
return HeaderSize();
return HeaderSize(true, quint32(sum + 32));
}

View File

@ -0,0 +1,59 @@
From 23c3fc483e8b6e21012a61f0bea884446f727776 Mon Sep 17 00:00:00 2001
From: Marc Mutz <marc.mutz@qt.io>
Date: Tue, 12 Dec 2023 22:08:07 +0100
Subject: [PATCH] HPack: fix incorrect integer overflow check
This code never worked:
For the comparison with max() - 32 to trigger, on 32-bit platforms (or
Qt 5) signed interger overflow would have had to happen in the
addition of the two sizes. The compiler can therefore remove the
overflow check as dead code.
On Qt 6 and 64-bit platforms, the signed integer addition would be
very unlikely to overflow, but the following truncation to uint32
would yield the correct result only in a narrow 32-value window just
below UINT_MAX, if even that.
Fix by using the proper tool, qAddOverflow.
Manual conflict resolutions:
- qAddOverflow doesn't exist in Qt 5, use private add_overflow
predecessor API instead
Change-Id: I7599f2e75ff7f488077b0c60b81022591005661c
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
(cherry picked from commit ee5da1f2eaf8932aeca02ffea6e4c618585e29e3)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
(cherry picked from commit debeb8878da2dc706ead04b6072ecbe7e5313860)
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
Reviewed-by: Marc Mutz <marc.mutz@qt.io>
(cherry picked from commit 811b9eef6d08d929af8708adbf2a5effb0eb62d7)
(cherry picked from commit f931facd077ce945f1e42eaa3bead208822d3e00)
(cherry picked from commit 9ef4ca5ecfed771dab890856130e93ef5ceabef5)
Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io>
---
diff --git a/src/network/access/http2/hpacktable.cpp b/src/network/access/http2/hpacktable.cpp
index ab166a6..de91fc0 100644
--- a/src/network/access/http2/hpacktable.cpp
+++ b/src/network/access/http2/hpacktable.cpp
@@ -40,6 +40,7 @@
#include "hpacktable_p.h"
#include <QtCore/qdebug.h>
+#include <QtCore/private/qnumeric_p.h>
#include <algorithm>
#include <cstddef>
@@ -62,7 +63,9 @@
// for counting the number of references to the name and value would have
// 32 octets of overhead."
- const unsigned sum = unsigned(name.size() + value.size());
+ size_t sum;
+ if (add_overflow(size_t(name.size()), size_t(value.size()), &sum))
+ return HeaderSize();
if (sum > (std::numeric_limits<unsigned>::max() - 32))
return HeaderSize();
return HeaderSize(true, quint32(sum + 32));

View File

@ -0,0 +1,203 @@
diff --git a/src/corelib/serialization/qxmlstream.cpp b/src/corelib/serialization/qxmlstream.cpp
index 7cd457ba3a..11d162cb79 100644
--- a/src/corelib/serialization/qxmlstream.cpp
+++ b/src/corelib/serialization/qxmlstream.cpp
@@ -1302,15 +1302,18 @@ inline int QXmlStreamReaderPrivate::fastScanContentCharList()
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::fastScanName(int *prefix)
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::fastScanName(int *prefix)
}
}
- 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::raiseWellFormedError(const QString &message)
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 implemnetation limits (4KiB "
+ "characters)."));
+}
+
void QXmlStreamReaderPrivate::parseError()
{
diff --git a/src/corelib/serialization/qxmlstream.g b/src/corelib/serialization/qxmlstream.g
index 4321fed68a..8c6a1a5887 100644
--- 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;
@@ -1811,7 +1821,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;
@@ -1822,7 +1837,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;
diff --git a/src/corelib/serialization/qxmlstream_p.h b/src/corelib/serialization/qxmlstream_p.h
index e5bde7b98e..b01484cac3 100644
--- 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;
@@ -1939,7 +1949,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;
@@ -1947,7 +1962,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;

View File

@ -0,0 +1,219 @@
diff --git a/src/corelib/serialization/qxmlstream.cpp b/src/corelib/serialization/qxmlstream.cpp
index bf8a2a9..6ab5d49 100644
--- a/src/corelib/serialization/qxmlstream.cpp
+++ b/src/corelib/serialization/qxmlstream.cpp
@@ -160,7 +160,7 @@
addData() or by waiting for it to arrive on the device().
\value UnexpectedElementError The parser encountered an element
- that was different to those it expected.
+ or token that was different to those it expected.
*/
@@ -295,13 +295,34 @@
QXmlStreamReader is a well-formed XML 1.0 parser that does \e not
include external parsed entities. As long as no error occurs, the
- application code can thus be assured that the data provided by the
- stream reader satisfies the W3C's criteria for well-formed XML. For
- example, you can be certain that all tags are indeed nested and
- closed properly, that references to internal entities have been
- replaced with the correct replacement text, and that attributes have
- been normalized or added according to the internal subset of the
- DTD.
+ application code can thus be assured, that
+ \list
+ \li the data provided by the stream reader satisfies the W3C's
+ criteria for well-formed XML,
+ \li tokens are provided in a valid order.
+ \endlist
+
+ Unless QXmlStreamReader raises an error, it guarantees the following:
+ \list
+ \li All tags are nested and closed properly.
+ \li References to internal entities have been replaced with the
+ correct replacement text.
+ \li Attributes have been normalized or added according to the
+ internal subset of the \l DTD.
+ \li Tokens of type \l StartDocument happen before all others,
+ aside from comments and processing instructions.
+ \li At most one DOCTYPE element (a token of type \l DTD) is present.
+ \li If present, the DOCTYPE appears before all other elements,
+ aside from StartDocument, comments and processing instructions.
+ \endlist
+
+ In particular, once any token of type \l StartElement, \l EndElement,
+ \l Characters, \l EntityReference or \l EndDocument is seen, no
+ tokens of type StartDocument or DTD will be seen. If one is present in
+ the input stream, out of order, an error is raised.
+
+ \note The token types \l Comment and \l ProcessingInstruction may appear
+ anywhere in the stream.
If an error occurs while parsing, atEnd() and hasError() return
true, and error() returns the error that occurred. The functions
@@ -620,6 +641,7 @@
d->token = -1;
return readNext();
}
+ d->checkToken();
return d->type;
}
@@ -740,6 +762,14 @@
};
+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 @@
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 @@
type = QXmlStreamReader::NoToken;
error = QXmlStreamReader::NoError;
+ currentContext = XmlContext::Prolog;
+ foundDTD = false;
}
/*
@@ -4061,6 +4103,92 @@
}
}
+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
diff --git a/src/corelib/serialization/qxmlstream_p.h b/src/corelib/serialization/qxmlstream_p.h
index 8f7c9e0..708059b 100644
--- a/src/corelib/serialization/qxmlstream_p.h
+++ b/src/corelib/serialization/qxmlstream_p.h
@@ -804,6 +804,17 @@
#endif
bool atEnd;
+ enum class XmlContext
+ {
+ Prolog,
+ Body,
+ };
+
+ XmlContext currentContext = XmlContext::Prolog;
+ bool foundDTD = false;
+ bool isValidToken(QXmlStreamReader::TokenType type);
+ void checkToken();
+
/*!
\sa setType()
*/

View File

@ -57,7 +57,7 @@ BuildRequires: pkgconfig(libsystemd)
Name: qt5-qtbase
Summary: Qt5 - QtBase components
Version: 5.15.9
Release: 6%{?dist}
Release: 8%{?dist}
# See LGPL_EXCEPTIONS.txt, for exception details
@ -147,6 +147,10 @@ Patch110: CVE-2023-32762-qtbase-5.15.patch
Patch111: CVE-2023-32763-qtbase-5.15.patch
Patch112: CVE-2023-33285-qtbase-5.15.patch
Patch113: CVE-2023-34410-qtbase-5.15.patch
Patch114: CVE-2023-37369-qtbase-5.15.patch
Patch115: CVE-2023-38197-qtbase-5.15.patch
Patch116: 0001-CVE-2023-51714-qtbase-5.15.patch
Patch117: 0002-CVE-2023-51714-qtbase-5.15.patch
# gating related patches
Patch200: qtbase-disable-tests-not-working-in-gating.patch
@ -434,6 +438,10 @@ Qt5 libraries used for drawing widgets and OpenGL items.
%patch -P111 -p1
%patch -P112 -p1
%patch -P113 -p1
%patch -P114 -p1
%patch -P115 -p1
%patch -P116 -p1
%patch -P117 -p1
## gating related patches
%patch -P200 -p1 -b .disable-tests-not-working-in-gating
@ -1134,6 +1142,14 @@ fi
%changelog
* Thu Jan 04 2024 Jan Grulich <jgrulich@redhat.com> - 5.15.9-8
- Fix incorrect integer overflow check in HTTP2 implementation
Resolves: RHEL-20239
* Fri Jul 21 2023 Jan Grulich <jgrulich@redhat.com> - 5.15.9-7
- Fix infinite loops in QXmlStreamReader (CVE-2023-38197)
Resolves: bz#2222771
* Fri Jun 09 2023 Jan Grulich <jgrulich@redhat.com> - 5.15.9-6
- Don't allow remote attacker to bypass security restrictions caused by
flaw in certificate validation (CVE-2023-34410) (version #2)