Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions ext/dom/html5_parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down
10 changes: 9 additions & 1 deletion ext/dom/private_data.c
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand All @@ -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);
}

Expand Down
1 change: 1 addition & 0 deletions ext/dom/private_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
17 changes: 17 additions & 0 deletions ext/dom/tests/gh25118.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--TEST--
GH-25118 (Dom\HTMLDocument::createFromString() crashes on OOM during conversion)
--EXTENSIONS--
dom
--SKIPIF--
<?php
if (PHP_INT_SIZE != 8) die("skip 64-bit only");
?>
--INI--
memory_limit=32M
--FILE--
<?php
$html = '<template><p>t</p><template><b>n</b></template></template><main>' . str_repeat('<i></i>', 100000) . '</main>';
Dom\HTMLDocument::createFromString($html, LIBXML_NOERROR);
?>
--EXPECTF--
Fatal error: Allowed memory size of 33554432 bytes exhausted %S(tried to allocate %d bytes) in %s on line %d
Loading