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