Commit 3dab5859122 for php.net
commit 3dab58591220c6b4da6c10755f47f11bde74a2b4
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date: Tue Apr 7 11:50:39 2026 -0400
Fix GH-16811: Crash in zend_test observer on runtime observe_function_names change (GH-21635)
OnUpdateCommaList called zend_observer_remove/add_begin_handler without
checking whether observer data was initialized. This null-dereferenced
when the function had never been called (no runtime cache), and hit
ZEND_UNREACHABLE() when observe_all had already installed the same
handler.
Guard both the remove and add blocks with runtime cache checks. Remove
existing handlers before re-adding to prevent slot overflow from
duplicates.
Fixes GH-16811
diff --git a/ext/zend_test/observer.c b/ext/zend_test/observer.c
index 85c7d82da0e..e348987359b 100644
--- a/ext/zend_test/observer.c
+++ b/ext/zend_test/observer.c
@@ -334,7 +334,8 @@ static ZEND_INI_MH(zend_test_observer_OnUpdateCommaList)
}
if (stage != PHP_INI_STAGE_STARTUP && stage != PHP_INI_STAGE_ACTIVATE && stage != PHP_INI_STAGE_DEACTIVATE && stage != PHP_INI_STAGE_SHUTDOWN) {
ZEND_HASH_FOREACH_STR_KEY(*p, funcname) {
- if ((func = zend_hash_find_ptr(EG(function_table), funcname))) {
+ if ((func = zend_hash_find_ptr(EG(function_table), funcname))
+ && RUN_TIME_CACHE(&func->common)) {
void *old_handler;
zend_observer_remove_begin_handler(func, observer_begin, (zend_observer_fcall_begin_handler *)&old_handler);
zend_observer_remove_end_handler(func, observer_end, (zend_observer_fcall_end_handler *)&old_handler);
@@ -357,7 +358,11 @@ static ZEND_INI_MH(zend_test_observer_OnUpdateCommaList)
zend_string_release(str);
if (stage != PHP_INI_STAGE_STARTUP && stage != PHP_INI_STAGE_ACTIVATE && stage != PHP_INI_STAGE_DEACTIVATE && stage != PHP_INI_STAGE_SHUTDOWN) {
ZEND_HASH_FOREACH_STR_KEY(*p, funcname) {
- if ((func = zend_hash_find_ptr(EG(function_table), funcname))) {
+ if ((func = zend_hash_find_ptr(EG(function_table), funcname))
+ && RUN_TIME_CACHE(&func->common) && *ZEND_OBSERVER_DATA(func)) {
+ void *old_handler;
+ zend_observer_remove_begin_handler(func, observer_begin, (zend_observer_fcall_begin_handler *)&old_handler);
+ zend_observer_remove_end_handler(func, observer_end, (zend_observer_fcall_end_handler *)&old_handler);
zend_observer_add_begin_handler(func, observer_begin);
zend_observer_add_end_handler(func, observer_end);
}
diff --git a/ext/zend_test/tests/gh16811.phpt b/ext/zend_test/tests/gh16811.phpt
new file mode 100644
index 00000000000..1b2b8ece9aa
--- /dev/null
+++ b/ext/zend_test/tests/gh16811.phpt
@@ -0,0 +1,18 @@
+--TEST--
+GH-16811 (Segmentation fault in zend observer)
+--EXTENSIONS--
+zend_test
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.show_output=1
+zend_test.observer.observe_function_names=a,d
+--FILE--
+<?php
+var_dump(ini_set("zend_test.observer.observe_function_names", "bar"));
+function d() {}
+?>
+--EXPECTF--
+<!-- init '%s' -->
+<!-- init ini_set() -->
+<!-- init var_dump() -->
+string(3) "a,d"
diff --git a/ext/zend_test/tests/gh16811_observe_all.phpt b/ext/zend_test/tests/gh16811_observe_all.phpt
new file mode 100644
index 00000000000..466aa0c6d4a
--- /dev/null
+++ b/ext/zend_test/tests/gh16811_observe_all.phpt
@@ -0,0 +1,17 @@
+--TEST--
+GH-16811 (Assertion failure adding duplicate observer handler)
+--EXTENSIONS--
+zend_test
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+zend_test.observer.show_output=0
+--FILE--
+<?php
+function foo() {}
+foo();
+ini_set("zend_test.observer.observe_function_names", "foo");
+echo "Done\n";
+?>
+--EXPECT--
+Done