Commit d044f28b808 for php

commit d044f28b808e13dcbf4ab3f7529e6e59d4327435
Author: David Carlier <devnexen@gmail.com>
Date:   Thu Oct 1 19:12:01 2026 +0100

    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. array_set_zval_key() now
    also fails when a float or resource key diagnostic throws, and the loop
    stops on failure.

    Close GH-22896

diff --git a/NEWS b/NEWS
index 41ec25f4f1f..439f7c83f2c 100644
--- a/NEWS
+++ b/NEWS
@@ -167,6 +167,10 @@ PHP                                                                        NEWS
   . Fixed reconstructing a SimpleXMLElement freeing a child element that
     another variable still references. (Ilia Alshanetsky)

+- SOAP:
+  . Fixed bug GH-22895 (Heap use-after-free while encoding a Traversable with
+    an illegal key). (David Carlier)
+
 - Sockets:
   . Fixed socket_select() silently truncating sets larger than FD_SETSIZE on
     Windows. (David Carlier)
diff --git a/Zend/zend_API.c b/Zend/zend_API.c
index 2b3068dadef..2a61b689106 100644
--- a/Zend/zend_API.c
+++ b/Zend/zend_API.c
@@ -2252,6 +2252,9 @@ ZEND_API zend_result array_set_zval_key(HashTable *ht, zval *key, zval *value) /
 			break;
 		case IS_RESOURCE:
 			zend_use_resource_as_offset(key);
+			if (UNEXPECTED(EG(exception))) {
+				return FAILURE;
+			}
 			result = zend_hash_index_update(ht, Z_RES_HANDLE_P(key), value);
 			break;
 		case IS_FALSE:
@@ -2263,9 +2266,14 @@ ZEND_API zend_result array_set_zval_key(HashTable *ht, zval *key, zval *value) /
 		case IS_LONG:
 			result = zend_hash_index_update(ht, Z_LVAL_P(key), value);
 			break;
-		case IS_DOUBLE:
-			result = zend_hash_index_update(ht, zend_dval_to_lval_safe(Z_DVAL_P(key)), value);
+		case IS_DOUBLE: {
+			zend_long lval = zend_dval_to_lval_safe(Z_DVAL_P(key));
+			if (UNEXPECTED(EG(exception))) {
+				return FAILURE;
+			}
+			result = zend_hash_index_update(ht, lval, value);
 			break;
+		}
 		default:
 			zend_illegal_container_offset(ZSTR_KNOWN(ZEND_STR_ARRAY), key, BP_VAR_W);
 			result = NULL;
diff --git a/ext/soap/php_encoding.c b/ext/soap/php_encoding.c
index f60b2b877f5..b344af77b3f 100644
--- a/ext/soap/php_encoding.c
+++ b/ext/soap/php_encoding.c
@@ -2317,13 +2317,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 00000000000..a253f33d3da
--- /dev/null
+++ b/ext/soap/tests/bugs/gh22895.phpt
@@ -0,0 +1,96 @@
+--TEST--
+GH-22895 (Heap use-after-free while encoding a Traversable with an illegal key)
+--CREDITS--
+Amorsec
+--EXTENSIONS--
+soap
+--FILE--
+<?php
+class LocalSoapClient extends SoapClient
+{
+    public function __doRequest(
+        $request,
+        $location,
+        $action,
+        $version,
+        $one_way = false
+    ): ?string {
+        return '';
+    }
+}
+
+class ArrayKeyIterator implements Iterator
+{
+    private int $i = 0;
+
+    public function current(): mixed
+    {
+        return new stdClass();
+    }
+
+    public function key(): mixed
+    {
+        return ['illegal', 'key'];
+    }
+
+    public function next(): void
+    {
+        $this->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::class, ': ', $e->getMessage(), PHP_EOL;
+    }
+}
+
+function gen($key) {
+    yield $key => new stdClass();
+    yield 2 => new stdClass();
+}
+
+set_error_handler(function ($errno, $errstr) {
+    throw new Exception($errstr);
+});
+foreach ([1.5, STDIN] as $key) {
+    try {
+        $client->__soapCall('audit', [new SoapVar(gen($key), SOAP_ENC_ARRAY)]);
+    } catch (Exception $e) {
+        echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+    }
+}
+restore_error_handler();
+
+/* 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();
+?>
+--EXPECTF--
+TypeError: Cannot access offset of type array on array
+TypeError: Cannot access offset of type array on array
+Exception: Implicit conversion from float 1.5 to int loses precision
+Exception: Resource ID#%d used as offset, casting to integer (%d)
+<?xml version="1.0" encoding="UTF-8"?>
+<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="urn:audit" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:audit><param0 SOAP-ENC:arrayType="xsd:int[1]" xsi:type="SOAP-ENC:Array"><item xsi:type="xsd:int">1</item></param0></ns1:audit></SOAP-ENV:Body></SOAP-ENV:Envelope>