Skip to content

Commit bc4b6ce

Browse files
committed
Prevent operands from being released during comparison
Fixes GH-19305 Closes GH-19309
1 parent 80022c0 commit bc4b6ce

File tree

7 files changed

+135
-8
lines changed

7 files changed

+135
-8
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ PHP NEWS
99
unpacking). (ilutov)
1010
. Fixed OSS-Fuzz #434346548 (Failed assertion with throwing __toString in
1111
binary const expr). (ilutov)
12+
. Fixed bug GH-19305 (Operands may be being released during comparison).
13+
(Arnaud)
1214

1315
- FTP:
1416
. Fix theoretical issues with hrtime() not being available. (nielsdos)

Zend/tests/gh19305-001.phpt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
GH-19305 001: Operands may be released during comparison
3+
--FILE--
4+
<?php
5+
6+
$a = (object)[
7+
'foo' => 'test',
8+
'bar' => 2,
9+
];
10+
$b = (object)[
11+
'foo' => new class {
12+
public function __toString() {
13+
global $a, $b;
14+
$a = $b = null;
15+
return '';
16+
}
17+
},
18+
'bar' => 2,
19+
];
20+
21+
// Comparison of $a->foo and $b->foo calls __toString(), which releases
22+
// both $a and $b.
23+
var_dump($a > $b);
24+
25+
?>
26+
--EXPECT--
27+
bool(true)

Zend/tests/gh19305-002.phpt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
GH-19305 002: Operands may be released during comparison
3+
--FILE--
4+
<?php
5+
6+
$a = [
7+
'foo' => 'test',
8+
'bar' => 2,
9+
];
10+
$b = [
11+
'foo' => new class {
12+
public function __toString() {
13+
global $a, $b;
14+
$a = $b = null;
15+
return '';
16+
}
17+
},
18+
'bar' => 2,
19+
];
20+
21+
// Comparison of $a['foo'] and $b['foo'] calls __toString(), which releases
22+
// both $a and $b.
23+
var_dump($a > $b);
24+
25+
?>
26+
--EXPECT--
27+
bool(true)

Zend/tests/gh19305-003.phpt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
GH-19305 003: Operands may be released during comparison
3+
--SKIPIF--
4+
<?php
5+
if (!method_exists('ReflectionClass', 'newLazyGhost')) {
6+
die('skip No lazy objects');
7+
}
8+
?>
9+
--FILE--
10+
<?php
11+
12+
class C
13+
{
14+
public $s;
15+
}
16+
$r = new ReflectionClass(C::class);
17+
$o = $r->newLazyProxy(function () { return new C; });
18+
19+
// Comparison calls initializers, which releases $o
20+
var_dump($o >
21+
$r->newLazyGhost(function () {
22+
global $o;
23+
$o = null;
24+
}));
25+
26+
?>
27+
--EXPECT--
28+
bool(false)

Zend/zend_object_handlers.c

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1792,6 +1792,10 @@ ZEND_API int zend_std_compare_objects(zval *o1, zval *o2) /* {{{ */
17921792
}
17931793
Z_PROTECT_RECURSION_P(o1);
17941794

1795+
GC_ADDREF(zobj1);
1796+
GC_ADDREF(zobj2);
1797+
int ret;
1798+
17951799
for (i = 0; i < zobj1->ce->default_properties_count; i++) {
17961800
zval *p1, *p2;
17971801

@@ -1806,35 +1810,50 @@ ZEND_API int zend_std_compare_objects(zval *o1, zval *o2) /* {{{ */
18061810

18071811
if (Z_TYPE_P(p1) != IS_UNDEF) {
18081812
if (Z_TYPE_P(p2) != IS_UNDEF) {
1809-
int ret;
1810-
18111813
ret = zend_compare(p1, p2);
18121814
if (ret != 0) {
18131815
Z_UNPROTECT_RECURSION_P(o1);
1814-
return ret;
1816+
goto done;
18151817
}
18161818
} else {
18171819
Z_UNPROTECT_RECURSION_P(o1);
1818-
return 1;
1820+
ret = 1;
1821+
goto done;
18191822
}
18201823
} else {
18211824
if (Z_TYPE_P(p2) != IS_UNDEF) {
18221825
Z_UNPROTECT_RECURSION_P(o1);
1823-
return 1;
1826+
ret = 1;
1827+
goto done;
18241828
}
18251829
}
18261830
}
18271831

18281832
Z_UNPROTECT_RECURSION_P(o1);
1829-
return 0;
1833+
ret = 0;
1834+
1835+
done:
1836+
OBJ_RELEASE(zobj1);
1837+
OBJ_RELEASE(zobj2);
1838+
1839+
return ret;
18301840
} else {
1841+
GC_ADDREF(zobj1);
1842+
GC_ADDREF(zobj2);
1843+
18311844
if (!zobj1->properties) {
18321845
rebuild_object_properties(zobj1);
18331846
}
18341847
if (!zobj2->properties) {
18351848
rebuild_object_properties(zobj2);
18361849
}
1837-
return zend_compare_symbol_tables(zobj1->properties, zobj2->properties);
1850+
1851+
int ret = zend_compare_symbol_tables(zobj1->properties, zobj2->properties);
1852+
1853+
OBJ_RELEASE(zobj1);
1854+
OBJ_RELEASE(zobj2);
1855+
1856+
return ret;
18381857
}
18391858
}
18401859
/* }}} */

Zend/zend_operators.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3384,7 +3384,19 @@ static int hash_zval_compare_function(zval *z1, zval *z2) /* {{{ */
33843384

33853385
ZEND_API int ZEND_FASTCALL zend_compare_symbol_tables(HashTable *ht1, HashTable *ht2) /* {{{ */
33863386
{
3387-
return ht1 == ht2 ? 0 : zend_hash_compare(ht1, ht2, (compare_func_t) hash_zval_compare_function, 0);
3387+
if (ht1 == ht2) {
3388+
return 0;
3389+
}
3390+
3391+
GC_TRY_ADDREF(ht1);
3392+
GC_TRY_ADDREF(ht2);
3393+
3394+
int ret = zend_hash_compare(ht1, ht2, (compare_func_t) hash_zval_compare_function, 0);
3395+
3396+
GC_TRY_DTOR_NO_REF(ht1);
3397+
GC_TRY_DTOR_NO_REF(ht2);
3398+
3399+
return ret;
33883400
}
33893401
/* }}} */
33903402

Zend/zend_types.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,18 @@ static zend_always_inline uint8_t zval_get_type(const zval* pz) {
731731
} \
732732
} while (0)
733733

734+
#define GC_TRY_DTOR_NO_REF(p) \
735+
do { \
736+
zend_refcounted_h *_p = &(p)->gc; \
737+
if (!(_p->u.type_info & GC_IMMUTABLE)) { \
738+
if (zend_gc_delref(_p) == 0) { \
739+
rc_dtor_func((zend_refcounted *)_p); \
740+
} else { \
741+
gc_check_possible_root_no_ref((zend_refcounted *)_p); \
742+
} \
743+
} \
744+
} while (0)
745+
734746
#define GC_TYPE_MASK 0x0000000f
735747
#define GC_FLAGS_MASK 0x000003f0
736748
#define GC_INFO_MASK 0xfffffc00

0 commit comments

Comments
 (0)