Skip to content

Commit

Permalink
Fix bug #55098: SimpleXML iteration produces infinite loop
Browse files Browse the repository at this point in the history
Closes GH-12247.
  • Loading branch information
nielsdos committed Sep 20, 2023
1 parent d61efdf commit 1a4e401
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 47 deletions.
1 change: 1 addition & 0 deletions NEWS
Expand Up @@ -25,6 +25,7 @@ PHP NEWS
var_dump/print_r). (nielsdos)
. Fixed bug GH-12208 (SimpleXML infinite loop when a cast is used inside a
foreach). (nielsdos)
. Fixed bug #55098 (SimpleXML iteration produces infinite loop). (nielsdos)

28 Sep 2023, PHP 8.1.24

Expand Down
71 changes: 26 additions & 45 deletions ext/simplexml/simplexml.c
Expand Up @@ -78,25 +78,6 @@ static void _node_as_zval(php_sxe_object *sxe, xmlNodePtr node, zval *value, SXE
}
/* }}} */

/* Important: this overwrites the iterator data, if you wish to keep it use php_sxe_get_first_node_non_destructive() instead! */
static xmlNodePtr php_sxe_get_first_node(php_sxe_object *sxe, xmlNodePtr node) /* {{{ */
{
php_sxe_object *intern;
xmlNodePtr retnode = NULL;

if (sxe && sxe->iter.type != SXE_ITER_NONE) {
php_sxe_reset_iterator(sxe, 1);
if (!Z_ISUNDEF(sxe->iter.data)) {
intern = Z_SXEOBJ_P(&sxe->iter.data);
GET_NODE(intern, retnode)
}
return retnode;
} else {
return node;
}
}
/* }}} */

static xmlNodePtr php_sxe_get_first_node_non_destructive(php_sxe_object *sxe, xmlNodePtr node)
{
if (sxe && sxe->iter.type != SXE_ITER_NONE) {
Expand Down Expand Up @@ -184,7 +165,7 @@ static xmlNodePtr sxe_get_element_by_name(php_sxe_object *sxe, xmlNodePtr node,
if (sxe->iter.type == SXE_ITER_NONE) {
sxe->iter.type = SXE_ITER_CHILD;
}
node = php_sxe_get_first_node(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);
sxe->iter.type = orgtype;
}

Expand Down Expand Up @@ -270,11 +251,11 @@ static zval *sxe_prop_dim_read(zend_object *object, zval *member, bool elements,
if (sxe->iter.type == SXE_ITER_ATTRLIST) {
attribs = 1;
elements = 0;
node = php_sxe_get_first_node(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);
attr = (xmlAttrPtr)node;
test = sxe->iter.name != NULL;
} else if (sxe->iter.type != SXE_ITER_CHILD) {
node = php_sxe_get_first_node(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);
attr = node ? node->properties : NULL;
test = 0;
if (!member && node && node->parent &&
Expand Down Expand Up @@ -322,7 +303,7 @@ static zval *sxe_prop_dim_read(zend_object *object, zval *member, bool elements,
xmlNodePtr mynode = node;

if (sxe->iter.type == SXE_ITER_CHILD) {
node = php_sxe_get_first_node(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);
}
if (sxe->iter.type == SXE_ITER_NONE) {
if (member && Z_LVAL_P(member) > 0) {
Expand Down Expand Up @@ -456,12 +437,12 @@ static zval *sxe_prop_dim_write(zend_object *object, zval *member, zval *value,
if (sxe->iter.type == SXE_ITER_ATTRLIST) {
attribs = 1;
elements = 0;
node = php_sxe_get_first_node(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);
attr = (xmlAttrPtr)node;
test = sxe->iter.name != NULL;
} else if (sxe->iter.type != SXE_ITER_CHILD) {
mynode = node;
node = php_sxe_get_first_node(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);
attr = node ? node->properties : NULL;
test = 0;
if (!member && node && node->parent &&
Expand Down Expand Up @@ -707,19 +688,19 @@ static int sxe_prop_dim_exists(zend_object *object, zval *member, int check_empt
attribs = 0;
elements = 1;
if (sxe->iter.type == SXE_ITER_CHILD) {
node = php_sxe_get_first_node(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);
}
}
}

if (sxe->iter.type == SXE_ITER_ATTRLIST) {
attribs = 1;
elements = 0;
node = php_sxe_get_first_node(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);
attr = (xmlAttrPtr)node;
test = sxe->iter.name != NULL;
} else if (sxe->iter.type != SXE_ITER_CHILD) {
node = php_sxe_get_first_node(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);
attr = node ? node->properties : NULL;
test = 0;
}
Expand Down Expand Up @@ -759,7 +740,7 @@ static int sxe_prop_dim_exists(zend_object *object, zval *member, int check_empt
if (elements) {
if (Z_TYPE_P(member) == IS_LONG) {
if (sxe->iter.type == SXE_ITER_CHILD) {
node = php_sxe_get_first_node(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);
}
node = sxe_get_element_by_offset(sxe, Z_LVAL_P(member), node, NULL);
} else {
Expand Down Expand Up @@ -829,19 +810,19 @@ static void sxe_prop_dim_delete(zend_object *object, zval *member, bool elements
attribs = 0;
elements = 1;
if (sxe->iter.type == SXE_ITER_CHILD) {
node = php_sxe_get_first_node(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);
}
}
}

if (sxe->iter.type == SXE_ITER_ATTRLIST) {
attribs = 1;
elements = 0;
node = php_sxe_get_first_node(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);
attr = (xmlAttrPtr)node;
test = sxe->iter.name != NULL;
} else if (sxe->iter.type != SXE_ITER_CHILD) {
node = php_sxe_get_first_node(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);
attr = node ? node->properties : NULL;
test = 0;
}
Expand Down Expand Up @@ -878,7 +859,7 @@ static void sxe_prop_dim_delete(zend_object *object, zval *member, bool elements
if (elements) {
if (Z_TYPE_P(member) == IS_LONG) {
if (sxe->iter.type == SXE_ITER_CHILD) {
node = php_sxe_get_first_node(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);
}
node = sxe_get_element_by_offset(sxe, Z_LVAL_P(member), node, NULL);
if (node) {
Expand Down Expand Up @@ -1011,7 +992,7 @@ static int sxe_prop_is_empty(zend_object *object) /* {{{ */
}

if (sxe->iter.type == SXE_ITER_ELEMENT) {
node = php_sxe_get_first_node(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);
}
if (!node || node->type != XML_ENTITY_DECL) {
attr = node ? (xmlAttrPtr)node->properties : NULL;
Expand All @@ -1025,7 +1006,7 @@ static int sxe_prop_is_empty(zend_object *object) /* {{{ */
}

GET_NODE(sxe, node);
node = php_sxe_get_first_node(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);
is_empty = 1;
ZVAL_UNDEF(&iter_data);
if (node && sxe->iter.type != SXE_ITER_ATTRLIST) {
Expand Down Expand Up @@ -1120,7 +1101,7 @@ static HashTable *sxe_get_prop_hash(zend_object *object, int is_debug) /* {{{ */
}
if (is_debug || sxe->iter.type != SXE_ITER_CHILD) {
if (sxe->iter.type == SXE_ITER_ELEMENT) {
node = php_sxe_get_first_node(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);
}
if (!node || node->type != XML_ENTITY_DECL) {
attr = node ? (xmlAttrPtr)node->properties : NULL;
Expand All @@ -1142,7 +1123,7 @@ static HashTable *sxe_get_prop_hash(zend_object *object, int is_debug) /* {{{ */
}

GET_NODE(sxe, node);
node = php_sxe_get_first_node(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);

if (node && sxe->iter.type != SXE_ITER_ATTRLIST) {
if (node->type == XML_ATTRIBUTE_NODE) {
Expand Down Expand Up @@ -1301,7 +1282,7 @@ PHP_METHOD(SimpleXMLElement, xpath)
}

GET_NODE(sxe, nodeptr);
nodeptr = php_sxe_get_first_node(sxe, nodeptr);
nodeptr = php_sxe_get_first_node_non_destructive(sxe, nodeptr);
if (!nodeptr) {
return;
}
Expand Down Expand Up @@ -1410,7 +1391,7 @@ PHP_METHOD(SimpleXMLElement, asXML)

sxe = Z_SXEOBJ_P(ZEND_THIS);
GET_NODE(sxe, node);
node = php_sxe_get_first_node(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);

if (!node) {
RETURN_FALSE;
Expand Down Expand Up @@ -1533,7 +1514,7 @@ PHP_METHOD(SimpleXMLElement, getNamespaces)

sxe = Z_SXEOBJ_P(ZEND_THIS);
GET_NODE(sxe, node);
node = php_sxe_get_first_node(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);

if (node) {
if (node->type == XML_ELEMENT_NODE) {
Expand Down Expand Up @@ -1618,7 +1599,7 @@ PHP_METHOD(SimpleXMLElement, children)
}

GET_NODE(sxe, node);
node = php_sxe_get_first_node(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);
if (!node) {
return;
}
Expand Down Expand Up @@ -1667,7 +1648,7 @@ PHP_METHOD(SimpleXMLElement, attributes)

sxe = Z_SXEOBJ_P(ZEND_THIS);
GET_NODE(sxe, node);
node = php_sxe_get_first_node(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);
if (!node) {
return;
}
Expand Down Expand Up @@ -1708,7 +1689,7 @@ PHP_METHOD(SimpleXMLElement, addChild)
return;
}

node = php_sxe_get_first_node(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);

if (node == NULL) {
php_error_docref(NULL, E_WARNING, "Cannot add child. Parent is not a permanent member of the XML tree");
Expand Down Expand Up @@ -1768,7 +1749,7 @@ PHP_METHOD(SimpleXMLElement, addAttribute)
sxe = Z_SXEOBJ_P(ZEND_THIS);
GET_NODE(sxe, node);

node = php_sxe_get_first_node(sxe, node);
node = php_sxe_get_first_node_non_destructive(sxe, node);

if (node && node->type != XML_ELEMENT_NODE) {
node = node->parent;
Expand Down Expand Up @@ -2615,7 +2596,7 @@ void *simplexml_export_node(zval *object) /* {{{ */

sxe = Z_SXEOBJ_P(object);
GET_NODE(sxe, node);
return php_sxe_get_first_node(sxe, node);
return php_sxe_get_first_node_non_destructive(sxe, node);
}
/* }}} */

Expand Down
92 changes: 92 additions & 0 deletions ext/simplexml/tests/bug55098.phpt
@@ -0,0 +1,92 @@
--TEST--
Bug #55098 (SimpleXML iteration produces infinite loop)
--EXTENSIONS--
simplexml
--FILE--
<?php
$xmlString = "<root><a><b>1</b><b>2</b><b>3</b></a></root>";
$xml = simplexml_load_string($xmlString);

$nodes = $xml->a->b;

function test($nodes, $name, $callable) {
echo "--- $name ---\n";
foreach ($nodes as $nodeData) {
echo "nodeData: " . $nodeData . "\n";
$callable($nodes);
}
}

test($nodes, "asXml", fn ($n) => $n->asXml());
test($nodes, "attributes", fn ($n) => $n->attributes());
test($nodes, "children", fn ($n) => $n->children());
test($nodes, "getNamespaces", fn ($n) => $n->getNamespaces());
test($nodes, "xpath", fn ($n) => $n->xpath("/root/a/b"));
test($nodes, "var_dump", fn ($n) => var_dump($n));
test($nodes, "manipulation combined with querying", function ($n) {
$n->addAttribute("attr", "value");
(bool) $n["attr"];
$n->addChild("child", "value");
$n->outer[]->inner = "foo";
(bool) $n->outer;
(bool) $n;
isset($n->outer);
isset($n["attr"]);
unset($n->outer);
unset($n["attr"]);
unset($n->child);
});
?>
--EXPECT--
--- asXml ---
nodeData: 1
nodeData: 2
nodeData: 3
--- attributes ---
nodeData: 1
nodeData: 2
nodeData: 3
--- children ---
nodeData: 1
nodeData: 2
nodeData: 3
--- getNamespaces ---
nodeData: 1
nodeData: 2
nodeData: 3
--- xpath ---
nodeData: 1
nodeData: 2
nodeData: 3
--- var_dump ---
nodeData: 1
object(SimpleXMLElement)#3 (3) {
[0]=>
string(1) "1"
[1]=>
string(1) "2"
[2]=>
string(1) "3"
}
nodeData: 2
object(SimpleXMLElement)#3 (3) {
[0]=>
string(1) "1"
[1]=>
string(1) "2"
[2]=>
string(1) "3"
}
nodeData: 3
object(SimpleXMLElement)#3 (3) {
[0]=>
string(1) "1"
[1]=>
string(1) "2"
[2]=>
string(1) "3"
}
--- manipulation combined with querying ---
nodeData: 1
nodeData: 2
nodeData: 3
4 changes: 2 additions & 2 deletions ext/simplexml/tests/bug62639.phpt
Expand Up @@ -41,7 +41,7 @@ foreach ($a2->b->c->children() as $key => $value) {
var_dump($value);
}?>
--EXPECT--
object(A)#2 (2) {
object(A)#4 (2) {
["@attributes"]=>
array(1) {
["attr"]=>
Expand All @@ -50,7 +50,7 @@ object(A)#2 (2) {
[0]=>
string(10) "Some Value"
}
object(A)#3 (2) {
object(A)#6 (2) {
["@attributes"]=>
array(1) {
["attr"]=>
Expand Down

0 comments on commit 1a4e401

Please sign in to comment.