fix(XML): fuzzing stack overflow (#4629). Limit maximum XML element depth.

This commit is contained in:
Günter Obiltschnig 2024-09-26 09:03:31 +02:00
parent 3a8c6a72b1
commit 3b4a8ea6e7
6 changed files with 76 additions and 15 deletions

View File

@ -46,9 +46,14 @@ class XML_API DOMBuilder: protected DTDHandler, protected ContentHandler, protec
/// must be supplied to the DOMBuilder.
{
public:
DOMBuilder(XMLReader& xmlReader, NamePool* pNamePool = 0);
DOMBuilder(XMLReader& xmlReader, NamePool* pNamePool = 0, std::size_t maxDepth = 0);
/// Creates a DOMBuilder using the given XMLReader.
/// If a NamePool is given, it becomes the Document's NamePool.
///
/// The maxDepth parameter can be used to limit the maximum element depth of the
/// document. This can be used to prevent excessive element depth, which
/// could lead to a stack overflow when destroying the document.
/// A maxDepth of 0 does not limit the depth.
virtual ~DOMBuilder();
/// Destroys the DOMBuilder.
@ -98,11 +103,13 @@ private:
XMLReader& _xmlReader;
NamePool* _pNamePool;
std::size_t _maxDepth;
Document* _pDocument;
AbstractContainerNode* _pParent;
AbstractNode* _pPrevious;
bool _inCDATA;
bool _namespaces;
std::size_t _depth;
};

View File

@ -99,12 +99,30 @@ public:
void setEntityResolver(EntityResolver* pEntityResolver);
/// Sets the entity resolver on the underlying SAXParser.
void setMaxElementDepth(std::size_t limit);
/// Limits the maximum element depth of the XML document to be loaded.
/// Setting the limit to zero disables the limit.
///
/// This can be used to prevent excessive element depth, which
/// could lead to a stack overflow when destroying the document.
///
/// The default limit is 256.
std::size_t getMaxElementDepth() const;
/// Returns the maximum element depth.
static const XMLString FEATURE_FILTER_WHITESPACE;
enum
{
DEFAULT_MAX_ELEMENT_DEPTH = 256
};
private:
SAXParser _saxParser;
NamePool* _pNamePool;
bool _filterWhitespace;
bool _filterWhitespace = false;
std::size_t _maxElementDepth = DEFAULT_MAX_ELEMENT_DEPTH;
};

View File

@ -28,6 +28,7 @@
#include "Poco/DOM/AutoPtr.h"
#include "Poco/SAX/XMLReader.h"
#include "Poco/SAX/AttributesImpl.h"
#include "Poco/XML/XMLException.h"
namespace Poco {
@ -37,14 +38,16 @@ namespace XML {
const XMLString DOMBuilder::EMPTY_STRING;
DOMBuilder::DOMBuilder(XMLReader& xmlReader, NamePool* pNamePool):
DOMBuilder::DOMBuilder(XMLReader& xmlReader, NamePool* pNamePool, std::size_t maxDepth):
_xmlReader(xmlReader),
_pNamePool(pNamePool),
_maxDepth(maxDepth),
_pDocument(0),
_pParent(0),
_pPrevious(0),
_inCDATA(false),
_namespaces(true)
_namespaces(true),
_depth(0)
{
_xmlReader.setContentHandler(this);
_xmlReader.setDTDHandler(this);
@ -188,6 +191,9 @@ void DOMBuilder::endDocument()
void DOMBuilder::startElement(const XMLString& uri, const XMLString& localName, const XMLString& qname, const Attributes& attributes)
{
++_depth;
if (_maxDepth > 0 && _depth > _maxDepth) throw XMLException("Maximum element depth exceeded");
AutoPtr<Element> pElem = _namespaces ? _pDocument->createElementNS(uri, qname.empty() ? localName : qname) : _pDocument->createElement(qname);
const AttributesImpl& attrs = dynamic_cast<const AttributesImpl&>(attributes);
@ -204,6 +210,8 @@ void DOMBuilder::startElement(const XMLString& uri, const XMLString& localName,
void DOMBuilder::endElement(const XMLString& uri, const XMLString& localName, const XMLString& qname)
{
--_depth;
_pPrevious = _pParent;
_pParent = static_cast<AbstractContainerNode*>(_pParent->parentNode());
}

View File

@ -28,8 +28,7 @@ const XMLString DOMParser::FEATURE_FILTER_WHITESPACE = toXMLString("http://www.a
DOMParser::DOMParser(NamePool* pNamePool):
_pNamePool(pNamePool),
_filterWhitespace(false)
_pNamePool(pNamePool)
{
if (_pNamePool) _pNamePool->duplicate();
_saxParser.setFeature(XMLReader::FEATURE_NAMESPACES, true);
@ -38,8 +37,7 @@ DOMParser::DOMParser(NamePool* pNamePool):
DOMParser::DOMParser(unsigned long namePoolSize):
_pNamePool(new NamePool(namePoolSize)),
_filterWhitespace(false)
_pNamePool(new NamePool(namePoolSize))
{
_saxParser.setFeature(XMLReader::FEATURE_NAMESPACES, true);
_saxParser.setFeature(XMLReader::FEATURE_NAMESPACE_PREFIXES, true);
@ -93,12 +91,12 @@ Document* DOMParser::parse(const XMLString& uri)
if (_filterWhitespace)
{
WhitespaceFilter filter(&_saxParser);
DOMBuilder builder(filter, _pNamePool);
DOMBuilder builder(filter, _pNamePool, _maxElementDepth);
return builder.parse(uri);
}
else
{
DOMBuilder builder(_saxParser, _pNamePool);
DOMBuilder builder(_saxParser, _pNamePool, _maxElementDepth);
return builder.parse(uri);
}
}
@ -109,12 +107,12 @@ Document* DOMParser::parse(InputSource* pInputSource)
if (_filterWhitespace)
{
WhitespaceFilter filter(&_saxParser);
DOMBuilder builder(filter, _pNamePool);
DOMBuilder builder(filter, _pNamePool, _maxElementDepth);
return builder.parse(pInputSource);
}
else
{
DOMBuilder builder(_saxParser, _pNamePool);
DOMBuilder builder(_saxParser, _pNamePool, _maxElementDepth);
return builder.parse(pInputSource);
}
}
@ -131,12 +129,12 @@ Document* DOMParser::parseMemory(const char* xml, std::size_t size)
if (_filterWhitespace)
{
WhitespaceFilter filter(&_saxParser);
DOMBuilder builder(filter, _pNamePool);
DOMBuilder builder(filter, _pNamePool, _maxElementDepth);
return builder.parseMemoryNP(xml, size);
}
else
{
DOMBuilder builder(_saxParser, _pNamePool);
DOMBuilder builder(_saxParser, _pNamePool, _maxElementDepth);
return builder.parseMemoryNP(xml, size);
}
}
@ -154,4 +152,16 @@ void DOMParser::setEntityResolver(EntityResolver* pEntityResolver)
}
void DOMParser::setMaxElementDepth(std::size_t limit)
{
_maxElementDepth = limit;
}
std::size_t DOMParser::getMaxElementDepth() const
{
return _maxElementDepth;
}
} } // namespace Poco::XML

View File

@ -104,6 +104,23 @@ void ParserWriterTest::testParseWriteSimple()
}
void ParserWriterTest::testMaxElementDepth()
{
DOMParser parser;
parser.setFeature(XMLReader::FEATURE_NAMESPACE_PREFIXES, false);
parser.setMaxElementDepth(2);
try
{
AutoPtr<Document> pDoc = parser.parseString(XHTML);
fail("max element depth exceeded - must throw");
}
catch (const Poco::Exception&)
{
}
}
void ParserWriterTest::setUp()
{
}
@ -121,6 +138,7 @@ CppUnit::Test* ParserWriterTest::suite()
CppUnit_addTest(pSuite, ParserWriterTest, testParseWriteXHTML);
CppUnit_addTest(pSuite, ParserWriterTest, testParseWriteXHTML2);
CppUnit_addTest(pSuite, ParserWriterTest, testParseWriteSimple);
CppUnit_addTest(pSuite, ParserWriterTest, testMaxElementDepth);
return pSuite;
}

View File

@ -26,8 +26,8 @@ public:
void testParseWriteXHTML();
void testParseWriteXHTML2();
void testParseWriteWSDL();
void testParseWriteSimple();
void testMaxElementDepth();
void setUp();
void tearDown();