Skip to content

Commit dca438e

Browse files
committed
Merge branch 'PHP-8.2' into PHP-8.3
* PHP-8.2: Add NEWS entry Also fix same issue in ArrayObject::exchangeArray() Fix use-after-free in ArrayObject::unset() with destructor
2 parents 7456842 + 418f820 commit dca438e

File tree

4 files changed

+70
-4
lines changed

4 files changed

+70
-4
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ PHP NEWS
132132
. Fixed bug GH-14687 (segfault on SplObjectIterator instance).
133133
(David Carlier)
134134
. Fixed bug GH-16604 (Memory leaks in SPL constructors). (nielsdos)
135+
. Fixed bug GH-16646 (UAF in ArrayObject::unset() and
136+
ArrayObject::exchangeArray()). (ilutov)
135137

136138
- Standard:
137139
. Fixed bug GH-16293 (Failed assertion when throwing in assert() callback with

ext/spl/spl_array.c

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -554,13 +554,15 @@ static void spl_array_unset_dimension_ex(int check_inherited, zend_object *objec
554554
if (Z_TYPE_P(data) == IS_INDIRECT) {
555555
data = Z_INDIRECT_P(data);
556556
if (Z_TYPE_P(data) != IS_UNDEF) {
557-
zval_ptr_dtor(data);
557+
zval garbage;
558+
ZVAL_COPY_VALUE(&garbage, data);
558559
ZVAL_UNDEF(data);
559560
HT_FLAGS(ht) |= HASH_FLAG_HAS_EMPTY_IND;
560561
zend_hash_move_forward_ex(ht, spl_array_get_pos_ptr(ht, intern));
561562
if (spl_array_is_object(intern)) {
562563
spl_array_skip_protected(intern, ht);
563564
}
565+
zval_ptr_dtor(&garbage);
564566
}
565567
} else {
566568
zend_hash_del(ht, key.key);
@@ -1051,8 +1053,10 @@ static HashTable *spl_array_it_get_gc(zend_object_iterator *iter, zval **table,
10511053
static void spl_array_set_array(zval *object, spl_array_object *intern, zval *array, zend_long ar_flags, bool just_array) {
10521054
/* Handled by ZPP prior to this, or for __unserialize() before passing to here */
10531055
ZEND_ASSERT(Z_TYPE_P(array) == IS_ARRAY || Z_TYPE_P(array) == IS_OBJECT);
1056+
zval garbage;
1057+
ZVAL_UNDEF(&garbage);
10541058
if (Z_TYPE_P(array) == IS_ARRAY) {
1055-
zval_ptr_dtor(&intern->array);
1059+
ZVAL_COPY_VALUE(&garbage, &intern->array);
10561060
if (Z_REFCOUNT_P(array) == 1) {
10571061
ZVAL_COPY(&intern->array, array);
10581062
} else {
@@ -1070,7 +1074,7 @@ static void spl_array_set_array(zval *object, spl_array_object *intern, zval *ar
10701074
}
10711075
} else {
10721076
if (Z_OBJ_HT_P(array) == &spl_handler_ArrayObject || Z_OBJ_HT_P(array) == &spl_handler_ArrayIterator) {
1073-
zval_ptr_dtor(&intern->array);
1077+
ZVAL_COPY_VALUE(&garbage, &intern->array);
10741078
if (just_array) {
10751079
spl_array_object *other = Z_SPLARRAY_P(array);
10761080
ar_flags = other->ar_flags & ~SPL_ARRAY_INT_MASK;
@@ -1088,9 +1092,10 @@ static void spl_array_set_array(zval *object, spl_array_object *intern, zval *ar
10881092
zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0,
10891093
"Overloaded object of type %s is not compatible with %s",
10901094
ZSTR_VAL(Z_OBJCE_P(array)->name), ZSTR_VAL(intern->std.ce->name));
1095+
ZEND_ASSERT(Z_TYPE(garbage) == IS_UNDEF);
10911096
return;
10921097
}
1093-
zval_ptr_dtor(&intern->array);
1098+
ZVAL_COPY_VALUE(&garbage, &intern->array);
10941099
ZVAL_COPY(&intern->array, array);
10951100
}
10961101
}
@@ -1101,6 +1106,8 @@ static void spl_array_set_array(zval *object, spl_array_object *intern, zval *ar
11011106
zend_hash_iterator_del(intern->ht_iter);
11021107
intern->ht_iter = (uint32_t)-1;
11031108
}
1109+
1110+
zval_ptr_dtor(&garbage);
11041111
}
11051112
/* }}} */
11061113

ext/spl/tests/gh16646.phpt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
--TEST--
2+
GH-16646: Use-after-free in ArrayObject::unset() with destructor
3+
--FILE--
4+
<?php
5+
6+
class B {
7+
public $b;
8+
function __construct($arg) {
9+
$this->b = $arg;
10+
}
11+
}
12+
13+
class C {
14+
function __destruct() {
15+
global $arr;
16+
echo __METHOD__, "\n";
17+
$arr->exchangeArray([]);
18+
}
19+
}
20+
21+
$arr = new ArrayObject(new B(new C));
22+
unset($arr["b"]);
23+
var_dump($arr);
24+
25+
?>
26+
--EXPECT--
27+
C::__destruct
28+
object(ArrayObject)#1 (1) {
29+
["storage":"ArrayObject":private]=>
30+
array(0) {
31+
}
32+
}

ext/spl/tests/gh16646_2.phpt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--TEST--
2+
GH-16646: Use-after-free in ArrayObject::exchangeArray() with destructor
3+
--FILE--
4+
<?php
5+
6+
class C {
7+
function __destruct() {
8+
global $arr;
9+
echo __METHOD__, "\n";
10+
$arr->exchangeArray([]);
11+
}
12+
}
13+
14+
$arr = new ArrayObject(new C);
15+
$arr->exchangeArray([]);
16+
var_dump($arr);
17+
18+
?>
19+
--EXPECT--
20+
C::__destruct
21+
object(ArrayObject)#1 (1) {
22+
["storage":"ArrayObject":private]=>
23+
array(0) {
24+
}
25+
}

0 commit comments

Comments
 (0)