|
|
09caf3 |
From f432c08882ffebe5074ea28de871559a98a4d094 Mon Sep 17 00:00:00 2001
|
|
|
09caf3 |
From: Lars Knoll <lars.knoll@qt.io>
|
|
|
09caf3 |
Date: Wed, 26 Feb 2020 10:42:10 +0100
|
|
|
09caf3 |
Subject: Add an expansion limit for entities
|
|
|
09caf3 |
|
|
|
09caf3 |
Recursively defined entities can easily exhaust all available
|
|
|
09caf3 |
memory. Limit entity expansion to a default of 4096 characters to
|
|
|
09caf3 |
avoid DoS attacks when a user loads untrusted content.
|
|
|
09caf3 |
|
|
|
09caf3 |
[ChangeLog][QtCore][QXmlStream] QXmlStreamReader does now
|
|
|
09caf3 |
limit the expansion of entities to 4096 characters. Documents where
|
|
|
09caf3 |
a single entity expands to more characters than the limit are not
|
|
|
09caf3 |
considered well formed. The limit is there to avoid DoS attacks through
|
|
|
09caf3 |
recursively expanding entities when loading untrusted content. Qt 5.15
|
|
|
09caf3 |
will add methods that allow changing that limit.
|
|
|
09caf3 |
|
|
|
09caf3 |
Fixes: QTBUG-47417
|
|
|
09caf3 |
Change-Id: I94387815d74fcf34783e136387ee57fac5ded0c9
|
|
|
09caf3 |
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
|
|
|
09caf3 |
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
|
|
|
09caf3 |
(cherry picked from commit fd4be84d23a0db4186cb42e736a9de3af722c7f7)
|
|
|
09caf3 |
Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io>
|
|
|
09caf3 |
---
|
|
|
09caf3 |
src/corelib/serialization/qxmlstream.g | 14 ++++++++++++-
|
|
|
09caf3 |
src/corelib/serialization/qxmlstream_p.h | 14 ++++++++++++-
|
|
|
09caf3 |
.../serialization/qxmlstream/tst_qxmlstream.cpp | 23 ++++++++++++++++++++--
|
|
|
09caf3 |
3 files changed, 47 insertions(+), 4 deletions(-)
|
|
|
09caf3 |
|
|
|
09caf3 |
diff --git a/src/corelib/serialization/qxmlstream.g b/src/corelib/serialization/qxmlstream.g
|
|
|
09caf3 |
index 10bfcd491c..5726bafb26 100644
|
|
|
09caf3 |
--- a/src/corelib/serialization/qxmlstream.g
|
|
|
09caf3 |
+++ b/src/corelib/serialization/qxmlstream.g
|
|
|
09caf3 |
@@ -277,9 +277,19 @@ public:
|
|
|
09caf3 |
QHash<QStringView, Entity> entityHash;
|
|
|
09caf3 |
QHash<QStringView, Entity> parameterEntityHash;
|
|
|
09caf3 |
QXmlStreamSimpleStack<Entity *>entityReferenceStack;
|
|
|
09caf3 |
+ int entityExpansionLimit = 4096;
|
|
|
09caf3 |
+ int entityLength = 0;
|
|
|
09caf3 |
inline bool referenceEntity(Entity &entity) {
|
|
|
09caf3 |
if (entity.isCurrentlyReferenced) {
|
|
|
09caf3 |
- raiseWellFormedError(QXmlStream::tr("Recursive entity detected."));
|
|
|
09caf3 |
+ raiseWellFormedError(QXmlStream::tr("Self-referencing entity detected."));
|
|
|
09caf3 |
+ return false;
|
|
|
09caf3 |
+ }
|
|
|
09caf3 |
+ // entityLength represents the amount of additional characters the
|
|
|
09caf3 |
+ // entity expands into (can be negative for e.g. &). It's used to
|
|
|
09caf3 |
+ // avoid DoS attacks through recursive entity expansions
|
|
|
09caf3 |
+ entityLength += entity.value.size() - entity.name.size() - 2;
|
|
|
09caf3 |
+ if (entityLength > entityExpansionLimit) {
|
|
|
09caf3 |
+ raiseWellFormedError(QXmlStream::tr("Entity expands to more characters than the entity expansion limit."));
|
|
|
09caf3 |
return false;
|
|
|
09caf3 |
}
|
|
|
09caf3 |
entity.isCurrentlyReferenced = true;
|
|
|
09caf3 |
@@ -830,6 +840,8 @@ entity_done ::= ENTITY_DONE;
|
|
|
09caf3 |
/.
|
|
|
09caf3 |
case $rule_number:
|
|
|
09caf3 |
entityReferenceStack.pop()->isCurrentlyReferenced = false;
|
|
|
09caf3 |
+ if (entityReferenceStack.isEmpty())
|
|
|
09caf3 |
+ entityLength = 0;
|
|
|
09caf3 |
clearSym();
|
|
|
09caf3 |
break;
|
|
|
09caf3 |
./
|
|
|
09caf3 |
diff --git a/src/corelib/serialization/qxmlstream_p.h b/src/corelib/serialization/qxmlstream_p.h
|
|
|
09caf3 |
index 61f501f81b..31053f8e0b 100644
|
|
|
09caf3 |
--- a/src/corelib/serialization/qxmlstream_p.h
|
|
|
09caf3 |
+++ b/src/corelib/serialization/qxmlstream_p.h
|
|
|
09caf3 |
@@ -774,9 +774,19 @@ public:
|
|
|
09caf3 |
QHash<QStringView, Entity> entityHash;
|
|
|
09caf3 |
QHash<QStringView, Entity> parameterEntityHash;
|
|
|
09caf3 |
QXmlStreamSimpleStack<Entity *>entityReferenceStack;
|
|
|
09caf3 |
+ int entityExpansionLimit = 4096;
|
|
|
09caf3 |
+ int entityLength = 0;
|
|
|
09caf3 |
inline bool referenceEntity(Entity &entity) {
|
|
|
09caf3 |
if (entity.isCurrentlyReferenced) {
|
|
|
09caf3 |
- raiseWellFormedError(QXmlStream::tr("Recursive entity detected."));
|
|
|
09caf3 |
+ raiseWellFormedError(QXmlStream::tr("Self-referencing entity detected."));
|
|
|
09caf3 |
+ return false;
|
|
|
09caf3 |
+ }
|
|
|
09caf3 |
+ // entityLength represents the amount of additional characters the
|
|
|
09caf3 |
+ // entity expands into (can be negative for e.g. &). It's used to
|
|
|
09caf3 |
+ // avoid DoS attacks through recursive entity expansions
|
|
|
09caf3 |
+ entityLength += entity.value.size() - entity.name.size() - 2;
|
|
|
09caf3 |
+ if (entityLength > entityExpansionLimit) {
|
|
|
09caf3 |
+ raiseWellFormedError(QXmlStream::tr("Entity expands to more characters than the entity expansion limit."));
|
|
|
09caf3 |
return false;
|
|
|
09caf3 |
}
|
|
|
09caf3 |
entity.isCurrentlyReferenced = true;
|
|
|
09caf3 |
@@ -1308,6 +1318,8 @@ bool QXmlStreamReaderPrivate::parse()
|
|
|
09caf3 |
|
|
|
09caf3 |
case 10:
|
|
|
09caf3 |
entityReferenceStack.pop()->isCurrentlyReferenced = false;
|
|
|
09caf3 |
+ if (entityReferenceStack.isEmpty())
|
|
|
09caf3 |
+ entityLength = 0;
|
|
|
09caf3 |
clearSym();
|
|
|
09caf3 |
break;
|
|
|
09caf3 |
|
|
|
09caf3 |
diff --git a/tests/auto/corelib/serialization/qxmlstream/tst_qxmlstream.cpp b/tests/auto/corelib/serialization/qxmlstream/tst_qxmlstream.cpp
|
|
|
09caf3 |
index 8fdf91b090..1f9a0d575d 100644
|
|
|
09caf3 |
--- a/tests/auto/corelib/serialization/qxmlstream/tst_qxmlstream.cpp
|
|
|
09caf3 |
+++ b/tests/auto/corelib/serialization/qxmlstream/tst_qxmlstream.cpp
|
|
|
09caf3 |
@@ -393,8 +393,6 @@ public:
|
|
|
09caf3 |
return true;
|
|
|
09caf3 |
}
|
|
|
09caf3 |
|
|
|
09caf3 |
- QXmlStreamReader reader(&inputFile);
|
|
|
09caf3 |
-
|
|
|
09caf3 |
/* See testcases.dtd which reads: 'Nonvalidating parsers
|
|
|
09caf3 |
* must also accept "invalid" testcases, but validating ones must reject them.' */
|
|
|
09caf3 |
if(type == QLatin1String("invalid") || type == QLatin1String("valid"))
|
|
|
09caf3 |
@@ -580,6 +578,8 @@ private slots:
|
|
|
09caf3 |
void roundTrip() const;
|
|
|
09caf3 |
void roundTrip_data() const;
|
|
|
09caf3 |
|
|
|
09caf3 |
+ void entityExpansionLimit() const;
|
|
|
09caf3 |
+
|
|
|
09caf3 |
private:
|
|
|
09caf3 |
static QByteArray readFile(const QString &filename);
|
|
|
09caf3 |
|
|
|
09caf3 |
@@ -1756,6 +1756,25 @@ void tst_QXmlStream::roundTrip_data() const
|
|
|
09caf3 |
"</root>\n";
|
|
|
09caf3 |
}
|
|
|
09caf3 |
|
|
|
09caf3 |
+void tst_QXmlStream::entityExpansionLimit() const
|
|
|
09caf3 |
+{
|
|
|
09caf3 |
+ QString xml = QStringLiteral(""
|
|
|
09caf3 |
+ "
|
|
|
09caf3 |
+ ""
|
|
|
09caf3 |
+ ""
|
|
|
09caf3 |
+ ""
|
|
|
09caf3 |
+ ""
|
|
|
09caf3 |
+ "]>"
|
|
|
09caf3 |
+ "<foo>&d;&d;&d;</foo>");
|
|
|
09caf3 |
+ {
|
|
|
09caf3 |
+ QXmlStreamReader reader(xml);
|
|
|
09caf3 |
+ do {
|
|
|
09caf3 |
+ reader.readNext();
|
|
|
09caf3 |
+ } while (!reader.atEnd());
|
|
|
09caf3 |
+ QCOMPARE(reader.error(), QXmlStreamReader::NotWellFormedError);
|
|
|
09caf3 |
+ }
|
|
|
09caf3 |
+}
|
|
|
09caf3 |
+
|
|
|
09caf3 |
void tst_QXmlStream::roundTrip() const
|
|
|
09caf3 |
{
|
|
|
09caf3 |
QFETCH(QString, in);
|
|
|
09caf3 |
--
|
|
|
09caf3 |
cgit v1.2.1
|