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>