Skip to content

Throw instead of silently failing when creating a too long text node #15014

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
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
23 changes: 21 additions & 2 deletions ext/dom/parentnode/tree.c
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ zend_result dom_parent_node_child_element_count(dom_object *obj, zval *retval)
}
/* }}} */

static ZEND_COLD void dom_cannot_create_temp_nodes(void)
{
php_dom_throw_error_with_message(INVALID_MODIFICATION_ERR, "Unable to allocate temporary nodes", /* strict */ true);
}

static bool dom_is_node_in_list(const zval *nodes, uint32_t nodesc, const xmlNode *node_to_find)
{
for (uint32_t i = 0; i < nodesc; i++) {
Expand Down Expand Up @@ -363,12 +368,17 @@ xmlNode* dom_zvals_to_single_node(php_libxml_ref_obj *document, xmlNode *context
return dom_object_get_node(Z_DOMOBJ_P(nodes));
} else {
ZEND_ASSERT(Z_TYPE_P(nodes) == IS_STRING);
return xmlNewDocTextLen(documentNode, BAD_CAST Z_STRVAL_P(nodes), Z_STRLEN_P(nodes));
node = xmlNewDocTextLen(documentNode, BAD_CAST Z_STRVAL_P(nodes), Z_STRLEN_P(nodes));
if (UNEXPECTED(node == NULL)) {
dom_cannot_create_temp_nodes();
}
return node;
}
}

node = xmlNewDocFragment(documentNode);
if (UNEXPECTED(!node)) {
dom_cannot_create_temp_nodes();
return NULL;
}

Expand Down Expand Up @@ -408,6 +418,10 @@ xmlNode* dom_zvals_to_single_node(php_libxml_ref_obj *document, xmlNode *context

/* Text nodes can't violate the hierarchy at this point. */
newNode = xmlNewDocTextLen(documentNode, BAD_CAST Z_STRVAL(nodes[i]), Z_STRLEN(nodes[i]));
if (UNEXPECTED(newNode == NULL)) {
dom_cannot_create_temp_nodes();
goto err;
}
dom_add_child_without_merging(node, newNode);
}
}
Expand All @@ -434,7 +448,12 @@ static zend_result dom_sanity_check_node_list_types(zval *nodes, uint32_t nodesc
zend_argument_type_error(i + 1, "must be of type %s|string, %s given", ZSTR_VAL(node_ce->name), zend_zval_type_name(&nodes[i]));
return FAILURE;
}
} else if (type != IS_STRING) {
} else if (type == IS_STRING) {
if (Z_STRLEN(nodes[i]) > INT_MAX) {
zend_argument_value_error(i + 1, "must be less than or equal to %d bytes long", INT_MAX);
return FAILURE;
}
} else {
zend_argument_type_error(i + 1, "must be of type %s|string, %s given", ZSTR_VAL(node_ce->name), zend_zval_type_name(&nodes[i]));
return FAILURE;
}
Expand Down
80 changes: 80 additions & 0 deletions ext/dom/tests/parentnode_childnode_too_long_text.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
--TEST--
Passing a too long string to ChildNode or ParentNode methods causes an exception
--EXTENSIONS--
dom
--INI--
memory_limit=-1
--SKIPIF--
<?php
if (PHP_INT_SIZE !== 8) die('skip Only for 64-bit');
if (getenv('SKIP_SLOW_TESTS')) die('skip slow test');
// Copied from file_get_contents_file_put_contents_5gb.phpt
function get_system_memory(): int|float|false
{
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
// Windows-based memory check
@exec('wmic OS get FreePhysicalMemory', $output);
if (isset($output[1])) {
return ((int)trim($output[1])) * 1024;
}
} else {
// Unix/Linux-based memory check
$memInfo = @file_get_contents("/proc/meminfo");
if ($memInfo) {
preg_match('/MemFree:\s+(\d+) kB/', $memInfo, $matches);
return $matches[1] * 1024; // Convert to bytes
}
}
return false;
}
if (get_system_memory() < 4 * 1024 * 1024 * 1024) {
die('skip Reason: Insufficient RAM (less than 4GB)');
}
?>
--FILE--
<?php
$dom = new DOMDocument;
$element = $dom->appendChild($dom->createElement('root'));
$str = str_repeat('X', 2**31 + 10);
try {
$element->append('x', $str);
} catch (ValueError $e) {
echo $e->getMessage(), "\n";
}
try {
$element->prepend('x', $str);
} catch (ValueError $e) {
echo $e->getMessage(), "\n";
}
try {
$element->after('x', $str);
} catch (ValueError $e) {
echo $e->getMessage(), "\n";
}
try {
$element->before('x', $str);
} catch (ValueError $e) {
echo $e->getMessage(), "\n";
}
try {
$element->replaceWith('x', $str);
} catch (ValueError $e) {
echo $e->getMessage(), "\n";
}
try {
$element->replaceChildren('x', $str);
} catch (ValueError $e) {
echo $e->getMessage(), "\n";
}
var_dump($dom->childNodes->count());
var_dump($element->childNodes->count());
?>
--EXPECT--
DOMElement::append(): Argument #2 must be less than or equal to 2147483647 bytes long
DOMElement::prepend(): Argument #2 must be less than or equal to 2147483647 bytes long
DOMElement::after(): Argument #2 must be less than or equal to 2147483647 bytes long
DOMElement::before(): Argument #2 must be less than or equal to 2147483647 bytes long
DOMElement::replaceWith(): Argument #2 must be less than or equal to 2147483647 bytes long
DOMElement::replaceChildren(): Argument #2 must be less than or equal to 2147483647 bytes long
int(1)
int(0)
Loading