160 lines
6.4 KiB
Diff
160 lines
6.4 KiB
Diff
From fd4be84d23a0db4186cb42e736a9de3af722c7f7 Mon Sep 17 00:00:00 2001
|
|
From: Lars Knoll <lars.knoll@qt.io>
|
|
Date: Wed, 26 Feb 2020 10:42:10 +0100
|
|
Subject: Add an expansion limit for entities
|
|
|
|
Recursively defined entities can easily exhaust all available
|
|
memory. Limit entity expansion to a default of 4096 characters to
|
|
avoid DoS attacks when a user loads untrusted content.
|
|
|
|
Added a setter and getter to allow modifying the expansion limit.
|
|
|
|
[ChangeLog][QtCore][QXmlStream] QXmlStreamReader does now by default
|
|
limit the expansion of entities to 4096 characters. Documents where
|
|
a single entity expands to more characters than the limit are not
|
|
considered well formed. The limit is there to avoid DoS attacks through
|
|
recursively expanding entities when loading untrusted content. The
|
|
limit can be changed through the QXmlStreamReader::setEntityExpansionLimit()
|
|
method.
|
|
|
|
Fixes: QTBUG-47417
|
|
Change-Id: I94387815d74fcf34783e136387ee57fac5ded0c9
|
|
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
|
|
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
|
|
---
|
|
src/corelib/serialization/qxmlstream.cpp | 36 ++++++++++++++++++
|
|
src/corelib/serialization/qxmlstream.g | 14 ++++++-
|
|
src/corelib/serialization/qxmlstream.h | 2 +
|
|
src/corelib/serialization/qxmlstream_p.h | 14 ++++++-
|
|
.../serialization/qxmlstream/tst_qxmlstream.cpp | 44 +++++++++++++++++++++-
|
|
5 files changed, 106 insertions(+), 4 deletions(-)
|
|
|
|
diff --git a/src/corelib/serialization/qxmlstream.cpp b/src/corelib/serialization/qxmlstream.cpp
|
|
index 7ff87885a5..d7fb0d0d41 100644
|
|
--- a/src/corelib/serialization/qxmlstream.cpp
|
|
+++ b/src/corelib/serialization/qxmlstream.cpp
|
|
@@ -2041,6 +2041,42 @@ QStringRef QXmlStreamReader::dtdSystemId() const
|
|
return QStringRef();
|
|
}
|
|
|
|
+/*!
|
|
+ \since 5.15
|
|
+
|
|
+ Returns the maximum amount of characters a single entity is
|
|
+ allowed to expand into. If a single entity expands past the
|
|
+ given limit, the document is not considered well formed.
|
|
+
|
|
+ \sa setEntityExpansionLimit
|
|
+*/
|
|
+int QXmlStreamReader::entityExpansionLimit() const
|
|
+{
|
|
+ Q_D(const QXmlStreamReader);
|
|
+ return d->entityExpansionLimit;
|
|
+}
|
|
+
|
|
+/*!
|
|
+ \since 5.15
|
|
+
|
|
+ Sets the maximum amount of characters a single entity is
|
|
+ allowed to expand into to \a limit. If a single entity expands
|
|
+ past the given limit, the document is not considered well formed.
|
|
+
|
|
+ The limit is there to prevent DoS attacks when loading unknown
|
|
+ XML documents where recursive entity expansion could otherwise
|
|
+ exhaust all available memory.
|
|
+
|
|
+ The default value for this property is 4096 characters.
|
|
+
|
|
+ \sa entityExpansionLimit
|
|
+*/
|
|
+void QXmlStreamReader::setEntityExpansionLimit(int limit)
|
|
+{
|
|
+ Q_D(QXmlStreamReader);
|
|
+ d->entityExpansionLimit = limit;
|
|
+}
|
|
+
|
|
/*! If the tokenType() is \l StartElement, this function returns the
|
|
element's namespace declarations. Otherwise an empty vector is
|
|
returned.
|
|
diff --git a/src/corelib/serialization/qxmlstream.g b/src/corelib/serialization/qxmlstream.g
|
|
index 12ecc9bdb2..b623de9505 100644
|
|
--- a/src/corelib/serialization/qxmlstream.g
|
|
+++ b/src/corelib/serialization/qxmlstream.g
|
|
@@ -285,9 +285,19 @@ public:
|
|
QHash<QStringView, Entity> entityHash;
|
|
QHash<QStringView, Entity> parameterEntityHash;
|
|
QXmlStreamSimpleStack<Entity *>entityReferenceStack;
|
|
+ int entityExpansionLimit = 4096;
|
|
+ int entityLength = 0;
|
|
inline bool referenceEntity(Entity &entity) {
|
|
if (entity.isCurrentlyReferenced) {
|
|
- raiseWellFormedError(QXmlStream::tr("Recursive entity detected."));
|
|
+ raiseWellFormedError(QXmlStream::tr("Self-referencing entity detected."));
|
|
+ return false;
|
|
+ }
|
|
+ // entityLength represents the amount of additional characters the
|
|
+ // entity expands into (can be negative for e.g. &). It's used to
|
|
+ // avoid DoS attacks through recursive entity expansions
|
|
+ entityLength += entity.value.size() - entity.name.size() - 2;
|
|
+ if (entityLength > entityExpansionLimit) {
|
|
+ raiseWellFormedError(QXmlStream::tr("Entity expands to more characters than the entity expansion limit."));
|
|
return false;
|
|
}
|
|
entity.isCurrentlyReferenced = true;
|
|
@@ -838,6 +848,8 @@ entity_done ::= ENTITY_DONE;
|
|
/.
|
|
case $rule_number:
|
|
entityReferenceStack.pop()->isCurrentlyReferenced = false;
|
|
+ if (entityReferenceStack.isEmpty())
|
|
+ entityLength = 0;
|
|
clearSym();
|
|
break;
|
|
./
|
|
diff --git a/src/corelib/serialization/qxmlstream.h b/src/corelib/serialization/qxmlstream.h
|
|
index 7d0aa64570..c8647e0465 100644
|
|
--- a/src/corelib/serialization/qxmlstream.h
|
|
+++ b/src/corelib/serialization/qxmlstream.h
|
|
@@ -426,6 +426,8 @@ public:
|
|
QStringRef dtdPublicId() const;
|
|
QStringRef dtdSystemId() const;
|
|
|
|
+ int entityExpansionLimit() const;
|
|
+ void setEntityExpansionLimit(int limit);
|
|
|
|
enum Error {
|
|
NoError,
|
|
diff --git a/src/corelib/serialization/qxmlstream_p.h b/src/corelib/serialization/qxmlstream_p.h
|
|
index 9c94e6d434..103b123b10 100644
|
|
--- a/src/corelib/serialization/qxmlstream_p.h
|
|
+++ b/src/corelib/serialization/qxmlstream_p.h
|
|
@@ -774,9 +774,19 @@ public:
|
|
QHash<QStringView, Entity> entityHash;
|
|
QHash<QStringView, Entity> parameterEntityHash;
|
|
QXmlStreamSimpleStack<Entity *>entityReferenceStack;
|
|
+ int entityExpansionLimit = 4096;
|
|
+ int entityLength = 0;
|
|
inline bool referenceEntity(Entity &entity) {
|
|
if (entity.isCurrentlyReferenced) {
|
|
- raiseWellFormedError(QXmlStream::tr("Recursive entity detected."));
|
|
+ raiseWellFormedError(QXmlStream::tr("Self-referencing entity detected."));
|
|
+ return false;
|
|
+ }
|
|
+ // entityLength represents the amount of additional characters the
|
|
+ // entity expands into (can be negative for e.g. &). It's used to
|
|
+ // avoid DoS attacks through recursive entity expansions
|
|
+ entityLength += entity.value.size() - entity.name.size() - 2;
|
|
+ if (entityLength > entityExpansionLimit) {
|
|
+ raiseWellFormedError(QXmlStream::tr("Entity expands to more characters than the entity expansion limit."));
|
|
return false;
|
|
}
|
|
entity.isCurrentlyReferenced = true;
|
|
@@ -1308,6 +1318,8 @@ bool QXmlStreamReaderPrivate::parse()
|
|
|
|
case 10:
|
|
entityReferenceStack.pop()->isCurrentlyReferenced = false;
|
|
+ if (entityReferenceStack.isEmpty())
|
|
+ entityLength = 0;
|
|
clearSym();
|
|
break;
|
|
|