Repository navigation
Heap buffer overflow in property hooks (zho_build_properties_ex) via re-entrant getter during var_export() #20479
Copy link
Copy link
Closed
Description
Description
The following code:
<?php
#[AllowDynamicProperties]
class ByRef {
private $_virtualByRef = 'virtualByRef';
public $virtualByRef {
&get {
return $this->_virtualByRef;
}
set {
$this->_virtualByRef = $value;
}
}
public $virtualSetOnly {
set {
}
}
public function __construct() {
$this->undef = 'dynamic';
$this->dynamic = 'dynamic';
}
}
#[AllowDynamicProperties]
class ByVal extends ByRef {
private $_virtualByVal = 'virtualByVal';
public $virtualByVal {
get {
return $this->_virtualByVal;
}
set {
$this->_virtualByVal = $value;
}
}
public $backed = 'backed' {
get {
return $this->backed;
}
set {
$this->backed = $value;
}
}
public string $backedUninixialized {
get {
$this->_this = $this->_thisx= $this;
$a[0] =//////
var_export($this);
return $this->backedUninitialized;
}
set {
$this->backedUninitialized = $value;
}
}
}
function testByVal($object) {
foreach ($object as $prop => $value) {
$object->{$prop}!= strtoupper($value);
}
var_dump($object);
}
testByVal(new ByVal);
testByVal(new ByRef);Resulted in this output:
=================================================================
==2914108==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x61200002f580 at pc 0x558dee39b4fe bp 0x7ffd74ab0f50 sp 0x7ffd74ab0f48
WRITE of size 8 at 0x61200002f580 thread T0
#0 0x558dee39b4fd in _zend_hash_append_ex /path/to/php-src/Zend/zend_hash.h:1641:2
#1 0x558dee39afa6 in _zend_hash_append /path/to/php-src/Zend/zend_hash.h:1659:9
#2 0x558dee39a060 in zho_build_properties_ex /path/to/php-src/Zend/zend_property_hooks.c:124:16
#3 0x558dee39921c in zend_hooked_object_build_properties /path/to/php-src/Zend/zend_property_hooks.c:141:9
#4 0x558dee3539e6 in zend_std_get_properties_for /path/to/php-src/Zend/zend_object_handlers.c:2508:12
#5 0x558dee353e67 in zend_get_properties_for /path/to/php-src/Zend/zend_object_handlers.c:2546:9
#6 0x558dedad55ce in php_var_export_ex /path/to/php-src/ext/standard/var.c:603:11
#7 0x558dedad7b58 in zif_var_export /path/to/php-src/ext/standard/var.c:707:23
#8 0x558dee140bd1 in ZEND_DO_ICALL_SPEC_RETVAL_USED_HANDLER /path/to/php-src/Zend/zend_vm_execute.h:1421:2
#9 0x558dedfabf42 in execute_ex /path/to/php-src/Zend/zend_vm_execute.h:116212:12
#10 0x558dedf86571 in zend_call_function /path/to/php-src/Zend/zend_execute_API.c:1014:3
#11 0x558dedf88782 in zend_call_known_function /path/to/php-src/Zend/zend_execute_API.c:1108:23
#12 0x558dee3546ea in zend_call_known_instance_method /path/to/php-src/Zend/zend_API.h:862:2
#13 0x558dee33a7fb in zend_call_known_instance_method_with_0_params /path/to/php-src/Zend/zend_API.h:868:2
#14 0x558dee340bfe in zend_call_get_hook /path/to/php-src/Zend/zend_object_handlers.c:733:2
#15 0x558dee33e731 in zend_std_read_property /path/to/php-src/Zend/zend_object_handlers.c:847:8
#16 0x558dede7242b in zend_read_property_ex /path/to/php-src/Zend/zend_API.c:5122:10
#17 0x558dee39cd5e in zho_declared_it_fetch_current /path/to/php-src/Zend/zend_property_hooks.c:179:17
#18 0x558dee39c7b8 in zho_it_fetch_current /path/to/php-src/Zend/zend_property_hooks.c:262:4
#19 0x558dee39bc4c in zho_it_valid /path/to/php-src/Zend/zend_property_hooks.c:288:2
#20 0x558dee1f80d4 in zend_fe_fetch_object_helper_SPEC /path/to/php-src/Zend/zend_vm_execute.h:3102:8
#21 0x558dee0c44e9 in ZEND_FE_FETCH_R_SPEC_VAR_HANDLER /path/to/php-src/Zend/zend_vm_execute.h:23604:3
#22 0x558dedfabf42 in execute_ex /path/to/php-src/Zend/zend_vm_execute.h:116212:12
#23 0x558dedfac847 in zend_execute /path/to/php-src/Zend/zend_vm_execute.h:121924:2
#24 0x558dee3dffd0 in zend_execute_script /path/to/php-src/Zend/zend.c:1975:3
#25 0x558dedbe07ab in php_execute_script_ex /path/to/php-src/main/main.c:2640:13
#26 0x558dedbe0ca8 in php_execute_script /path/to/php-src/main/main.c:2680:9
#27 0x558dee3e7ed2 in do_cli /path/to/php-src/sapi/cli/php_cli.c:951:5
#28 0x558dee3e4e2c in main /path/to/php-src/sapi/cli/php_cli.c:1362:18
#29 0x7fdf152f7d8f (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 4f7b0c955c3d81d7cac1501a2498b69d1d82bfe7)
#30 0x7fdf152f7e3f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x29e3f) (BuildId: 4f7b0c955c3d81d7cac1501a2498b69d1d82bfe7)
#31 0x558dec803374 in _start (/path/to/php-src/sapi/cli/php+0x603374) (BuildId: 7e30eed09f929fbd58cc0b481c8117cf438919d5)
0x61200002f580 is located 0 bytes to the right of 320-byte region [0x61200002f440,0x61200002f580)
allocated by thread T0 here:
#0 0x558dec8861be in malloc (/path/to/php-src/sapi/cli/php+0x6861be) (BuildId: 7e30eed09f929fbd58cc0b481c8117cf438919d5)
#1 0x558dede2f143 in __zend_malloc /path/to/php-src/Zend/zend_alloc.c:3543:14
#2 0x558dede2ead0 in _emalloc /path/to/php-src/Zend/zend_alloc.c:2780:10
#3 0x558dee23a1ff in zend_hash_real_init_mixed_ex /path/to/php-src/Zend/zend_hash.c:176:10
#4 0x558dee23a037 in zend_hash_real_init_mixed /path/to/php-src/Zend/zend_hash.c:342:2
#5 0x558dee3993b4 in zho_build_properties_ex /path/to/php-src/Zend/zend_property_hooks.c:61:2
#6 0x558dee39921c in zend_hooked_object_build_properties /path/to/php-src/Zend/zend_property_hooks.c:141:9
#7 0x558dee3539e6 in zend_std_get_properties_for /path/to/php-src/Zend/zend_object_handlers.c:2508:12
#8 0x558dee353e67 in zend_get_properties_for /path/to/php-src/Zend/zend_object_handlers.c:2546:9
#9 0x558dedad55ce in php_var_export_ex /path/to/php-src/ext/standard/var.c:603:11
#10 0x558dedad7b58 in zif_var_export /path/to/php-src/ext/standard/var.c:707:23
#11 0x558dee140bd1 in ZEND_DO_ICALL_SPEC_RETVAL_USED_HANDLER /path/to/php-src/Zend/zend_vm_execute.h:1421:2
#12 0x558dedfabf42 in execute_ex /path/to/php-src/Zend/zend_vm_execute.h:116212:12
#13 0x558dedf86571 in zend_call_function /path/to/php-src/Zend/zend_execute_API.c:1014:3
#14 0x558dedf88782 in zend_call_known_function /path/to/php-src/Zend/zend_execute_API.c:1108:23
#15 0x558dee3546ea in zend_call_known_instance_method /path/to/php-src/Zend/zend_API.h:862:2
#16 0x558dee33a7fb in zend_call_known_instance_method_with_0_params /path/to/php-src/Zend/zend_API.h:868:2
#17 0x558dee340bfe in zend_call_get_hook /path/to/php-src/Zend/zend_object_handlers.c:733:2
#18 0x558dee33e731 in zend_std_read_property /path/to/php-src/Zend/zend_object_handlers.c:847:8
#19 0x558dede7242b in zend_read_property_ex /path/to/php-src/Zend/zend_API.c:5122:10
#20 0x558dee39cd5e in zho_declared_it_fetch_current /path/to/php-src/Zend/zend_property_hooks.c:179:17
#21 0x558dee39c7b8 in zho_it_fetch_current /path/to/php-src/Zend/zend_property_hooks.c:262:4
#22 0x558dee39bc4c in zho_it_valid /path/to/php-src/Zend/zend_property_hooks.c:288:2
#23 0x558dee1f80d4 in zend_fe_fetch_object_helper_SPEC /path/to/php-src/Zend/zend_vm_execute.h:3102:8
#24 0x558dee0c44e9 in ZEND_FE_FETCH_R_SPEC_VAR_HANDLER /path/to/php-src/Zend/zend_vm_execute.h:23604:3
#25 0x558dedfabf42 in execute_ex /path/to/php-src/Zend/zend_vm_execute.h:116212:12
#26 0x558dedfac847 in zend_execute /path/to/php-src/Zend/zend_vm_execute.h:121924:2
#27 0x558dee3dffd0 in zend_execute_script /path/to/php-src/Zend/zend.c:1975:3
#28 0x558dedbe07ab in php_execute_script_ex /path/to/php-src/main/main.c:2640:13
#29 0x558dedbe0ca8 in php_execute_script /path/to/php-src/main/main.c:2680:9
SUMMARY: AddressSanitizer: heap-buffer-overflow /path/to/php-src/Zend/zend_hash.h:1641:2 in _zend_hash_append_ex
Shadow bytes around the buggy address:
0x0c247fffde60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c247fffde70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c247fffde80: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00
0x0c247fffde90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c247fffdea0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c247fffdeb0:[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c247fffdec0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c247fffded0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c247fffdee0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c247fffdef0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c247fffdf00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==2914108==ABORTING
Commit:
9cd367362da5442861f30d3b41e967d641b90cbd
Build configuration:
CC="clang" CXX="clang++" CFLAGS="-fsanitize=address -g -O0" CXXFLAGS="-fsanitize=address -g -O0" ./configure --enable-debug --enable-address-sanitizer --disable-shared --with-pic
PHP Version
PHP 8.6.0-dev (cli) (built: Nov 14 2025 16:07:53) (NTS DEBUG)
Copyright (c) The PHP Group
Zend Engine v4.6.0-dev, Copyright (c) Zend Technologies
with Zend OPcache v8.6.0-dev, Copyright (c), by Zend Technologies
Operating System
Ubuntu 22.04
Reactions are currently unavailable
Activity
devnexen commentedon Nov 14, 2025
Reproduced with
USE_ZEND_ALLOC=0, a bug in zend mm happens instead if set as 1.ndossche commentedon Nov 14, 2025
Reduced:
ndossche commentedon Nov 15, 2025
I think the logic error is that
zho_build_properties_excomputes too few number of required buckets. It does so by looking at the number of elements in the properties hash table; however this won't include the entry fortriggerbecause it doesn't have a backing value.EDIT: There might also be an issue in how UNDEF properties may be handled, because the property table building code skips those but the hooks code adds INDIRECTs to those.
ndossche commentedon Nov 15, 2025
Assuming the semantics are right, we'd either need to compute the number of buckets precisely a priori, or we can just do this:
Using add instead of append is slower but less error-prone.
My gut feeling also tells me we need to do this (see EDIT in previous comment):
cc @iluuu1994
iluuu1994 commentedon Jan 21, 2026
Sorry for the slight delay. 😅 The first part looks ok to me. The indirect part is actually important because it maintains the correct order for child properties where
offsetchanges (recall GH-17870). I didn't quite understand what you meant by "property table building code skips those", I'm not sure if you're referring toif (Z_TYPE_P(prop_value) == IS_INDIRECT) { continue; }just below, but the intention here is to skip declared properties and only add dynamic properties, which don't have anINDIRECTindirection. Do you want to create a PR? Edit: Nevermind, I'll create one for you. Edit 2: #20988ndossche commentedon Jan 21, 2026
Oh of course. I remember. That clears up the confusion I had about this.
Yeah I got confused about this because I didn't remember the true purpose of what I cited previously, so I confused the two.
Fix hooked object properties overflow
iluuu1994 commentedon Jan 21, 2026
Perfect, thanks for the feedback and patch. 🙂