diff options
author | Mitch Curtis <mitch.curtis@digia.com> | 2013-09-27 12:32:28 +0200 |
---|---|---|
committer | The Qt Project <gerrit-noreply@qt-project.org> | 2013-11-05 15:55:17 +0100 |
commit | 46a8885ae486e238a39efa5119c2714f328b08e4 (patch) | |
tree | 25301bafd9e16642b4929876a10b9727da1a674b | |
parent | 55c14e35f10d5a293ae6535a1ee31963b80109a6 (diff) |
Disallow deep or widely nested entity references.
Nested references with a depth of 2 or greater will fail. References
that partially expand to greater than 1024 characters will also fail.
Change-Id: Id4e49d6f7cf51e3a247efdb4c6c7c9bd9b223f6e
Reviewed-by: Richard J. Moore <rich@kde.org>
Reviewed-by: Lars Knoll <lars.knoll@digia.com>
5 files changed, 149 insertions, 0 deletions
diff --git a/src/xml/sax/qxml.cpp b/src/xml/sax/qxml.cpp index 45c0f3e17b..e6d78d3434 100644 --- a/src/xml/sax/qxml.cpp +++ b/src/xml/sax/qxml.cpp @@ -424,6 +424,10 @@ private: int stringValueLen; QString emptyStr; + // The limit to the amount of times the DTD parsing functions can be called + // for the DTD currently being parsed. + int dtdRecursionLimit; + const QString &string(); void stringClear(); void stringAddC(QChar); @@ -493,6 +497,8 @@ private: void parseFailed(ParseFunction where, int state); void pushParseState(ParseFunction function, int state); + bool isPartiallyExpandedEntityValueTooLarge(QString *errorMessage); + Q_DECLARE_PUBLIC(QXmlSimpleReader) QXmlSimpleReader *q_ptr; @@ -2757,6 +2763,8 @@ QXmlSimpleReaderPrivate::QXmlSimpleReaderPrivate(QXmlSimpleReader *reader) useNamespacePrefixes = false; reportWhitespaceCharData = true; reportEntities = false; + + dtdRecursionLimit = 2; } QXmlSimpleReaderPrivate::~QXmlSimpleReaderPrivate() @@ -5035,6 +5043,11 @@ bool QXmlSimpleReaderPrivate::parseDoctype() } break; case Mup: + if (dtdRecursionLimit > 0 && parameterEntities.size() > dtdRecursionLimit) { + reportParseError(QString::fromLatin1( + "DTD parsing exceeded recursion limit of %1.").arg(dtdRecursionLimit)); + return false; + } if (!parseMarkupdecl()) { parseFailed(&QXmlSimpleReaderPrivate::parseDoctype, state); return false; @@ -6644,6 +6657,37 @@ bool QXmlSimpleReaderPrivate::parseChoiceSeq() return false; } +bool QXmlSimpleReaderPrivate::isPartiallyExpandedEntityValueTooLarge(QString *errorMessage) +{ + const QString value = string(); + QMap<QString, int> referencedEntityCounts; + foreach (QString entityName, entities.keys()) { + for (int i = 0; i < value.size() && i != -1; ) { + i = value.indexOf(entityName, i); + if (i != -1) { + // The entityName we're currently trying to find + // was matched in this string; increase our count. + ++referencedEntityCounts[entityName]; + i += entityName.size(); + } + } + } + + foreach (QString entityName, referencedEntityCounts.keys()) { + const int timesReferenced = referencedEntityCounts[entityName]; + const QString entityValue = entities[entityName]; + if (entityValue.size() * timesReferenced > 1024) { + if (errorMessage) { + *errorMessage = QString::fromLatin1("The XML entity \"%1\"" + "expands too a string that is too large to process when " + "referencing \"%2\" %3 times.").arg(entityName).arg(entityName).arg(timesReferenced); + } + return true; + } + } + return false; +} + /* Parse a EntityDecl [70]. @@ -6738,6 +6782,15 @@ bool QXmlSimpleReaderPrivate::parseEntityDecl() switch (state) { case EValue: if ( !entityExist(name())) { + QString errorMessage; + if (isPartiallyExpandedEntityValueTooLarge(&errorMessage)) { + // The entity at entityName is entityValue.size() characters + // long in its unexpanded form, and was mentioned timesReferenced times, + // resulting in a string that would be greater than 1024 characters. + reportParseError(errorMessage); + return false; + } + entities.insert(name(), string()); if (declHnd) { if (!declHnd->internalEntityDecl(name(), string())) { diff --git a/tests/auto/xml/sax/qxmlsimplereader/tst_qxmlsimplereader.cpp b/tests/auto/xml/sax/qxmlsimplereader/tst_qxmlsimplereader.cpp index f09fbff6c4..57d078ba65 100644 --- a/tests/auto/xml/sax/qxmlsimplereader/tst_qxmlsimplereader.cpp +++ b/tests/auto/xml/sax/qxmlsimplereader/tst_qxmlsimplereader.cpp @@ -160,6 +160,7 @@ class tst_QXmlSimpleReader : public QObject void reportNamespace() const; void reportNamespace_data() const; void roundtripWithNamespaces() const; + void dtdRecursionLimit(); private: static QDomDocument fromByteArray(const QString &title, const QByteArray &ba, bool *ok); @@ -755,5 +756,62 @@ void tst_QXmlSimpleReader::roundtripWithNamespaces() const } } +class TestHandler : public QXmlDefaultHandler +{ +public: + TestHandler() : + recursionCount(0) + { + } + + bool internalEntityDecl(const QString &name, const QString &value) + { + ++recursionCount; + return QXmlDefaultHandler::internalEntityDecl(name, value); + } + + int recursionCount; +}; + +void tst_QXmlSimpleReader::dtdRecursionLimit() +{ + QFile file("xmldocs/2-levels-nested-dtd.xml"); + QVERIFY(file.open(QIODevice::ReadOnly)); + QXmlSimpleReader xmlReader; + { + QXmlInputSource *source = new QXmlInputSource(&file); + TestHandler handler; + xmlReader.setDeclHandler(&handler); + xmlReader.setErrorHandler(&handler); + QVERIFY(!xmlReader.parse(source)); + } + + file.close(); + file.setFileName("xmldocs/1-levels-nested-dtd.xml"); + QVERIFY(file.open(QIODevice::ReadOnly)); + { + QXmlInputSource *source = new QXmlInputSource(&file); + TestHandler handler; + xmlReader.setDeclHandler(&handler); + xmlReader.setErrorHandler(&handler); + QVERIFY(!xmlReader.parse(source)); + // The error wasn't because of the recursion limit being reached, + // it was because the document is not valid. + QVERIFY(handler.recursionCount < 2); + } + + file.close(); + file.setFileName("xmldocs/internal-entity-polynomial-attribute.xml"); + QVERIFY(file.open(QIODevice::ReadOnly)); + { + QXmlInputSource *source = new QXmlInputSource(&file); + TestHandler handler; + xmlReader.setDeclHandler(&handler); + xmlReader.setErrorHandler(&handler); + QVERIFY(!xmlReader.parse(source)); + QVERIFY(handler.recursionCount == 1); + } +} + QTEST_MAIN(tst_QXmlSimpleReader) #include "tst_qxmlsimplereader.moc" diff --git a/tests/auto/xml/sax/qxmlsimplereader/xmldocs/1-levels-nested-dtd.xml b/tests/auto/xml/sax/qxmlsimplereader/xmldocs/1-levels-nested-dtd.xml new file mode 100644 index 0000000000..0dfc15b165 --- /dev/null +++ b/tests/auto/xml/sax/qxmlsimplereader/xmldocs/1-levels-nested-dtd.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- Test non-deterministic content model matching. + +Entity references are not part of the internal DTD subset (for good reason). + +--> +<!DOCTYPE root [ +<!ELEMENT e0 EMPTY> +<!ENTITY % e1 "(e0,e0)"> +<!ELEMENT root (%e1;)?> +]> +<root/>
\ No newline at end of file diff --git a/tests/auto/xml/sax/qxmlsimplereader/xmldocs/2-levels-nested-dtd.xml b/tests/auto/xml/sax/qxmlsimplereader/xmldocs/2-levels-nested-dtd.xml new file mode 100644 index 0000000000..7ec06db85f --- /dev/null +++ b/tests/auto/xml/sax/qxmlsimplereader/xmldocs/2-levels-nested-dtd.xml @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<!-- Test non-deterministic content model matching. + +Entity references are not part of the internal DTD subset (for good reason). + +--> +<!DOCTYPE root [ +<!ELEMENT e0 EMPTY> +<!ENTITY % e1 "(e0,e0)"> +<!ENTITY % e2 "(%e1;,%e1;,%e1;,%e1;,%e1;,%e1;,%e1;,%e1;,%e1;,%e1;,%e1;,%e1;,%e1;)"> +<!ELEMENT root (%e2;)?> +]> +<root/> diff --git a/tests/auto/xml/sax/qxmlsimplereader/xmldocs/internal-entity-polynomial-attribute.xml b/tests/auto/xml/sax/qxmlsimplereader/xmldocs/internal-entity-polynomial-attribute.xml new file mode 100644 index 0000000000..bbb88f39f6 --- /dev/null +++ b/tests/auto/xml/sax/qxmlsimplereader/xmldocs/internal-entity-polynomial-attribute.xml @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<!-- Test polynomial growth of expanded XML. + Expansion happens in an attribute. --> +<!DOCTYPE root [ +<!ELEMENT root EMPTY> +<!ATTLIST root id CDATA #IMPLIED> +<!ENTITY e1 "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"> +<!ENTITY e2 "&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;&e1;"> +<!ENTITY e3 "&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;&e2;"> +<!ENTITY e4 "&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;&e3;"> +]> +<root id="&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;&e4;"/> + |