diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 854dd2979f75..20f119f8dc6f 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -20,6 +20,11 @@ #endif #include "php.h" + +#ifdef __APPLE__ +# include +#endif + #include "php_ini.h" #include "ext/standard/info.h" #include "php_test.h" @@ -1880,3 +1885,42 @@ static PHP_FUNCTION(zend_test_gh19792) zend_error(E_WARNING, "a warning"); zend_throw_error(NULL, "an exception"); } + +#ifdef __APPLE__ +static char **zend_test_argv_at_register; +static bool zend_test_argv_atexit_registered; +static bool zend_test_argv_atexit_enabled; + +static void zend_test_argv_atexit_handler(void) +{ + char **argv; + + /* arginfo_zpp_mismatch calls every function, so only report when the test + * asks for it. */ + if (!zend_test_argv_atexit_enabled) { + return; + } + + argv = *_NSGetArgv(); + + printf("argv_changed=%d\n", argv != zend_test_argv_at_register); + printf("argv_valid=%d\n", argv != NULL && argv[0] != NULL && strlen(argv[0]) > 0); + fflush(stdout); +} +#endif + +static PHP_FUNCTION(zend_test_register_argv_atexit) +{ + ZEND_PARSE_PARAMETERS_NONE(); + +#ifdef __APPLE__ + zend_test_argv_atexit_enabled = getenv("ZEND_TEST_ARGV_ATEXIT") != NULL; + zend_test_argv_at_register = *_NSGetArgv(); + + /* A single handler keeps --repeat from printing it more than once. */ + if (!zend_test_argv_atexit_registered) { + zend_test_argv_atexit_registered = true; + atexit(zend_test_argv_atexit_handler); + } +#endif +} diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index 36d1be17a177..9321e8d0aa14 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -343,6 +343,8 @@ function zend_test_handle_post_data(mixed $stream, string $content_type, int $co function zend_new_singleton_array(): array {} function zend_test_set_local_var(string $name, mixed $value): bool {} + + function zend_test_register_argv_atexit(): void {} } namespace ZendTestNS { diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index e2a388659ecd..02bbc9042fbc 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: f25b9c451734f903ba385f9fbf05fb198ecd83af */ + * Stub hash: 1faa92dc6a3d6a61deaed0d65094c751746d36e8 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -205,6 +205,8 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_set_local_var, 0, 2, _ ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) ZEND_END_ARG_INFO() +#define arginfo_zend_test_register_argv_atexit 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 @@ -349,6 +351,7 @@ static ZEND_FUNCTION(zend_test_gh19792); static ZEND_FUNCTION(zend_test_handle_post_data); static ZEND_FUNCTION(zend_new_singleton_array); static ZEND_FUNCTION(zend_test_set_local_var); +static ZEND_FUNCTION(zend_test_register_argv_atexit); static ZEND_FUNCTION(ZendTestNS2_namespaced_func); static ZEND_FUNCTION(ZendTestNS2_namespaced_deprecated_func); static ZEND_FUNCTION(ZendTestNS2_ZendSubNS_namespaced_func); @@ -472,6 +475,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(zend_test_handle_post_data, arginfo_zend_test_handle_post_data) ZEND_FE(zend_new_singleton_array, arginfo_zend_new_singleton_array) ZEND_FE(zend_test_set_local_var, arginfo_zend_test_set_local_var) + ZEND_FE(zend_test_register_argv_atexit, arginfo_zend_test_register_argv_atexit) #if (PHP_VERSION_ID >= 80400) ZEND_RAW_FENTRY(ZEND_NS_NAME("ZendTestNS2", "namespaced_func"), zif_ZendTestNS2_namespaced_func, arginfo_ZendTestNS2_namespaced_func, 0, NULL, NULL) #else diff --git a/ext/zend_test/tests/gh25203.phpt b/ext/zend_test/tests/gh25203.phpt new file mode 100644 index 000000000000..0fb818bb1a0f --- /dev/null +++ b/ext/zend_test/tests/gh25203.phpt @@ -0,0 +1,26 @@ +--TEST-- +The original argv is restored before atexit() handlers run +--EXTENSIONS-- +zend_test +--SKIPIF-- + +--ENV-- +ZEND_DONT_UNLOAD_MODULES=1 +ZEND_TEST_ARGV_ATEXIT=1 +--FILE-- + +--EXPECT-- +argv_changed=1 +argv_valid=1 diff --git a/sapi/cli/ps_title.c b/sapi/cli/ps_title.c index b6f0e0400e71..1032246545d3 100644 --- a/sapi/cli/ps_title.c +++ b/sapi/cli/ps_title.c @@ -437,6 +437,10 @@ void cleanup_ps_args(char **argv) #ifndef PS_USE_NONE if (save_argv) { +#if defined(__APPLE__) + char **saved_argv = save_argv; +#endif + save_argv = NULL; save_argc = 0; @@ -456,6 +460,16 @@ void cleanup_ps_args(char **argv) #if defined(PS_USE_CHANGE_ARGV) || defined(PS_USE_CLOBBER_ARGV) { int i; + +#if defined(__APPLE__) + /* + * Apple's Cyrus SASL reads the process arguments through + * _NSGetArgv() from an atexit() handler, which still points to + * the copy freed below. Restore the original array first. + */ + *_NSGetArgv() = saved_argv; +#endif + for (i=0; argv[i] != NULL; i++) free(argv[i]); free(argv);