mirror of
https://github.com/php/php-src.git
synced 2026-03-24 00:02:20 +01:00
Fix infinite loop in GC destructor fiber
zend_object_release(&fiber->std) may restart the fiber due to finally. Any thrown exception must be remembered and unset so that the next fiber may successfully start. Fixes OSS-Fuzz #471533782 Closes GH-20884
This commit is contained in:
1
NEWS
1
NEWS
@@ -5,6 +5,7 @@ PHP NEWS
|
||||
- Core:
|
||||
. Fixed bug GH-20837 (NULL dereference when calling ob_start() in shutdown
|
||||
function triggered by bailout in php_output_lock_error()). (timwolla)
|
||||
. Fix OSS-Fuzz #471533782 (Infinite loop in GC destructor fiber). (ilutov)
|
||||
|
||||
- MbString:
|
||||
. Fixed bug GH-20833 (mb_str_pad() divide by zero if padding string is
|
||||
|
||||
33
Zend/tests/fibers/oss-fuzz-471533782-001.phpt
Normal file
33
Zend/tests/fibers/oss-fuzz-471533782-001.phpt
Normal file
@@ -0,0 +1,33 @@
|
||||
--TEST--
|
||||
OSS-Fuzz #471533782: Infinite loop in GC destructor fiber
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Cycle {
|
||||
public $self;
|
||||
public function __construct() {
|
||||
$this->self = $this;
|
||||
}
|
||||
public function __destruct() {
|
||||
try {
|
||||
Fiber::suspend();
|
||||
} finally {
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$f = new Fiber(function () {
|
||||
new Cycle();
|
||||
gc_collect_cycles();
|
||||
});
|
||||
$f->start();
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Uncaught Exception in %s:%d
|
||||
Stack trace:
|
||||
#0 [internal function]: Cycle->__destruct()
|
||||
#1 [internal function]: gc_destructor_fiber()
|
||||
#2 {main}
|
||||
thrown in %s on line %d
|
||||
34
Zend/tests/fibers/oss-fuzz-471533782-002.phpt
Normal file
34
Zend/tests/fibers/oss-fuzz-471533782-002.phpt
Normal file
@@ -0,0 +1,34 @@
|
||||
--TEST--
|
||||
OSS-Fuzz #471533782: Infinite loop in GC destructor fiber
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Cycle {
|
||||
public $self;
|
||||
public function __construct() {
|
||||
$this->self = $this;
|
||||
}
|
||||
public function __destruct() {
|
||||
try {
|
||||
Fiber::suspend();
|
||||
} finally {
|
||||
Fiber::suspend();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$f = new Fiber(function () {
|
||||
new Cycle();
|
||||
gc_collect_cycles();
|
||||
});
|
||||
$f->start();
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Uncaught FiberError: Cannot suspend in a force-closed fiber in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(%d): Fiber::suspend()
|
||||
#1 [internal function]: Cycle->__destruct()
|
||||
#2 [internal function]: gc_destructor_fiber()
|
||||
#3 {main}
|
||||
thrown in %s on line %d
|
||||
@@ -76,6 +76,7 @@
|
||||
#include "zend_types.h"
|
||||
#include "zend_weakrefs.h"
|
||||
#include "zend_string.h"
|
||||
#include "zend_exceptions.h"
|
||||
|
||||
#ifndef GC_BENCH
|
||||
# define GC_BENCH 0
|
||||
@@ -1872,6 +1873,17 @@ static zend_fiber *gc_create_destructor_fiber(void)
|
||||
return fiber;
|
||||
}
|
||||
|
||||
static void remember_prev_exception(zend_object **prev_exception)
|
||||
{
|
||||
if (EG(exception)) {
|
||||
if (*prev_exception) {
|
||||
zend_exception_set_previous(EG(exception), *prev_exception);
|
||||
}
|
||||
*prev_exception = EG(exception);
|
||||
EG(exception) = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end)
|
||||
{
|
||||
ZEND_ASSERT(!GC_G(dtor_fiber_running));
|
||||
@@ -1887,6 +1899,9 @@ static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end)
|
||||
zend_fiber_resume(fiber, NULL, NULL);
|
||||
}
|
||||
|
||||
zend_object *exception = NULL;
|
||||
remember_prev_exception(&exception);
|
||||
|
||||
for (;;) {
|
||||
/* At this point, fiber has executed until suspension */
|
||||
GC_TRACE("resumed from destructor fiber");
|
||||
@@ -1900,7 +1915,9 @@ static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end)
|
||||
/* We do not own the fiber anymore. It may be collected if the
|
||||
* application does not reference it. */
|
||||
zend_object_release(&fiber->std);
|
||||
remember_prev_exception(&exception);
|
||||
fiber = gc_create_destructor_fiber();
|
||||
remember_prev_exception(&exception);
|
||||
continue;
|
||||
} else {
|
||||
/* Fiber suspended itself after calling all destructors */
|
||||
@@ -1908,6 +1925,8 @@ static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
EG(exception) = exception;
|
||||
}
|
||||
|
||||
ZEND_API int zend_gc_collect_cycles(void)
|
||||
|
||||
Reference in New Issue
Block a user