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

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