Skip to content

Commit 3d5a8bf

Browse files
committed
ext/soap: Fix integer overflow when decoding SOAP array indexes
1 parent f79f921 commit 3d5a8bf

5 files changed

Lines changed: 171 additions & 23 deletions

File tree

NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ PHP NEWS
1212
- OpenSSL:
1313
. Fix compatibility issues with OpenSSL 4.0. (jordikroon, Remi)
1414

15+
- SOAP:
16+
. Fixed integer overflow when decoding SOAP array indexes. (Weilin Du)
17+
1518
- SPL:
1619
. Fix SplFixedArray::setSize leak when destructor grows during clear.
1720
(David Carlier)

ext/soap/php_encoding.c

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
+----------------------------------------------------------------------+
1717
*/
1818

19+
#include <limits.h>
1920
#include <time.h>
2021

2122
#include "php_soap.h"
@@ -2075,6 +2076,15 @@ static int calc_dimension_12(const char* str)
20752076
return i;
20762077
}
20772078

2079+
static void soap_array_position_add_digit(int *position, int digit)
2080+
{
2081+
if (*position > (INT_MAX - digit) / 10) {
2082+
soap_error0(E_ERROR, "Encoding: array index out of range");
2083+
}
2084+
2085+
*position = (*position * 10) + digit;
2086+
}
2087+
20782088
static int* get_position_12(int dimension, const char* str)
20792089
{
20802090
int *pos;
@@ -2095,7 +2105,7 @@ static int* get_position_12(int dimension, const char* str)
20952105
i++;
20962106
flag = 1;
20972107
}
2098-
pos[i] = (pos[i]*10)+(*str-'0');
2108+
soap_array_position_add_digit(&pos[i], *str - '0');
20992109
} else if (*str == '*') {
21002110
soap_error0(E_ERROR, "Encoding: '*' may only be first arraySize value in list");
21012111
} else {
@@ -2125,7 +2135,7 @@ static void get_position_ex(int dimension, const char* str, int** pos)
21252135
memset(*pos,0,sizeof(int)*dimension);
21262136
while (*str != ']' && *str != '\0' && i < dimension) {
21272137
if (*str >= '0' && *str <= '9') {
2128-
(*pos)[i] = ((*pos)[i]*10)+(*str-'0');
2138+
soap_array_position_add_digit(&(*pos)[i], *str - '0');
21292139
} else if (*str == ',') {
21302140
i++;
21312141
}
@@ -2698,16 +2708,20 @@ static zval *to_zval_array(zval *ret, encodeTypePtr type, xmlNodePtr data)
26982708
/* Increment position */
26992709
i = dimension;
27002710
while (i > 0) {
2701-
i--;
2702-
pos[i]++;
2703-
if (pos[i] >= dims[i]) {
2704-
if (i > 0) {
2705-
pos[i] = 0;
2706-
} else {
2707-
/* TODO: Array index overflow */
2708-
}
2709-
} else {
2710-
break;
2711+
i--;
2712+
if (pos[i] == INT_MAX) {
2713+
efree(dims);
2714+
efree(pos);
2715+
zval_ptr_dtor(ret);
2716+
ZVAL_UNDEF(ret);
2717+
soap_error0(E_ERROR, "Encoding: array index out of range");
2718+
}
2719+
pos[i]++;
2720+
if (pos[i] < dims[i]) {
2721+
break;
2722+
}
2723+
if (i > 0) {
2724+
pos[i] = 0;
27112725
}
27122726
}
27132727
}

ext/soap/php_packet_soap.c

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,28 @@
1818

1919
#include "php_soap.h"
2020

21+
static void master_to_zval_with_doc_cleanup(zval *ret, encodePtr encode, xmlNodePtr data, xmlDocPtr doc)
22+
{
23+
bool bailout = false;
24+
25+
ZVAL_UNDEF(ret);
26+
27+
/* SoapClient can turn decode errors into a bailout before parse_packet_soap() frees the response doc. */
28+
zend_try {
29+
master_to_zval(ret, encode, data);
30+
} zend_catch {
31+
bailout = true;
32+
} zend_end_try();
33+
34+
if (bailout) {
35+
if (!Z_ISUNDEF_P(ret)) {
36+
zval_ptr_dtor(ret);
37+
}
38+
xmlFreeDoc(doc);
39+
zend_bailout();
40+
}
41+
}
42+
2143
/* SOAP client calls this function to parse response from SOAP server */
2244
bool parse_packet_soap(zval *this_ptr, char *buffer, int buffer_size, sdlFunctionPtr fn, char *fn_name, zval *return_value, zval *soap_headers)
2345
{
@@ -191,22 +213,22 @@ bool parse_packet_soap(zval *this_ptr, char *buffer, int buffer_size, sdlFunctio
191213
tmp = get_node(fault->children, "faultstring");
192214
if (tmp != NULL && tmp->children != NULL) {
193215
zval zv;
194-
master_to_zval(&zv, get_conversion(IS_STRING), tmp);
216+
master_to_zval_with_doc_cleanup(&zv, get_conversion(IS_STRING), tmp, response);
195217
convert_to_string(&zv)
196218
faultstring = Z_STR(zv);
197219
}
198220

199221
tmp = get_node(fault->children, "faultactor");
200222
if (tmp != NULL && tmp->children != NULL) {
201223
zval zv;
202-
master_to_zval(&zv, get_conversion(IS_STRING), tmp);
224+
master_to_zval_with_doc_cleanup(&zv, get_conversion(IS_STRING), tmp, response);
203225
convert_to_string(&zv)
204226
faultactor = Z_STR(zv);
205227
}
206228

207229
tmp = get_node(fault->children, "detail");
208230
if (tmp != NULL) {
209-
master_to_zval(&details, NULL, tmp);
231+
master_to_zval_with_doc_cleanup(&details, NULL, tmp, response);
210232
}
211233
} else {
212234
tmp = get_node(fault->children, "Code");
@@ -223,15 +245,15 @@ bool parse_packet_soap(zval *this_ptr, char *buffer, int buffer_size, sdlFunctio
223245
tmp = get_node(tmp->children,"Text");
224246
if (tmp != NULL && tmp->children != NULL) {
225247
zval zv;
226-
master_to_zval(&zv, get_conversion(IS_STRING), tmp);
248+
master_to_zval_with_doc_cleanup(&zv, get_conversion(IS_STRING), tmp, response);
227249
convert_to_string(&zv)
228250
faultstring = Z_STR(zv);
229251
}
230252
}
231253

232254
tmp = get_node(fault->children,"Detail");
233255
if (tmp != NULL) {
234-
master_to_zval(&details, NULL, tmp);
256+
master_to_zval_with_doc_cleanup(&details, NULL, tmp, response);
235257
}
236258
}
237259
add_soap_fault(this_ptr, faultcode, faultstring ? ZSTR_VAL(faultstring) : NULL, faultactor ? ZSTR_VAL(faultactor) : NULL, &details);
@@ -324,9 +346,9 @@ bool parse_packet_soap(zval *this_ptr, char *buffer, int buffer_size, sdlFunctio
324346
} else {
325347
/* Decoding value of parameter */
326348
if (param != NULL) {
327-
master_to_zval(&tmp, param->encode, val);
349+
master_to_zval_with_doc_cleanup(&tmp, param->encode, val, response);
328350
} else {
329-
master_to_zval(&tmp, NULL, val);
351+
master_to_zval_with_doc_cleanup(&tmp, NULL, val, response);
330352
}
331353
}
332354
add_assoc_zval(return_value, param->paramName, &tmp);
@@ -347,7 +369,7 @@ bool parse_packet_soap(zval *this_ptr, char *buffer, int buffer_size, sdlFunctio
347369
zval tmp;
348370
zval *arr;
349371

350-
master_to_zval(&tmp, NULL, val);
372+
master_to_zval_with_doc_cleanup(&tmp, NULL, val, response);
351373
if (val->name) {
352374
if ((arr = zend_hash_str_find(Z_ARRVAL_P(return_value), (char*)val->name, strlen((char*)val->name))) != NULL) {
353375
add_next_index_zval(arr, &tmp);
@@ -412,7 +434,7 @@ bool parse_packet_soap(zval *this_ptr, char *buffer, int buffer_size, sdlFunctio
412434
}
413435
smart_str_free(&key);
414436
}
415-
master_to_zval(&val, enc, trav);
437+
master_to_zval_with_doc_cleanup(&val, enc, trav, response);
416438
add_assoc_zval(soap_headers, (char*)trav->name, &val);
417439
}
418440
trav = trav->next;

ext/soap/soap.c

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2408,9 +2408,19 @@ static void do_soap_call(zend_execute_data *execute_data,
24082408
request = NULL;
24092409

24102410
if (ret && Z_TYPE(response) == IS_STRING) {
2411+
bool parse_bailout = false;
2412+
24112413
encode_reset_ns();
2412-
ret = parse_packet_soap(this_ptr, Z_STRVAL(response), Z_STRLEN(response), fn, NULL, return_value, output_headers);
2414+
zend_try {
2415+
ret = parse_packet_soap(this_ptr, Z_STRVAL(response), Z_STRLEN(response), fn, NULL, return_value, output_headers);
2416+
} zend_catch {
2417+
parse_bailout = true;
2418+
} zend_end_try();
24132419
encode_finish();
2420+
if (parse_bailout) {
2421+
zval_ptr_dtor(&response);
2422+
zend_bailout();
2423+
}
24142424
}
24152425

24162426
zval_ptr_dtor(&response);
@@ -2452,9 +2462,19 @@ static void do_soap_call(zend_execute_data *execute_data,
24522462
request = NULL;
24532463

24542464
if (ret && Z_TYPE(response) == IS_STRING) {
2465+
bool parse_bailout = false;
2466+
24552467
encode_reset_ns();
2456-
ret = parse_packet_soap(this_ptr, Z_STRVAL(response), Z_STRLEN(response), NULL, NULL, return_value, output_headers);
2468+
zend_try {
2469+
ret = parse_packet_soap(this_ptr, Z_STRVAL(response), Z_STRLEN(response), NULL, NULL, return_value, output_headers);
2470+
} zend_catch {
2471+
parse_bailout = true;
2472+
} zend_end_try();
24572473
encode_finish();
2474+
if (parse_bailout) {
2475+
zval_ptr_dtor(&response);
2476+
zend_bailout();
2477+
}
24582478
}
24592479

24602480
zval_ptr_dtor(&response);
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
--TEST--
2+
SOAP array index overflow is rejected
3+
--EXTENSIONS--
4+
soap
5+
--FILE--
6+
<?php
7+
class TestSoapClient extends SoapClient {
8+
public string $response;
9+
10+
public function __doRequest($request, $location, $action, $version, $one_way = false, ?string $uriParserClass = null): string {
11+
return $this->response;
12+
}
13+
}
14+
15+
function soap_response(string $attributes, string $itemAttributes = ''): string {
16+
return <<<XML
17+
<?xml version="1.0" encoding="UTF-8"?>
18+
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
19+
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
20+
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
21+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
22+
xmlns:ns1="http://example.org/"
23+
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
24+
<SOAP-ENV:Body>
25+
<ns1:testResponse>
26+
<return $attributes>
27+
<item xsi:type="xsd:string" $itemAttributes>value</item>
28+
</return>
29+
</ns1:testResponse>
30+
</SOAP-ENV:Body>
31+
</SOAP-ENV:Envelope>
32+
XML;
33+
}
34+
35+
function test_overflow(string $name, string $response): void {
36+
$client = new TestSoapClient(NULL, [
37+
'location' => 'test://',
38+
'uri' => 'http://example.org/',
39+
'exceptions' => true,
40+
]);
41+
$client->response = $response;
42+
43+
try {
44+
$client->test();
45+
echo "$name: no fault\n";
46+
} catch (SoapFault $e) {
47+
echo "$name: $e->faultstring\n";
48+
}
49+
}
50+
51+
function test_boundary_position(): void {
52+
$client = new TestSoapClient(NULL, [
53+
'location' => 'test://',
54+
'uri' => 'http://example.org/',
55+
'exceptions' => true,
56+
]);
57+
$client->response = soap_response(
58+
'SOAP-ENC:arrayType="xsd:string[1]" xsi:type="SOAP-ENC:Array"',
59+
'SOAP-ENC:position="[2147483646]"'
60+
);
61+
62+
var_dump($client->test());
63+
}
64+
65+
test_overflow(
66+
'arrayType',
67+
soap_response('SOAP-ENC:arrayType="xsd:string[2147483648]" xsi:type="SOAP-ENC:Array"')
68+
);
69+
70+
test_overflow(
71+
'offset',
72+
soap_response('SOAP-ENC:arrayType="xsd:string[1]" SOAP-ENC:offset="[2147483648]" xsi:type="SOAP-ENC:Array"')
73+
);
74+
75+
test_overflow(
76+
'position',
77+
soap_response('SOAP-ENC:arrayType="xsd:string[1]" xsi:type="SOAP-ENC:Array"', 'SOAP-ENC:position="[2147483647]"')
78+
);
79+
80+
test_boundary_position();
81+
?>
82+
--EXPECT--
83+
arrayType: SOAP-ERROR: Encoding: array index out of range
84+
offset: SOAP-ERROR: Encoding: array index out of range
85+
position: SOAP-ERROR: Encoding: array index out of range
86+
array(1) {
87+
[2147483646]=>
88+
string(5) "value"
89+
}

0 commit comments

Comments
 (0)