Commit f09a81ddee3 for php.net
commit f09a81ddee3e58a8c5af5b311a101b3c1b20a637
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date: Sat Aug 29 08:16:19 2026 -0400
[SOAP] Fix WSDL cache corruption when header defines headerfaults
sdl_serialize_soap_body() counted j headerfaults per header but then
serialized body->headers instead of tmp->headerfaults, writing N header
records where j fault records were expected by sdl_deserialize_soap_body(),
misaligning the cache stream and crashing on load whenever a soap:header
carries headerfaults. Iterate tmp->headerfaults instead; sibling audit of
the other serialize/deserialize loops in php_sdl.c found no further
hash-mismatched iteration. Bump WSDL_CACHE_VERSION so existing on-disk
caches are discarded.
Closes GH-23502
diff --git a/NEWS b/NEWS
index 921ca372b29..208ccaef35f 100644
--- a/NEWS
+++ b/NEWS
@@ -66,6 +66,8 @@ PHP NEWS
- SOAP:
. Fixed bug GH-23447 (Segfault when a class passed to SoapServer::setClass()
fails to initialize). (Lazizbek Ergashev)
+ . Fixed WSDL cache corruption when a soap:header defines headerfaults.
+ (Ilia Alshanetsky)
- Standard:
. Fixed a segfault when a stream filter callback unsets StreamBucket::$data
diff --git a/ext/soap/php_sdl.c b/ext/soap/php_sdl.c
index a44fc16f971..7aa9e9fae25 100644
--- a/ext/soap/php_sdl.c
+++ b/ext/soap/php_sdl.c
@@ -1155,7 +1155,7 @@ static sdlPtr load_wsdl(zval *this_ptr, char *struri)
return ctx.sdl;
}
-#define WSDL_CACHE_VERSION 0x10
+#define WSDL_CACHE_VERSION 0x11
#define WSDL_CACHE_GET(ret,type,buf) memcpy(&ret,*buf,sizeof(type)); *buf += sizeof(type);
#define WSDL_CACHE_GET_INT(ret,buf) ret = ((unsigned char)(*buf)[0])|((unsigned char)(*buf)[1]<<8)|((unsigned char)(*buf)[2]<<16)|((unsigned)(*buf)[3]<<24); *buf += 4;
@@ -2066,7 +2066,7 @@ static void sdl_serialize_soap_body(const sdlSoapBindingFunctionBodyPtr body, co
sdlSoapBindingFunctionHeaderPtr tmp2;
const zend_string *key_inner;
- ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(body->headers, key_inner, tmp2) {
+ ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(tmp->headerfaults, key_inner, tmp2) {
sdl_serialize_key(key_inner, out);
WSDL_CACHE_PUT_1(tmp2->use, out);
if (tmp2->use == SOAP_ENCODED) {
diff --git a/ext/soap/tests/headerfault_cache.phpt b/ext/soap/tests/headerfault_cache.phpt
new file mode 100644
index 00000000000..6da747902d8
--- /dev/null
+++ b/ext/soap/tests/headerfault_cache.phpt
@@ -0,0 +1,45 @@
+--TEST--
+WSDL cache corruption when soap:header has headerfaults
+--EXTENSIONS--
+soap
+--INI--
+soap.wsdl_cache_enabled=1
+--FILE--
+<?php
+$dir = __DIR__ . '/headerfault_cache_dir';
+@mkdir($dir);
+ini_set('soap.wsdl_cache_dir', $dir);
+
+$options = ['cache_wsdl' => WSDL_CACHE_DISK];
+
+$c1 = new SoapClient(__DIR__ . '/headerfault_cache.wsdl', $options);
+var_dump($c1->__getFunctions());
+
+$c2 = new SoapClient(__DIR__ . '/headerfault_cache.wsdl', $options);
+var_dump($c2->__getFunctions());
+
+echo "ok\n";
+?>
+--CLEAN--
+<?php
+$dir = __DIR__ . '/headerfault_cache_dir';
+if (is_dir($dir)) {
+ foreach (scandir($dir) as $f) {
+ if ($f === '.' || $f === '..') {
+ continue;
+ }
+ @unlink($dir . '/' . $f);
+ }
+ @rmdir($dir);
+}
+?>
+--EXPECT--
+array(1) {
+ [0]=>
+ string(32) "string testHeader(string $param)"
+}
+array(1) {
+ [0]=>
+ string(32) "string testHeader(string $param)"
+}
+ok
diff --git a/ext/soap/tests/headerfault_cache.wsdl b/ext/soap/tests/headerfault_cache.wsdl
new file mode 100644
index 00000000000..8a844c0b899
--- /dev/null
+++ b/ext/soap/tests/headerfault_cache.wsdl
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://example.com/hf" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://example.com/hf">
+ <wsdl:message name="message">
+ <wsdl:part name="param" type="xsd:string"/>
+ </wsdl:message>
+ <wsdl:message name="authToken">
+ <wsdl:part name="authToken" type="xsd:string"/>
+ </wsdl:message>
+ <wsdl:message name="traceId">
+ <wsdl:part name="traceId" type="xsd:string"/>
+ </wsdl:message>
+ <wsdl:message name="faultToken">
+ <wsdl:part name="faultToken" type="xsd:string"/>
+ </wsdl:message>
+ <wsdl:message name="faultTrace">
+ <wsdl:part name="faultTrace" type="xsd:string"/>
+ </wsdl:message>
+ <wsdl:portType name="hf">
+ <wsdl:operation name="testHeader">
+ <wsdl:input message="tns:message"/>
+ <wsdl:output message="tns:message"/>
+ </wsdl:operation>
+ </wsdl:portType>
+ <wsdl:binding name="hfBinding" type="tns:hf">
+ <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
+ <wsdl:operation name="testHeader">
+ <soap:operation soapAction="http://example.com/hf/testHeader"/>
+ <wsdl:input>
+ <soap:body namespace="http://example.com/hf" use="literal"/>
+ <soap:header namespace="http://example.com/hf" use="literal" part="authToken" message="tns:authToken">
+ <soap:headerfault namespace="http://example.com/hf" use="literal" part="faultToken" message="tns:faultToken"/>
+ </soap:header>
+ <soap:header namespace="http://example.com/hf" use="literal" part="traceId" message="tns:traceId">
+ <soap:headerfault namespace="http://example.com/hf" use="literal" part="faultTrace" message="tns:faultTrace"/>
+ </soap:header>
+ </wsdl:input>
+ <wsdl:output>
+ <soap:body namespace="http://example.com/hf" use="literal"/>
+ </wsdl:output>
+ </wsdl:operation>
+ </wsdl:binding>
+ <wsdl:service name="hfService">
+ <wsdl:port binding="tns:hfBinding" name="hfPort">
+ <soap:address location="http://localhost/hf.php"/>
+ </wsdl:port>
+ </wsdl:service>
+</wsdl:definitions>