From 943720bb2ad1bcbb961e419c5aaee3fc57082e8e Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Wed, 13 May 2026 14:54:43 -0400 Subject: [PATCH 1/2] ext/openssl: Throw on repeat Openssl\Session::__unserialize Calling __unserialize() on an Openssl\Session that already holds an SSL_SESSION overwrote obj->session without freeing the prior one, leaking an SSL_SESSION (OpenSSL's allocator, bypasses memory_limit). There is no legitimate use for re-running __unserialize() on a live instance, so reject it: throw when obj->session is already populated, before any overwrite. Closes GH-22040 --- ext/openssl/openssl.c | 7 +- .../tests/session_unserialize_repeat.phpt | 84 +++++++++++++++++++ 2 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 ext/openssl/tests/session_unserialize_repeat.phpt diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index e7f29c6a354b..2db966c1661e 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -504,6 +504,12 @@ PHP_METHOD(Openssl_Session, __unserialize) Z_PARAM_ARRAY_HT(data) ZEND_PARSE_PARAMETERS_END(); + php_openssl_session_object *obj = Z_OPENSSL_SESSION_P(ZEND_THIS); + if (obj->session != NULL) { + zend_throw_error(NULL, "Cannot call Openssl\\Session::__unserialize() on an already-initialized session"); + RETURN_THROWS(); + } + zval *pem_zv = zend_hash_str_find(data, ZEND_STRL("pem")); if (!pem_zv || Z_TYPE_P(pem_zv) != IS_STRING) { zend_throw_exception(php_openssl_exception_ce, "Invalid serialization data", 0); @@ -524,7 +530,6 @@ PHP_METHOD(Openssl_Session, __unserialize) RETURN_THROWS(); } - php_openssl_session_object *obj = Z_OPENSSL_SESSION_P(ZEND_THIS); obj->session = session; /* Populate id property */ diff --git a/ext/openssl/tests/session_unserialize_repeat.phpt b/ext/openssl/tests/session_unserialize_repeat.phpt new file mode 100644 index 000000000000..b1e33c2d03bd --- /dev/null +++ b/ext/openssl/tests/session_unserialize_repeat.phpt @@ -0,0 +1,84 @@ +--TEST-- +Openssl\Session::__unserialize throws on a repeat call +--EXTENSIONS-- +openssl +--SKIPIF-- + +--FILE-- + [ + 'local_cert' => '%s', + ]]); + + $server = stream_socket_server('tls://127.0.0.1:0', $errno, $errstr, + STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $serverCtx); + phpt_notify_server_start($server); + + $client = @stream_socket_accept($server, 10); + if ($client) { + fwrite($client, "ok\n"); + fclose($client); + } +CODE; +$serverCode = sprintf($serverCode, $certFile); + +$clientCode = <<<'CODE' + $captured = null; + $ctx = stream_context_create(['ssl' => [ + 'verify_peer' => false, + 'verify_peer_name' => false, + 'session_new_cb' => function ($s, $session) use (&$captured) { + $captured = $session; + return true; + }, + 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT, + ]]); + + $c = stream_socket_client("tls://{{ ADDR }}", $errno, $errstr, 10, + STREAM_CLIENT_CONNECT, $ctx); + if (!$c) { + echo "connect failed: $errstr\n"; + return; + } + fread($c, 8); + fclose($c); + + if (!$captured instanceof Openssl\Session) { + echo "no session captured\n"; + return; + } + + $payload = $captured->__serialize(); + $sess = unserialize(serialize($captured)); + echo "first: " . (is_object($sess) ? get_class($sess) : "fail") . "\n"; + + try { + $sess->__unserialize($payload); + echo "second: no throw\n"; + } catch (Error $e) { + echo "second: " . $e->getMessage() . "\n"; + } + + echo "alive\n"; +CODE; + +include 'CertificateGenerator.inc'; +$certificateGenerator = new CertificateGenerator(); +$certificateGenerator->saveNewCertAsFileWithKey('session-unserialize-repeat', $certFile); + +include 'ServerClientTestCase.inc'; +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--CLEAN-- + +--EXPECTF-- +first: Openssl\Session +second: Cannot call Openssl\Session::__unserialize() on an already-initialized session +alive From d8e741838a534d30e9e329c25d90592d988398a7 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Tue, 16 Jun 2026 20:53:37 +0800 Subject: [PATCH 2/2] ext/intl: Fix argument positions in transliterator_transliterate() error message Fix a missed case in #22044, that argument positions in transliterator_transliterate() error message could be wrong. Closes #22331 --- NEWS | 3 +- .../transliterator_transliterate_error.phpt | 36 ++++++++++++++++--- .../transliterator/transliterator_methods.c | 10 +++--- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/NEWS b/NEWS index 3c7f104a24d3..05bdd1b35ff7 100644 --- a/NEWS +++ b/NEWS @@ -33,7 +33,8 @@ PHP NEWS - Intl: . Fix incorrect argument positions for uninitialized calendar arguments in - IntlCalendar::equals(), ::before(), ::after(), and ::isEquivalentTo(). + IntlCalendar::equals(), ::before(), ::after(), and ::isEquivalentTo(), and + for invalid start/end arguments in transliterator_transliterate(). (Weilin Du) . Fixed IntlTimeZone::getDisplayName() to synchronize object error state for invalid display types. (Weilin Du) diff --git a/ext/intl/tests/transliterator_transliterate_error.phpt b/ext/intl/tests/transliterator_transliterate_error.phpt index 4a354465bf21..aefdd72b4009 100644 --- a/ext/intl/tests/transliterator_transliterate_error.phpt +++ b/ext/intl/tests/transliterator_transliterate_error.phpt @@ -9,14 +9,36 @@ ini_set("intl.error_level", E_WARNING); $tr = Transliterator::create("latin"); +function dump_value_error(callable $callback): void { + try { + $callback(); + } catch (ValueError $exception) { + echo $exception->getMessage() . "\n"; + } +} + //Arguments var_dump(transliterator_transliterate($tr,"str",7)); -try { +dump_value_error(function() use ($tr) { transliterator_transliterate($tr,"str",7,6); -} catch (ValueError $exception) { - echo $exception->getMessage() . "\n"; -} +}); + +dump_value_error(function() use ($tr) { + transliterator_transliterate($tr,"str", 0, -2); +}); + +dump_value_error(function() use ($tr) { + transliterator_transliterate($tr,"str", -1); +}); + +dump_value_error(function() { + transliterator_transliterate("latin","str", -1); +}); + +dump_value_error(function() use ($tr) { + $tr->transliterate("str", 7, 6); +}); //bad UTF-8 transliterator_transliterate($tr, "\x80\x03"); @@ -26,7 +48,11 @@ echo "Done.\n"; --EXPECTF-- Warning: transliterator_transliterate(): transliterator_transliterate: Neither "start" nor the "end" arguments can exceed the number of UTF-16 code units (in this case, 3) in %s on line %d bool(false) -transliterator_transliterate(): Argument #2 ($string) must be less than or equal to argument #3 ($end) +transliterator_transliterate(): Argument #3 ($start) must be less than or equal to argument #4 ($end) +transliterator_transliterate(): Argument #4 ($end) must be greater than or equal to -1 +transliterator_transliterate(): Argument #3 ($start) must be greater than or equal to 0 +transliterator_transliterate(): Argument #3 ($start) must be greater than or equal to 0 +Transliterator::transliterate(): Argument #2 ($start) must be less than or equal to argument #3 ($end) Warning: transliterator_transliterate(): String conversion of string to UTF-16 failed in %s on line %d Done. diff --git a/ext/intl/transliterator/transliterator_methods.c b/ext/intl/transliterator/transliterator_methods.c index 81e83414a4f5..5bdfc567a572 100644 --- a/ext/intl/transliterator/transliterator_methods.c +++ b/ext/intl/transliterator/transliterator_methods.c @@ -271,14 +271,16 @@ PHP_FUNCTION( transliterator_transliterate ) zend_long start = 0, limit = -1; int success = 0; + bool is_method; zval tmp_object; TRANSLITERATOR_METHOD_INIT_VARS; object = getThis(); + is_method = object != NULL; ZVAL_UNDEF(&tmp_object); - if (object == NULL) { + if (!is_method) { /* in non-OOP version, accept both a transliterator and a string */ zend_string *arg1_str; zend_object *arg1_obj; @@ -315,17 +317,17 @@ PHP_FUNCTION( transliterator_transliterate ) } if (limit < -1) { - zend_argument_value_error(object ? 3 : 4, "must be greater than or equal to -1"); + zend_argument_value_error(is_method ? 3 : 4, "must be greater than or equal to -1"); goto cleanup_object; } if (start < 0) { - zend_argument_value_error(object ? 2 : 3, "must be greater than or equal to 0"); + zend_argument_value_error(is_method ? 2 : 3, "must be greater than or equal to 0"); goto cleanup_object; } if (limit != -1 && start > limit) { - zend_argument_value_error(object ? 2 : 3, "must be less than or equal to argument #%d ($end)", object ? 3 : 4); + zend_argument_value_error(is_method ? 2 : 3, "must be less than or equal to argument #%d ($end)", is_method ? 3 : 4); goto cleanup_object; }