1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

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

Lower branches suffer from this as well but we cannot change the
behaviour there.
We also add NULL checks to check for allocation failure.

Closes GH-15014.
This commit is contained in:
Niels Dossche
2024-07-18 19:41:29 +02:00
parent c4e1f2b4e8
commit 9435f4d55b
3 changed files with 103 additions and 2 deletions

2
NEWS
View File

@@ -11,6 +11,8 @@ PHP NEWS
- DOM:
. Fix trampoline leak in xpath callables. (nielsdos)
. Throw instead of silently failing when creating a too long text node in
(DOM)ParentNode and (DOM)ChildNode. (nielsdos)
- OpenSSL:
. Bumped minimum required OpenSSL version to 1.1.0. (cmb)

View File

@@ -88,6 +88,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++) {
@@ -353,12 +358,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;
}
@@ -397,6 +407,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);
}
}
@@ -423,7 +437,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;
}

View File

@@ -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)