Mitigate #51561: SoapServer with a extented class and using sessions, lost the setPersistence()

The problem is that in the testcase, the session is started before the
parent class is loaded. This causes an incomplete class in the session
storage. Then in the soap code the check
`Z_OBJCE_P(tmp_soap_p) == service->soap_class.ce` fails because it is
the incomplete class. It is a silent failure.

We cannot fix this easily. But we should let the user know something is
wrong, because it leaves them confused otherwise. So emit an error to
let them know and suggest a fix.

Closes GH-12540.
This commit is contained in:
Niels Dossche 2023-10-27 20:54:14 +02:00
parent e71522419f
commit 53218b1a32
4 changed files with 61 additions and 3 deletions

2
NEWS
View File

@ -33,6 +33,8 @@ SimpleXML:
SOAP:
. Add support for clark notation for namespaces in class map. (lxShaDoWxl)
. Mitigate #51561 (SoapServer with a extented class and using sessions,
lost the setPersistence()). (nielsdos)
Standard:
. Implement GH-12188 (Indication for the int size in phpinfo()). (timwolla)

View File

@ -26,6 +26,7 @@
#include "soap_arginfo.h"
#include "zend_exceptions.h"
#include "zend_interfaces.h"
#include "ext/standard/php_incomplete_class.h"
static int le_sdl = 0;
@ -1330,9 +1331,13 @@ PHP_METHOD(SoapServer, handle)
ZVAL_DEREF(session_vars);
if (Z_TYPE_P(session_vars) == IS_ARRAY &&
(tmp_soap_p = zend_hash_str_find(Z_ARRVAL_P(session_vars), "_bogus_session_name", sizeof("_bogus_session_name")-1)) != NULL &&
Z_TYPE_P(tmp_soap_p) == IS_OBJECT &&
Z_OBJCE_P(tmp_soap_p) == service->soap_class.ce) {
soap_obj = tmp_soap_p;
Z_TYPE_P(tmp_soap_p) == IS_OBJECT) {
if (EXPECTED(Z_OBJCE_P(tmp_soap_p) == service->soap_class.ce)) {
soap_obj = tmp_soap_p;
} else if (Z_OBJCE_P(tmp_soap_p) == php_ce_incomplete_class) {
/* See #51561, communicate limitation to user */
soap_server_fault("Server", "SoapServer class was deserialized from the session prior to loading the class passed to SoapServer::setClass(). Start the session after loading all classes to resolve this issue.", NULL, NULL, NULL);
}
}
}
#endif

View File

@ -0,0 +1,2 @@
<?php
class Server2 {}

View File

@ -0,0 +1,49 @@
--TEST--
Bug #51561 (SoapServer with a extended class and using sessions, lost the setPersistence())
--EXTENSIONS--
soap
--SKIPIF--
<?php
if (!file_exists(__DIR__ . "/../../../../sapi/cli/tests/php_cli_server.inc")) {
echo "skip sapi/cli/tests/php_cli_server.inc required but not found";
}
?>
--FILE--
<?php
include __DIR__ . "/../../../../sapi/cli/tests/php_cli_server.inc";
$args = ["-d", "extension_dir=" . ini_get("extension_dir"), "-d", "extension=" . (substr(PHP_OS, 0, 3) == "WIN" ? "php_" : "") . "soap." . PHP_SHLIB_SUFFIX];
if (php_ini_loaded_file()) {
// Necessary such that it works from a development directory in which case extension_dir might not be the real extension dir
$args[] = "-c";
$args[] = php_ini_loaded_file();
}
$code = "session_start();" .
"require_once '" . __DIR__ . "/bug51561.inc';" .
<<<'PHP'
class Server extends Server2 {
private $value;
public function setValue($param) { $this->value = $param; }
public function getValue() { return $this->value; }
}
$server = new SoapServer(null, array('uri' => "blablabla.com",'encoding' => "ISO-8859-1",'soap_version' => SOAP_1_2));
$server->setClass("Server");
$server->setPersistence(SOAP_PERSISTENCE_SESSION);
$server->handle();
PHP;
php_cli_server_start($code, null, $args);
$cli = new SoapClient(null, array('location' => "http://".PHP_CLI_SERVER_ADDRESS, 'uri' => "blablabla.com",'encoding' => "ISO-8859-1",'soap_version' => SOAP_1_2));
$cli->setValue(100);
$response = $cli->getValue();
echo "Get = ".$response;
?>
--EXPECTF--
Fatal error: Uncaught SoapFault exception: [env:Receiver] SoapServer class was deserialized from the session prior to loading the class passed to SoapServer::setClass(). Start the session after loading all classes to resolve this issue. in %s:%d
Stack trace:
#0 %s(%d): SoapClient->__call('getValue', Array)
#1 {main}
thrown in %s on line %d