diff --git a/Zend/tests/fibers/oss-fuzz-471533782-001.phpt b/Zend/tests/fibers/oss-fuzz-471533782-001.phpt new file mode 100644 index 0000000000000..755f18449b007 --- /dev/null +++ b/Zend/tests/fibers/oss-fuzz-471533782-001.phpt @@ -0,0 +1,33 @@ +--TEST-- +OSS-Fuzz #471533782: Infinite loop in GC destructor fiber +--FILE-- +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 diff --git a/Zend/tests/fibers/oss-fuzz-471533782-002.phpt b/Zend/tests/fibers/oss-fuzz-471533782-002.phpt new file mode 100644 index 0000000000000..3dbac0aac75fb --- /dev/null +++ b/Zend/tests/fibers/oss-fuzz-471533782-002.phpt @@ -0,0 +1,34 @@ +--TEST-- +OSS-Fuzz #471533782: Infinite loop in GC destructor fiber +--FILE-- +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 diff --git a/Zend/zend_gc.c b/Zend/zend_gc.c index f73fef50e16bb..7b2ff49a73632 100644 --- a/Zend/zend_gc.c +++ b/Zend/zend_gc.c @@ -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)