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
98 changes: 98 additions & 0 deletions Zend/tests/gh21639.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
--TEST--
GH-21639: Frameless calls keep volatile CV arguments alive during __toString()
--FILE--
<?php
class ImplodeElement {
public function __toString(): string {
global $separator, $pieces;

$separator = null;
$pieces = null;

return "C";
}
}

$separator = str_repeat(",", 1) . " ";
$pieces = [new ImplodeElement(), 42];

var_dump(implode($separator, $pieces));
var_dump($separator, $pieces);

class ImplodeElementWithoutSeparator {
public function __toString(): string {
global $oneArgPieces;

$oneArgPieces = null;

return "D";
}
}

$oneArgPieces = [new ImplodeElementWithoutSeparator(), 42];

var_dump(implode($oneArgPieces));
var_dump($oneArgPieces);

class InArrayNeedle {
public function __toString(): string {
global $inArrayHaystack;

$inArrayHaystack = null;

return "needle";
}
}

$inArrayHaystack = [new InArrayNeedle()];

var_dump(in_array("needle", $inArrayHaystack));
var_dump($inArrayHaystack);

class StrtrReplacement {
public function __toString(): string {
global $strtrReplacements;

$strtrReplacements = null;

return "b";
}
}

$strtrReplacements = ["a" => new StrtrReplacement()];

var_dump(strtr("a", $strtrReplacements));
var_dump($strtrReplacements);

class StrReplaceSubject {
public function __toString(): string {
global $strReplaceSubject;

$strReplaceSubject = null;

return "a";
}
}

$strReplaceSubject = [new StrReplaceSubject(), "aa"];

var_dump(str_replace("a", "b", $strReplaceSubject));
var_dump($strReplaceSubject);
?>
--EXPECT--
string(5) "C, 42"
NULL
NULL
string(3) "D42"
NULL
bool(true)
NULL
string(1) "b"
NULL
array(2) {
[0]=>
string(1) "b"
[1]=>
string(2) "bb"
}
NULL
1 change: 1 addition & 0 deletions Zend/zend.c
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,7 @@ static void executor_globals_ctor(zend_executor_globals *executor_globals) /* {{
ZVAL_UNDEF(&executor_globals->user_exception_handler);
executor_globals->in_autoload = NULL;
executor_globals->current_execute_data = NULL;
executor_globals->frameless_reentry_copies = NULL;
executor_globals->current_module = NULL;
executor_globals->exit_status = 0;
#if XPFPA_HAVE_CW
Expand Down
160 changes: 160 additions & 0 deletions Zend/zend_execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -1582,6 +1582,165 @@ static zend_never_inline void zend_assign_to_object_dim(zend_object *obj, zval *
}
}

struct _zend_frameless_reentry_copies {
struct _zend_frameless_reentry_copies *prev;
zend_execute_data *execute_data;
const zend_op *opline;
uint8_t copied_args;
zval args[3];
};

static zend_always_inline bool zend_frameless_arg_needs_reentry_copy(zval *zv)
{
ZVAL_DEREF(zv);
return Z_TYPE_P(zv) == IS_ARRAY || Z_TYPE_P(zv) == IS_STRING;
}

static void zend_frameless_reentry_copy_arg(zend_frameless_reentry_copies *copies, uint32_t arg, zval *zv)
{
if (!zend_frameless_arg_needs_reentry_copy(zv)) {
return;
}

ZVAL_COPY_DEREF(&copies->args[arg], zv);
copies->copied_args |= (1u << arg);
}

static bool zend_frameless_reentry_has_copies(zend_execute_data *execute_data, const zend_op *opline)
{
for (zend_frameless_reentry_copies *copies = EG(frameless_reentry_copies);
copies;
copies = copies->prev) {
if (copies->execute_data == execute_data && copies->opline == opline) {
return true;
}
}

return false;
}

ZEND_API bool zend_frameless_protect_args_for_reentry(void)
{
zend_execute_data *execute_data = EG(current_execute_data);
if (!execute_data) {
return false;
}

if (!EX(func) || !ZEND_USER_CODE(EX(func)->type)) {
return false;
}

const zend_op *opline = EX(opline);
if (!opline || !ZEND_OP_IS_FRAMELESS_ICALL(opline->opcode)) {
return false;
}

if (zend_frameless_reentry_has_copies(execute_data, opline)) {
return true;
}

uint8_t num_args = ZEND_FLF_NUM_ARGS(opline->opcode);
if (num_args == 0) {
return false;
}

zend_frameless_reentry_copies *copies = emalloc(sizeof(zend_frameless_reentry_copies));
copies->execute_data = execute_data;
copies->opline = opline;
copies->copied_args = 0;

if (opline->op1_type == IS_CV) {
zend_frameless_reentry_copy_arg(copies, 0,
zend_get_zval_ptr(opline, opline->op1_type, &opline->op1, execute_data));
}
if (num_args >= 2 && opline->op2_type == IS_CV) {
zend_frameless_reentry_copy_arg(copies, 1,
zend_get_zval_ptr(opline, opline->op2_type, &opline->op2, execute_data));
}
if (num_args >= 3 && (opline + 1)->op1_type == IS_CV) {
zend_frameless_reentry_copy_arg(copies, 2,
zend_get_zval_ptr(opline + 1, (opline + 1)->op1_type, &(opline + 1)->op1, execute_data));
}

if (copies->copied_args == 0) {
efree(copies);
return false;
}

copies->prev = EG(frameless_reentry_copies);
EG(frameless_reentry_copies) = copies;

return true;
}

static bool zend_frameless_reentry_copies_in_use(zend_frameless_reentry_copies *copies)
{
for (zend_execute_data *execute_data = EG(current_execute_data);
execute_data;
execute_data = execute_data->prev_execute_data) {
if (execute_data == copies->execute_data && execute_data->opline == copies->opline) {
return true;
}
}

return false;
}

static void zend_frameless_free_reentry_copies(zend_frameless_reentry_copies *copies)
{
for (uint32_t i = 0; i < 3; i++) {
if (copies->copied_args & (1u << i)) {
zval_ptr_dtor(&copies->args[i]);
}
}

efree(copies);
}

ZEND_API void zend_frameless_cleanup_reentry_copies_for_handler(zend_execute_data *execute_data, const zend_op *opline)
{
zend_frameless_reentry_copies **next = &EG(frameless_reentry_copies);

while (*next) {
zend_frameless_reentry_copies *copies = *next;

if (copies->execute_data != execute_data || copies->opline != opline) {
next = &copies->prev;
continue;
}

*next = copies->prev;
zend_frameless_free_reentry_copies(copies);
}
}

static void zend_frameless_cleanup_reentry_copies_ex(bool force)
{
zend_frameless_reentry_copies **next = &EG(frameless_reentry_copies);

while (*next) {
zend_frameless_reentry_copies *copies = *next;

if (!force && zend_frameless_reentry_copies_in_use(copies)) {
next = &copies->prev;
continue;
}

*next = copies->prev;
zend_frameless_free_reentry_copies(copies);
}
}

ZEND_API void zend_frameless_cleanup_reentry_copies(void)
{
zend_frameless_cleanup_reentry_copies_ex(false);
}

ZEND_API void zend_frameless_cleanup_reentry_copies_force(void)
{
zend_frameless_cleanup_reentry_copies_ex(true);
}

static void frameless_observed_call_copy(zend_execute_data *call, uint32_t arg, zval *zv)
{
if (Z_ISUNDEF_P(zv)) {
Expand Down Expand Up @@ -4081,6 +4240,7 @@ ZEND_API void ZEND_FASTCALL zend_free_compiled_variables(zend_execute_data *exec
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_fcall_interrupt(zend_execute_data *call)
{
zend_atomic_bool_store_ex(&EG(vm_interrupt), false);
zend_frameless_cleanup_reentry_copies();
if (zend_atomic_bool_load_ex(&EG(timed_out))) {
zend_timeout();
} else if (zend_interrupt_function) {
Expand Down
4 changes: 4 additions & 0 deletions Zend/zend_execute.h
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,10 @@ ZEND_API zend_result zend_set_user_opcode_handler(uint8_t opcode, user_opcode_ha
ZEND_API user_opcode_handler_t zend_get_user_opcode_handler(uint8_t opcode);

ZEND_API zval *zend_get_zval_ptr(const zend_op *opline, int op_type, const znode_op *node, const zend_execute_data *execute_data);
ZEND_API bool zend_frameless_protect_args_for_reentry(void);
ZEND_API void zend_frameless_cleanup_reentry_copies_for_handler(zend_execute_data *execute_data, const zend_op *opline);
ZEND_API void zend_frameless_cleanup_reentry_copies(void);
ZEND_API void zend_frameless_cleanup_reentry_copies_force(void);

ZEND_API void zend_clean_and_cache_symbol_table(zend_array *symbol_table);
ZEND_API void ZEND_FASTCALL zend_free_compiled_variables(zend_execute_data *execute_data);
Expand Down
4 changes: 4 additions & 0 deletions Zend/zend_execute_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ void init_executor(void) /* {{{ */
EG(full_tables_cleanup) = 0;
ZEND_ATOMIC_BOOL_INIT(&EG(vm_interrupt), false);
ZEND_ATOMIC_BOOL_INIT(&EG(timed_out), false);
EG(frameless_reentry_copies) = NULL;

EG(exception) = NULL;
EG(prev_exception) = NULL;
Expand Down Expand Up @@ -275,6 +276,8 @@ ZEND_API void zend_shutdown_executor_values(bool fast_shutdown)
zend_string *key;
zval *zv;

zend_frameless_cleanup_reentry_copies_force();

EG(flags) |= EG_FLAGS_IN_RESOURCE_SHUTDOWN;
zend_close_rsrc_list(&EG(regular_list));

Expand Down Expand Up @@ -1039,6 +1042,7 @@ zend_result zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_
/* This flag is regularly checked while running user functions, but not internal
* So see whether interrupt flag was set while the function was running... */
if (zend_atomic_bool_exchange_ex(&EG(vm_interrupt), false)) {
zend_frameless_cleanup_reentry_copies();
if (zend_atomic_bool_load_ex(&EG(timed_out))) {
zend_timeout();
} else if (zend_interrupt_function) {
Expand Down
2 changes: 2 additions & 0 deletions Zend/zend_globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ typedef struct _zend_vm_stack *zend_vm_stack;
typedef struct _zend_ini_entry zend_ini_entry;
typedef struct _zend_fiber_context zend_fiber_context;
typedef struct _zend_fiber zend_fiber;
typedef struct _zend_frameless_reentry_copies zend_frameless_reentry_copies;

typedef enum {
ZEND_MEMOIZE_NONE,
Expand Down Expand Up @@ -215,6 +216,7 @@ struct _zend_executor_globals {

zend_atomic_bool vm_interrupt;
zend_atomic_bool timed_out;
zend_frameless_reentry_copies *frameless_reentry_copies;

HashTable *in_autoload;

Expand Down
1 change: 1 addition & 0 deletions Zend/zend_object_handlers.c
Original file line number Diff line number Diff line change
Expand Up @@ -2558,6 +2558,7 @@ ZEND_API zend_result zend_std_cast_object_tostring(zend_object *readobj, zval *w
zend_class_entry *ce = readobj->ce;
if (ce->__tostring) {
zval retval;
zend_frameless_protect_args_for_reentry();
GC_ADDREF(readobj);
zend_call_known_instance_method_with_0_params(ce->__tostring, readobj, &retval);
zend_object_release(readobj);
Expand Down
13 changes: 13 additions & 0 deletions Zend/zend_vm_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -9690,6 +9690,9 @@ ZEND_VM_HANDLER(204, ZEND_FRAMELESS_ICALL_0, UNUSED, UNUSED, SPEC(OBSERVER))
zend_frameless_function_0 function = (zend_frameless_function_0)ZEND_FLF_HANDLER(opline);
function(EX_VAR(opline->result.var));
}
if (UNEXPECTED(EG(frameless_reentry_copies))) {
zend_frameless_cleanup_reentry_copies_for_handler(execute_data, opline);
}
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}

Expand All @@ -9715,6 +9718,9 @@ ZEND_VM_HANDLER(205, ZEND_FRAMELESS_ICALL_1, ANY, UNUSED, SPEC(OBSERVER))
zend_frameless_function_1 function = (zend_frameless_function_1)ZEND_FLF_HANDLER(opline);
function(result, arg1);
}
if (UNEXPECTED(EG(frameless_reentry_copies))) {
zend_frameless_cleanup_reentry_copies_for_handler(execute_data, opline);
}
FREE_OP1();
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}
Expand Down Expand Up @@ -9743,6 +9749,9 @@ ZEND_VM_HANDLER(206, ZEND_FRAMELESS_ICALL_2, ANY, ANY, SPEC(OBSERVER))
zend_frameless_function_2 function = (zend_frameless_function_2)ZEND_FLF_HANDLER(opline);
function(result, arg1, arg2);
}
if (UNEXPECTED(EG(frameless_reentry_copies))) {
zend_frameless_cleanup_reentry_copies_for_handler(execute_data, opline);
}

FREE_OP1();
/* Set OP1 to UNDEF in case FREE_OP2() throws. */
Expand Down Expand Up @@ -9779,6 +9788,9 @@ ZEND_VM_HANDLER(207, ZEND_FRAMELESS_ICALL_3, ANY, ANY, SPEC(OBSERVER))
zend_frameless_function_3 function = (zend_frameless_function_3)ZEND_FLF_HANDLER(opline);
function(result, arg1, arg2, arg3);
}
if (UNEXPECTED(EG(frameless_reentry_copies))) {
zend_frameless_cleanup_reentry_copies_for_handler(execute_data, opline);
}

FREE_OP1();
/* Set to UNDEF in case FREE_OP2() throws. */
Expand Down Expand Up @@ -10411,6 +10423,7 @@ ZEND_VM_HELPER(zend_interrupt_helper, ANY, ANY)
{
zend_atomic_bool_store_ex(&EG(vm_interrupt), false);
SAVE_OPLINE();
zend_frameless_cleanup_reentry_copies();
if (zend_atomic_bool_load_ex(&EG(timed_out))) {
zend_timeout();
} else if (zend_interrupt_function) {
Expand Down
Loading
Loading