Commit 81a83bb33a6 for php

commit 81a83bb33a62838a7f2986d02daf8769afac4ef4
Merge: ebd3f8d3e28 d044f28b808
Author: David Carlier <devnexen@gmail.com>
Date:   Thu Oct 1 19:12:52 2026 +0100

    Merge branch 'PHP-8.4' into PHP-8.5

    * PHP-8.4:
      ext/soap: to_xml_array() heap use-after-free with illegal iterator keys.

    # Conflicts:
    #       Zend/zend_API.c

diff --cc NEWS
index 4749cdf78ce,439f7c83f2c..1cc0c1891fe
--- a/NEWS
+++ b/NEWS
@@@ -172,6 -167,20 +172,10 @@@ PH
    . 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)
 -
 -- SPL:
 -  . Fixed SplFixedArray::setSize() doing nothing on subclasses whose
 -    constructor does not call parent::__construct(). (Marc Bennewitz)
 -  . Fixed memory leak when __construct(), __wakeup() or __unserialize() is
 -    called from an element destructor during setSize(0). (Marc Bennewitz)
 -
  - SQLite:
    . Fixed a crash when SQLite3::close() is called from a userland callback.
      (Ilia Alshanetsky)
diff --cc Zend/zend_API.c
index 5d1f58d6163,2a61b689106..99d3125bf2b
--- a/Zend/zend_API.c
+++ b/Zend/zend_API.c
@@@ -2261,8 -2247,14 +2261,11 @@@ ZEND_API zend_result array_set_zval_key
  		case IS_STRING:
  			result = zend_symtable_update(ht, Z_STR_P(key), value);
  			break;
 -		case IS_NULL:
 -			result = zend_hash_update(ht, ZSTR_EMPTY_ALLOC(), 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:
@@@ -2274,16 -2266,14 +2277,21 @@@
  		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;
+ 		}
 +		case IS_NULL:
 +			zend_error(E_DEPRECATED, "Using null as an array offset is deprecated, use an empty string instead");
 +			if (UNEXPECTED(EG(exception))) {
 +				return FAILURE;
 +			}
 +			result = zend_hash_update(ht, ZSTR_EMPTY_ALLOC(), value);
 +			break;
  		default:
  			zend_illegal_container_offset(ZSTR_KNOWN(ZEND_STR_ARRAY), key, BP_VAR_W);
  			result = NULL;
diff --cc ext/soap/tests/bugs/gh22895.phpt
index 00000000000,a253f33d3da..feed80065a4
mode 000000,100644..100644
--- a/ext/soap/tests/bugs/gh22895.phpt
+++ b/ext/soap/tests/bugs/gh22895.phpt
@@@ -1,0 -1,96 +1,97 @@@
+ --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) {
++foreach ([1.5, STDIN, null] 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)
++Exception: Using null as an array offset is deprecated, use an empty string instead
+ <?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>