Skip to content

Commit f2858c8

Browse files
committed
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
1 parent 226f68b commit f2858c8

File tree

3 files changed

+79
-0
lines changed

3 files changed

+79
-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: 12 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
@@ -1887,6 +1888,8 @@ static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end)
18871888
zend_fiber_resume(fiber, NULL, NULL);
18881889
}
18891890

1891+
zend_object *exception = NULL;
1892+
18901893
for (;;) {
18911894
/* At this point, fiber has executed until suspension */
18921895
GC_TRACE("resumed from destructor fiber");
@@ -1900,6 +1903,13 @@ static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end)
19001903
/* We do not own the fiber anymore. It may be collected if the
19011904
* application does not reference it. */
19021905
zend_object_release(&fiber->std);
1906+
if (EG(exception)) {
1907+
if (exception) {
1908+
zend_exception_set_previous(EG(exception), exception);
1909+
}
1910+
exception = EG(exception);
1911+
EG(exception) = NULL;
1912+
}
19031913
fiber = gc_create_destructor_fiber();
19041914
continue;
19051915
} else {
@@ -1908,6 +1918,8 @@ static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end)
19081918
break;
19091919
}
19101920
}
1921+
1922+
EG(exception) = exception;
19111923
}
19121924

19131925
ZEND_API int zend_gc_collect_cycles(void)

0 commit comments

Comments
 (0)