Skip to content

Commit cfbb47b

Browse files
committed
Merge branch 'PHP-8.1' into PHP-8.2
2 parents ab3f584 + 05bd142 commit cfbb47b

File tree

4 files changed

+70
-7
lines changed

4 files changed

+70
-7
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ PHP NEWS
77
names). (ilutov)
88
. Fixed bug GH-9068 (Conditional jump or move depends on uninitialised
99
value(s)). (nielsdos)
10+
. Fixed bug GH-11189 (Exceeding memory limit in zend_hash_do_resize leaves
11+
the array in an invalid state). (Bob)
1012

1113
- Opcache:
1214
. Fixed bug GH-11134 (Incorrect match default branch optimization). (ilutov)

Zend/tests/gh11189.phpt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
GH-11189: Exceeding memory limit in zend_hash_do_resize leaves the array in an invalid state (packed array)
3+
--SKIPIF--
4+
<?php
5+
if (getenv("USE_ZEND_ALLOC") === "0") die("skip ZMM is disabled");
6+
?>
7+
--INI--
8+
memory_limit=2M
9+
--FILE--
10+
<?php
11+
12+
ob_start(function() {
13+
global $a;
14+
for ($i = count($a); $i > 0; --$i) {
15+
$a[] = 2;
16+
}
17+
fwrite(STDOUT, "Success");
18+
});
19+
20+
$a = [];
21+
// trigger OOM in a resize operation
22+
while (1) {
23+
$a[] = 1;
24+
}
25+
26+
?>
27+
--EXPECTF--
28+
Success
29+
Fatal error: Allowed memory size of %s bytes exhausted%s(tried to allocate %s bytes) in %s on line %d

Zend/tests/gh11189_1.phpt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
GH-11189: Exceeding memory limit in zend_hash_do_resize leaves the array in an invalid state (not packed array)
3+
--SKIPIF--
4+
<?php
5+
if (getenv("USE_ZEND_ALLOC") === "0") die("skip ZMM is disabled");
6+
?>
7+
--INI--
8+
memory_limit=2M
9+
--FILE--
10+
<?php
11+
12+
ob_start(function() {
13+
global $a;
14+
for ($i = count($a); $i > 0; --$i) {
15+
$a[] = 2;
16+
}
17+
fwrite(STDOUT, "Success");
18+
});
19+
20+
$a = ["not packed" => 1];
21+
// trigger OOM in a resize operation
22+
while (1) {
23+
$a[] = 1;
24+
}
25+
26+
?>
27+
--EXPECTF--
28+
Success
29+
Fatal error: Allowed memory size of %s bytes exhausted%s(tried to allocate %s bytes) in %s on line %d

Zend/zend_hash.c

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -304,8 +304,9 @@ ZEND_API void ZEND_FASTCALL zend_hash_packed_grow(HashTable *ht)
304304
if (ht->nTableSize >= HT_MAX_SIZE) {
305305
zend_error_noreturn(E_ERROR, "Possible integer overflow in memory allocation (%u * %zu + %zu)", ht->nTableSize * 2, sizeof(Bucket), sizeof(Bucket));
306306
}
307-
ht->nTableSize += ht->nTableSize;
308-
HT_SET_DATA_ADDR(ht, perealloc2(HT_GET_DATA_ADDR(ht), HT_PACKED_SIZE_EX(ht->nTableSize, HT_MIN_MASK), HT_PACKED_USED_SIZE(ht), GC_FLAGS(ht) & IS_ARRAY_PERSISTENT));
307+
uint32_t newTableSize = ht->nTableSize * 2;
308+
HT_SET_DATA_ADDR(ht, perealloc2(HT_GET_DATA_ADDR(ht), HT_PACKED_SIZE_EX(newTableSize, HT_MIN_MASK), HT_PACKED_USED_SIZE(ht), GC_FLAGS(ht) & IS_ARRAY_PERSISTENT));
309+
ht->nTableSize = newTableSize;
309310
}
310311

311312
ZEND_API void ZEND_FASTCALL zend_hash_real_init(HashTable *ht, bool packed)
@@ -343,8 +344,9 @@ ZEND_API void ZEND_FASTCALL zend_hash_packed_to_hash(HashTable *ht)
343344
ZEND_ASSERT(HT_SIZE_TO_MASK(nSize));
344345

345346
HT_ASSERT_RC1(ht);
346-
HT_FLAGS(ht) &= ~HASH_FLAG_PACKED;
347+
// Alloc before assign to avoid inconsistencies on OOM
347348
new_data = pemalloc(HT_SIZE_EX(nSize, HT_SIZE_TO_MASK(nSize)), GC_FLAGS(ht) & IS_ARRAY_PERSISTENT);
349+
HT_FLAGS(ht) &= ~HASH_FLAG_PACKED;
348350
ht->nTableMask = HT_SIZE_TO_MASK(ht->nTableSize);
349351
HT_SET_DATA_ADDR(ht, new_data);
350352
dst = ht->arData;
@@ -398,17 +400,18 @@ ZEND_API void ZEND_FASTCALL zend_hash_extend(HashTable *ht, uint32_t nSize, bool
398400
if (packed) {
399401
ZEND_ASSERT(HT_IS_PACKED(ht));
400402
if (nSize > ht->nTableSize) {
401-
ht->nTableSize = zend_hash_check_size(nSize);
402-
HT_SET_DATA_ADDR(ht, perealloc2(HT_GET_DATA_ADDR(ht), HT_PACKED_SIZE_EX(ht->nTableSize, HT_MIN_MASK), HT_PACKED_USED_SIZE(ht), GC_FLAGS(ht) & IS_ARRAY_PERSISTENT));
403+
uint32_t newTableSize = zend_hash_check_size(nSize);
404+
HT_SET_DATA_ADDR(ht, perealloc2(HT_GET_DATA_ADDR(ht), HT_PACKED_SIZE_EX(newTableSize, HT_MIN_MASK), HT_PACKED_USED_SIZE(ht), GC_FLAGS(ht) & IS_ARRAY_PERSISTENT));
405+
ht->nTableSize = newTableSize;
403406
}
404407
} else {
405408
ZEND_ASSERT(!HT_IS_PACKED(ht));
406409
if (nSize > ht->nTableSize) {
407410
void *new_data, *old_data = HT_GET_DATA_ADDR(ht);
408411
Bucket *old_buckets = ht->arData;
409412
nSize = zend_hash_check_size(nSize);
410-
ht->nTableSize = nSize;
411413
new_data = pemalloc(HT_SIZE_EX(nSize, HT_SIZE_TO_MASK(nSize)), GC_FLAGS(ht) & IS_ARRAY_PERSISTENT);
414+
ht->nTableSize = nSize;
412415
ht->nTableMask = HT_SIZE_TO_MASK(ht->nTableSize);
413416
HT_SET_DATA_ADDR(ht, new_data);
414417
memcpy(ht->arData, old_buckets, sizeof(Bucket) * ht->nNumUsed);
@@ -1237,8 +1240,8 @@ static void ZEND_FASTCALL zend_hash_do_resize(HashTable *ht)
12371240

12381241
ZEND_ASSERT(HT_SIZE_TO_MASK(nSize));
12391242

1240-
ht->nTableSize = nSize;
12411243
new_data = pemalloc(HT_SIZE_EX(nSize, HT_SIZE_TO_MASK(nSize)), GC_FLAGS(ht) & IS_ARRAY_PERSISTENT);
1244+
ht->nTableSize = nSize;
12421245
ht->nTableMask = HT_SIZE_TO_MASK(ht->nTableSize);
12431246
HT_SET_DATA_ADDR(ht, new_data);
12441247
memcpy(ht->arData, old_buckets, sizeof(Bucket) * ht->nNumUsed);

0 commit comments

Comments
 (0)