Skip to content

Commit 4a7ee3d

Browse files
committed
Address review: clarify ordering comment + add unset variant test
1 parent f761d44 commit 4a7ee3d

4 files changed

Lines changed: 513 additions & 297 deletions

File tree

Zend/tests/gh20482_assign_dim_uaf_output_handler.phpt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
--TEST--
2-
GH-20482: Heap use-after-free in ZEND_ASSIGN_DIM when an output handler clobbers the array during the undefined-variable warning
2+
GH-20482: heap use-after-free in ZEND_ASSIGN_DIM when an output handler clobbers the array during the undefined-variable warning
33
--FILE--
44
<?php
55
$a = [1, 2, 3];
@@ -21,7 +21,6 @@ ob_end_clean();
2121
var_dump($a);
2222
echo "ok\n";
2323
?>
24-
--EXPECTF--
25-
%AWarning: Undefined variable $undef in %s on line %d
24+
--EXPECT--
2625
string(5) "freed"
2726
ok
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
GH-20482: heap use-after-free in ZEND_ASSIGN_DIM when the output handler unsets the array during the undefined-variable warning
3+
--FILE--
4+
<?php
5+
$a = [1, 2, 3];
6+
7+
ob_start(function () use (&$a) {
8+
// Drop the last reference via unset, freeing the original array.
9+
unset($a);
10+
return '';
11+
}, 1);
12+
13+
$a[0] = $undef;
14+
15+
ob_end_clean();
16+
var_dump($a ?? null);
17+
echo "ok\n";
18+
?>
19+
--EXPECT--
20+
NULL
21+
ok

Zend/zend_vm_def.h

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2732,14 +2732,18 @@ ZEND_VM_C_LABEL(try_assign_dim_array):
27322732
} else {
27332733
HashTable *ht = Z_ARRVAL_P(object_ptr);
27342734
dim = GET_OP2_ZVAL_PTR_UNDEF(BP_VAR_R);
2735+
/* Order is critical: fetch the data value (which may emit an
2736+
* "Undefined variable" warning that can recurse into user code
2737+
* via a user output handler) before resolving variable_ptr.
2738+
* Resolving variable_ptr only afterwards guarantees a fresh
2739+
* bucket pointer that cannot have been invalidated by user
2740+
* code freeing the array, rehashing it, or unsetting keys. */
27352741
value = GET_OP_DATA_ZVAL_PTR_UNDEF(BP_VAR_R);
27362742
if (OP_DATA_TYPE == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) {
2737-
/* The undefined-variable warning may recurse into user code
2738-
* (e.g. a user output handler) and clobber or destroy the
2739-
* array we are about to write to. Temporarily addref the
2740-
* array to detect destruction, and refetch the dim address
2741-
* after the warning since the previous variable_ptr may have
2742-
* been invalidated by hash table mutations. */
2743+
/* Temporarily addref the array around the warning so we
2744+
* can detect a full destruction (last ref dropped by
2745+
* reentrant user code) and bail out cleanly. Mirrors the
2746+
* IS_UNUSED branch above. */
27432747
if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) {
27442748
GC_ADDREF(ht);
27452749
}

0 commit comments

Comments
 (0)