From 7bdf74496d8ea6366b6e80a35c0e919e04b61e39 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Wed, 5 Aug 2026 09:16:12 -0400 Subject: [PATCH] Fix negative SimpleXML offsets aliasing the first element sxe_get_element_by_offset scanned with nodendx <= offset, so a negative offset skipped the loop and returned the node it started from. Reads and isset() reported the first element, and a write overwrote it. Negative offsets now miss, and writing to one warns like an out-of-range positive offset instead of creating a node. Closes GH-23068 --- NEWS | 3 ++ ext/simplexml/simplexml.c | 18 ++++++---- .../tests/bug_sxe_negative_offset.phpt | 34 +++++++++++++++++++ 3 files changed, 49 insertions(+), 6 deletions(-) create mode 100644 ext/simplexml/tests/bug_sxe_negative_offset.phpt diff --git a/NEWS b/NEWS index 474db936ef15..9ce1698bce56 100644 --- a/NEWS +++ b/NEWS @@ -42,6 +42,9 @@ PHP NEWS . Fixed bug GH-23043 (broken session id code can cause zend_mm_heap corrupted). (ndossche) +- SimpleXML: + . Fixed negative element offsets aliasing the first element. (iliaal) + - Sockets: . Fixed various memory related issues in ext/sockets. (David Carlier) diff --git a/ext/simplexml/simplexml.c b/ext/simplexml/simplexml.c index 8cf8e657e58f..2976885f6043 100644 --- a/ext/simplexml/simplexml.c +++ b/ext/simplexml/simplexml.c @@ -134,7 +134,7 @@ static xmlNodePtr sxe_get_element_by_offset(php_sxe_object *sxe, zend_long offse return NULL; } } - while (node && nodendx <= offset) { + while (node && (offset < 0 || nodendx <= offset)) { if (node->type == XML_ELEMENT_NODE && match_ns(node, sxe->iter.nsprefix, sxe->iter.isprefix)) { if (sxe->iter.type == SXE_ITER_CHILD || ( sxe->iter.type == SXE_ITER_ELEMENT && xmlStrEqual(node->name, BAD_CAST ZSTR_VAL(sxe->iter.name)))) { @@ -319,11 +319,13 @@ static zval *sxe_prop_dim_read(zend_object *object, zval *member, bool elements, if (node) { node_as_zval(sxe, node, rv, SXE_ITER_NONE, NULL, sxe->iter.nsprefix, sxe->iter.isprefix); } else if (type == BP_VAR_W || type == BP_VAR_RW) { - if (member && cnt < Z_LVAL_P(member)) { + if (member && (Z_LVAL_P(member) < 0 || cnt < Z_LVAL_P(member))) { php_error_docref(NULL, E_WARNING, "Cannot add element %s number " ZEND_LONG_FMT " when only " ZEND_LONG_FMT " such elements exist", mynode->name, Z_LVAL_P(member), cnt); } - node = xmlNewTextChild(mynode->parent, mynode->ns, mynode->name, NULL); - node_as_zval(sxe, node, rv, SXE_ITER_NONE, NULL, sxe->iter.nsprefix, sxe->iter.isprefix); + if (!member || Z_LVAL_P(member) >= 0) { + node = xmlNewTextChild(mynode->parent, mynode->ns, mynode->name, NULL); + node_as_zval(sxe, node, rv, SXE_ITER_NONE, NULL, sxe->iter.nsprefix, sxe->iter.isprefix); + } } } else { /* In BP_VAR_IS mode only return a proper node if it actually exists. */ @@ -586,10 +588,14 @@ static zval *sxe_prop_dim_write(zend_object *object, zval *member, zval *value, newnode = xmlNewTextChild(mynode, NULL, (xmlChar *)Z_STRVAL_P(member), value_str ? (xmlChar *)ZSTR_VAL(value_str) : NULL); } } else if (!member || Z_TYPE_P(member) == IS_LONG) { - if (member && cnt < Z_LVAL_P(member)) { + if (member && (Z_LVAL_P(member) < 0 || cnt < Z_LVAL_P(member))) { php_error_docref(NULL, E_WARNING, "Cannot add element %s number " ZEND_LONG_FMT " when only " ZEND_LONG_FMT " such elements exist", mynode->name, Z_LVAL_P(member), cnt); } - newnode = xmlNewTextChild(mynode->parent, mynode->ns, mynode->name, value_str ? (xmlChar *)ZSTR_VAL(value_str) : NULL); + if (member && Z_LVAL_P(member) < 0) { + value = &EG(error_zval); + } else { + newnode = xmlNewTextChild(mynode->parent, mynode->ns, mynode->name, value_str ? (xmlChar *)ZSTR_VAL(value_str) : NULL); + } } } else if (attribs) { if (Z_TYPE_P(member) == IS_LONG) { diff --git a/ext/simplexml/tests/bug_sxe_negative_offset.phpt b/ext/simplexml/tests/bug_sxe_negative_offset.phpt new file mode 100644 index 000000000000..5df3cb2548e2 --- /dev/null +++ b/ext/simplexml/tests/bug_sxe_negative_offset.phpt @@ -0,0 +1,34 @@ +--TEST-- +Negative SimpleXML element offsets must not alias the first element +--EXTENSIONS-- +simplexml +--FILE-- +abc'); +$items = $xml->item; + +echo "isset[-1]: "; +var_dump(isset($items[-1])); +echo "read[-1]: "; +var_dump($items[-1]); +echo "read[0]: "; +var_dump((string)$items[0]); + +$items[-1] = 'Z'; +echo "after negative write: ", $xml->asXML(); + +$items[5] = 'P'; +echo "after out-of-range write: ", $xml->asXML(); +?> +--EXPECTF-- +isset[-1]: bool(false) +read[-1]: NULL +read[0]: string(1) "a" + +Warning: main(): Cannot add element item number -1 when only 3 such elements exist in %s on line %d +after negative write: +abc + +Warning: main(): Cannot add element item number 5 when only 3 such elements exist in %s on line %d +after out-of-range write: +abcP