Skip to content
Open
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
1 change: 1 addition & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ PHP NEWS
- Soap:
. Soap::__setCookie() when cookie name is a digit is now not stored and
represented as a string anymore but a int. (David Carlier)
. Fixed integer overflow when decoding SOAP array indexes. (Weilin Du)
. Fixed bug GH-21421 (SoapClient typemap property breaks engine assumptions).
(ndossche)

Expand Down
28 changes: 19 additions & 9 deletions ext/soap/php_encoding.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
+----------------------------------------------------------------------+
*/

#include <limits.h>
#include <time.h>

#include "php_soap.h"
Expand Down Expand Up @@ -2048,6 +2049,15 @@ static int calc_dimension_12(const char* str)
return i;
}

static void soap_array_position_add_digit(int *position, int digit)
{
if (*position > (INT_MAX - digit) / 10) {
soap_error0(E_ERROR, "Encoding: array index out of range");
}

*position = (*position * 10) + digit;
}

static int* get_position_12(int dimension, const char* str)
{
int *pos;
Expand All @@ -2068,7 +2078,7 @@ static int* get_position_12(int dimension, const char* str)
i++;
flag = 1;
}
pos[i] = (pos[i]*10)+(*str-'0');
soap_array_position_add_digit(&pos[i], *str - '0');
} else if (*str == '*') {
soap_error0(E_ERROR, "Encoding: '*' may only be first arraySize value in list");
} else {
Expand Down Expand Up @@ -2098,7 +2108,7 @@ static void get_position_ex(int dimension, const char* str, int** pos)
memset(*pos,0,sizeof(int)*dimension);
while (*str != ']' && *str != '\0' && i < dimension) {
if (*str >= '0' && *str <= '9') {
(*pos)[i] = ((*pos)[i]*10)+(*str-'0');
soap_array_position_add_digit(&(*pos)[i], *str - '0');
} else if (*str == ',') {
i++;
}
Expand Down Expand Up @@ -2686,16 +2696,16 @@ static zval *to_zval_array(zval *ret, encodeTypePtr type, xmlNodePtr data)
i = dimension;
while (i > 0) {
i--;
if (pos[i] == INT_MAX) {
soap_error0(E_ERROR, "Encoding: array index out of range");
}
pos[i]++;
if (pos[i] >= dims[i]) {
if (i > 0) {
pos[i] = 0;
} else {
/* TODO: Array index overflow */
}
} else {
if (pos[i] < dims[i]) {
break;
}
if (i > 0) {
pos[i] = 0;
}
}
}
trav = trav->next;
Expand Down
38 changes: 29 additions & 9 deletions ext/soap/php_packet_soap.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,26 @@

#include "php_soap.h"

static void master_to_zval_with_doc_cleanup(zval *ret, encodePtr encode, xmlNodePtr data, xmlDocPtr doc)
{
bool bailout = false;

Comment thread
LamentXU123 marked this conversation as resolved.
ZVAL_UNDEF(ret);

/* SoapClient can turn decode errors into a bailout before parse_packet_soap() frees the response doc. */
zend_try {
master_to_zval(ret, encode, data);
} zend_catch {
bailout = true;
} zend_end_try();

if (bailout) {
zval_ptr_dtor(ret);
xmlFreeDoc(doc);
zend_bailout();
}
}

/* SOAP client calls this function to parse response from SOAP server */
bool parse_packet_soap(zval *this_ptr, char *buffer, int buffer_size, sdlFunctionPtr fn, char *fn_name, zval *return_value, zval *soap_headers)
{
Expand Down Expand Up @@ -190,22 +210,22 @@ bool parse_packet_soap(zval *this_ptr, char *buffer, int buffer_size, sdlFunctio
tmp = get_node(fault->children, "faultstring");
if (tmp != NULL && tmp->children != NULL) {
zval zv;
master_to_zval(&zv, get_conversion(IS_STRING), tmp);
master_to_zval_with_doc_cleanup(&zv, get_conversion(IS_STRING), tmp, response);
convert_to_string(&zv)
faultstring = Z_STR(zv);
}

tmp = get_node(fault->children, "faultactor");
if (tmp != NULL && tmp->children != NULL) {
zval zv;
master_to_zval(&zv, get_conversion(IS_STRING), tmp);
master_to_zval_with_doc_cleanup(&zv, get_conversion(IS_STRING), tmp, response);
convert_to_string(&zv)
faultactor = Z_STR(zv);
}

tmp = get_node(fault->children, "detail");
if (tmp != NULL) {
master_to_zval(&details, NULL, tmp);
master_to_zval_with_doc_cleanup(&details, NULL, tmp, response);
}
} else {
tmp = get_node(fault->children, "Code");
Expand All @@ -221,7 +241,7 @@ bool parse_packet_soap(zval *this_ptr, char *buffer, int buffer_size, sdlFunctio
tmp = get_node(tmp->children,"Text");
if (tmp != NULL && tmp->children != NULL) {
zval zv;
master_to_zval(&zv, get_conversion(IS_STRING), tmp);
master_to_zval_with_doc_cleanup(&zv, get_conversion(IS_STRING), tmp, response);
convert_to_string(&zv)
faultstring = Z_STR(zv);

Expand All @@ -236,7 +256,7 @@ bool parse_packet_soap(zval *this_ptr, char *buffer, int buffer_size, sdlFunctio

tmp = get_node(fault->children,"Detail");
if (tmp != NULL) {
master_to_zval(&details, NULL, tmp);
master_to_zval_with_doc_cleanup(&details, NULL, tmp, response);
}
}
add_soap_fault(this_ptr, faultcode, faultstring ? ZSTR_VAL(faultstring) : NULL, faultactor ? ZSTR_VAL(faultactor) : NULL, &details, lang);
Expand Down Expand Up @@ -330,9 +350,9 @@ bool parse_packet_soap(zval *this_ptr, char *buffer, int buffer_size, sdlFunctio
} else {
/* Decoding value of parameter */
if (param != NULL) {
master_to_zval(&tmp, param->encode, val);
master_to_zval_with_doc_cleanup(&tmp, param->encode, val, response);
} else {
master_to_zval(&tmp, NULL, val);
master_to_zval_with_doc_cleanup(&tmp, NULL, val, response);
}
}
add_assoc_zval(return_value, param->paramName, &tmp);
Expand All @@ -353,7 +373,7 @@ bool parse_packet_soap(zval *this_ptr, char *buffer, int buffer_size, sdlFunctio
zval tmp;
zval *arr;

master_to_zval(&tmp, NULL, val);
master_to_zval_with_doc_cleanup(&tmp, NULL, val, response);
if (val->name) {
if ((arr = zend_hash_str_find(Z_ARRVAL_P(return_value), (char*)val->name, strlen((char*)val->name))) != NULL) {
add_next_index_zval(arr, &tmp);
Expand Down Expand Up @@ -418,7 +438,7 @@ bool parse_packet_soap(zval *this_ptr, char *buffer, int buffer_size, sdlFunctio
}
smart_str_free(&key);
}
master_to_zval(&val, enc, trav);
master_to_zval_with_doc_cleanup(&val, enc, trav, response);
add_assoc_zval(soap_headers, (char*)trav->name, &val);
}
trav = trav->next;
Expand Down
24 changes: 22 additions & 2 deletions ext/soap/soap.c
Original file line number Diff line number Diff line change
Expand Up @@ -2449,9 +2449,19 @@ static void do_soap_call(zend_execute_data *execute_data,
request = NULL;

if (ret && Z_TYPE(response) == IS_STRING) {
bool parse_bailout = false;

encode_reset_ns();
ret = parse_packet_soap(this_ptr, Z_STRVAL(response), Z_STRLEN(response), fn, NULL, return_value, output_headers);
zend_try {
ret = parse_packet_soap(this_ptr, Z_STRVAL(response), Z_STRLEN(response), fn, NULL, return_value, output_headers);
} zend_catch {
parse_bailout = true;
} zend_end_try();
encode_finish();
if (parse_bailout) {
zval_ptr_dtor(&response);
zend_bailout();
}
}

zval_ptr_dtor(&response);
Expand Down Expand Up @@ -2493,9 +2503,19 @@ static void do_soap_call(zend_execute_data *execute_data,
request = NULL;

if (ret && Z_TYPE(response) == IS_STRING) {
bool parse_bailout = false;

encode_reset_ns();
ret = parse_packet_soap(this_ptr, Z_STRVAL(response), Z_STRLEN(response), NULL, NULL, return_value, output_headers);
zend_try {
ret = parse_packet_soap(this_ptr, Z_STRVAL(response), Z_STRLEN(response), NULL, NULL, return_value, output_headers);
} zend_catch {
parse_bailout = true;
} zend_end_try();
encode_finish();
if (parse_bailout) {
zval_ptr_dtor(&response);
zend_bailout();
}
}

zval_ptr_dtor(&response);
Expand Down
69 changes: 69 additions & 0 deletions ext/soap/tests/soap_array_index_overflow.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
--TEST--
SOAP array index overflow is rejected
--EXTENSIONS--
soap
--FILE--
<?php
class TestSoapClient extends SoapClient {
public string $response;

public function __doRequest($request, $location, $action, $version, $one_way = false, ?string $uriParserClass = null): string {
return $this->response;
}
}

function soap_response(string $attributes, string $itemAttributes = ''): string {
return <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ns1="http://example.org/"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<ns1:testResponse>
<return $attributes>
<item xsi:type="xsd:string" $itemAttributes>value</item>
</return>
</ns1:testResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
XML;
}

function test_overflow(string $name, string $response): void {
$client = new TestSoapClient(NULL, [
'location' => 'test://',
'uri' => 'http://example.org/',
'exceptions' => true,
]);
$client->response = $response;

try {
$client->test();
echo "$name: no fault\n";
} catch (SoapFault $e) {
echo "$name: $e->faultstring\n";
}
}

test_overflow(
'arrayType',
soap_response('SOAP-ENC:arrayType="xsd:string[2147483648]" xsi:type="SOAP-ENC:Array"')
);

test_overflow(
'offset',
soap_response('SOAP-ENC:arrayType="xsd:string[1]" SOAP-ENC:offset="[2147483648]" xsi:type="SOAP-ENC:Array"')
);

test_overflow(
'position',
soap_response('SOAP-ENC:arrayType="xsd:string[1]" xsi:type="SOAP-ENC:Array"', 'SOAP-ENC:position="[2147483647]"')
);
?>
--EXPECT--
arrayType: SOAP-ERROR: Encoding: array index out of range
offset: SOAP-ERROR: Encoding: array index out of range
position: SOAP-ERROR: Encoding: array index out of range
Loading