From 0439e917998c364a3ab21f8215f34b05751941e7 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Mon, 27 Jul 2026 05:12:24 +0100 Subject: [PATCH 1/2] ext/soap: to_xml_array() heap use-after-free with illegal iterator keys. Fix #22895 The return value of array_set_zval_key() was ignored, so when the key is not a legal array offset, e.g. MultipleIterator::key() returning an array, it threw and took no reference. The unconditional zval_ptr_dtor() then dropped the iterator's only reference to the borrowed value and the following Z_TRY_ADDREF_P() read freed memory. Let array_set_zval_key() own its reference the way spl_iterator_to_array_apply() does, and stop iterating on failure. --- ext/soap/php_encoding.c | 8 ++-- ext/soap/tests/bugs/gh22895.phpt | 77 ++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 ext/soap/tests/bugs/gh22895.phpt diff --git a/ext/soap/php_encoding.c b/ext/soap/php_encoding.c index 4eedbffc5a4b..7f845c956ed3 100644 --- a/ext/soap/php_encoding.c +++ b/ext/soap/php_encoding.c @@ -2312,13 +2312,15 @@ static xmlNodePtr to_xml_array(encodeTypePtr type, zval *data, int style, xmlNod if (EG(exception)) { goto iterator_done; } - array_set_zval_key(Z_ARRVAL(array_copy), &key, val); - zval_ptr_dtor(val); + zend_result status = array_set_zval_key(Z_ARRVAL(array_copy), &key, val); zval_ptr_dtor(&key); + if (status == FAILURE) { + goto iterator_done; + } } else { + Z_TRY_ADDREF_P(val); add_next_index_zval(&array_copy, val); } - Z_TRY_ADDREF_P(val); iter->funcs->move_forward(iter); if (EG(exception)) { diff --git a/ext/soap/tests/bugs/gh22895.phpt b/ext/soap/tests/bugs/gh22895.phpt new file mode 100644 index 000000000000..1ee8a99d7308 --- /dev/null +++ b/ext/soap/tests/bugs/gh22895.phpt @@ -0,0 +1,77 @@ +--TEST-- +GH-22895 (Heap use-after-free while encoding a Traversable with an illegal key) +--CREDITS-- +Amorsec +--EXTENSIONS-- +soap +--FILE-- +i++; + } + + public function rewind(): void + { + $this->i = 0; + } + + public function valid(): bool + { + return $this->i < 2; + } +} + +$client = new LocalSoapClient(null, [ + 'location' => 'http://127.0.0.1/', + 'uri' => 'urn:audit', + 'trace' => 1, +]); + +$multiple = new MultipleIterator(); +$multiple->attachIterator(new ArrayIterator([0])); + +foreach ([$multiple, new ArrayKeyIterator()] as $iterator) { + try { + $client->__soapCall('audit', [new SoapVar($iterator, SOAP_ENC_ARRAY)]); + } catch (TypeError $e) { + echo $e->getMessage(), PHP_EOL; + } +} + +/* A key the encoder can use must still be serialized, without leaking. */ +$client->__soapCall('audit', [new SoapVar(new ArrayIterator(['a' => 1]), SOAP_ENC_ARRAY)]); +echo $client->__getLastRequest(); +?> +--EXPECT-- +Cannot access offset of type array on array +Cannot access offset of type array on array + +1 From 88678aaa571ea46a2eca33262de579434422a2f6 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Tue, 28 Jul 2026 22:06:46 +0100 Subject: [PATCH 2/2] update test --- ext/soap/tests/bugs/gh22895.phpt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/soap/tests/bugs/gh22895.phpt b/ext/soap/tests/bugs/gh22895.phpt index 1ee8a99d7308..1d60935707c1 100644 --- a/ext/soap/tests/bugs/gh22895.phpt +++ b/ext/soap/tests/bugs/gh22895.phpt @@ -62,7 +62,7 @@ foreach ([$multiple, new ArrayKeyIterator()] as $iterator) { try { $client->__soapCall('audit', [new SoapVar($iterator, SOAP_ENC_ARRAY)]); } catch (TypeError $e) { - echo $e->getMessage(), PHP_EOL; + echo $e::class, ': ', $e->getMessage(), PHP_EOL; } } @@ -71,7 +71,7 @@ $client->__soapCall('audit', [new SoapVar(new ArrayIterator(['a' => 1]), SOAP_EN echo $client->__getLastRequest(); ?> --EXPECT-- -Cannot access offset of type array on array -Cannot access offset of type array on array +TypeError: Cannot access offset of type array on array +TypeError: Cannot access offset of type array on array 1