[php-src] Issue #24730: extract() with EXTR_REFS: use-after-free still present in modes other than EXTR_OVERWRITE (follow-up to #18209)
| From: | IzaiahSun | Date: | Mon, 26 Oct 2026 08:49:03 +0000 |
| Subject: | [php-src] Issue #24730: extract() with EXTR_REFS: use-after-free still present in modes other than EXTR_OVERWRITE (follow-up to #18209) | ||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-253208@lists.php.net to get a copy of this message | ||
Issue: https://github.com/php/php-src/issues/24730
Author: IzaiahSun
### Description
The following code:
```php
<?php
class Old { public function __destruct() { $GLOBALS['b'] = 43; } }
$b = new Old;
$array = ['b' => 42];
extract($array, EXTR_REFS | EXTR_IF_EXISTS);
```
Resulted in this output:
```
==72028==ERROR: AddressSanitizer: heap-use-after-free on address 0x604000028f90 at pc 0x000103a9b2f4
bp 0x00016d37fa90 sp 0x00016d37fa88
READ of size 4 at 0x604000028f90 thread T0
#0 zend_gc_delref zend_types.h:1370
#1 zend_objects_store_del zend_objects_API.c:182
#2 rc_dtor_func zend_variables.c:57
#3 i_zval_ptr_dtor zend_variables.h:45
#4 zval_ptr_dtor zend_variables.c:84
#5 php_extract_ref_if_exists array.c:1911
#6 zif_extract array.c:2663
#7 ZEND_DO_ICALL_SPEC_RETVAL_UNUSED_HANDLER zend_vm_execute.h:1287
#8 execute_ex zend_vm_execute.h:58729
#9 zend_execute zend_vm_execute.h:64381
#10 zend_execute_script zend.c:1943
freed by thread T0 here:
#0 free(libclang_rt.asan_osx_dynamic.dylib:arm64e)
#1 __zend_free zend_alloc.c:3728
#2 _efree zend_alloc.c:2889
#3 zend_objects_store_del zend_objects_API.c:200
#4 zend_object_release zend_objects_API.h:77
#5 zend_objects_destroy_object zend_objects.c:209
#6 zend_objects_store_del zend_objects_API.c:181
#7 rc_dtor_func zend_variables.c:57
#8 i_zval_ptr_dtor zend_variables.h:45
#9 zval_ptr_dtor zend_variables.c:84
#10 php_extract_ref_if_exists array.c:1911
SUMMARY: AddressSanitizer: heap-use-after-free zend_types.h:1370 in zend_gc_delref
```
But I expected this output instead:
```
(no output, clean exit)
```
---
Debug + ASan build, run with
USE_ZEND_ALLOC=0.
The fix for #18209 was applied to php_extract_ref_overwrite() only. The five sibling
functions still destroy the old variable before overwriting the slot, so the same
use-after-free is reachable through the other EXTR_REFS modes.
ext/standard/array.c, e.g. php_extract_ref_if_exists():
```c
zval_ptr_dtor(orig_var);
ZVAL_REF(orig_var, Z_REF_P(entry));
```
Affected: php_extract_ref_if_exists, php_extract_ref_prefix_if_exists,
php_extract_ref_prefix_all, php_extract_ref_prefix_same,
php_extract_ref_prefix_invalid.
EXTR_REFS|EXTR_OVERWRITE is clean, as expected after #18209.
The other four modes crash the same way with the variable named to match the mode:
| Mode | Reproducer differs by |
| --- | --- |
| EXTR_REFS\|EXTR_PREFIX_ALL, prefix p | $p_b = new Old; |
| EXTR_REFS\|EXTR_PREFIX_SAME, prefix p | $b = 1; $p_b = new
Old; |
| EXTR_REFS\|EXTR_PREFIX_IF_EXISTS, prefix p | $b = 1; $p_b = new
Old; |
| EXTR_REFS\|EXTR_PREFIX_INVALID, prefix p | $p_0 = new Old;
with [0 => 42] |
### PHP Version
```plain
PHP 8.4.28-dev (cli) (built: Oct 25 2026 01:02:13) (NTS DEBUG)
Copyright (c) The PHP Group
Zend Engine v4.4.28-dev, Copyright (c) Zend Technologies
Also reproduced on master (29b5e168bf):
PHP 8.7.0-dev (cli) (built: Oct 24 2026 21:13:17) (NTS DEBUG)
Copyright © The PHP Group and Contributors
Zend Engine v4.7.0-dev, Copyright © Zend by Perforce
with Zend OPcache v8.7.0-dev, Copyright ©, by Zend by Perforce
```
### Operating System
macOS 27.0.1 (arm64)