From 2ad0b5cf052eba0c3c16fb0800fafe74412e8629 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 10 Sep 2025 21:28:23 +0200 Subject: [PATCH] Fix GH-19792: SCCP causes UAF for return value if both warning and exception are triggered If an exception _and_ a warning (or deprecation) is emitted, then the result is destroyed twice. Use an `else if` to prevent this. This is tested via zend_test because the deprecation that triggered the original reproducer may disappear in the future. Closes GH-19793. --- NEWS | 2 ++ Zend/Optimizer/sccp.c | 4 +--- ext/opcache/tests/opt/gh19792.phpt | 27 +++++++++++++++++++++++++++ ext/zend_test/test.c | 9 +++++++++ ext/zend_test/test.stub.php | 3 +++ ext/zend_test/test_arginfo.h | 6 +++++- 6 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 ext/opcache/tests/opt/gh19792.phpt diff --git a/NEWS b/NEWS index 1af55dd4c84..9f97a6985f9 100644 --- a/NEWS +++ b/NEWS @@ -6,6 +6,8 @@ PHP NEWS . Fixed bug GH-19765 (object_properties_load() bypasses readonly property checks). (timwolla) . Fixed hard_timeout with --enable-zend-max-execution-timers. (Appla) + . Fixed bug GH-19792 (SCCP causes UAF for return value if both warning and + exception are triggered). (nielsdos) - Standard: . Fixed bug GH-12265 (Cloning an object breaks serialization recursion). diff --git a/Zend/Optimizer/sccp.c b/Zend/Optimizer/sccp.c index d5486803e19..4b7a77abb09 100644 --- a/Zend/Optimizer/sccp.c +++ b/Zend/Optimizer/sccp.c @@ -842,9 +842,7 @@ static inline zend_result ct_eval_func_call( zval_ptr_dtor(result); zend_clear_exception(); retval = FAILURE; - } - - if (EG(capture_warnings_during_sccp) > 1) { + } else if (EG(capture_warnings_during_sccp) > 1) { zval_ptr_dtor(result); retval = FAILURE; } diff --git a/ext/opcache/tests/opt/gh19792.phpt b/ext/opcache/tests/opt/gh19792.phpt new file mode 100644 index 00000000000..edd805ca57a --- /dev/null +++ b/ext/opcache/tests/opt/gh19792.phpt @@ -0,0 +1,27 @@ +--TEST-- +GH-19792 (SCCP causes UAF for return value if both warning and exception are triggered) +--EXTENSIONS-- +opcache +zend_test +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +--FILE-- +getMessage(), "\n"; +} + +?> +--EXPECTF-- +Warning: a warning in %s on line %d +an exception diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index e7bb97cb44a..67150d47b04 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -1528,3 +1528,12 @@ static PHP_FUNCTION(zend_test_gh18756) zend_mm_gc(heap); zend_mm_shutdown(heap, true, false); } + +static PHP_FUNCTION(zend_test_gh19792) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + RETVAL_STRING("this is a non-interned string"); + zend_error(E_WARNING, "a warning"); + zend_throw_error(NULL, "an exception"); +} diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index f9cb93b5a1c..290f55976d1 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -264,6 +264,9 @@ function zend_test_override_libxml_global_state(): void {} function zend_test_log_err_debug(string $str): void {} function zend_test_gh18756(): void {} + + /** @compile-time-eval */ + function zend_test_gh19792(): void {} } namespace ZendTestNS { diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index c7e3df5c58d..cccd4608d98 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 2f161861ab09b6b5b594dc2db7c2c9df49d76aa7 */ + * Stub hash: fa769dbbf7ba1d10f0141fed6a45fc06a84cd94c */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -164,6 +164,8 @@ ZEND_END_ARG_INFO() #define arginfo_zend_test_gh18756 arginfo_zend_test_void_return +#define arginfo_zend_test_gh19792 arginfo_zend_test_void_return + #define arginfo_ZendTestNS2_namespaced_func arginfo_zend_test_is_pcre_bundled #define arginfo_ZendTestNS2_namespaced_deprecated_func arginfo_zend_test_void_return @@ -295,6 +297,7 @@ static ZEND_FUNCTION(zend_test_cast_fread); static ZEND_FUNCTION(zend_test_is_zend_ptr); static ZEND_FUNCTION(zend_test_log_err_debug); static ZEND_FUNCTION(zend_test_gh18756); +static ZEND_FUNCTION(zend_test_gh19792); static ZEND_FUNCTION(ZendTestNS2_namespaced_func); static ZEND_FUNCTION(ZendTestNS2_namespaced_deprecated_func); static ZEND_FUNCTION(ZendTestNS2_ZendSubNS_namespaced_func); @@ -376,6 +379,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(zend_test_is_zend_ptr, arginfo_zend_test_is_zend_ptr) ZEND_FE(zend_test_log_err_debug, arginfo_zend_test_log_err_debug) ZEND_FE(zend_test_gh18756, arginfo_zend_test_gh18756) + ZEND_SUPPORTS_COMPILE_TIME_EVAL_FE(zend_test_gh19792, arginfo_zend_test_gh19792) ZEND_NS_FALIAS("ZendTestNS2", namespaced_func, ZendTestNS2_namespaced_func, arginfo_ZendTestNS2_namespaced_func) ZEND_NS_DEP_FALIAS("ZendTestNS2", namespaced_deprecated_func, ZendTestNS2_namespaced_deprecated_func, arginfo_ZendTestNS2_namespaced_deprecated_func) ZEND_NS_FALIAS("ZendTestNS2", namespaced_aliased_func, zend_test_void_return, arginfo_ZendTestNS2_namespaced_aliased_func)