1
0
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:
Ilija Tovilo
2026-01-09 16:00:28 +01:00
parent a6e0d8e359
commit 6f6c9e35e8
4 changed files with 87 additions and 0 deletions

1
NEWS
View File

@@ -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

View 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

View 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

View File

@@ -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)