Skip to content

Commit 6f6c9e3

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 Closes GH-20884
1 parent a6e0d8e commit 6f6c9e3

File tree

4 files changed

+87
-0
lines changed

4 files changed

+87
-0
lines changed

NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ PHP NEWS
55
- Core:
66
. Fixed bug GH-20837 (NULL dereference when calling ob_start() in shutdown
77
function triggered by bailout in php_output_lock_error()). (timwolla)
8+
. Fix OSS-Fuzz #471533782 (Infinite loop in GC destructor fiber). (ilutov)
89

910
- MbString:
1011
. Fixed bug GH-20833 (mb_str_pad() divide by zero if padding string is
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
@@ -1872,6 +1873,17 @@ static zend_fiber *gc_create_destructor_fiber(void)
18721873
return fiber;
18731874
}
18741875

1876+
static void remember_prev_exception(zend_object **prev_exception)
1877+
{
1878+
if (EG(exception)) {
1879+
if (*prev_exception) {
1880+
zend_exception_set_previous(EG(exception), *prev_exception);
1881+
}
1882+
*prev_exception = EG(exception);
1883+
EG(exception) = NULL;
1884+
}
1885+
}
1886+
18751887
static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end)
18761888
{
18771889
ZEND_ASSERT(!GC_G(dtor_fiber_running));
@@ -1887,6 +1899,9 @@ static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end)
18871899
zend_fiber_resume(fiber, NULL, NULL);
18881900
}
18891901

1902+
zend_object *exception = NULL;
1903+
remember_prev_exception(&exception);
1904+
18901905
for (;;) {
18911906
/* At this point, fiber has executed until suspension */
18921907
GC_TRACE("resumed from destructor fiber");
@@ -1900,14 +1915,18 @@ static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end)
19001915
/* We do not own the fiber anymore. It may be collected if the
19011916
* application does not reference it. */
19021917
zend_object_release(&fiber->std);
1918+
remember_prev_exception(&exception);
19031919
fiber = gc_create_destructor_fiber();
1920+
remember_prev_exception(&exception);
19041921
continue;
19051922
} else {
19061923
/* Fiber suspended itself after calling all destructors */
19071924
GC_TRACE("destructor fiber suspended itself");
19081925
break;
19091926
}
19101927
}
1928+
1929+
EG(exception) = exception;
19111930
}
19121931

19131932
ZEND_API int zend_gc_collect_cycles(void)

0 commit comments

Comments
 (0)