mirror of
https://github.com/php/php-src.git
synced 2025-08-15 21:48:51 +02:00
Fix bug #80072: Root live tmpvars after GC
TMPVAR operands are destroyed using zval_ptr_dtor_nogc(), because they usually cannot contain cycles. However, there are some rare exceptions where this is possible, e.g. unserialize() return value. In such cases we rely on the producing code to root the value. If a GC run occurs between the rooting and consumption of the value, we would end up leaking it. To avoid this, root all live TMPVAR values after a GC run. Closes GH-7210.
This commit is contained in:
parent
083d7f5886
commit
52cf7ab8a2
3 changed files with 56 additions and 2 deletions
1
NEWS
1
NEWS
|
@ -4,6 +4,7 @@ PHP NEWS
|
||||||
|
|
||||||
- Core:
|
- Core:
|
||||||
. Fixed bug #81202 (powerpc64 build fails on fibers). (krakjoe)
|
. Fixed bug #81202 (powerpc64 build fails on fibers). (krakjoe)
|
||||||
|
. Fixed bug #80072 (Cyclic unserialize in TMPVAR operand may leak). (Nikita)
|
||||||
|
|
||||||
- Curl:
|
- Curl:
|
||||||
. Fixed bug #81085 (Support CURLOPT_SSLCERT_BLOB for cert strings).
|
. Fixed bug #81085 (Support CURLOPT_SSLCERT_BLOB for cert strings).
|
||||||
|
|
17
Zend/tests/bug80072.phpt
Normal file
17
Zend/tests/bug80072.phpt
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
--TEST--
|
||||||
|
Bug #80072: Cyclic unserialize in TMPVAR operand may leak
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
try {
|
||||||
|
$s = 'O:8:"stdClass":1:{s:1:"x";r:1;}';
|
||||||
|
unserialize($s) % gc_collect_cycles();
|
||||||
|
} catch (Error $e) {
|
||||||
|
echo $e->getMessage(), "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
$a[]=&$a == $a=&$b > gc_collect_cycles();
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
Unsupported operand types: stdClass % int
|
|
@ -1427,6 +1427,7 @@ next:
|
||||||
}
|
}
|
||||||
|
|
||||||
static void zend_get_gc_buffer_release(void);
|
static void zend_get_gc_buffer_release(void);
|
||||||
|
static void zend_gc_root_tmpvars(void);
|
||||||
|
|
||||||
ZEND_API int zend_gc_collect_cycles(void)
|
ZEND_API int zend_gc_collect_cycles(void)
|
||||||
{
|
{
|
||||||
|
@ -1465,9 +1466,8 @@ rerun_gc:
|
||||||
/* nothing to free */
|
/* nothing to free */
|
||||||
GC_TRACE("Nothing to free");
|
GC_TRACE("Nothing to free");
|
||||||
gc_stack_free(&stack);
|
gc_stack_free(&stack);
|
||||||
zend_get_gc_buffer_release();
|
|
||||||
GC_G(gc_active) = 0;
|
GC_G(gc_active) = 0;
|
||||||
return 0;
|
goto finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
zend_fiber_switch_block();
|
zend_fiber_switch_block();
|
||||||
|
@ -1627,7 +1627,9 @@ rerun_gc:
|
||||||
goto rerun_gc;
|
goto rerun_gc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
finish:
|
||||||
zend_get_gc_buffer_release();
|
zend_get_gc_buffer_release();
|
||||||
|
zend_gc_root_tmpvars();
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1661,6 +1663,40 @@ static void zend_get_gc_buffer_release() {
|
||||||
gc_buffer->start = gc_buffer->end = gc_buffer->cur = NULL;
|
gc_buffer->start = gc_buffer->end = gc_buffer->cur = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* TMPVAR operands are destroyed using zval_ptr_dtor_nogc(), because they usually cannot contain
|
||||||
|
* cycles. However, there are some rare exceptions where this is possible, in which case we rely
|
||||||
|
* on the producing code to root the value. If a GC run occurs between the rooting and consumption
|
||||||
|
* of the value, we would end up leaking it. To avoid this, root all live TMPVAR values here. */
|
||||||
|
static void zend_gc_root_tmpvars(void) {
|
||||||
|
zend_execute_data *ex = EG(current_execute_data);
|
||||||
|
for (; ex; ex = ex->prev_execute_data) {
|
||||||
|
zend_function *func = ex->func;
|
||||||
|
if (!func || !ZEND_USER_CODE(func->type)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t op_num = ex->opline - ex->func->op_array.opcodes;
|
||||||
|
for (uint32_t i = 0; i < func->op_array.last_live_range; i++) {
|
||||||
|
const zend_live_range *range = &func->op_array.live_range[i];
|
||||||
|
if (range->start > op_num) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (range->end <= op_num) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t kind = range->var & ZEND_LIVE_MASK;
|
||||||
|
if (kind == ZEND_LIVE_TMPVAR) {
|
||||||
|
uint32_t var_num = range->var & ~ZEND_LIVE_MASK;
|
||||||
|
zval *var = ZEND_CALL_VAR(ex, var_num);
|
||||||
|
if (Z_REFCOUNTED_P(var)) {
|
||||||
|
gc_check_possible_root(Z_COUNTED_P(var));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef ZTS
|
#ifdef ZTS
|
||||||
size_t zend_gc_globals_size(void)
|
size_t zend_gc_globals_size(void)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue