diff --git a/NEWS b/NEWS index 6d4d1865d790..08a852a5a0d8 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,10 @@ PHP NEWS - DBA: . Fixed OOB read on malformed length field in dba flatfile handler. (alhudz) +- DOM: + . Fixed bug GH-22570 (Stack overflow when serializing a deeply nested + Dom\XMLDocument). (iliaal) + - Exif: . Fixed bug GH-11020 (exif_read_data() emits a spurious "Illegal IFD size" warning when an IFD is not followed by a next-IFD offset). (Eyüp Can Akman) diff --git a/ext/dom/document.c b/ext/dom/document.c index e4d285c990fe..9c269e4cb14d 100644 --- a/ext/dom/document.c +++ b/ext/dom/document.c @@ -1678,7 +1678,9 @@ static void dom_document_save_xml(INTERNAL_FUNCTION_PARAMETERS, zend_class_entry } if (!res) { - php_error_docref(NULL, E_WARNING, "Could not save document"); + if (!EG(exception)) { + php_error_docref(NULL, E_WARNING, "Could not save document"); + } RETURN_FALSE; } else { RETURN_NEW_STR(res); diff --git a/ext/dom/inner_html_mixin.c b/ext/dom/inner_html_mixin.c index 19f640e6b4ac..e81ca5a7b238 100644 --- a/ext/dom/inner_html_mixin.c +++ b/ext/dom/inner_html_mixin.c @@ -103,7 +103,9 @@ zend_result dom_element_inner_html_read(dom_object *obj, zval *retval) } if (UNEXPECTED(status < 0)) { smart_str_free_ex(&str, false); - php_dom_throw_error_with_message(SYNTAX_ERR, "The resulting XML serialization is not well-formed", true); + if (!EG(exception)) { + php_dom_throw_error_with_message(SYNTAX_ERR, "The resulting XML serialization is not well-formed", true); + } return FAILURE; } ZVAL_STR(retval, smart_str_extract(&str)); diff --git a/ext/dom/tests/modern/xml/gh22570.phpt b/ext/dom/tests/modern/xml/gh22570.phpt new file mode 100644 index 000000000000..af78acf00dfa --- /dev/null +++ b/ext/dom/tests/modern/xml/gh22570.phpt @@ -0,0 +1,40 @@ +--TEST-- +GH-22570 (Stack overflow when serializing a deeply nested Dom\XMLDocument) +--EXTENSIONS-- +dom +--SKIPIF-- + +--INI-- +zend.max_allowed_stack_size=512K +--FILE-- +appendChild($doc->createElement('root')); +for ($i = 0; $i < 100000; $i++) { + $node = $node->appendChild($doc->createElement('a')); +} + +try { + $doc->saveXml(); +} catch (\Error $e) { + echo "saveXml: ", $e::class, ": ", $e->getMessage(), "\n"; +} + +try { + $doc->documentElement->innerHTML; +} catch (\Error $e) { + echo "innerHTML: ", $e::class, ": ", $e->getMessage(), "\n"; +} +?> +--EXPECT-- +saveXml: Error: Maximum call stack size reached. Infinite recursion? +innerHTML: Error: Maximum call stack size reached. Infinite recursion? diff --git a/ext/dom/xml_serializer.c b/ext/dom/xml_serializer.c index 5e6a6f93b91c..2d9e55a7b706 100644 --- a/ext/dom/xml_serializer.c +++ b/ext/dom/xml_serializer.c @@ -1250,6 +1250,15 @@ static int dom_xml_serializing_a_document_node( return 0; } +static zend_always_inline bool dom_xml_serialize_check_stack_limit(void) +{ +#ifdef ZEND_CHECK_STACK_LIMIT + return zend_call_stack_overflowed(EG(stack_limit)); +#else + return false; +#endif +} + /* https://w3c.github.io/DOM-Parsing/#dfn-xml-serialization-algorithm */ static int dom_xml_serialization_algorithm( dom_xml_serialize_ctx *ctx, @@ -1261,6 +1270,11 @@ static int dom_xml_serialization_algorithm( bool require_well_formed ) { + if (UNEXPECTED(dom_xml_serialize_check_stack_limit())) { + zend_throw_error(NULL, "Maximum call stack size reached. Infinite recursion?"); + return -1; + } + /* If node's interface is: */ switch (node->type) { case XML_ELEMENT_NODE: