Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ PHP NEWS
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
?? ??? ????, PHP 8.4.24

- Core:
. Fixed use-after-free when an autoloader modifies a callable. (PuH4ck3rX)

- Calendar:
. Fixed bug GH-22602 (gregoriantojd() and juliantojd() integer overflow with
INT_MAX year). (arshidkv12)
Expand Down
41 changes: 41 additions & 0 deletions Zend/tests/callable_resolution_autoload_mutation.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
--TEST--
Autoloading must not invalidate callables during callable resolution
--FILE--
<?php
spl_autoload_register(function (string $class): void {
if ($class === 'IsCallableAutoloadTest') {
$GLOBALS['method1'] = 'missing';
eval('class IsCallableAutoloadTest {
public static function original(): void {}
}');
} elseif ($class === 'CallUserFuncAutoloadTest') {
$GLOBALS['method2'] = 'changed';
eval('class CallUserFuncAutoloadTest {
public static function original(): void { echo "original\\n"; }
public static function changed(): void { echo "changed\\n"; }
}');
} elseif ($class === 'CallUserFuncStringAutoloadTest') {
$GLOBALS['callable3'][strlen($GLOBALS['callable3']) - 1] = 'o';
eval('class CallUserFuncStringAutoloadTest {
public static function mOne(): void { echo "mOne\\n"; }
public static function mOno(): void { echo "mOno\\n"; }
}');
}
});

$method1 = 'original';
$callable1 = ['IsCallableAutoloadTest', &$method1];
var_dump(is_callable($callable1));

$method2 = 'original';
$callable2 = ['CallUserFuncAutoloadTest', &$method2];
call_user_func($callable2);

$callable3 = strrev('enOm::tseTdaolotuAgnirtScnuFresUllaC');
$alias3 = &$callable3;
call_user_func($alias3);
?>
--EXPECT--
bool(true)
original
mOne
18 changes: 18 additions & 0 deletions Zend/tests/dynamic_call_array_autoload_mutation.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
--TEST--
Autoloading must not invalidate an array callable during a dynamic call
--FILE--
<?php
spl_autoload_register(function (string $class): void {
$GLOBALS['method'] = 'changed';
eval('class DynamicArrayCallableTest {
public static function original(): void { echo "original\\n"; }
public static function changed(): void { echo "changed\\n"; }
}');
});

$method = 'original';
$callable = ['DynamicArrayCallableTest', &$method];
$callable();
?>
--EXPECT--
original
18 changes: 18 additions & 0 deletions Zend/tests/dynamic_call_string_autoload_mutation.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
--TEST--
Autoloading must not invalidate a string callable during a dynamic call
--FILE--
<?php
spl_autoload_register(function (string $class): void {
$GLOBALS['callable'][strlen($GLOBALS['callable']) - 1] = 'o';
eval('class DynamicStringCallableTest {
public static function mOne(): void { echo "mOne\\n"; }
public static function mOno(): void { echo "mOno\\n"; }
}');
});

// Keep this non-interned and uniquely owned so the offset write is in-place.
$callable = strrev('enOm::tseTelballaCgnirtScimanyD');
$callable();
?>
--EXPECT--
mOne
14 changes: 11 additions & 3 deletions Zend/zend_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -4179,6 +4179,7 @@ ZEND_API bool zend_is_callable_at_frame(
bool ret;
zend_fcall_info_cache fcc_local;
bool strict_class = 0;
zval callable_copy;

if (fcc == NULL) {
fcc = &fcc_local;
Expand All @@ -4205,9 +4206,11 @@ ZEND_API bool zend_is_callable_at_frame(
fcc->called_scope = fcc->calling_scope;
return 1;
}
ZVAL_STR_COPY(&callable_copy, Z_STR_P(callable));

check_func:
ret = zend_is_callable_check_func(callable, frame, fcc, strict_class, error, check_flags & IS_CALLABLE_SUPPRESS_DEPRECATIONS);
ret = zend_is_callable_check_func(&callable_copy, frame, fcc, strict_class, error, check_flags & IS_CALLABLE_SUPPRESS_DEPRECATIONS);
zval_ptr_dtor(&callable_copy);
if (fcc == &fcc_local) {
zend_release_fcall_info_cache(fcc);
}
Expand Down Expand Up @@ -4244,7 +4247,12 @@ ZEND_API bool zend_is_callable_at_frame(
return 1;
}

if (!zend_is_callable_check_class(Z_STR_P(obj), get_scope(frame), frame, fcc, &strict_class, error, check_flags & IS_CALLABLE_SUPPRESS_DEPRECATIONS)) {
ZVAL_STR_COPY(&callable_copy, Z_STR_P(method));
zend_string *class_name = zend_string_copy(Z_STR_P(obj));
ret = zend_is_callable_check_class(class_name, get_scope(frame), frame, fcc, &strict_class, error, check_flags & IS_CALLABLE_SUPPRESS_DEPRECATIONS);
zend_string_release(class_name);
if (!ret) {
zval_ptr_dtor(&callable_copy);
return 0;
}
} else {
Expand All @@ -4256,9 +4264,9 @@ ZEND_API bool zend_is_callable_at_frame(
fcc->called_scope = fcc->calling_scope;
return 1;
}
ZVAL_STR_COPY(&callable_copy, Z_STR_P(method));
}

callable = method;
goto check_func;
}
return 0;
Expand Down
18 changes: 12 additions & 6 deletions Zend/zend_execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -4871,15 +4871,15 @@ static zend_never_inline zend_execute_data *zend_init_dynamic_call_string(zend_s
size_t mname_length = ZSTR_LEN(function) - cname_length - (sizeof("::") - 1);

lcname = zend_string_init(ZSTR_VAL(function), cname_length, 0);
mname = zend_string_init(ZSTR_VAL(function) + (cname_length + sizeof("::") - 1), mname_length, 0);

called_scope = zend_fetch_class_by_name(lcname, NULL, ZEND_FETCH_CLASS_DEFAULT | ZEND_FETCH_CLASS_EXCEPTION);
if (UNEXPECTED(called_scope == NULL)) {
zend_string_release_ex(lcname, 0);
zend_string_release_ex(mname, 0);
return NULL;
}

mname = zend_string_init(ZSTR_VAL(function) + (cname_length + sizeof("::") - 1), mname_length, 0);

if (called_scope->get_static_method) {
fbc = called_scope->get_static_method(called_scope, mname);
} else {
Expand Down Expand Up @@ -5008,23 +5008,29 @@ static zend_never_inline zend_execute_data *zend_init_dynamic_call_array(zend_ar
}

if (Z_TYPE_P(obj) == IS_STRING) {
zend_class_entry *called_scope = zend_fetch_class_by_name(Z_STR_P(obj), NULL, ZEND_FETCH_CLASS_DEFAULT | ZEND_FETCH_CLASS_EXCEPTION);
zend_string *class_name = zend_string_copy(Z_STR_P(obj));
zend_string *method_name = zend_string_copy(Z_STR_P(method));
zend_class_entry *called_scope = zend_fetch_class_by_name(class_name, NULL, ZEND_FETCH_CLASS_DEFAULT | ZEND_FETCH_CLASS_EXCEPTION);
zend_string_release(class_name);

if (UNEXPECTED(called_scope == NULL)) {
zend_string_release(method_name);
return NULL;
}

if (called_scope->get_static_method) {
fbc = called_scope->get_static_method(called_scope, Z_STR_P(method));
fbc = called_scope->get_static_method(called_scope, method_name);
} else {
fbc = zend_std_get_static_method(called_scope, Z_STR_P(method), NULL);
fbc = zend_std_get_static_method(called_scope, method_name, NULL);
}
if (UNEXPECTED(fbc == NULL)) {
if (EXPECTED(!EG(exception))) {
zend_undefined_method(called_scope, Z_STR_P(method));
zend_undefined_method(called_scope, method_name);
}
zend_string_release(method_name);
return NULL;
}
zend_string_release(method_name);
if (!(fbc->common.fn_flags & ZEND_ACC_STATIC)) {
zend_non_static_method_call(fbc);
if (fbc->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) {
Expand Down
Loading