diff --git a/NEWS b/NEWS index ba832ef05888..81f448cbd56e 100644 --- a/NEWS +++ b/NEWS @@ -6,6 +6,10 @@ PHP NEWS . Fixed leak on double DatePeriod::__construct() call. (ilutov) - DOM: + . Fixed bug GH-23116 (Stack overflow when normalizing a deeply nested + DOMDocument). (Lazizbek Ergashev) + . Fixed bug GH-23117 (Stack overflow when normalizing a deeply nested + Dom\XMLDocument). (Lazizbek Ergashev) . Fixed bug GH-22825 (DOMElement::setAttribute() fails silently when the DTD declares a default value for the attribute). (iliaal) . Fixed bug GH-22447 (UAF at dom_objects_free_storage when setting an diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c index 135b3cdc5caa..00f63d475032 100644 --- a/ext/dom/php_dom.c +++ b/ext/dom/php_dom.c @@ -1966,9 +1966,26 @@ static void dom_merge_adjacent_exclusive_text_nodes(xmlNodePtr node) } } +static zend_always_inline bool dom_normalize_check_stack_limit(void) +{ +#ifdef ZEND_CHECK_STACK_LIMIT + if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) { + if (!EG(exception)) { + zend_throw_error(NULL, "Maximum call stack size reached. Infinite recursion?"); + } + return true; + } +#endif + return false; +} + /* {{{ void php_dom_normalize_legacy(xmlNodePtr nodep) */ void php_dom_normalize_legacy(xmlNodePtr nodep) { + if (UNEXPECTED(dom_normalize_check_stack_limit())) { + return; + } + xmlNodePtr child = nodep->children; while(child != NULL) { switch (child->type) { @@ -2001,6 +2018,10 @@ void php_dom_normalize_legacy(xmlNodePtr nodep) /* https://dom.spec.whatwg.org/#dom-node-normalize */ void php_dom_normalize_modern(xmlNodePtr this) { + if (UNEXPECTED(dom_normalize_check_stack_limit())) { + return; + } + /* for each descendant exclusive Text node node of this: */ xmlNodePtr node = this->children; while (node != NULL) { diff --git a/ext/dom/tests/gh23116.phpt b/ext/dom/tests/gh23116.phpt new file mode 100644 index 000000000000..20cabc4c535a --- /dev/null +++ b/ext/dom/tests/gh23116.phpt @@ -0,0 +1,50 @@ +--TEST-- +GH-23116 (Stack overflow when normalizing a deeply nested DOMDocument) +--EXTENSIONS-- +dom +--SKIPIF-- + +--INI-- +zend.max_allowed_stack_size=256K +--FILE-- +createElement('root'); +for ($s = 0; $s < 2; $s++) { + $node = $doc->createElement('a'); + for ($i = 0; $i < 25000; $i++) { + $parent = $doc->createElement('a'); + $parent->appendChild($node); + $node = $parent; + } + $root->appendChild($node); +} +$doc->appendChild($root); + +try { + $doc->normalize(); +} catch (\Error $e) { + echo "normalize: ", $e::class, ": ", $e->getMessage(), "\n"; + var_dump($e->getPrevious()); +} + +try { + $doc->normalizeDocument(); +} catch (\Error $e) { + echo "normalizeDocument: ", $e::class, ": ", $e->getMessage(), "\n"; + var_dump($e->getPrevious()); +} +?> +--EXPECT-- +normalize: Error: Maximum call stack size reached. Infinite recursion? +NULL +normalizeDocument: Error: Maximum call stack size reached. Infinite recursion? +NULL diff --git a/ext/dom/tests/modern/spec/gh23117.phpt b/ext/dom/tests/modern/spec/gh23117.phpt new file mode 100644 index 000000000000..d284677799cd --- /dev/null +++ b/ext/dom/tests/modern/spec/gh23117.phpt @@ -0,0 +1,41 @@ +--TEST-- +GH-23117 (Stack overflow when normalizing a deeply nested Dom\XMLDocument) +--EXTENSIONS-- +dom +--SKIPIF-- + +--INI-- +zend.max_allowed_stack_size=256K +--FILE-- +createElement('root'); +for ($s = 0; $s < 2; $s++) { + $node = $doc->createElement('a'); + for ($i = 0; $i < 25000; $i++) { + $parent = $doc->createElement('a'); + $parent->appendChild($node); + $node = $parent; + } + $root->appendChild($node); +} +$doc->appendChild($root); + +try { + $doc->normalize(); +} catch (\Error $e) { + echo "normalize: ", $e::class, ": ", $e->getMessage(), "\n"; + var_dump($e->getPrevious()); +} +?> +--EXPECT-- +normalize: Error: Maximum call stack size reached. Infinite recursion? +NULL