Skip to content

Commit 21c2318

Browse files
authored
Reject out-of-range SOAP schema integers (#22178)
Fix the bug where SOAP schema parsing silently accepts out-of-range XML Schema integer values, see #22167
1 parent b747e19 commit 21c2318

4 files changed

Lines changed: 161 additions & 3 deletions

File tree

NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ PHP NEWS
178178
represented as a string anymore but a int. (David Carlier)
179179
. Fixed bug GH-21421 (SoapClient typemap property breaks engine assumptions).
180180
(ndossche)
181+
. WSDL/XML Schema parsing now rejects out-of-range integer values for
182+
occurrence constraints and integer restriction facets. Negative minOccurs
183+
and maxOccurs values are rejected as well. (Weilin Du)
181184

182185
- Sockets:
183186
. Added the TCP_USER_TIMEOUT constant for Linux to set the maximum time in

UPGRADING

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ PHP 8.6 UPGRADE NOTES
120120
to 1).
121121
RFC: https://wiki.php.net/rfc/session_security_defaults
122122

123+
- SOAP:
124+
. WSDL/XML Schema parsing now rejects out-of-range integer values for
125+
occurrence constraints and integer restriction facets. Negative minOccurs
126+
and maxOccurs values are rejected as well.
127+
123128
- SPL:
124129
. SplObjectStorage::getHash() implementations may no longer mutate any
125130
SplObjectStorage instance. Attempting to do so now throws an Error.

ext/soap/php_schema.c

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,28 @@ static bool node_is_equal_xsd(xmlNodePtr node, const char *name)
5353
return node_is_equal_ex_one_of(node, name, ns);
5454
}
5555

56+
static int schema_parse_int(const xmlChar *value, const char *name, bool allow_negative)
57+
{
58+
const char *str = (const char *) value;
59+
zend_long lval = 0;
60+
int oflow_info = 0;
61+
uint8_t type = is_numeric_string_ex(str, strlen(str), &lval, NULL, true, &oflow_info, NULL);
62+
63+
if (type != IS_LONG) {
64+
errno = 0;
65+
lval = ZEND_STRTOL(str, NULL, 10);
66+
if (oflow_info || (errno == ERANGE && lval != 0)) {
67+
soap_error1(E_ERROR, "Parsing Schema: %s value is out of range", name);
68+
}
69+
}
70+
71+
if (ZEND_LONG_EXCEEDS_INT(lval) || (!allow_negative && lval < 0)) {
72+
soap_error1(E_ERROR, "Parsing Schema: %s value is out of range", name);
73+
}
74+
75+
return (int) lval;
76+
}
77+
5678
static encodePtr create_encoder(sdlPtr sdl, sdlTypePtr cur_type, const xmlChar *ns, const xmlChar *type)
5779
{
5880
smart_str nscat = {0};
@@ -854,7 +876,7 @@ static int schema_restriction_var_int(xmlNodePtr val, sdlRestrictionIntPtr *valp
854876
soap_error0(E_ERROR, "Parsing Schema: missing restriction value");
855877
}
856878

857-
(*valptr)->value = atoi((char*)value->children->content);
879+
(*valptr)->value = schema_parse_int(value->children->content, (const char *) val->name, true);
858880

859881
return TRUE;
860882
}
@@ -1016,7 +1038,7 @@ void schema_min_max(xmlNodePtr node, sdlContentModelPtr model)
10161038
xmlAttrPtr attr = get_attribute(node->properties, "minOccurs");
10171039

10181040
if (attr) {
1019-
model->min_occurs = atoi((char*)attr->children->content);
1041+
model->min_occurs = schema_parse_int(attr->children->content, "minOccurs", false);
10201042
} else {
10211043
model->min_occurs = 1;
10221044
}
@@ -1026,7 +1048,7 @@ void schema_min_max(xmlNodePtr node, sdlContentModelPtr model)
10261048
if (!strncmp((char*)attr->children->content, "unbounded", sizeof("unbounded"))) {
10271049
model->max_occurs = -1;
10281050
} else {
1029-
model->max_occurs = atoi((char*)attr->children->content);
1051+
model->max_occurs = schema_parse_int(attr->children->content, "maxOccurs", false);
10301052
}
10311053
} else {
10321054
model->max_occurs = 1;

ext/soap/tests/bugs/gh22167.phpt

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
--TEST--
2+
GH-22167 (Out-of-range XML Schema integer values in SOAP WSDL)
3+
--EXTENSIONS--
4+
soap
5+
--INI--
6+
soap.wsdl_cache_enabled=0
7+
--FILE--
8+
<?php
9+
function wsdl_with_schema(string $schema): string {
10+
return <<<XML
11+
<?xml version="1.0"?>
12+
<definitions
13+
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
14+
xmlns:tns="http://test-uri/"
15+
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
16+
xmlns="http://schemas.xmlsoap.org/wsdl/"
17+
targetNamespace="http://test-uri/">
18+
<types>
19+
<xsd:schema targetNamespace="http://test-uri/">
20+
$schema
21+
</xsd:schema>
22+
</types>
23+
<message name="m"><part name="p" type="tns:T"/></message>
24+
<portType name="p"><operation name="op"><input message="tns:m"/></operation></portType>
25+
<binding name="b" type="tns:p">
26+
<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
27+
<operation name="op">
28+
<soap:operation soapAction="#op"/>
29+
<input>
30+
<soap:body use="encoded"
31+
namespace="http://test-uri/"
32+
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
33+
</input>
34+
</operation>
35+
</binding>
36+
<service name="s"><port name="p" binding="tns:b"><soap:address location="test://"/></port></service>
37+
</definitions>
38+
XML;
39+
}
40+
41+
function occurrence_schema(string $attribute, string $value = "2147483648"): string {
42+
return <<<XML
43+
<xsd:complexType name="T">
44+
<xsd:sequence>
45+
<xsd:element name="x" type="xsd:string" $attribute="$value"/>
46+
</xsd:sequence>
47+
</xsd:complexType>
48+
XML;
49+
}
50+
51+
function restriction_schema(string $facet, string $value = "2147483648"): string {
52+
return <<<XML
53+
<xsd:simpleType name="T">
54+
<xsd:restriction base="xsd:int">
55+
<xsd:$facet value="$value"/>
56+
</xsd:restriction>
57+
</xsd:simpleType>
58+
XML;
59+
}
60+
61+
$cases = [
62+
"minOccurs" => occurrence_schema("minOccurs"),
63+
"maxOccurs" => occurrence_schema("maxOccurs"),
64+
"negative minOccurs" => occurrence_schema("minOccurs", "-1"),
65+
"negative maxOccurs" => occurrence_schema("maxOccurs", "-1"),
66+
"minExclusive" => restriction_schema("minExclusive"),
67+
"minInclusive" => restriction_schema("minInclusive"),
68+
"maxExclusive" => restriction_schema("maxExclusive"),
69+
"maxInclusive" => restriction_schema("maxInclusive"),
70+
"totalDigits" => restriction_schema("totalDigits"),
71+
"fractionDigits" => restriction_schema("fractionDigits"),
72+
"length" => restriction_schema("length"),
73+
"minLength" => restriction_schema("minLength"),
74+
"maxLength" => restriction_schema("maxLength"),
75+
];
76+
77+
$numeric_string_cases = [
78+
"leading whitespace numeric-string" => " 2147483648",
79+
"leading plus numeric-string" => "+2147483648",
80+
"leading zero numeric-string" => "00000000002147483648",
81+
"leading numeric-string with trailing data" => "2147483648abc",
82+
"negative out-of-range numeric-string" => "-2147483649",
83+
"decimal numeric-string" => "2147483648.0",
84+
"exponent numeric-string" => "2147483648e0",
85+
];
86+
87+
foreach ($numeric_string_cases as $name => $value) {
88+
$cases[$name] = occurrence_schema("maxOccurs", $value);
89+
}
90+
91+
$cases["fractional numeric-string within int range"] = occurrence_schema("maxOccurs", "3.141");
92+
93+
foreach ($cases as $name => $schema) {
94+
$file = tempnam(sys_get_temp_dir(), "wsdl");
95+
file_put_contents($file, wsdl_with_schema($schema));
96+
97+
try {
98+
new SoapClient($file, ["cache_wsdl" => WSDL_CACHE_NONE]);
99+
echo "$name: parsed\n";
100+
} catch (SoapFault $e) {
101+
echo "$name: {$e->getMessage()}\n";
102+
} finally {
103+
unlink($file);
104+
}
105+
}
106+
?>
107+
--EXPECT--
108+
minOccurs: SOAP-ERROR: Parsing Schema: minOccurs value is out of range
109+
maxOccurs: SOAP-ERROR: Parsing Schema: maxOccurs value is out of range
110+
negative minOccurs: SOAP-ERROR: Parsing Schema: minOccurs value is out of range
111+
negative maxOccurs: SOAP-ERROR: Parsing Schema: maxOccurs value is out of range
112+
minExclusive: SOAP-ERROR: Parsing Schema: minExclusive value is out of range
113+
minInclusive: SOAP-ERROR: Parsing Schema: minInclusive value is out of range
114+
maxExclusive: SOAP-ERROR: Parsing Schema: maxExclusive value is out of range
115+
maxInclusive: SOAP-ERROR: Parsing Schema: maxInclusive value is out of range
116+
totalDigits: SOAP-ERROR: Parsing Schema: totalDigits value is out of range
117+
fractionDigits: SOAP-ERROR: Parsing Schema: fractionDigits value is out of range
118+
length: SOAP-ERROR: Parsing Schema: length value is out of range
119+
minLength: SOAP-ERROR: Parsing Schema: minLength value is out of range
120+
maxLength: SOAP-ERROR: Parsing Schema: maxLength value is out of range
121+
leading whitespace numeric-string: SOAP-ERROR: Parsing Schema: maxOccurs value is out of range
122+
leading plus numeric-string: SOAP-ERROR: Parsing Schema: maxOccurs value is out of range
123+
leading zero numeric-string: SOAP-ERROR: Parsing Schema: maxOccurs value is out of range
124+
leading numeric-string with trailing data: SOAP-ERROR: Parsing Schema: maxOccurs value is out of range
125+
negative out-of-range numeric-string: SOAP-ERROR: Parsing Schema: maxOccurs value is out of range
126+
decimal numeric-string: SOAP-ERROR: Parsing Schema: maxOccurs value is out of range
127+
exponent numeric-string: SOAP-ERROR: Parsing Schema: maxOccurs value is out of range
128+
fractional numeric-string within int range: parsed

0 commit comments

Comments
 (0)