From 3e1e852dfcee64ec32c047eb08d6aa057c9f6c16 Mon Sep 17 00:00:00 2001 From: lamentxu <1372449351@qq.com> Date: Wed, 6 May 2026 22:50:37 +0800 Subject: [PATCH 1/5] ext/soap: Fix SOAP array index integer overflow --- NEWS | 1 + ext/soap/php_encoding.c | 18 ++++- ext/soap/tests/soap_array_index_overflow.phpt | 69 +++++++++++++++++++ 3 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 ext/soap/tests/soap_array_index_overflow.phpt diff --git a/NEWS b/NEWS index 4ace7d3dc462..2f093c8124df 100644 --- a/NEWS +++ b/NEWS @@ -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) diff --git a/ext/soap/php_encoding.c b/ext/soap/php_encoding.c index fece87e9797e..9f3a4c6f0c9a 100644 --- a/ext/soap/php_encoding.c +++ b/ext/soap/php_encoding.c @@ -14,6 +14,7 @@ +----------------------------------------------------------------------+ */ +#include #include #include "php_soap.h" @@ -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; @@ -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 { @@ -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++; } @@ -2686,12 +2696,14 @@ 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 { break; diff --git a/ext/soap/tests/soap_array_index_overflow.phpt b/ext/soap/tests/soap_array_index_overflow.phpt new file mode 100644 index 000000000000..88474bee854c --- /dev/null +++ b/ext/soap/tests/soap_array_index_overflow.phpt @@ -0,0 +1,69 @@ +--TEST-- +SOAP array index overflow is rejected +--EXTENSIONS-- +soap +--FILE-- +response; + } +} + +function soap_response(string $attributes, string $itemAttributes = ''): string { + return << + + + + + value + + + + +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 From 75071f0de7cc4590a57fff4fa723a7cb87072711 Mon Sep 17 00:00:00 2001 From: lamentxu <1372449351@qq.com> Date: Thu, 7 May 2026 00:04:22 +0800 Subject: [PATCH 2/5] Fix CI --- ext/soap/tests/soap_array_index_overflow.phpt | 139 ++++++++++++------ 1 file changed, 97 insertions(+), 42 deletions(-) diff --git a/ext/soap/tests/soap_array_index_overflow.phpt b/ext/soap/tests/soap_array_index_overflow.phpt index 88474bee854c..2d25441e8e76 100644 --- a/ext/soap/tests/soap_array_index_overflow.phpt +++ b/ext/soap/tests/soap_array_index_overflow.phpt @@ -4,16 +4,28 @@ SOAP array index overflow is rejected soap --FILE-- 'http://example.org/']); +$server->addFunction('test'); +$server->handle(file_get_contents('php://stdin')); +PHP; - public function __doRequest($request, $location, $action, $version, $one_way = false, ?string $uriParserClass = null): string { - return $this->response; - } +$phpArgs = [ + '-d', + 'display_startup_errors=0', + '-d', + 'extension_dir=' . ini_get('extension_dir'), + '-d', + 'extension=' . (substr(PHP_OS, 0, 3) === 'WIN' ? 'php_' : '') . 'soap.' . PHP_SHLIB_SUFFIX, + '-r', + $serverCode, +]; +if (php_ini_loaded_file()) { + array_splice($phpArgs, 0, 0, ['-c', php_ini_loaded_file()]); } -function soap_response(string $attributes, string $itemAttributes = ''): string { - return << - - - value - - + + + value + + XML; -} -function test_overflow(string $name, string $response): void { - $client = new TestSoapClient(NULL, [ - 'location' => 'test://', - 'uri' => 'http://example.org/', - 'exceptions' => true, - ]); - $client->response = $response; +echo "arrayType:\n"; +$process = proc_open([PHP_BINARY, ...$phpArgs], [ + 0 => ['pipe', 'r'], + 1 => ['pipe', 'w'], +], $pipes); +fwrite($pipes[0], $arrayTypeRequest); +fclose($pipes[0]); +echo stream_get_contents($pipes[1]); +fclose($pipes[1]); +proc_close($process); - try { - $client->test(); - echo "$name: no fault\n"; - } catch (SoapFault $e) { - echo "$name: $e->faultstring\n"; - } -} +$offsetRequest = << + + + + + value + + + + +XML; -test_overflow( - 'arrayType', - soap_response('SOAP-ENC:arrayType="xsd:string[2147483648]" xsi:type="SOAP-ENC:Array"') -); +echo "offset:\n"; +$process = proc_open([PHP_BINARY, ...$phpArgs], [ + 0 => ['pipe', 'r'], + 1 => ['pipe', 'w'], +], $pipes); +fwrite($pipes[0], $offsetRequest); +fclose($pipes[0]); +echo stream_get_contents($pipes[1]); +fclose($pipes[1]); +proc_close($process); -test_overflow( - 'offset', - soap_response('SOAP-ENC:arrayType="xsd:string[1]" SOAP-ENC:offset="[2147483648]" xsi:type="SOAP-ENC:Array"') -); +$positionRequest = << + + + + + value + + + + +XML; -test_overflow( - 'position', - soap_response('SOAP-ENC:arrayType="xsd:string[1]" xsi:type="SOAP-ENC:Array"', 'SOAP-ENC:position="[2147483647]"') -); +echo "position:\n"; +$process = proc_open([PHP_BINARY, ...$phpArgs], [ + 0 => ['pipe', 'r'], + 1 => ['pipe', 'w'], +], $pipes); +fwrite($pipes[0], $positionRequest); +fclose($pipes[0]); +echo stream_get_contents($pipes[1]); +fclose($pipes[1]); +proc_close($process); ?> --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 +arrayType: + +SOAP-ENV:ServerSOAP-ERROR: Encoding: array index out of range +offset: + +SOAP-ENV:ServerSOAP-ERROR: Encoding: array index out of range +position: + +SOAP-ENV:ServerSOAP-ERROR: Encoding: array index out of range From 6e2de881f75128bf839421cb34f770c991922539 Mon Sep 17 00:00:00 2001 From: lamentxu <1372449351@qq.com> Date: Thu, 7 May 2026 00:10:22 +0800 Subject: [PATCH 3/5] fix bailout logic --- ext/soap/php_packet_soap.c | 35 +++-- ext/soap/soap.c | 24 ++- ext/soap/tests/soap_array_index_overflow.phpt | 137 +++++------------- 3 files changed, 88 insertions(+), 108 deletions(-) diff --git a/ext/soap/php_packet_soap.c b/ext/soap/php_packet_soap.c index 6cf94b0a3b36..698ef863e756 100644 --- a/ext/soap/php_packet_soap.c +++ b/ext/soap/php_packet_soap.c @@ -16,6 +16,23 @@ #include "php_soap.h" +static void master_to_zval_with_doc_cleanup(zval *ret, encodePtr encode, xmlNodePtr data, xmlDocPtr doc) +{ + bool bailout = false; + + /* 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) { + 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) { @@ -190,7 +207,7 @@ 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); } @@ -198,14 +215,14 @@ bool parse_packet_soap(zval *this_ptr, char *buffer, int buffer_size, sdlFunctio 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"); @@ -221,7 +238,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); @@ -236,7 +253,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); @@ -330,9 +347,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); @@ -353,7 +370,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); @@ -418,7 +435,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; diff --git a/ext/soap/soap.c b/ext/soap/soap.c index 5cc87e7cc905..92d94608d29f 100644 --- a/ext/soap/soap.c +++ b/ext/soap/soap.c @@ -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); @@ -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); diff --git a/ext/soap/tests/soap_array_index_overflow.phpt b/ext/soap/tests/soap_array_index_overflow.phpt index 2d25441e8e76..c3183022b3c5 100644 --- a/ext/soap/tests/soap_array_index_overflow.phpt +++ b/ext/soap/tests/soap_array_index_overflow.phpt @@ -4,28 +4,14 @@ SOAP array index overflow is rejected soap --FILE-- 'http://example.org/']); -$server->addFunction('test'); -$server->handle(file_get_contents('php://stdin')); -PHP; -$phpArgs = [ - '-d', - 'display_startup_errors=0', - '-d', - 'extension_dir=' . ini_get('extension_dir'), - '-d', - 'extension=' . (substr(PHP_OS, 0, 3) === 'WIN' ? 'php_' : '') . 'soap.' . PHP_SHLIB_SUFFIX, - '-r', - $serverCode, -]; -if (php_ini_loaded_file()) { - array_splice($phpArgs, 0, 0, ['-c', php_ini_loaded_file()]); + public function __doRequest($request, $location, $action, $version, $one_way = false, ?string $uriParserClass = null): string { + return $this->response; + } } -$arrayTypeRequest = << - - - value - - + + + value + + XML; +} -echo "arrayType:\n"; -$process = proc_open([PHP_BINARY, ...$phpArgs], [ - 0 => ['pipe', 'r'], - 1 => ['pipe', 'w'], -], $pipes); -fwrite($pipes[0], $arrayTypeRequest); -fclose($pipes[0]); -echo stream_get_contents($pipes[1]); -fclose($pipes[1]); -proc_close($process); +function test_overflow(string $name, string $response): void { + $client = new TestSoapClient(NULL, [ + 'location' => 'test://', + 'uri' => 'http://example.org/', + 'exceptions' => true, + ]); + $client->response = $response; -$offsetRequest = << - - - - - value - - - - -XML; + try { + $client->test(); + echo "$name: no fault\n"; + } catch (SoapFault $e) { + echo "$name: $e->faultstring\n"; + } +} -echo "offset:\n"; -$process = proc_open([PHP_BINARY, ...$phpArgs], [ - 0 => ['pipe', 'r'], - 1 => ['pipe', 'w'], -], $pipes); -fwrite($pipes[0], $offsetRequest); -fclose($pipes[0]); -echo stream_get_contents($pipes[1]); -fclose($pipes[1]); -proc_close($process); +test_overflow( + 'arrayType', + soap_response('SOAP-ENC:arrayType="xsd:string[2147483648]" xsi:type="SOAP-ENC:Array"') +); -$positionRequest = << - - - - - value - - - - -XML; +test_overflow( + 'offset', + soap_response('SOAP-ENC:arrayType="xsd:string[1]" SOAP-ENC:offset="[2147483648]" xsi:type="SOAP-ENC:Array"') +); -echo "position:\n"; -$process = proc_open([PHP_BINARY, ...$phpArgs], [ - 0 => ['pipe', 'r'], - 1 => ['pipe', 'w'], -], $pipes); -fwrite($pipes[0], $positionRequest); -fclose($pipes[0]); -echo stream_get_contents($pipes[1]); -fclose($pipes[1]); -proc_close($process); +test_overflow( + 'position', + soap_response('SOAP-ENC:arrayType="xsd:string[1]" xsi:type="SOAP-ENC:Array"', 'SOAP-ENC:position="[2147483647]"') +); ?> --EXPECT-- -arrayType: - -SOAP-ENV:ServerSOAP-ERROR: Encoding: array index out of range -offset: - -SOAP-ENV:ServerSOAP-ERROR: Encoding: array index out of range -position: - -SOAP-ENV:ServerSOAP-ERROR: Encoding: array index out of range +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 From 19ecf09dc96563e8eb40f42d9df24a1458e13fb8 Mon Sep 17 00:00:00 2001 From: lamentxu <1372449351@qq.com> Date: Thu, 7 May 2026 00:11:51 +0800 Subject: [PATCH 4/5] fix CI --- ext/soap/tests/soap_array_index_overflow.phpt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/soap/tests/soap_array_index_overflow.phpt b/ext/soap/tests/soap_array_index_overflow.phpt index c3183022b3c5..88474bee854c 100644 --- a/ext/soap/tests/soap_array_index_overflow.phpt +++ b/ext/soap/tests/soap_array_index_overflow.phpt @@ -4,6 +4,8 @@ SOAP array index overflow is rejected soap --FILE-- response; From f968bdac69114f3f17be3245724801a1cee12488 Mon Sep 17 00:00:00 2001 From: lamentxu <1372449351@qq.com> Date: Thu, 7 May 2026 07:01:03 +0800 Subject: [PATCH 5/5] fix empty else --- ext/soap/php_encoding.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/ext/soap/php_encoding.c b/ext/soap/php_encoding.c index 9f3a4c6f0c9a..d99e88af2a10 100644 --- a/ext/soap/php_encoding.c +++ b/ext/soap/php_encoding.c @@ -2700,14 +2700,12 @@ static zval *to_zval_array(zval *ret, encodeTypePtr type, xmlNodePtr data) soap_error0(E_ERROR, "Encoding: array index out of range"); } pos[i]++; - if (pos[i] >= dims[i]) { - if (i > 0) { - pos[i] = 0; - } else { - } - } else { + if (pos[i] < dims[i]) { break; } + if (i > 0) { + pos[i] = 0; + } } } trav = trav->next;