Blame SOURCES/qtbase-add-expansion-limit-for-entities.patch

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