Skip to content

Commit a9d11b6

Browse files
committed
Merge branch 'PHP-8.2'
2 parents 7ec8ae1 + cfbb47b commit a9d11b6

File tree

3 files changed

+68
-7
lines changed

3 files changed

+68
-7
lines changed

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
@@ -314,8 +314,9 @@ ZEND_API void ZEND_FASTCALL zend_hash_packed_grow(HashTable *ht)
314314
if (ht->nTableSize >= HT_MAX_SIZE) {
315315
zend_error_noreturn(E_ERROR, "Possible integer overflow in memory allocation (%u * %zu + %zu)", ht->nTableSize * 2, sizeof(Bucket), sizeof(Bucket));
316316
}
317-
ht->nTableSize += ht->nTableSize;
318-
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));
317+
uint32_t newTableSize = ht->nTableSize * 2;
318+
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));
319+
ht->nTableSize = newTableSize;
319320
}
320321

321322
ZEND_API void ZEND_FASTCALL zend_hash_real_init(HashTable *ht, bool packed)
@@ -353,8 +354,9 @@ ZEND_API void ZEND_FASTCALL zend_hash_packed_to_hash(HashTable *ht)
353354
ZEND_ASSERT(HT_SIZE_TO_MASK(nSize));
354355

355356
HT_ASSERT_RC1(ht);
356-
HT_FLAGS(ht) &= ~HASH_FLAG_PACKED;
357+
// Alloc before assign to avoid inconsistencies on OOM
357358
new_data = pemalloc(HT_SIZE_EX(nSize, HT_SIZE_TO_MASK(nSize)), GC_FLAGS(ht) & IS_ARRAY_PERSISTENT);
359+
HT_FLAGS(ht) &= ~HASH_FLAG_PACKED;
358360
ht->nTableMask = HT_SIZE_TO_MASK(ht->nTableSize);
359361
HT_SET_DATA_ADDR(ht, new_data);
360362
dst = ht->arData;
@@ -408,17 +410,18 @@ ZEND_API void ZEND_FASTCALL zend_hash_extend(HashTable *ht, uint32_t nSize, bool
408410
if (packed) {
409411
ZEND_ASSERT(HT_IS_PACKED(ht));
410412
if (nSize > ht->nTableSize) {
411-
ht->nTableSize = zend_hash_check_size(nSize);
412-
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));
413+
uint32_t newTableSize = zend_hash_check_size(nSize);
414+
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));
415+
ht->nTableSize = newTableSize;
413416
}
414417
} else {
415418
ZEND_ASSERT(!HT_IS_PACKED(ht));
416419
if (nSize > ht->nTableSize) {
417420
void *new_data, *old_data = HT_GET_DATA_ADDR(ht);
418421
Bucket *old_buckets = ht->arData;
419422
nSize = zend_hash_check_size(nSize);
420-
ht->nTableSize = nSize;
421423
new_data = pemalloc(HT_SIZE_EX(nSize, HT_SIZE_TO_MASK(nSize)), GC_FLAGS(ht) & IS_ARRAY_PERSISTENT);
424+
ht->nTableSize = nSize;
422425
ht->nTableMask = HT_SIZE_TO_MASK(ht->nTableSize);
423426
HT_SET_DATA_ADDR(ht, new_data);
424427
memcpy(ht->arData, old_buckets, sizeof(Bucket) * ht->nNumUsed);
@@ -1247,8 +1250,8 @@ static void ZEND_FASTCALL zend_hash_do_resize(HashTable *ht)
12471250

12481251
ZEND_ASSERT(HT_SIZE_TO_MASK(nSize));
12491252

1250-
ht->nTableSize = nSize;
12511253
new_data = pemalloc(HT_SIZE_EX(nSize, HT_SIZE_TO_MASK(nSize)), GC_FLAGS(ht) & IS_ARRAY_PERSISTENT);
1254+
ht->nTableSize = nSize;
12521255
ht->nTableMask = HT_SIZE_TO_MASK(ht->nTableSize);
12531256
HT_SET_DATA_ADDR(ht, new_data);
12541257
memcpy(ht->arData, old_buckets, sizeof(Bucket) * ht->nNumUsed);

0 commit comments

Comments
 (0)