[php-src] PHP-8.4: [SOAP] Fix WSDL cache corruption when header defines headerfaults

Ilia Alshanetsky <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Ilia Alshanetsky (iliaal)
Date: 2026-08-30T12:05:50-04:00

Commit: https://github.com/php/php-src/commit/f09a81ddee3e58a8c5af5b311a101b3c1b20a637
Raw diff: https://github.com/php/php-src/commit/f09a81ddee3e58a8c5af5b311a101b3c1b20a637.diff

[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

Changed paths:
  A  ext/soap/tests/headerfault_cache.phpt
  A  ext/soap/tests/headerfault_cache.wsdl
  M  NEWS
  M  ext/soap/php_sdl.c


Diff:

diff --git a/NEWS b/NEWS
index 921ca372b294..208ccaef35fb 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 a44fc16f9716..7aa9e9fae259 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 000000000000..6da747902d80
--- /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 000000000000..8a844c0b899d
--- /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>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.