diff --git a/ext/dom/html5_parser.c b/ext/dom/html5_parser.c index d946979b705d..74c76cb95f6d 100644 --- a/ext/dom/html5_parser.c +++ b/ext/dom/html5_parser.c @@ -42,7 +42,7 @@ typedef struct work_list_item { bool in_template; } work_list_item; -static void lexbor_libxml2_bridge_work_list_item_push( +static bool lexbor_libxml2_bridge_work_list_item_push( lexbor_array_obj_t *array, lxb_dom_node_t *node, uintptr_t current_active_namespace, @@ -52,11 +52,15 @@ static void lexbor_libxml2_bridge_work_list_item_push( ) { work_list_item *item = (work_list_item *) lexbor_array_obj_push_wo_cls(array); + if (UNEXPECTED(item == NULL)) { + return false; + } item->node = node; item->current_active_namespace = current_active_namespace; item->lxml_parent = lxml_parent; item->lxml_ns = lxml_ns; item->in_template = in_template; + return true; } static unsigned short sanitize_line_nr(size_t line) @@ -125,10 +129,15 @@ static lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert( xmlNsPtr prefixed_xmlns_ns = NULL; lexbor_array_obj_t work_list; - lexbor_array_obj_init(&work_list, WORK_LIST_INIT_SIZE, sizeof(work_list_item)); + if (UNEXPECTED(lexbor_array_obj_init(&work_list, WORK_LIST_INIT_SIZE, sizeof(work_list_item)) != LXB_STATUS_OK)) { + return LEXBOR_LIBXML2_BRIDGE_STATUS_OOM; + } for (lxb_dom_node_t *node = start_node; node != NULL; node = node->prev) { - lexbor_libxml2_bridge_work_list_item_push(&work_list, node, LXB_NS__UNDEF, root, NULL, in_template_contents); + if (UNEXPECTED(!lexbor_libxml2_bridge_work_list_item_push(&work_list, node, LXB_NS__UNDEF, root, NULL, in_template_contents))) { + lexbor_array_obj_destroy(&work_list, false); + return LEXBOR_LIBXML2_BRIDGE_STATUS_OOM; + } } work_list_item *current_stack_item; @@ -200,14 +209,20 @@ static lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert( } for (; child_node != NULL; child_node = child_node->prev) { - lexbor_libxml2_bridge_work_list_item_push( + if (UNEXPECTED(!lexbor_libxml2_bridge_work_list_item_push( &work_list, child_node, entering_namespace, lxml_child_parent, current_lxml_ns, child_in_template - ); + ))) { + retval = LEXBOR_LIBXML2_BRIDGE_STATUS_OOM; + break; + } + } + if (UNEXPECTED(retval != LEXBOR_LIBXML2_BRIDGE_STATUS_OK)) { + break; } xmlAttrPtr last_added_attr = NULL; @@ -381,6 +396,7 @@ lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert_document( private_data ); if (status != LEXBOR_LIBXML2_BRIDGE_STATUS_OK) { + php_dom_remove_templated_content_in_subtree(private_data, (xmlNodePtr) lxml_doc); xmlFreeDoc(lxml_doc); return status; } @@ -412,6 +428,7 @@ lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert_fragment( private_data ); if (status != LEXBOR_LIBXML2_BRIDGE_STATUS_OK) { + php_dom_remove_templated_content_in_subtree(private_data, fragment); xmlFreeNode(fragment); return status; } diff --git a/ext/dom/private_data.c b/ext/dom/private_data.c index 66b3f4f87f29..213eff8a61b5 100644 --- a/ext/dom/private_data.c +++ b/ext/dom/private_data.c @@ -70,8 +70,12 @@ void php_dom_private_data_destroy(php_dom_private_data *data) efree(data); } -static void php_dom_free_templated_content(php_dom_private_data *private_data, xmlNodePtr base) +void php_dom_remove_templated_content_in_subtree(php_dom_private_data *private_data, xmlNodePtr base) { + if (private_data->template_fragments == NULL) { + return; + } + /* Note: it's not possible to obtain a userland reference to these yet, so we can just free them without worrying * about their proxies. * Note 2: it's possible to have nested template content. */ @@ -87,7 +91,11 @@ static void php_dom_free_templated_content(php_dom_private_data *private_data, x current = php_dom_next_in_tree_order(current, base); } } +} +static void php_dom_free_templated_content(php_dom_private_data *private_data, xmlNodePtr base) +{ + php_dom_remove_templated_content_in_subtree(private_data, base); xmlFreeNode(base); } diff --git a/ext/dom/private_data.h b/ext/dom/private_data.h index 109f06c90502..df6cf407cf6d 100644 --- a/ext/dom/private_data.h +++ b/ext/dom/private_data.h @@ -50,6 +50,7 @@ void php_dom_add_templated_content(php_dom_private_data *private_data, const xml xmlNodePtr php_dom_retrieve_templated_content(php_dom_private_data *private_data, const xmlNode *template_node); xmlNodePtr php_dom_ensure_templated_content(php_dom_private_data *private_data, xmlNodePtr template_node); void php_dom_remove_templated_content(php_dom_private_data *private_data, const xmlNode *template_node); +void php_dom_remove_templated_content_in_subtree(php_dom_private_data *private_data, xmlNodePtr base); uint32_t php_dom_get_template_count(const php_dom_private_data *private_data); void dom_add_element_ns_hook(php_dom_private_data *private_data, xmlNodePtr element); diff --git a/ext/dom/tests/gh25118.phpt b/ext/dom/tests/gh25118.phpt new file mode 100644 index 000000000000..790ae71ef337 --- /dev/null +++ b/ext/dom/tests/gh25118.phpt @@ -0,0 +1,17 @@ +--TEST-- +GH-25118 (Dom\HTMLDocument::createFromString() crashes on OOM during conversion) +--EXTENSIONS-- +dom +--SKIPIF-- + +--INI-- +memory_limit=32M +--FILE-- +
t
n