Skip to content

Commit 2813f62

Browse files
committed
Merge branch 'PHP-8.5'
* PHP-8.5: Fix infinite loop in GC destructor fiber
2 parents fb27990 + 99aec78 commit 2813f62

File tree

3 files changed

+86
-0
lines changed

3 files changed

+86
-0
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--TEST--
2+
OSS-Fuzz #471533782: Infinite loop in GC destructor fiber
3+
--FILE--
4+
<?php
5+
6+
class Cycle {
7+
public $self;
8+
public function __construct() {
9+
$this->self = $this;
10+
}
11+
public function __destruct() {
12+
try {
13+
Fiber::suspend();
14+
} finally {
15+
throw new Exception();
16+
}
17+
}
18+
}
19+
20+
$f = new Fiber(function () {
21+
new Cycle();
22+
gc_collect_cycles();
23+
});
24+
$f->start();
25+
26+
?>
27+
--EXPECTF--
28+
Fatal error: Uncaught Exception in %s:%d
29+
Stack trace:
30+
#0 [internal function]: Cycle->__destruct()
31+
#1 [internal function]: gc_destructor_fiber()
32+
#2 {main}
33+
thrown in %s on line %d
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
--TEST--
2+
OSS-Fuzz #471533782: Infinite loop in GC destructor fiber
3+
--FILE--
4+
<?php
5+
6+
class Cycle {
7+
public $self;
8+
public function __construct() {
9+
$this->self = $this;
10+
}
11+
public function __destruct() {
12+
try {
13+
Fiber::suspend();
14+
} finally {
15+
Fiber::suspend();
16+
}
17+
}
18+
}
19+
20+
$f = new Fiber(function () {
21+
new Cycle();
22+
gc_collect_cycles();
23+
});
24+
$f->start();
25+
26+
?>
27+
--EXPECTF--
28+
Fatal error: Uncaught FiberError: Cannot suspend in a force-closed fiber in %s:%d
29+
Stack trace:
30+
#0 %s(%d): Fiber::suspend()
31+
#1 [internal function]: Cycle->__destruct()
32+
#2 [internal function]: gc_destructor_fiber()
33+
#3 {main}
34+
thrown in %s on line %d

Zend/zend_gc.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
#include "zend_types.h"
7777
#include "zend_weakrefs.h"
7878
#include "zend_string.h"
79+
#include "zend_exceptions.h"
7980

8081
#ifndef GC_BENCH
8182
# define GC_BENCH 0
@@ -1932,6 +1933,17 @@ static zend_fiber *gc_create_destructor_fiber(void)
19321933
return fiber;
19331934
}
19341935

1936+
static void remember_prev_exception(zend_object **prev_exception)
1937+
{
1938+
if (EG(exception)) {
1939+
if (*prev_exception) {
1940+
zend_exception_set_previous(EG(exception), *prev_exception);
1941+
}
1942+
*prev_exception = EG(exception);
1943+
EG(exception) = NULL;
1944+
}
1945+
}
1946+
19351947
static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end)
19361948
{
19371949
ZEND_ASSERT(!GC_G(dtor_fiber_running));
@@ -1947,6 +1959,9 @@ static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end)
19471959
zend_fiber_resume(fiber, NULL, NULL);
19481960
}
19491961

1962+
zend_object *exception = NULL;
1963+
remember_prev_exception(&exception);
1964+
19501965
for (;;) {
19511966
/* At this point, fiber has executed until suspension */
19521967
GC_TRACE("resumed from destructor fiber");
@@ -1960,14 +1975,18 @@ static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end)
19601975
/* We do not own the fiber anymore. It may be collected if the
19611976
* application does not reference it. */
19621977
zend_object_release(&fiber->std);
1978+
remember_prev_exception(&exception);
19631979
fiber = gc_create_destructor_fiber();
1980+
remember_prev_exception(&exception);
19641981
continue;
19651982
} else {
19661983
/* Fiber suspended itself after calling all destructors */
19671984
GC_TRACE("destructor fiber suspended itself");
19681985
break;
19691986
}
19701987
}
1988+
1989+
EG(exception) = exception;
19711990
}
19721991

19731992
/* Perform a garbage collection run. The default implementation of gc_collect_cycles. */

0 commit comments

Comments
 (0)