From 32b98bdb7b87b421c34ea02538ffba9b001d73f4 Mon Sep 17 00:00:00 2001 From: TommusRhodus Date: Fri, 15 May 2026 14:12:43 +0100 Subject: [PATCH 1/2] XML-RPC: Prevent fatal error when system.multicall params are not an array. Validates the per-call structure inside IXR_Server::multiCall() and returns a spec-compliant fault (-32602) for malformed entries, rather than passing non-array values to IXR_Server::call() where count() would TypeError on PHP 8+. Also adds a defensive guard inside call() mirroring the existing pattern in IXR_IntrospectionServer::call(). Props TommusRhodus. Fixes #65124. --- src/wp-includes/IXR/class-IXR-server.php | 16 ++++++ tests/phpunit/tests/xmlrpc/basic.php | 71 ++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/src/wp-includes/IXR/class-IXR-server.php b/src/wp-includes/IXR/class-IXR-server.php index e329602910d61..4c3ed0a45502b 100644 --- a/src/wp-includes/IXR/class-IXR-server.php +++ b/src/wp-includes/IXR/class-IXR-server.php @@ -91,6 +91,12 @@ function call($methodname, $args) } $method = $this->callbacks[$methodname]; + // Ensure $args is an array. Callers such as system.multicall can pass + // non-array params derived from untrusted client input. + if (!is_array($args)) { + $args = array($args); + } + // Perform the callback and send the response if (count($args) == 1) { // If only one parameter just send that instead of the whole array @@ -198,7 +204,17 @@ function multiCall($methodcalls) { // See http://www.xmlrpc.com/discuss/msgReader$1208 $return = array(); + if (!is_array($methodcalls)) { + return new IXR_Error(-32600, 'server error. invalid xml-rpc. methodcalls is not an array'); + } foreach ($methodcalls as $call) { + if (!is_array($call) || !isset($call['methodName']) || !isset($call['params']) || !is_array($call['params'])) { + $return[] = array( + 'faultCode' => -32602, + 'faultString' => 'server error. invalid method call structure', + ); + continue; + } $method = $call['methodName']; $params = $call['params']; if ($method == 'system.multicall') { diff --git a/tests/phpunit/tests/xmlrpc/basic.php b/tests/phpunit/tests/xmlrpc/basic.php index a56a721a07fc6..5cf924f3f896d 100644 --- a/tests/phpunit/tests/xmlrpc/basic.php +++ b/tests/phpunit/tests/xmlrpc/basic.php @@ -95,6 +95,77 @@ public function test_multicall_invalidates_all_calls_after_invalid_call() { $this->assertArrayHasKey( 'faultCode', $result[2] ); } + /** + * Ensures IXR_Server::call() does not fatal when $args is not an array. + * + * @ticket 65124 + */ + public function test_call_with_non_array_args_does_not_fatal() { + $this->myxmlrpcserver->callbacks = $this->myxmlrpcserver->methods; + + // Passing a string instead of an array must not produce a TypeError on PHP 8+. + $result = $this->myxmlrpcserver->call( 'system.listMethods', 'not-an-array' ); + + // The dispatch may return an IXR_Error or a value, but it must not fatal. + $this->assertNotNull( $result ); + } + + /** + * Ensures system.multicall returns a fault for malformed per-call entries + * rather than triggering a fatal error. + * + * @ticket 65124 + * + * @dataProvider data_malformed_multicall_payloads + * + * @param array $method_calls Method calls payload supplied to multiCall(). + * @param int $expected_index Index in the response expected to contain a fault. + */ + public function test_multicall_rejects_malformed_calls( $method_calls, $expected_index ) { + $this->myxmlrpcserver->callbacks = $this->myxmlrpcserver->methods; + + $result = $this->myxmlrpcserver->multiCall( $method_calls ); + + $this->assertArrayHasKey( 'faultCode', $result[ $expected_index ] ); + $this->assertSame( -32602, $result[ $expected_index ]['faultCode'] ); + } + + public function data_malformed_multicall_payloads() { + return array( + 'params is a string' => array( + array( array( 'methodName' => 'system.listMethods', 'params' => 'evil' ) ), + 0, + ), + 'params is null' => array( + array( array( 'methodName' => 'system.listMethods', 'params' => null ) ), + 0, + ), + 'missing params key' => array( + array( array( 'methodName' => 'system.listMethods' ) ), + 0, + ), + 'call is a scalar' => array( + array( 'just-a-string' ), + 0, + ), + ); + } + + /** + * Ensures system.multicall returns a top-level error when the methodcalls + * payload itself is not an array. + * + * @ticket 65124 + */ + public function test_multicall_with_non_array_methodcalls_returns_error() { + $this->myxmlrpcserver->callbacks = $this->myxmlrpcserver->methods; + + $result = $this->myxmlrpcserver->multiCall( 'not-an-array' ); + + $this->assertInstanceOf( 'IXR_Error', $result ); + $this->assertSame( -32600, $result->code ); + } + /** * @ticket 36586 */ From a8e9d27d35d56a18060567e93495b6ee805baa03 Mon Sep 17 00:00:00 2001 From: TommusRhodus Date: Fri, 15 May 2026 14:30:49 +0100 Subject: [PATCH 2/2] Chore: PHPCS fixes by PHPCBF --- tests/phpunit/tests/xmlrpc/basic.php | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/tests/phpunit/tests/xmlrpc/basic.php b/tests/phpunit/tests/xmlrpc/basic.php index 5cf924f3f896d..c0cbab2dce9b2 100644 --- a/tests/phpunit/tests/xmlrpc/basic.php +++ b/tests/phpunit/tests/xmlrpc/basic.php @@ -132,19 +132,31 @@ public function test_multicall_rejects_malformed_calls( $method_calls, $expected public function data_malformed_multicall_payloads() { return array( - 'params is a string' => array( - array( array( 'methodName' => 'system.listMethods', 'params' => 'evil' ) ), + 'params is a string' => array( + array( + array( + 'methodName' => 'system.listMethods', + 'params' => 'evil', + ), + ), 0, ), - 'params is null' => array( - array( array( 'methodName' => 'system.listMethods', 'params' => null ) ), + 'params is null' => array( + array( + array( + 'methodName' => 'system.listMethods', + 'params' => null, + ), + ), 0, ), - 'missing params key' => array( - array( array( 'methodName' => 'system.listMethods' ) ), + 'missing params key' => array( + array( + array( 'methodName' => 'system.listMethods' ), + ), 0, ), - 'call is a scalar' => array( + 'call is a scalar' => array( array( 'just-a-string' ), 0, ),