Skip to content

Commit 605ac68

Browse files
committed
Fix GH-22878: Use-after-free of callable via autoloader
Validating an array or string callable runs user code before its borrowed method name is used: resolving a string class name can trigger an autoloader, and a compound "Class::method" name emits an E_DEPRECATED that reaches a user error handler. Either can free the callable, leaving the borrowed method string dangling when the method is resolved. Hold that string across the reentrant lookup in zend_is_callable_at_frame(), zend_is_method_callable() and zend_init_dynamic_call_array(). Fixes GH-22878
1 parent fbdf959 commit 605ac68

4 files changed

Lines changed: 62 additions & 4 deletions

File tree

‎NEWS‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ PHP NEWS
55
- Core:
66
. Changed run-tests.php to run test subprocesses without a shell where
77
possible. (NickSdot)
8+
. Fixed bug GH-22878 (Use-after-free of callable via autoloader). (iliaal)
89

910
- GMP:
1011
. Added optional $definitely_prime output parameter to gmp_prevprime().

‎Zend/tests/gh22878.phpt‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--TEST--
2+
GH-22878 (Use-after-free of callable via autoloader)
3+
--FILE--
4+
<?php
5+
spl_autoload_register(function (string $class): void {
6+
$GLOBALS['cb'] = null;
7+
eval("class $class { public static function __callStatic(\$name, \$args) { echo strlen(\$name), \"\\n\"; } }");
8+
});
9+
10+
$method = str_repeat('m', 256);
11+
$cb = ['GH22878ArrayCuf', $method];
12+
unset($method);
13+
call_user_func($cb);
14+
15+
$method = str_repeat('m', 256);
16+
$cb = ['GH22878ArrayCufa', $method];
17+
unset($method);
18+
call_user_func_array($cb, []);
19+
20+
$method = str_repeat('m', 256);
21+
$cb = ['GH22878ArrayDynamic', $method];
22+
unset($method);
23+
$cb();
24+
25+
$method = str_repeat('m', 256);
26+
$cb = 'GH22878Str::' . $method;
27+
unset($method);
28+
call_user_func($cb);
29+
30+
$cb = 'GH22878Lit::literalMethod';
31+
call_user_func($cb);
32+
33+
echo "done\n";
34+
?>
35+
--EXPECT--
36+
256
37+
256
38+
256
39+
256
40+
13
41+
done

‎Zend/zend_API.c‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3859,6 +3859,7 @@ static zend_always_inline bool zend_is_method_callable(zend_string *callable, co
38593859
scope = get_scope(frame);
38603860
}
38613861

3862+
zend_string *held_callable = zend_string_copy(callable);
38623863
zend_string *class_name = zend_string_init_interned(ZSTR_VAL(callable), class_name_len, 0);
38633864
if (ZSTR_HAS_CE_CACHE(class_name) && ZSTR_GET_CE_CACHE(class_name)) {
38643865
fcc->calling_scope = ZSTR_GET_CE_CACHE(class_name);
@@ -3879,13 +3880,15 @@ static zend_always_inline bool zend_is_method_callable(zend_string *callable, co
38793880
strict_class = true;
38803881
} else if (!zend_is_callable_check_class(class_name, scope, frame, fcc, &strict_class, error, suppress_deprecation || ce_org != NULL)) {
38813882
zend_string_release_ex(class_name, 0);
3883+
zend_string_release(held_callable);
38823884
return 0;
38833885
}
38843886
zend_string_release_ex(class_name, 0);
38853887

38863888
ftable = &fcc->calling_scope->function_table;
38873889
if (ce_org && !instanceof_function(ce_org, fcc->calling_scope)) {
38883890
if (error) zend_spprintf(error, 0, "class %s is not a subclass of %s", ZSTR_VAL(ce_org->name), ZSTR_VAL(fcc->calling_scope->name));
3891+
zend_string_release(held_callable);
38893892
return 0;
38903893
}
38913894
if (ce_org && !suppress_deprecation) {
@@ -3894,6 +3897,7 @@ static zend_always_inline bool zend_is_method_callable(zend_string *callable, co
38943897
ZSTR_VAL(ce_org->name), ZSTR_VAL(callable));
38953898
}
38963899
mname = zend_string_init(ZSTR_VAL(callable) + class_name_len + 2, mlen, 0);
3900+
zend_string_release(held_callable);
38973901
} else if (ce_org) {
38983902
/* Try to fetch find static method of given class. */
38993903
mname = callable;
@@ -4216,12 +4220,17 @@ ZEND_API bool zend_is_callable_at_frame(
42164220
return 0;
42174221
}
42184222

4223+
zend_string *method_name;
4224+
42194225
if (Z_TYPE_P(obj) == IS_STRING) {
42204226
if (check_flags & IS_CALLABLE_CHECK_SYNTAX_ONLY) {
42214227
return 1;
42224228
}
42234229

4230+
method_name = zend_string_copy(Z_STR_P(method));
4231+
42244232
if (!zend_is_callable_check_class(Z_STR_P(obj), get_scope(frame), frame, fcc, &strict_class, error, check_flags & IS_CALLABLE_SUPPRESS_DEPRECATIONS)) {
4233+
zend_string_release(method_name);
42254234
return 0;
42264235
}
42274236
} else {
@@ -4233,9 +4242,12 @@ ZEND_API bool zend_is_callable_at_frame(
42334242
fcc->called_scope = fcc->calling_scope;
42344243
return 1;
42354244
}
4245+
4246+
method_name = zend_string_copy(Z_STR_P(method));
42364247
}
42374248

4238-
ret = zend_is_method_callable(Z_STR_P(method), frame, fcc, strict_class, error, check_flags & IS_CALLABLE_SUPPRESS_DEPRECATIONS);
4249+
ret = zend_is_method_callable(method_name, frame, fcc, strict_class, error, check_flags & IS_CALLABLE_SUPPRESS_DEPRECATIONS);
4250+
zend_string_release(method_name);
42394251
break;
42404252
}
42414253

‎Zend/zend_execute.c‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5259,23 +5259,27 @@ static zend_never_inline zend_execute_data *zend_init_dynamic_call_array(const z
52595259
}
52605260

52615261
if (Z_TYPE_P(obj) == IS_STRING) {
5262+
zend_string *method_name = zend_string_copy(Z_STR_P(method));
52625263
zend_class_entry *called_scope = zend_fetch_class_by_name(Z_STR_P(obj), NULL, ZEND_FETCH_CLASS_DEFAULT | ZEND_FETCH_CLASS_EXCEPTION);
52635264

52645265
if (UNEXPECTED(called_scope == NULL)) {
5266+
zend_string_release(method_name);
52655267
return NULL;
52665268
}
52675269

52685270
if (called_scope->get_static_method) {
5269-
fbc = called_scope->get_static_method(called_scope, Z_STR_P(method));
5271+
fbc = called_scope->get_static_method(called_scope, method_name);
52705272
} else {
5271-
fbc = zend_std_get_static_method(called_scope, Z_STR_P(method), NULL);
5273+
fbc = zend_std_get_static_method(called_scope, method_name, NULL);
52725274
}
52735275
if (UNEXPECTED(fbc == NULL)) {
52745276
if (EXPECTED(!EG(exception))) {
5275-
zend_undefined_method(called_scope, Z_STR_P(method));
5277+
zend_undefined_method(called_scope, method_name);
52765278
}
5279+
zend_string_release(method_name);
52775280
return NULL;
52785281
}
5282+
zend_string_release(method_name);
52795283
if (!(fbc->common.fn_flags & ZEND_ACC_STATIC)) {
52805284
zend_non_static_method_call(fbc);
52815285
if (fbc->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) {

0 commit comments

Comments
 (0)