Commit 797e3ea9759 for php.net

commit 797e3ea97599e3276bc71c1350ed056af435e4a4
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Sun Sep 6 11:00:21 2026 -0400

    soap: reject self-referential schema group and attributeGroup fix-up

    schema_content_model_fixup() and schema_attributegroup_fixup() followed a
    group ref back into schema_type_fixup() with nothing tracking the types
    already being fixed up, so a schema whose group references itself
    recursed until the C stack ran out. Track the in-progress types in the
    sdl context and raise a parse error when one is re-entered.

    Closes GH-23600

diff --git a/NEWS b/NEWS
index a9335d0840d..6a6d6110970 100644
--- a/NEWS
+++ b/NEWS
@@ -107,6 +107,8 @@ PHP                                                                        NEWS
     fails to initialize). (Lazizbek Ergashev)
   . Fixed WSDL cache corruption when a soap:header defines headerfaults.
     (Ilia Alshanetsky)
+  . Fixed stack overflow when parsing a WSDL with self-referential schema
+    groups or attributeGroups. (Ilia Alshanetsky)

 - Standard:
   . Fixed a segfault when a stream filter callback unsets StreamBucket::$data
diff --git a/ext/soap/php_schema.c b/ext/soap/php_schema.c
index a4911c65984..90d175a0253 100644
--- a/ext/soap/php_schema.c
+++ b/ext/soap/php_schema.c
@@ -2161,6 +2161,10 @@ static void schema_attributegroup_fixup(sdlCtx *ctx, sdlAttributePtr attr, HashT
 		if (ctx->attributeGroups != NULL) {
 			tmp = (sdlTypePtr)schema_find_by_ref(ctx->attributeGroups, attr->ref);
 			if (tmp) {
+				if (zend_hash_index_find_ptr(&ctx->fixupInProgress, (zend_ulong)tmp) != NULL) {
+					soap_error1(E_ERROR, "Parsing Schema: recursive attributeGroup 'ref' attribute '%s'", attr->ref);
+				}
+				zend_hash_index_add_ptr(&ctx->fixupInProgress, (zend_ulong)tmp, tmp);
 				if (tmp->attributes) {
 					zend_hash_internal_pointer_reset(tmp->attributes);
 					while ((tmp_attr = zend_hash_get_current_data_ptr(tmp->attributes)) != NULL) {
@@ -2196,6 +2200,7 @@ static void schema_attributegroup_fixup(sdlCtx *ctx, sdlAttributePtr attr, HashT
 						}
 					}
 				}
+				zend_hash_index_del(&ctx->fixupInProgress, (zend_ulong)tmp);
 			}
 		}
 		efree(attr->ref);
@@ -2210,6 +2215,9 @@ static void schema_content_model_fixup(sdlCtx *ctx, sdlContentModelPtr model)
 			sdlTypePtr tmp;

 			if (ctx->sdl->groups && (tmp = zend_hash_str_find_ptr(ctx->sdl->groups, model->u.group_ref, strlen(model->u.group_ref))) != NULL) {
+				if (zend_hash_index_find_ptr(&ctx->fixupInProgress, (zend_ulong)tmp) != NULL) {
+					soap_error1(E_ERROR, "Parsing Schema: recursive group 'ref' attribute '%s'", model->u.group_ref);
+				}
 				schema_type_fixup(ctx, tmp);
 				efree(model->u.group_ref);
 				model->kind = XSD_CONTENT_GROUP;
@@ -2253,6 +2261,8 @@ static void schema_type_fixup(sdlCtx *ctx, sdlTypePtr type)
 	sdlTypePtr tmp;
 	sdlAttributePtr attr;

+	zend_hash_index_add_ptr(&ctx->fixupInProgress, (zend_ulong)type, type);
+
 	if (type->ref != NULL) {
 		if (ctx->sdl->elements != NULL) {
 			tmp = (sdlTypePtr)schema_find_by_ref(ctx->sdl->elements, type->ref);
@@ -2305,6 +2315,7 @@ static void schema_type_fixup(sdlCtx *ctx, sdlTypePtr type)
 			}
 		}
 	}
+	zend_hash_index_del(&ctx->fixupInProgress, (zend_ulong)type);
 }

 void schema_pass2(sdlCtx *ctx)
@@ -2313,6 +2324,8 @@ void schema_pass2(sdlCtx *ctx)
 	sdlAttributePtr attr;
 	sdlTypePtr type;

+	zend_hash_init(&ctx->fixupInProgress, 0, NULL, NULL, 0);
+
 	if (ctx->attributes) {
 		ZEND_HASH_FOREACH_PTR(ctx->attributes, attr) {
 			schema_attribute_fixup(ctx, attr);
@@ -2346,6 +2359,8 @@ void schema_pass2(sdlCtx *ctx)
 		zend_hash_destroy(ctx->attributeGroups);
 		efree(ctx->attributeGroups);
 	}
+
+	zend_hash_destroy(&ctx->fixupInProgress);
 }

 void delete_model(zval *zv)
diff --git a/ext/soap/php_sdl.h b/ext/soap/php_sdl.h
index 3df4fbdca01..843b13141cc 100644
--- a/ext/soap/php_sdl.h
+++ b/ext/soap/php_sdl.h
@@ -73,6 +73,7 @@ typedef struct sdlCtx {

 	HashTable *attributes;       /* array of sdlAttributePtr */
 	HashTable *attributeGroups;  /* array of sdlTypesPtr */
+	HashTable fixupInProgress;
 	php_stream_context *context;
 	zval               old_header;
 } sdlCtx;
diff --git a/ext/soap/tests/schema-selfref-attrgroup.phpt b/ext/soap/tests/schema-selfref-attrgroup.phpt
new file mode 100644
index 00000000000..668826ea6c3
--- /dev/null
+++ b/ext/soap/tests/schema-selfref-attrgroup.phpt
@@ -0,0 +1,44 @@
+--TEST--
+SOAP XML Schema: self-referential attributeGroup fix-up recursion is rejected
+--EXTENSIONS--
+soap
+--FILE--
+<?php
+$wsdl = '<?xml version="1.0"?>
+<definitions xmlns="http://schemas.xmlsoap.org/wsdl/"
+             xmlns:tns="urn:test" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+ <types>
+  <xsd:schema targetNamespace="urn:test" xmlns="http://www.w3.org/2001/XMLSchema">
+   <attributeGroup name="A">
+    <attribute name="a" type="string"/>
+    <attributeGroup ref="tns:A"/>
+   </attributeGroup>
+   <element name="root">
+    <complexType><attributeGroup ref="tns:A"/></complexType>
+   </element>
+  </xsd:schema>
+ </types>
+ <message name="m"><part name="p" element="tns:root"/></message>
+ <portType name="pt"><operation name="op"><input message="tns:m"/></operation></portType>
+ <binding name="b" type="tns:pt"><soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"/>
+  <operation name="op"><soap:operation soapAction="" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"/><input><soap:body use="literal" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"/></input></operation>
+ </binding>
+ <service name="s"><port name="p1" binding="tns:b"><soap:address location="http://localhost/x" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"/></port></service>
+</definitions>';
+$file = __DIR__ . '/schema-selfref-attrgroup.wsdl';
+file_put_contents($file, $wsdl);
+try {
+	$c = new SoapClient($file, ['exceptions' => true, 'cache_wsdl' => WSDL_CACHE_NONE]);
+	echo "parsed ok\n";
+} catch (Throwable $e) {
+	echo $e::class, ": ", substr($e->getMessage(), 0, 120), "\n";
+}
+echo "done\n";
+?>
+--CLEAN--
+<?php
+@unlink(__DIR__ . '/schema-selfref-attrgroup.wsdl');
+?>
+--EXPECTF--
+SoapFault: SOAP-ERROR: Parsing Schema: recursive attributeGroup 'ref' attribute '%s'
+done
diff --git a/ext/soap/tests/schema-selfref-group.phpt b/ext/soap/tests/schema-selfref-group.phpt
new file mode 100644
index 00000000000..be9d7df524f
--- /dev/null
+++ b/ext/soap/tests/schema-selfref-group.phpt
@@ -0,0 +1,46 @@
+--TEST--
+SOAP XML Schema: self-referential group fix-up recursion is rejected
+--EXTENSIONS--
+soap
+--FILE--
+<?php
+$wsdl = '<?xml version="1.0"?>
+<definitions xmlns="http://schemas.xmlsoap.org/wsdl/"
+             xmlns:tns="urn:test" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+ <types>
+  <xsd:schema targetNamespace="urn:test" xmlns="http://www.w3.org/2001/XMLSchema">
+   <group name="A">
+    <sequence>
+     <element name="a" type="string"/>
+     <group ref="tns:A"/>
+    </sequence>
+   </group>
+   <element name="root">
+    <complexType><group ref="tns:A"/></complexType>
+   </element>
+  </xsd:schema>
+ </types>
+ <message name="m"><part name="p" element="tns:root"/></message>
+ <portType name="pt"><operation name="op"><input message="tns:m"/></operation></portType>
+ <binding name="b" type="tns:pt"><soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"/>
+  <operation name="op"><soap:operation soapAction="" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"/><input><soap:body use="literal" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"/></input></operation>
+ </binding>
+ <service name="s"><port name="p1" binding="tns:b"><soap:address location="http://localhost/x" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"/></port></service>
+</definitions>';
+$file = __DIR__ . '/schema-selfref-group.wsdl';
+file_put_contents($file, $wsdl);
+try {
+	$c = new SoapClient($file, ['exceptions' => true, 'cache_wsdl' => WSDL_CACHE_NONE]);
+	echo "parsed ok\n";
+} catch (Throwable $e) {
+	echo $e::class, ": ", substr($e->getMessage(), 0, 120), "\n";
+}
+echo "done\n";
+?>
+--CLEAN--
+<?php
+@unlink(__DIR__ . '/schema-selfref-group.wsdl');
+?>
+--EXPECTF--
+SoapFault: SOAP-ERROR: Parsing Schema: recursive group 'ref' attribute '%s'
+done