From eb208b577ae2d811ea2655f7e14ed43a5269f6ef Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 29 May 2024 18:34:24 +0200 Subject: [PATCH] Fix bug GH-11941: soap with session persistence will silently fails when "seession" built as a shared object This adds an optional dependency on the session extension and adds the necessary APIs to make the functionality work with lazy binding. This can be tested by configuring PHP with `--enable-session=shared` and `--enable-soap=shared` and running the test suite, in particular the buggy behaviour can be observed by the existing test `server009.phpt`. --- NEWS | 2 + UPGRADING | 5 ++ UPGRADING.INTERNALS | 6 +++ ext/session/php_session.h | 2 + ext/session/session.c | 13 +++++ ext/soap/config.m4 | 1 + ext/soap/config.w32 | 1 + ext/soap/soap.c | 69 +++++++++++-------------- ext/soap/tests/bugs/gh11941_errors.phpt | 17 ++++++ 9 files changed, 78 insertions(+), 38 deletions(-) create mode 100644 ext/soap/tests/bugs/gh11941_errors.phpt diff --git a/NEWS b/NEWS index 42168212aa523..d7aaf5df0d908 100644 --- a/NEWS +++ b/NEWS @@ -245,6 +245,8 @@ PHP NEWS . Fixed bug #49278 (SoapClient::__getLastResponseHeaders returns NULL if wsdl operation !has output). (nielsdos) . Fixed bug #44383 (PHP DateTime not converted to xsd:datetime). (nielsdos) + . Fixed bug GH-11941 (soap with session persistence will silently fail when + "session" built as a shared object). (nielsdos) - Sockets: . Removed the deprecated inet_ntoa call support. (David Carlier) diff --git a/UPGRADING b/UPGRADING index ab17035d54adf..d9d3f03b9ec1a 100644 --- a/UPGRADING +++ b/UPGRADING @@ -145,6 +145,10 @@ PHP 8.4 UPGRADE NOTES . SoapClient::$typemap is now an array rather than a resource. Checks using is_resource() (i.e. is_resource($client->typemap)) should be replaced with checks for null (i.e. $client->typemap !== null). + . The SOAP extension gained an optional dependency on the session extension. + If you build PHP without the session extension and with --enable-rtld-now, + you will experience errors on startup if you also use the SOAP extension. + To solve this, either don't use rtld-now or load the session extension. - SPL: . Out of bounds accesses in SplFixedArray now throw an exception of type @@ -300,6 +304,7 @@ PHP 8.4 UPGRADE NOTES . Instances of DateTimeInterface that are passed to xsd:datetime or similar elements are now serialized as such instead of being serialized as an empty string. + . Session persistence now works with a shared session module. - XSL: . It is now possible to use parameters that contain both single and double diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index f3fece4ed00bb..8f43e83933f6f 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -253,6 +253,12 @@ PHP 8.4 INTERNALS UPGRADE NOTES Moreover, providing it with a binary safe string is the responsibility of the caller now. + h. ext/session + - Added the php_get_session_status() API to get the session status, which is + equivalent to reading PS(session_status) but works with shared objects too. + - Added the php_get_session_var_str() API to set a session variable without + needing to create a zend_string. + ======================== 4. OpCode changes ======================== diff --git a/ext/session/php_session.h b/ext/session/php_session.h index c9611e6159709..3b728090898c6 100644 --- a/ext/session/php_session.h +++ b/ext/session/php_session.h @@ -256,6 +256,7 @@ PHPAPI zend_result php_session_destroy(void); PHPAPI void php_add_session_var(zend_string *name); PHPAPI zval *php_set_session_var(zend_string *name, zval *state_val, php_unserialize_data_t *var_hash); PHPAPI zval *php_get_session_var(zend_string *name); +PHPAPI zval* php_get_session_var_str(const char *name, size_t name_len); PHPAPI zend_result php_session_register_module(const ps_module *); @@ -265,6 +266,7 @@ PHPAPI zend_result php_session_register_serializer(const char *name, PHPAPI zend_result php_session_start(void); PHPAPI zend_result php_session_flush(int write); +PHPAPI php_session_status php_get_session_status(void); PHPAPI const ps_module *_php_find_ps_module(const char *name); PHPAPI const ps_serializer *_php_find_ps_serializer(const char *name); diff --git a/ext/session/session.c b/ext/session/session.c index f3296e37f271a..4a369898a8eb2 100644 --- a/ext/session/session.c +++ b/ext/session/session.c @@ -225,6 +225,14 @@ PHPAPI zval* php_get_session_var(zend_string *name) /* {{{ */ } /* }}} */ +PHPAPI zval* php_get_session_var_str(const char *name, size_t name_len) +{ + IF_SESSION_VARS() { + return zend_hash_str_find(Z_ARRVAL_P(Z_REFVAL(PS(http_session_vars))), name, name_len); + } + return NULL; +} + static void php_session_track_init(void) /* {{{ */ { zval session_vars; @@ -1632,6 +1640,11 @@ PHPAPI zend_result php_session_flush(int write) /* {{{ */ } /* }}} */ +PHPAPI php_session_status php_get_session_status(void) +{ + return PS(session_status); +} + static zend_result php_session_abort(void) /* {{{ */ { if (PS(session_status) == php_session_active) { diff --git a/ext/soap/config.m4 b/ext/soap/config.m4 index 879035f33417b..a83424ef39702 100644 --- a/ext/soap/config.m4 +++ b/ext/soap/config.m4 @@ -10,4 +10,5 @@ if test "$PHP_SOAP" != "no"; then PHP_SUBST(SOAP_SHARED_LIBADD) ]) PHP_ADD_EXTENSION_DEP(soap, libxml) + PHP_ADD_EXTENSION_DEP(soap, session, true) fi diff --git a/ext/soap/config.w32 b/ext/soap/config.w32 index fa5c76588d004..31e1f0da39ce0 100644 --- a/ext/soap/config.w32 +++ b/ext/soap/config.w32 @@ -10,6 +10,7 @@ if (PHP_SOAP != "no") { ) { EXTENSION('soap', 'soap.c php_encoding.c php_http.c php_packet_soap.c php_schema.c php_sdl.c php_xml.c', null, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); AC_DEFINE('HAVE_SOAP', 1, "SOAP support"); + ADD_EXTENSION_DEP('soap', 'session', true); if (!PHP_SOAP_SHARED) { ADD_FLAG('CFLAGS_SOAP', "/D LIBXML_STATIC "); diff --git a/ext/soap/soap.c b/ext/soap/soap.c index b42917f8ad12c..09b9f8f5fba17 100644 --- a/ext/soap/soap.c +++ b/ext/soap/soap.c @@ -20,14 +20,19 @@ #include "config.h" #endif #include "php_soap.h" -#if defined(HAVE_PHP_SESSION) && !defined(COMPILE_DL_SESSION) #include "ext/session/php_session.h" -#endif #include "soap_arginfo.h" #include "zend_exceptions.h" #include "zend_interfaces.h" #include "ext/standard/php_incomplete_class.h" +/* We only have session support if PHP was configured with session support + * or if the session module could be loaded dynamically, which will only + * work if soap is loaded dynamically as well. */ +#if defined(HAVE_PHP_SESSION) || defined(COMPILE_DL_SOAP) +# define SOAP_HAS_SESSION_SUPPORT +#endif + typedef struct _soapHeader { sdlFunctionPtr function; zval function_name; @@ -298,6 +303,7 @@ PHP_MINFO_FUNCTION(soap); static const zend_module_dep soap_deps[] = { ZEND_MOD_REQUIRED("date") ZEND_MOD_REQUIRED("libxml") + ZEND_MOD_OPTIONAL("session") ZEND_MOD_END }; @@ -995,6 +1001,12 @@ PHP_METHOD(SoapServer, setPersistence) if (service->type == SOAP_CLASS) { if (value == SOAP_PERSISTENCE_SESSION || value == SOAP_PERSISTENCE_REQUEST) { + if (value == SOAP_PERSISTENCE_SESSION && !zend_hash_str_exists(&module_registry, "session", sizeof("session")-1)) { + SOAP_SERVER_END_CODE(); + zend_throw_error(NULL, "SoapServer::setPersistence(): Session persistence cannot be enabled because the session module is not enabled"); + RETURN_THROWS(); + } + service->soap_class.persistence = value; } else { zend_argument_value_error( @@ -1406,22 +1418,18 @@ PHP_METHOD(SoapServer, handle) soap_obj = &service->soap_object; function_table = &((Z_OBJCE_P(soap_obj))->function_table); } else if (service->type == SOAP_CLASS) { -#if defined(HAVE_PHP_SESSION) && !defined(COMPILE_DL_SESSION) - /* If persistent then set soap_obj from from the previous created session (if available) */ + /* If persistent then set soap_obj from the previous created session (if available) */ +#ifdef SOAP_HAS_SESSION_SUPPORT if (service->soap_class.persistence == SOAP_PERSISTENCE_SESSION) { - zval *session_vars, *tmp_soap_p; - - if (PS(session_status) != php_session_active && - PS(session_status) != php_session_disabled) { + php_session_status session_status = php_get_session_status(); + if (session_status != php_session_active && + session_status != php_session_disabled) { php_session_start(); } /* Find the soap object and assign */ - session_vars = &PS(http_session_vars); - 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) { + zval *tmp_soap_p = php_get_session_var_str("_bogus_session_name", sizeof("_bogus_session_name")-1); + if (tmp_soap_p != NULL && 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) { @@ -1431,9 +1439,9 @@ PHP_METHOD(SoapServer, handle) } } #endif + /* If new session or something weird happned */ if (soap_obj == NULL) { - object_init_ex(&tmp_soap, service->soap_class.ce); /* Call constructor */ @@ -1448,25 +1456,23 @@ PHP_METHOD(SoapServer, handle) goto fail; } } -#if defined(HAVE_PHP_SESSION) && !defined(COMPILE_DL_SESSION) + /* If session then update session hash with new object */ +#ifdef SOAP_HAS_SESSION_SUPPORT if (service->soap_class.persistence == SOAP_PERSISTENCE_SESSION) { - zval *session_vars = &PS(http_session_vars), *tmp_soap_p; - - ZVAL_DEREF(session_vars); - if (Z_TYPE_P(session_vars) == IS_ARRAY && - (tmp_soap_p = zend_hash_str_update(Z_ARRVAL_P(session_vars), "_bogus_session_name", sizeof("_bogus_session_name")-1, &tmp_soap)) != NULL) { + zend_string *session_var_name = ZSTR_INIT_LITERAL("_bogus_session_name", false); + zval *tmp_soap_p = php_set_session_var(session_var_name, &tmp_soap, NULL); + if (tmp_soap_p != NULL) { soap_obj = tmp_soap_p; } else { soap_obj = &tmp_soap; } - } else { + zend_string_release_ex(session_var_name, false); + } else +#endif + { soap_obj = &tmp_soap; } -#else - soap_obj = &tmp_soap; -#endif - } function_table = &((Z_OBJCE_P(soap_obj))->function_table); } else { @@ -1534,15 +1540,10 @@ PHP_METHOD(SoapServer, handle) if (service->type == SOAP_CLASS || service->type == SOAP_OBJECT) { call_status = call_user_function(NULL, soap_obj, &function_name, &retval, num_params, params); if (service->type == SOAP_CLASS) { -#if defined(HAVE_PHP_SESSION) && !defined(COMPILE_DL_SESSION) if (service->soap_class.persistence != SOAP_PERSISTENCE_SESSION) { zval_ptr_dtor(soap_obj); soap_obj = NULL; } -#else - zval_ptr_dtor(soap_obj); - soap_obj = NULL; -#endif } } else { call_status = call_user_function(EG(function_table), NULL, &function_name, &retval, num_params, params); @@ -1557,11 +1558,7 @@ PHP_METHOD(SoapServer, handle) php_output_discard(); _soap_server_exception(service, function, ZEND_THIS); if (service->type == SOAP_CLASS) { -#if defined(HAVE_PHP_SESSION) && !defined(COMPILE_DL_SESSION) if (soap_obj && service->soap_class.persistence != SOAP_PERSISTENCE_SESSION) { -#else - if (soap_obj) { -#endif zval_ptr_dtor(soap_obj); } } @@ -1597,11 +1594,7 @@ PHP_METHOD(SoapServer, handle) php_output_discard(); _soap_server_exception(service, function, ZEND_THIS); if (service->type == SOAP_CLASS) { -#if defined(HAVE_PHP_SESSION) && !defined(COMPILE_DL_SESSION) if (soap_obj && service->soap_class.persistence != SOAP_PERSISTENCE_SESSION) { -#else - if (soap_obj) { -#endif zval_ptr_dtor(soap_obj); } } diff --git a/ext/soap/tests/bugs/gh11941_errors.phpt b/ext/soap/tests/bugs/gh11941_errors.phpt new file mode 100644 index 0000000000000..6d7bb18193674 --- /dev/null +++ b/ext/soap/tests/bugs/gh11941_errors.phpt @@ -0,0 +1,17 @@ +--TEST-- +GH-11941 (soap with session persistence will silently fail when "session" built as a shared object) +--EXTENSIONS-- +soap +--SKIPIF-- + +--FILE-- +"http://testuri.org")); +$server->setPersistence(SOAP_PERSISTENCE_SESSION); +?> +--EXPECTF-- +%aUncaught Error: SoapServer::setPersistence(): Persistence cannot be set when the SOAP server is used in function mode in %s:%d +%a