Skip to content

Commit 732faef

Browse files
committed
ext/soap: Fix SOAP classmap validation for integer keys
1 parent bf46492 commit 732faef

5 files changed

Lines changed: 128 additions & 3 deletions

File tree

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ PHP NEWS
5151
- SOAP:
5252
. Fixed header injection through the Content-Type context option, the
5353
soapaction and the cookie names and values. (David Carlier)
54+
. Fixed the SoapClient and SoapServer "classmap" option to reject arrays
55+
containing integer keys. (Weilin Du)
5456

5557
- MBString:
5658
. Fixed bug GH-22779 (mb_strrpos() returns the wrong position for a negative

UPGRADING

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,9 @@ PHP 8.6 UPGRADE NOTES
155155
system.
156156

157157
- SOAP:
158+
. The "classmap" option of SoapClient and SoapServer now rejects arrays
159+
containing integer keys. Previously, sparse integer-keyed and mixed-keyed
160+
arrays could be accepted. (Weilin Du)
158161
. WSDL/XML Schema parsing now rejects out-of-range integer values for
159162
occurrence constraints and integer restriction facets. Negative minOccurs
160163
and maxOccurs values are rejected as well.

ext/soap/soap.c

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -930,6 +930,18 @@ static HashTable* soap_create_typemap(sdlPtr sdl, HashTable *ht) /* {{{ */
930930
}
931931
/* }}} */
932932

933+
static bool soap_class_map_is_valid(const HashTable *class_map)
934+
{
935+
zend_string *key;
936+
ZEND_HASH_FOREACH_STR_KEY(class_map, key) {
937+
if (UNEXPECTED(key == NULL)) {
938+
return false;
939+
}
940+
} ZEND_HASH_FOREACH_END();
941+
942+
return true;
943+
}
944+
933945
/* {{{ SoapServer constructor */
934946
PHP_METHOD(SoapServer, __construct)
935947
{
@@ -1007,8 +1019,7 @@ PHP_METHOD(SoapServer, __construct)
10071019
zend_argument_type_error(2, "\"classmap\" option must be of type array, %s given", zend_zval_type_name(class_map_zv));
10081020
goto cleanup;
10091021
}
1010-
// TODO: this still accepts mixed keys arrays and not all numerically indexed arrays are packed
1011-
if (UNEXPECTED(HT_IS_PACKED(Z_ARRVAL_P(class_map_zv)))) {
1022+
if (UNEXPECTED(!soap_class_map_is_valid(Z_ARRVAL_P(class_map_zv)))) {
10121023
zend_argument_value_error(2, "\"classmap\" option must be an associative array");
10131024
goto cleanup;
10141025
}
@@ -2233,7 +2244,7 @@ PHP_METHOD(SoapClient, __construct)
22332244
}
22342245
if ((tmp = zend_hash_str_find(ht, "classmap", sizeof("classmap")-1)) != NULL &&
22352246
Z_TYPE_P(tmp) == IS_ARRAY) {
2236-
if (UNEXPECTED(HT_IS_PACKED(Z_ARRVAL_P(tmp)))) {
2247+
if (UNEXPECTED(!soap_class_map_is_valid(Z_ARRVAL_P(tmp)))) {
22372248
php_error_docref(NULL, E_ERROR, "'classmap' option must be an associative array");
22382249
}
22392250
ZVAL_COPY(Z_CLIENT_CLASSMAP_P(this_ptr), tmp);
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
--TEST--
2+
SoapClient and SoapServer classmap options must only contain string keys
3+
--EXTENSIONS--
4+
soap
5+
--FILE--
6+
<?php
7+
8+
$emptyHash = ['type' => 'stdClass'];
9+
unset($emptyHash['type']);
10+
11+
$cases = [
12+
'empty' => [],
13+
'empty hash' => $emptyHash,
14+
'packed' => ['stdClass'],
15+
'sparse numeric' => [100 => 'stdClass'],
16+
'mixed' => ['type' => 'stdClass', 1 => 'stdClass'],
17+
'associative' => ['type' => 'stdClass'],
18+
];
19+
20+
foreach ($cases as $name => $classmap) {
21+
echo "-- $name --\n";
22+
23+
try {
24+
new SoapClient(null, [
25+
'location' => 'http://example.com/',
26+
'uri' => 'urn:test',
27+
'classmap' => $classmap,
28+
]);
29+
echo "SoapClient: OK\n";
30+
} catch (Throwable $e) {
31+
echo $e->getMessage(), "\n";
32+
}
33+
34+
try {
35+
new SoapServer(null, [
36+
'uri' => 'urn:test',
37+
'classmap' => $classmap,
38+
]);
39+
echo "SoapServer: OK\n";
40+
} catch (Throwable $e) {
41+
echo $e->getMessage(), "\n";
42+
}
43+
}
44+
45+
?>
46+
--EXPECT--
47+
-- empty --
48+
SoapClient: OK
49+
SoapServer: OK
50+
-- empty hash --
51+
SoapClient: OK
52+
SoapServer: OK
53+
-- packed --
54+
SoapClient::__construct(): 'classmap' option must be an associative array
55+
SoapServer::__construct(): Argument #2 ($options) "classmap" option must be an associative array
56+
-- sparse numeric --
57+
SoapClient::__construct(): 'classmap' option must be an associative array
58+
SoapServer::__construct(): Argument #2 ($options) "classmap" option must be an associative array
59+
-- mixed --
60+
SoapClient::__construct(): 'classmap' option must be an associative array
61+
SoapServer::__construct(): Argument #2 ($options) "classmap" option must be an associative array
62+
-- associative --
63+
SoapClient: OK
64+
SoapServer: OK
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
--TEST--
2+
SoapClient must not read packed private classmap as string-keyed map
3+
--EXTENSIONS--
4+
soap
5+
--FILE--
6+
<?php
7+
8+
class LocalSoapClient extends SoapClient {
9+
public function __doRequest($request, $location, $action, $version, $one_way = false, ?string $uriParserClass = null): string {
10+
echo "__doRequest called\n";
11+
12+
return <<<'XML'
13+
<?xml version="1.0" encoding="UTF-8"?>
14+
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
15+
<SOAP-ENV:Body>
16+
<SOAP-ENV:Fault>
17+
<faultcode>SOAP-ENV:Server</faultcode>
18+
<faultstring>expected fault</faultstring>
19+
</SOAP-ENV:Fault>
20+
</SOAP-ENV:Body>
21+
</SOAP-ENV:Envelope>
22+
XML;
23+
}
24+
}
25+
26+
class Foo {}
27+
28+
$client = new LocalSoapClient(null, [
29+
'location' => 'http://example.org/',
30+
'uri' => 'http://example.org/',
31+
]);
32+
33+
$property = new ReflectionProperty(SoapClient::class, '_classmap');
34+
$property->setValue($client, ['Foo']);
35+
36+
try {
37+
$client->__soapCall('foo', [new Foo()]);
38+
} catch (SoapFault) {
39+
echo "SOAP Fault thrown\n";
40+
}
41+
42+
?>
43+
--EXPECT--
44+
__doRequest called
45+
SOAP Fault thrown

0 commit comments

Comments
 (0)