mirror of
https://github.com/pocoproject/poco.git
synced 2024-12-13 10:32:57 +01:00
fix(XML): fuzzing stack overflow (#4629). Limit maximum XML element depth.
This commit is contained in:
parent
3a8c6a72b1
commit
3b4a8ea6e7
@ -46,9 +46,14 @@ class XML_API DOMBuilder: protected DTDHandler, protected ContentHandler, protec
|
|||||||
/// must be supplied to the DOMBuilder.
|
/// must be supplied to the DOMBuilder.
|
||||||
{
|
{
|
||||||
public:
|
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.
|
/// Creates a DOMBuilder using the given XMLReader.
|
||||||
/// If a NamePool is given, it becomes the Document's NamePool.
|
/// 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();
|
virtual ~DOMBuilder();
|
||||||
/// Destroys the DOMBuilder.
|
/// Destroys the DOMBuilder.
|
||||||
@ -98,11 +103,13 @@ private:
|
|||||||
|
|
||||||
XMLReader& _xmlReader;
|
XMLReader& _xmlReader;
|
||||||
NamePool* _pNamePool;
|
NamePool* _pNamePool;
|
||||||
|
std::size_t _maxDepth;
|
||||||
Document* _pDocument;
|
Document* _pDocument;
|
||||||
AbstractContainerNode* _pParent;
|
AbstractContainerNode* _pParent;
|
||||||
AbstractNode* _pPrevious;
|
AbstractNode* _pPrevious;
|
||||||
bool _inCDATA;
|
bool _inCDATA;
|
||||||
bool _namespaces;
|
bool _namespaces;
|
||||||
|
std::size_t _depth;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -99,12 +99,30 @@ public:
|
|||||||
void setEntityResolver(EntityResolver* pEntityResolver);
|
void setEntityResolver(EntityResolver* pEntityResolver);
|
||||||
/// Sets the entity resolver on the underlying SAXParser.
|
/// 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;
|
static const XMLString FEATURE_FILTER_WHITESPACE;
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
DEFAULT_MAX_ELEMENT_DEPTH = 256
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SAXParser _saxParser;
|
SAXParser _saxParser;
|
||||||
NamePool* _pNamePool;
|
NamePool* _pNamePool;
|
||||||
bool _filterWhitespace;
|
bool _filterWhitespace = false;
|
||||||
|
std::size_t _maxElementDepth = DEFAULT_MAX_ELEMENT_DEPTH;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
#include "Poco/DOM/AutoPtr.h"
|
#include "Poco/DOM/AutoPtr.h"
|
||||||
#include "Poco/SAX/XMLReader.h"
|
#include "Poco/SAX/XMLReader.h"
|
||||||
#include "Poco/SAX/AttributesImpl.h"
|
#include "Poco/SAX/AttributesImpl.h"
|
||||||
|
#include "Poco/XML/XMLException.h"
|
||||||
|
|
||||||
|
|
||||||
namespace Poco {
|
namespace Poco {
|
||||||
@ -37,14 +38,16 @@ namespace XML {
|
|||||||
const XMLString DOMBuilder::EMPTY_STRING;
|
const XMLString DOMBuilder::EMPTY_STRING;
|
||||||
|
|
||||||
|
|
||||||
DOMBuilder::DOMBuilder(XMLReader& xmlReader, NamePool* pNamePool):
|
DOMBuilder::DOMBuilder(XMLReader& xmlReader, NamePool* pNamePool, std::size_t maxDepth):
|
||||||
_xmlReader(xmlReader),
|
_xmlReader(xmlReader),
|
||||||
_pNamePool(pNamePool),
|
_pNamePool(pNamePool),
|
||||||
|
_maxDepth(maxDepth),
|
||||||
_pDocument(0),
|
_pDocument(0),
|
||||||
_pParent(0),
|
_pParent(0),
|
||||||
_pPrevious(0),
|
_pPrevious(0),
|
||||||
_inCDATA(false),
|
_inCDATA(false),
|
||||||
_namespaces(true)
|
_namespaces(true),
|
||||||
|
_depth(0)
|
||||||
{
|
{
|
||||||
_xmlReader.setContentHandler(this);
|
_xmlReader.setContentHandler(this);
|
||||||
_xmlReader.setDTDHandler(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)
|
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);
|
AutoPtr<Element> pElem = _namespaces ? _pDocument->createElementNS(uri, qname.empty() ? localName : qname) : _pDocument->createElement(qname);
|
||||||
|
|
||||||
const AttributesImpl& attrs = dynamic_cast<const AttributesImpl&>(attributes);
|
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)
|
void DOMBuilder::endElement(const XMLString& uri, const XMLString& localName, const XMLString& qname)
|
||||||
{
|
{
|
||||||
|
--_depth;
|
||||||
|
|
||||||
_pPrevious = _pParent;
|
_pPrevious = _pParent;
|
||||||
_pParent = static_cast<AbstractContainerNode*>(_pParent->parentNode());
|
_pParent = static_cast<AbstractContainerNode*>(_pParent->parentNode());
|
||||||
}
|
}
|
||||||
|
@ -28,8 +28,7 @@ const XMLString DOMParser::FEATURE_FILTER_WHITESPACE = toXMLString("http://www.a
|
|||||||
|
|
||||||
|
|
||||||
DOMParser::DOMParser(NamePool* pNamePool):
|
DOMParser::DOMParser(NamePool* pNamePool):
|
||||||
_pNamePool(pNamePool),
|
_pNamePool(pNamePool)
|
||||||
_filterWhitespace(false)
|
|
||||||
{
|
{
|
||||||
if (_pNamePool) _pNamePool->duplicate();
|
if (_pNamePool) _pNamePool->duplicate();
|
||||||
_saxParser.setFeature(XMLReader::FEATURE_NAMESPACES, true);
|
_saxParser.setFeature(XMLReader::FEATURE_NAMESPACES, true);
|
||||||
@ -38,8 +37,7 @@ DOMParser::DOMParser(NamePool* pNamePool):
|
|||||||
|
|
||||||
|
|
||||||
DOMParser::DOMParser(unsigned long namePoolSize):
|
DOMParser::DOMParser(unsigned long namePoolSize):
|
||||||
_pNamePool(new NamePool(namePoolSize)),
|
_pNamePool(new NamePool(namePoolSize))
|
||||||
_filterWhitespace(false)
|
|
||||||
{
|
{
|
||||||
_saxParser.setFeature(XMLReader::FEATURE_NAMESPACES, true);
|
_saxParser.setFeature(XMLReader::FEATURE_NAMESPACES, true);
|
||||||
_saxParser.setFeature(XMLReader::FEATURE_NAMESPACE_PREFIXES, true);
|
_saxParser.setFeature(XMLReader::FEATURE_NAMESPACE_PREFIXES, true);
|
||||||
@ -93,12 +91,12 @@ Document* DOMParser::parse(const XMLString& uri)
|
|||||||
if (_filterWhitespace)
|
if (_filterWhitespace)
|
||||||
{
|
{
|
||||||
WhitespaceFilter filter(&_saxParser);
|
WhitespaceFilter filter(&_saxParser);
|
||||||
DOMBuilder builder(filter, _pNamePool);
|
DOMBuilder builder(filter, _pNamePool, _maxElementDepth);
|
||||||
return builder.parse(uri);
|
return builder.parse(uri);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DOMBuilder builder(_saxParser, _pNamePool);
|
DOMBuilder builder(_saxParser, _pNamePool, _maxElementDepth);
|
||||||
return builder.parse(uri);
|
return builder.parse(uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,12 +107,12 @@ Document* DOMParser::parse(InputSource* pInputSource)
|
|||||||
if (_filterWhitespace)
|
if (_filterWhitespace)
|
||||||
{
|
{
|
||||||
WhitespaceFilter filter(&_saxParser);
|
WhitespaceFilter filter(&_saxParser);
|
||||||
DOMBuilder builder(filter, _pNamePool);
|
DOMBuilder builder(filter, _pNamePool, _maxElementDepth);
|
||||||
return builder.parse(pInputSource);
|
return builder.parse(pInputSource);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DOMBuilder builder(_saxParser, _pNamePool);
|
DOMBuilder builder(_saxParser, _pNamePool, _maxElementDepth);
|
||||||
return builder.parse(pInputSource);
|
return builder.parse(pInputSource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,12 +129,12 @@ Document* DOMParser::parseMemory(const char* xml, std::size_t size)
|
|||||||
if (_filterWhitespace)
|
if (_filterWhitespace)
|
||||||
{
|
{
|
||||||
WhitespaceFilter filter(&_saxParser);
|
WhitespaceFilter filter(&_saxParser);
|
||||||
DOMBuilder builder(filter, _pNamePool);
|
DOMBuilder builder(filter, _pNamePool, _maxElementDepth);
|
||||||
return builder.parseMemoryNP(xml, size);
|
return builder.parseMemoryNP(xml, size);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DOMBuilder builder(_saxParser, _pNamePool);
|
DOMBuilder builder(_saxParser, _pNamePool, _maxElementDepth);
|
||||||
return builder.parseMemoryNP(xml, size);
|
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
|
} } // namespace Poco::XML
|
||||||
|
@ -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()
|
void ParserWriterTest::setUp()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -121,6 +138,7 @@ CppUnit::Test* ParserWriterTest::suite()
|
|||||||
CppUnit_addTest(pSuite, ParserWriterTest, testParseWriteXHTML);
|
CppUnit_addTest(pSuite, ParserWriterTest, testParseWriteXHTML);
|
||||||
CppUnit_addTest(pSuite, ParserWriterTest, testParseWriteXHTML2);
|
CppUnit_addTest(pSuite, ParserWriterTest, testParseWriteXHTML2);
|
||||||
CppUnit_addTest(pSuite, ParserWriterTest, testParseWriteSimple);
|
CppUnit_addTest(pSuite, ParserWriterTest, testParseWriteSimple);
|
||||||
|
CppUnit_addTest(pSuite, ParserWriterTest, testMaxElementDepth);
|
||||||
|
|
||||||
return pSuite;
|
return pSuite;
|
||||||
}
|
}
|
||||||
|
@ -26,8 +26,8 @@ public:
|
|||||||
|
|
||||||
void testParseWriteXHTML();
|
void testParseWriteXHTML();
|
||||||
void testParseWriteXHTML2();
|
void testParseWriteXHTML2();
|
||||||
void testParseWriteWSDL();
|
|
||||||
void testParseWriteSimple();
|
void testParseWriteSimple();
|
||||||
|
void testMaxElementDepth();
|
||||||
|
|
||||||
void setUp();
|
void setUp();
|
||||||
void tearDown();
|
void tearDown();
|
||||||
|
Loading…
Reference in New Issue
Block a user