Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ PHP NEWS
types. (Weilin Du)
. Fixed MessageFormatter::parse() and parseMessage() returning PHP_INT_MIN
as float rather than int on 64-bit platforms. (Weilin Du)
. Fixed UConverter::transcode() silently truncating from_subst and to_subst
option lengths greater than 127 bytes. (Weilin Du)

- JSON:
. Enriched JSON last error / exception message with error location.
Expand Down Expand Up @@ -176,6 +178,9 @@ PHP NEWS
represented as a string anymore but a int. (David Carlier)
. Fixed bug GH-21421 (SoapClient typemap property breaks engine assumptions).
(ndossche)
. 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. (Weilin Du)

- Sockets:
. Added the TCP_USER_TIMEOUT constant for Linux to set the maximum time in
Expand Down
8 changes: 8 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ PHP 8.6 UPGRADE NOTES
int, rather than float, on 64-bit platforms when parsing integer values.
. The $type parameter of IntlBreakIterator::getPartsIterator() has been
changed from string to int to match the underlying implementation.
. UConverter::transcode() now rejects from_subst and to_subst option values
longer than 127 bytes instead of silently truncating the length before
passing it to ICU.

- PCNTL:
. pcntl_alarm() now raises a ValueError if the seconds argument is
Expand Down Expand Up @@ -117,6 +120,11 @@ PHP 8.6 UPGRADE NOTES
to 1).
RFC: https://wiki.php.net/rfc/session_security_defaults

- 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.

- SPL:
. SplObjectStorage::getHash() implementations may no longer mutate any
SplObjectStorage instance. Attempting to do so now throws an Error.
Expand Down
14 changes: 12 additions & 2 deletions ext/intl/converter/converter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,16 @@ static zend_string* php_converter_do_convert(UConverter *dest_cnv,
}
/* }}} */

static void php_converter_set_subst_chars(UConverter *cnv, zend_string *subst, UErrorCode *error)
{
if (ZSTR_LEN(subst) > SCHAR_MAX) {
*error = U_ILLEGAL_ARGUMENT_ERROR;
return;
}

ucnv_setSubstChars(cnv, ZSTR_VAL(subst), (int8_t) ZSTR_LEN(subst), error);
}

/* {{{ */
#define UCNV_REASON_CASE(v) case (UCNV_ ## v) : RETURN_STRINGL( "REASON_" #v , sizeof( "REASON_" #v ) - 1);
PHP_METHOD(UConverter, reasonText) {
Expand Down Expand Up @@ -761,13 +771,13 @@ PHP_METHOD(UConverter, transcode) {
(tmpzval = zend_hash_str_find_deref(Z_ARRVAL_P(options), "from_subst", sizeof("from_subst") - 1)) != NULL &&
Z_TYPE_P(tmpzval) == IS_STRING) {
error = U_ZERO_ERROR;
ucnv_setSubstChars(src_cnv, Z_STRVAL_P(tmpzval), Z_STRLEN_P(tmpzval) & 0x7F, &error);
php_converter_set_subst_chars(src_cnv, Z_STR_P(tmpzval), &error);
}
if (U_SUCCESS(error) &&
(tmpzval = zend_hash_str_find_deref(Z_ARRVAL_P(options), "to_subst", sizeof("to_subst") - 1)) != NULL &&
Z_TYPE_P(tmpzval) == IS_STRING) {
error = U_ZERO_ERROR;
ucnv_setSubstChars(dest_cnv, Z_STRVAL_P(tmpzval), Z_STRLEN_P(tmpzval) & 0x7F, &error);
php_converter_set_subst_chars(dest_cnv, Z_STR_P(tmpzval), &error);
}
}

Expand Down
17 changes: 15 additions & 2 deletions ext/intl/rangeformatter/rangeformatter_class.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,19 @@ zend_object *IntlNumberRangeFormatter_object_create(zend_class_entry *ce)
return &intern->zo;
}

static icu::Formattable rangeformatter_create_formattable(zval *number)
{
icu::Formattable formattable;

if (Z_TYPE_P(number) == IS_DOUBLE) {
formattable.setDouble(Z_DVAL_P(number));
} else {
formattable.setInt64(static_cast<int64_t>(Z_LVAL_P(number)));
}

return formattable;
}

U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, __construct)
{
ZEND_PARSE_PARAMETERS_NONE();
Expand Down Expand Up @@ -154,8 +167,8 @@ U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, format)

UErrorCode error = U_ZERO_ERROR;

icu::Formattable start_formattable(Z_TYPE_P(start) == IS_DOUBLE ? Z_DVAL_P(start) : Z_LVAL_P(start));
icu::Formattable end_formattable(Z_TYPE_P(end) == IS_DOUBLE ? Z_DVAL_P(end) : Z_LVAL_P(end));
icu::Formattable start_formattable = rangeformatter_create_formattable(start);
icu::Formattable end_formattable = rangeformatter_create_formattable(end);

UnicodeString result = RANGEFORMATTER_OBJECT(obj)->formatFormattableRange(start_formattable, end_formattable, error).toString(error);

Expand Down
27 changes: 27 additions & 0 deletions ext/intl/tests/rangeformatter/rangeformatter_format_int64.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
--TEST--
IntlNumberRangeFormatter::format() preserves int64 precision
--EXTENSIONS--
intl
--SKIPIF--
<?php
if (!class_exists('IntlNumberRangeFormatter')) {
die('skip IntlNumberRangeFormatter not available');
}
if (PHP_INT_SIZE < 8) {
die('skip 64-bit only');
}
?>
--FILE--
<?php
$formatter = IntlNumberRangeFormatter::createFromSkeleton(
'',
'en_US',
IntlNumberRangeFormatter::COLLAPSE_AUTO,
IntlNumberRangeFormatter::IDENTITY_FALLBACK_SINGLE_VALUE
);

$formatted = $formatter->format(9007199254740993, 9007199254740993);
var_dump(preg_replace('/[^0-9]/', '', $formatted));
?>
--EXPECT--
string(16) "9007199254740993"
20 changes: 20 additions & 0 deletions ext/intl/tests/uconverter_transcode_subst_length.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
--TEST--
UConverter::transcode() rejects too long substitution strings
--EXTENSIONS--
intl
--INI--
intl.use_exceptions=false
--FILE--
<?php
$subst = str_repeat('A', 129);

var_dump(UConverter::transcode('abc', 'UTF-8', 'ASCII', ['from_subst' => $subst]));
echo intl_get_error_message(), "\n";
var_dump(UConverter::transcode('abc', 'UTF-8', 'ASCII', ['to_subst' => $subst]));
echo intl_get_error_message(), "\n";
?>
--EXPECT--
bool(false)
UConverter::transcode(): returned error 1: U_ILLEGAL_ARGUMENT_ERROR: U_ILLEGAL_ARGUMENT_ERROR
bool(false)
UConverter::transcode(): returned error 1: U_ILLEGAL_ARGUMENT_ERROR: U_ILLEGAL_ARGUMENT_ERROR
28 changes: 25 additions & 3 deletions ext/soap/php_schema.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,28 @@ 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, 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 (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);
}
}

if (ZEND_LONG_EXCEEDS_INT(lval) || (!allow_negative && lval < 0)) {
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};
Expand Down Expand Up @@ -854,7 +876,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, true);

return TRUE;
}
Expand Down Expand Up @@ -1016,7 +1038,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", false);
} else {
model->min_occurs = 1;
}
Expand All @@ -1026,7 +1048,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", false);
}
} else {
model->max_occurs = 1;
Expand Down
128 changes: 128 additions & 0 deletions ext/soap/tests/bugs/gh22167.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
--TEST--
GH-22167 (Out-of-range XML Schema integer values in SOAP WSDL)
--EXTENSIONS--
soap
--INI--
soap.wsdl_cache_enabled=0
--FILE--
<?php
function wsdl_with_schema(string $schema): string {
return <<<XML
<?xml version="1.0"?>
<definitions
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://test-uri/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns="http://schemas.xmlsoap.org/wsdl/"
targetNamespace="http://test-uri/">
<types>
<xsd:schema targetNamespace="http://test-uri/">
$schema
</xsd:schema>
</types>
<message name="m"><part name="p" type="tns:T"/></message>
<portType name="p"><operation name="op"><input message="tns:m"/></operation></portType>
<binding name="b" type="tns:p">
<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="op">
<soap:operation soapAction="#op"/>
<input>
<soap:body use="encoded"
namespace="http://test-uri/"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
</input>
</operation>
</binding>
<service name="s"><port name="p" binding="tns:b"><soap:address location="test://"/></port></service>
</definitions>
XML;
}

function occurrence_schema(string $attribute, string $value = "2147483648"): string {
return <<<XML
<xsd:complexType name="T">
<xsd:sequence>
<xsd:element name="x" type="xsd:string" $attribute="$value"/>
</xsd:sequence>
</xsd:complexType>
XML;
}

function restriction_schema(string $facet, string $value = "2147483648"): string {
return <<<XML
<xsd:simpleType name="T">
<xsd:restriction base="xsd:int">
<xsd:$facet value="$value"/>
</xsd:restriction>
</xsd:simpleType>
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"),
"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",
"negative out-of-range numeric-string" => "-2147483649",
"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
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
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
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