From 5427a74cca58550a56f48e8bee1e1599cc57e9f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CLamentXU123=E2=80=9D?= <108666168+LamentXU123@users.noreply.github.com> Date: Fri, 29 May 2026 17:11:05 +0800 Subject: [PATCH 1/3] Fix GH-22167: reject out-of-range SOAP schema integers --- NEWS | 2 + ext/soap/php_schema.c | 30 +++++++- ext/soap/tests/bugs/gh22167.phpt | 122 +++++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 ext/soap/tests/bugs/gh22167.phpt diff --git a/NEWS b/NEWS index 23212414d361..9629191f19af 100644 --- a/NEWS +++ b/NEWS @@ -176,6 +176,8 @@ PHP NEWS represented as a string anymore but a int. (David Carlier) . Fixed bug GH-21421 (SoapClient typemap property breaks engine assumptions). (ndossche) + . Fixed bug GH-22167 (Out-of-range XML Schema integer values were silently + accepted during WSDL parsing). (Weilin Du) - Sockets: . Added the TCP_USER_TIMEOUT constant for Linux to set the maximum time in diff --git a/ext/soap/php_schema.c b/ext/soap/php_schema.c index 73d6691af262..5f5b176d26c5 100644 --- a/ext/soap/php_schema.c +++ b/ext/soap/php_schema.c @@ -53,6 +53,30 @@ static bool node_is_equal_xsd(xmlNodePtr node, const char *name) return node_is_equal_ex_one_of(node, name, ns); } +static int schema_parse_int(const xmlChar *value, const char *name) +{ + const char *str = (const char *) value; + zend_long lval = 0; + int oflow_info = 0; + uint8_t type = is_numeric_string_ex(str, strlen(str), &lval, NULL, true, &oflow_info, NULL); + + if (oflow_info > 0 || (type == IS_LONG && ZEND_LONG_INT_OVFL(lval))) { + soap_error1(E_ERROR, "Parsing Schema: %s value is out of range", name); + } + + if (type == IS_LONG) { + return (int) lval; + } + + errno = 0; + lval = ZEND_STRTOL(str, NULL, 10); + if ((errno == ERANGE && lval > 0) || ZEND_LONG_INT_OVFL(lval)) { + soap_error1(E_ERROR, "Parsing Schema: %s value is out of range", name); + } + + return (int) lval; +} + static encodePtr create_encoder(sdlPtr sdl, sdlTypePtr cur_type, const xmlChar *ns, const xmlChar *type) { smart_str nscat = {0}; @@ -854,7 +878,7 @@ static int schema_restriction_var_int(xmlNodePtr val, sdlRestrictionIntPtr *valp soap_error0(E_ERROR, "Parsing Schema: missing restriction value"); } - (*valptr)->value = atoi((char*)value->children->content); + (*valptr)->value = schema_parse_int(value->children->content, (const char *) val->name); return TRUE; } @@ -1016,7 +1040,7 @@ void schema_min_max(xmlNodePtr node, sdlContentModelPtr model) xmlAttrPtr attr = get_attribute(node->properties, "minOccurs"); if (attr) { - model->min_occurs = atoi((char*)attr->children->content); + model->min_occurs = schema_parse_int(attr->children->content, "minOccurs"); } else { model->min_occurs = 1; } @@ -1026,7 +1050,7 @@ void schema_min_max(xmlNodePtr node, sdlContentModelPtr model) if (!strncmp((char*)attr->children->content, "unbounded", sizeof("unbounded"))) { model->max_occurs = -1; } else { - model->max_occurs = atoi((char*)attr->children->content); + model->max_occurs = schema_parse_int(attr->children->content, "maxOccurs"); } } else { model->max_occurs = 1; diff --git a/ext/soap/tests/bugs/gh22167.phpt b/ext/soap/tests/bugs/gh22167.phpt new file mode 100644 index 000000000000..6fe9aef5475a --- /dev/null +++ b/ext/soap/tests/bugs/gh22167.phpt @@ -0,0 +1,122 @@ +--TEST-- +GH-22167 (Out-of-range XML Schema integer values in SOAP WSDL) +--EXTENSIONS-- +soap +--INI-- +soap.wsdl_cache_enabled=0 +--FILE-- + + + + + $schema + + + + + + + + + + + + + + + +XML; +} + +function occurrence_schema(string $attribute, string $value = "2147483648"): string { + return << + + + + +XML; +} + +function restriction_schema(string $facet, string $value = "2147483648"): string { + return << + + + + +XML; +} + +$cases = [ + "minOccurs" => occurrence_schema("minOccurs"), + "maxOccurs" => occurrence_schema("maxOccurs"), + "minExclusive" => restriction_schema("minExclusive"), + "minInclusive" => restriction_schema("minInclusive"), + "maxExclusive" => restriction_schema("maxExclusive"), + "maxInclusive" => restriction_schema("maxInclusive"), + "totalDigits" => restriction_schema("totalDigits"), + "fractionDigits" => restriction_schema("fractionDigits"), + "length" => restriction_schema("length"), + "minLength" => restriction_schema("minLength"), + "maxLength" => restriction_schema("maxLength"), +]; + +$numeric_string_cases = [ + "leading whitespace numeric-string" => " 2147483648", + "leading plus numeric-string" => "+2147483648", + "leading zero numeric-string" => "00000000002147483648", + "leading numeric-string with trailing data" => "2147483648abc", + "decimal numeric-string" => "2147483648.0", + "exponent numeric-string" => "2147483648e0", +]; + +foreach ($numeric_string_cases as $name => $value) { + $cases[$name] = occurrence_schema("maxOccurs", $value); +} + +$cases["fractional numeric-string within int range"] = occurrence_schema("maxOccurs", "3.141"); + +foreach ($cases as $name => $schema) { + $file = tempnam(sys_get_temp_dir(), "wsdl"); + file_put_contents($file, wsdl_with_schema($schema)); + + try { + new SoapClient($file, ["cache_wsdl" => WSDL_CACHE_NONE]); + echo "$name: parsed\n"; + } catch (SoapFault $e) { + echo "$name: {$e->getMessage()}\n"; + } finally { + unlink($file); + } +} +?> +--EXPECT-- +minOccurs: SOAP-ERROR: Parsing Schema: minOccurs value is out of range +maxOccurs: SOAP-ERROR: Parsing Schema: maxOccurs value is out of range +minExclusive: SOAP-ERROR: Parsing Schema: minExclusive value is out of range +minInclusive: SOAP-ERROR: Parsing Schema: minInclusive value is out of range +maxExclusive: SOAP-ERROR: Parsing Schema: maxExclusive value is out of range +maxInclusive: SOAP-ERROR: Parsing Schema: maxInclusive value is out of range +totalDigits: SOAP-ERROR: Parsing Schema: totalDigits value is out of range +fractionDigits: SOAP-ERROR: Parsing Schema: fractionDigits value is out of range +length: SOAP-ERROR: Parsing Schema: length value is out of range +minLength: SOAP-ERROR: Parsing Schema: minLength value is out of range +maxLength: SOAP-ERROR: Parsing Schema: maxLength value is out of range +leading whitespace numeric-string: SOAP-ERROR: Parsing Schema: maxOccurs value is out of range +leading plus numeric-string: SOAP-ERROR: Parsing Schema: maxOccurs value is out of range +leading zero numeric-string: SOAP-ERROR: Parsing Schema: maxOccurs value is out of range +leading numeric-string with trailing data: SOAP-ERROR: Parsing Schema: maxOccurs value is out of range +decimal numeric-string: SOAP-ERROR: Parsing Schema: maxOccurs value is out of range +exponent numeric-string: SOAP-ERROR: Parsing Schema: maxOccurs value is out of range +fractional numeric-string within int range: parsed From 16d949d9eba7929e45546892f8680e4b979c12cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CLamentXU123=E2=80=9D?= <108666168+LamentXU123@users.noreply.github.com> Date: Sun, 31 May 2026 18:43:44 +0800 Subject: [PATCH 2/3] Reject Negative values --- NEWS | 3 ++- UPGRADING | 5 +++++ ext/soap/php_schema.c | 17 +++++++++++------ ext/soap/tests/bugs/gh22167.phpt | 6 ++++++ 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/NEWS b/NEWS index 9629191f19af..469318219c29 100644 --- a/NEWS +++ b/NEWS @@ -177,7 +177,8 @@ PHP NEWS . Fixed bug GH-21421 (SoapClient typemap property breaks engine assumptions). (ndossche) . Fixed bug GH-22167 (Out-of-range XML Schema integer values were silently - accepted during WSDL parsing). (Weilin Du) + accepted during WSDL parsing; negative occurrence values are now rejected). + (Weilin Du) - Sockets: . Added the TCP_USER_TIMEOUT constant for Linux to set the maximum time in diff --git a/UPGRADING b/UPGRADING index 3d6c89e90e05..500e045c0732 100644 --- a/UPGRADING +++ b/UPGRADING @@ -341,6 +341,11 @@ PHP 8.6 UPGRADE NOTES - mysqli . Added new constant MYSQLI_OPT_COMPRESS. +- Soap: + . WSDL/XML Schema parsing now rejects out-of-range integer values for + occurrence constraints and integer restriction facets. Negative minOccurs + and maxOccurs values are rejected as well. + ======================================== 10. New Global Constants ======================================== diff --git a/ext/soap/php_schema.c b/ext/soap/php_schema.c index 5f5b176d26c5..ccc39baad411 100644 --- a/ext/soap/php_schema.c +++ b/ext/soap/php_schema.c @@ -53,24 +53,29 @@ static bool node_is_equal_xsd(xmlNodePtr node, const char *name) return node_is_equal_ex_one_of(node, name, ns); } -static int schema_parse_int(const xmlChar *value, const char *name) +static int schema_parse_int(const xmlChar *value, const char *name, bool allow_negative) { const char *str = (const char *) value; zend_long lval = 0; int oflow_info = 0; uint8_t type = is_numeric_string_ex(str, strlen(str), &lval, NULL, true, &oflow_info, NULL); - if (oflow_info > 0 || (type == IS_LONG && ZEND_LONG_INT_OVFL(lval))) { + if (oflow_info || (type == IS_LONG && ZEND_LONG_EXCEEDS_INT(lval))) { soap_error1(E_ERROR, "Parsing Schema: %s value is out of range", name); } if (type == IS_LONG) { + if (!allow_negative && lval < 0) { + soap_error1(E_ERROR, "Parsing Schema: %s value is out of range", name); + } return (int) lval; } errno = 0; lval = ZEND_STRTOL(str, NULL, 10); - if ((errno == ERANGE && lval > 0) || ZEND_LONG_INT_OVFL(lval)) { + if ((errno == ERANGE && (lval > 0 || lval < 0)) + || ZEND_LONG_EXCEEDS_INT(lval) + || (!allow_negative && lval < 0)) { soap_error1(E_ERROR, "Parsing Schema: %s value is out of range", name); } @@ -878,7 +883,7 @@ static int schema_restriction_var_int(xmlNodePtr val, sdlRestrictionIntPtr *valp soap_error0(E_ERROR, "Parsing Schema: missing restriction value"); } - (*valptr)->value = schema_parse_int(value->children->content, (const char *) val->name); + (*valptr)->value = schema_parse_int(value->children->content, (const char *) val->name, true); return TRUE; } @@ -1040,7 +1045,7 @@ void schema_min_max(xmlNodePtr node, sdlContentModelPtr model) xmlAttrPtr attr = get_attribute(node->properties, "minOccurs"); if (attr) { - model->min_occurs = schema_parse_int(attr->children->content, "minOccurs"); + model->min_occurs = schema_parse_int(attr->children->content, "minOccurs", false); } else { model->min_occurs = 1; } @@ -1050,7 +1055,7 @@ void schema_min_max(xmlNodePtr node, sdlContentModelPtr model) if (!strncmp((char*)attr->children->content, "unbounded", sizeof("unbounded"))) { model->max_occurs = -1; } else { - model->max_occurs = schema_parse_int(attr->children->content, "maxOccurs"); + model->max_occurs = schema_parse_int(attr->children->content, "maxOccurs", false); } } else { model->max_occurs = 1; diff --git a/ext/soap/tests/bugs/gh22167.phpt b/ext/soap/tests/bugs/gh22167.phpt index 6fe9aef5475a..f24bfb0eac32 100644 --- a/ext/soap/tests/bugs/gh22167.phpt +++ b/ext/soap/tests/bugs/gh22167.phpt @@ -61,6 +61,8 @@ XML; $cases = [ "minOccurs" => occurrence_schema("minOccurs"), "maxOccurs" => occurrence_schema("maxOccurs"), + "negative minOccurs" => occurrence_schema("minOccurs", "-1"), + "negative maxOccurs" => occurrence_schema("maxOccurs", "-1"), "minExclusive" => restriction_schema("minExclusive"), "minInclusive" => restriction_schema("minInclusive"), "maxExclusive" => restriction_schema("maxExclusive"), @@ -77,6 +79,7 @@ $numeric_string_cases = [ "leading plus numeric-string" => "+2147483648", "leading zero numeric-string" => "00000000002147483648", "leading numeric-string with trailing data" => "2147483648abc", + "negative out-of-range numeric-string" => "-2147483649", "decimal numeric-string" => "2147483648.0", "exponent numeric-string" => "2147483648e0", ]; @@ -104,6 +107,8 @@ foreach ($cases as $name => $schema) { --EXPECT-- minOccurs: SOAP-ERROR: Parsing Schema: minOccurs value is out of range maxOccurs: SOAP-ERROR: Parsing Schema: maxOccurs value is out of range +negative minOccurs: SOAP-ERROR: Parsing Schema: minOccurs value is out of range +negative maxOccurs: SOAP-ERROR: Parsing Schema: maxOccurs value is out of range minExclusive: SOAP-ERROR: Parsing Schema: minExclusive value is out of range minInclusive: SOAP-ERROR: Parsing Schema: minInclusive value is out of range maxExclusive: SOAP-ERROR: Parsing Schema: maxExclusive value is out of range @@ -117,6 +122,7 @@ leading whitespace numeric-string: SOAP-ERROR: Parsing Schema: maxOccurs value i leading plus numeric-string: SOAP-ERROR: Parsing Schema: maxOccurs value is out of range leading zero numeric-string: SOAP-ERROR: Parsing Schema: maxOccurs value is out of range leading numeric-string with trailing data: SOAP-ERROR: Parsing Schema: maxOccurs value is out of range +negative out-of-range numeric-string: SOAP-ERROR: Parsing Schema: maxOccurs value is out of range decimal numeric-string: SOAP-ERROR: Parsing Schema: maxOccurs value is out of range exponent numeric-string: SOAP-ERROR: Parsing Schema: maxOccurs value is out of range fractional numeric-string within int range: parsed From 234c53a8ac92a2510f53b15cb5c2beeffc1ce9d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CLamentXU123=E2=80=9D?= <108666168+LamentXU123@users.noreply.github.com> Date: Mon, 1 Jun 2026 20:13:00 +0800 Subject: [PATCH 3/3] optimization --- ext/soap/php_schema.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/ext/soap/php_schema.c b/ext/soap/php_schema.c index ccc39baad411..d97a0eac05b5 100644 --- a/ext/soap/php_schema.c +++ b/ext/soap/php_schema.c @@ -60,22 +60,15 @@ static int schema_parse_int(const xmlChar *value, const char *name, bool allow_n int oflow_info = 0; uint8_t type = is_numeric_string_ex(str, strlen(str), &lval, NULL, true, &oflow_info, NULL); - if (oflow_info || (type == IS_LONG && ZEND_LONG_EXCEEDS_INT(lval))) { - soap_error1(E_ERROR, "Parsing Schema: %s value is out of range", name); - } - - if (type == IS_LONG) { - if (!allow_negative && lval < 0) { + if (type != IS_LONG) { + errno = 0; + lval = ZEND_STRTOL(str, NULL, 10); + if (oflow_info || (errno == ERANGE && lval != 0)) { soap_error1(E_ERROR, "Parsing Schema: %s value is out of range", name); } - return (int) lval; } - errno = 0; - lval = ZEND_STRTOL(str, NULL, 10); - if ((errno == ERANGE && (lval > 0 || lval < 0)) - || ZEND_LONG_EXCEEDS_INT(lval) - || (!allow_negative && lval < 0)) { + if (ZEND_LONG_EXCEEDS_INT(lval) || (!allow_negative && lval < 0)) { soap_error1(E_ERROR, "Parsing Schema: %s value is out of range", name); }