From f2bf8343fabe0b084f7aaec7ae95017864c4c0af Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 1 Aug 2026 17:32:18 +0100 Subject: [PATCH 1/2] ext/soap: to_zval_array() native stack overflow on cyclic encoded references. Fix #22984 The array and apache Map decoders never registered the node being decoded, unlike to_zval_object_ex(), so a child href or enc:ref pointing back at an enclosing array recursed until the native stack was exhausted. An empty holder is now registered before the children are converted, and replaced by the completed value afterwards. --- ext/soap/php_encoding.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/ext/soap/php_encoding.c b/ext/soap/php_encoding.c index 4eedbffc5a4b..d0911c863d89 100644 --- a/ext/soap/php_encoding.c +++ b/ext/soap/php_encoding.c @@ -380,6 +380,16 @@ static void soap_add_xml_ref(zval *data, xmlNodePtr node) } } +static void soap_add_xml_ref_holder(zval *data, xmlNodePtr node) +{ + if (SOAP_GLOBAL(ref_map)) { + zval holder; + + array_init(&holder); + zend_hash_index_update(SOAP_GLOBAL(ref_map), (zend_ulong)node, &holder); + } +} + static xmlNodePtr master_to_xml_int(encodePtr encode, zval *data, int style, xmlNodePtr parent, int check_class_map) { xmlNodePtr node = NULL; @@ -2536,6 +2546,9 @@ static zval *to_zval_array(zval *ret, encodeTypePtr type, xmlNodePtr data) ZVAL_NULL(ret); FIND_XML_NULL(data, ret); + if (soap_check_xml_ref(ret, data)) { + return ret; + } if (data && (attr = get_attribute(data->properties,"arrayType")) && @@ -2672,6 +2685,7 @@ static zval *to_zval_array(zval *ret, encodeTypePtr type, xmlNodePtr data) } array_init(ret); + soap_add_xml_ref_holder(ret, data); trav = data->children; while (trav) { if (trav->type == XML_ELEMENT_NODE) { @@ -2729,6 +2743,7 @@ static zval *to_zval_array(zval *ret, encodeTypePtr type, xmlNodePtr data) } efree(dims); efree(pos); + soap_add_xml_ref(ret, data); return ret; } @@ -2798,9 +2813,13 @@ static zval *to_zval_map(zval *ret, encodeTypePtr type, xmlNodePtr data) ZVAL_NULL(ret); FIND_XML_NULL(data, ret); + if (soap_check_xml_ref(ret, data)) { + return ret; + } if (data && data->children) { array_init(ret); + soap_add_xml_ref_holder(ret, data); trav = data->children; trav = data->children; @@ -2830,6 +2849,7 @@ static zval *to_zval_map(zval *ret, encodeTypePtr type, xmlNodePtr data) zval_ptr_dtor(&key); } ENDFOREACH(trav); + soap_add_xml_ref(ret, data); } else { ZVAL_NULL(ret); } From 5ad30985542e5b3e6f243bad6a6eb8c80c83de7d Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 1 Aug 2026 18:10:01 +0100 Subject: [PATCH 2/2] add tests --- ext/soap/tests/bugs/gh22984_1.phpt | 32 ++++++++++++++++++++ ext/soap/tests/bugs/gh22984_2.phpt | 47 ++++++++++++++++++++++++++++++ ext/soap/tests/bugs/gh22984_3.phpt | 35 ++++++++++++++++++++++ ext/soap/tests/bugs/gh22984_4.phpt | 40 +++++++++++++++++++++++++ 4 files changed, 154 insertions(+) create mode 100644 ext/soap/tests/bugs/gh22984_1.phpt create mode 100644 ext/soap/tests/bugs/gh22984_2.phpt create mode 100644 ext/soap/tests/bugs/gh22984_3.phpt create mode 100644 ext/soap/tests/bugs/gh22984_4.phpt diff --git a/ext/soap/tests/bugs/gh22984_1.phpt b/ext/soap/tests/bugs/gh22984_1.phpt new file mode 100644 index 000000000000..4257555e086c --- /dev/null +++ b/ext/soap/tests/bugs/gh22984_1.phpt @@ -0,0 +1,32 @@ +--TEST-- +GH-22984 (Native stack overflow while decoding a cyclic SOAP 1.1 href reference) +--EXTENSIONS-- +soap +--FILE-- + + + + + + + + + + +XML; + } +} + +$client = new TestSoapClient(null, ['location' => 'test://', 'uri' => 'http://testuri.org', 'soap_version' => SOAP_1_1]); +var_dump($client->__soapCall('probe', [])); +?> +--EXPECT-- +array(1) { + [0]=> + array(0) { + } +} diff --git a/ext/soap/tests/bugs/gh22984_2.phpt b/ext/soap/tests/bugs/gh22984_2.phpt new file mode 100644 index 000000000000..07fd190cf34a --- /dev/null +++ b/ext/soap/tests/bugs/gh22984_2.phpt @@ -0,0 +1,47 @@ +--TEST-- +GH-22984 (Native stack overflow while decoding a cyclic SOAP 1.2 enc:ref reference) +--EXTENSIONS-- +soap +--FILE-- + + + + + + + + + + + + + +XML; + } +} + +$client = new TestSoapClient(null, ['location' => 'test://', 'uri' => 'http://testuri.org', 'soap_version' => SOAP_1_2]); +var_dump($client->__soapCall('probe', [])); +?> +--EXPECT-- +array(2) { + ["ret"]=> + array(1) { + [0]=> + array(1) { + [0]=> + array(0) { + } + } + } + ["node"]=> + array(1) { + [0]=> + array(0) { + } + } +} diff --git a/ext/soap/tests/bugs/gh22984_3.phpt b/ext/soap/tests/bugs/gh22984_3.phpt new file mode 100644 index 000000000000..ebe0eb3d9d38 --- /dev/null +++ b/ext/soap/tests/bugs/gh22984_3.phpt @@ -0,0 +1,35 @@ +--TEST-- +GH-22984 (Native stack overflow while decoding a cyclic reference inside an apache Map) +--EXTENSIONS-- +soap +--FILE-- + + + + + + + self + + + + + + +XML; + } +} + +$client = new TestSoapClient(null, ['location' => 'test://', 'uri' => 'http://testuri.org', 'soap_version' => SOAP_1_1]); +var_dump($client->__soapCall('probe', [])); +?> +--EXPECT-- +array(1) { + ["self"]=> + array(0) { + } +} diff --git a/ext/soap/tests/bugs/gh22984_4.phpt b/ext/soap/tests/bugs/gh22984_4.phpt new file mode 100644 index 000000000000..41e2d3b11182 --- /dev/null +++ b/ext/soap/tests/bugs/gh22984_4.phpt @@ -0,0 +1,40 @@ +--TEST-- +GH-22984 (Native stack overflow while a SoapServer decodes a cyclic href reference) +--EXTENSIONS-- +soap +--FILE-- + + + + + + + + + + +XML; + +$server = new SoapServer(null, ['uri' => 'http://testuri.org']); +$server->addFunction('probe'); +$server->handle($request); +var_dump($decoded); +?> +--EXPECT-- + +1 +array(1) { + [0]=> + array(0) { + } +}