Commit bb0ab98b9d2 for php.net
commit bb0ab98b9d2f415caa35398099cecff8f3247ac3
Author: David Carlier <devnexen@gmail.com>
Date: Sat Aug 8 05:21:01 2026 +0100
Zend: is_callable() wrongly accepts objects with no get_closure handler.
Fix #23121
zend_is_callable_at_frame() tested `get_closure && get_closure(...) == FAILURE`,
so a NULL handler short circuited past the error branch into the success path,
leaving fcc->function_handler NULL. zend_fcc_addref() then asserted and
call_user_func() dereferenced the null zend_function.
Close GH-23123
diff --git a/NEWS b/NEWS
index 8c84bee9f71..447d052e21d 100644
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,8 @@ PHP NEWS
possible. (NickSdot)
. Fixed GH-23083 (SEGV build_trace_args in zend_exceptions.c with
-d error_include_args=On). (David Carlier)
+ . Fixed GH-23121 (is_callable() wrongly accepts objects with no get_closure
+ handler). (David Carlier)
- Curl:
. Improved cURL option validation errors to include the option name.
diff --git a/Zend/tests/gh23121.phpt b/Zend/tests/gh23121.phpt
new file mode 100644
index 00000000000..158794ba04e
--- /dev/null
+++ b/Zend/tests/gh23121.phpt
@@ -0,0 +1,36 @@
+--TEST--
+GH-23121 (Assertion failure in zend_fcc_addref() for an object whose class has no get_closure handler)
+--EXTENSIONS--
+simplexml
+--FILE--
+<?php
+$sxe = new SimpleXMLElement('<root/>');
+
+var_dump(is_callable($sxe));
+
+try {
+ $sxe();
+} catch (Error $e) {
+ echo $e->getMessage(), PHP_EOL;
+}
+
+try {
+ call_user_func($sxe);
+} catch (TypeError $e) {
+ echo $e->getMessage(), PHP_EOL;
+}
+
+try {
+ libxml_set_external_entity_loader($sxe);
+} catch (TypeError $e) {
+ echo $e->getMessage(), PHP_EOL;
+}
+
+var_dump(libxml_get_external_entity_loader());
+?>
+--EXPECT--
+bool(false)
+Object of type SimpleXMLElement is not callable
+call_user_func(): Argument #1 ($callback) must be a valid callback, no array or string given
+libxml_set_external_entity_loader(): Argument #1 ($resolver_function) must be a valid callback or null, no array or string given
+NULL
diff --git a/Zend/zend_API.c b/Zend/zend_API.c
index de3570c5e84..7d407f7e905 100644
--- a/Zend/zend_API.c
+++ b/Zend/zend_API.c
@@ -4240,7 +4240,7 @@ ZEND_API bool zend_is_callable_at_frame(
}
case IS_OBJECT:
- if (Z_OBJ_HANDLER_P(callable, get_closure) && Z_OBJ_HANDLER_P(callable, get_closure)(Z_OBJ_P(callable), &fcc->calling_scope, &fcc->function_handler, &fcc->object, 1) == FAILURE) {
+ if (!Z_OBJ_HANDLER_P(callable, get_closure) || Z_OBJ_HANDLER_P(callable, get_closure)(Z_OBJ_P(callable), &fcc->calling_scope, &fcc->function_handler, &fcc->object, 1) == FAILURE) {
if (error) *error = estrdup("no array or string given");
return 0;
}