Skip to content

use-after-free at phpdbg shutdown #13230

Open
@junwha

Description

@junwha

Summary

phpdbg accesses freed watchpoint object at php_module_shutdown which is already freed from php_request_shutdown.

Details

  • OS: Ubuntu 22.04
  • commit: 6fa4286 (8.4.0-dev)
  • compiler: Clang 15
  • build command: ./buildconf && ./configure && make
  • Credit: Junwha Hong from S2Lab in UNIST

This bug is triggered by the test case sapi/phpdbg/tests/bug73927.phpt

  1. The PoC creates 4 watch points from $value and $lower[]

  2. After receiving “q”, phpdbg calls php_request_shutdown, which calls zend_deactivate → … → phpdbg_remove_warchpoint. then, it frees the watchpoint 0x7ffff5260460 atphpdbg_remove_warchpoint (sapi/phpdbg/phpdbg_watch.c:968)

    void phpdbg_remove_watchpoint(phpdbg_watchpoint_t *watch) {
    	...
    	efree(watch);
    }
  3. The problem is that, phpdbg accesses to this watchpoint again through its parent element at the real shutdown php_module_shutdown, which calls zend_shutdown → … → phpdbg_unwatch_parent_ht

    void phpdbg_unwatch_parent_ht(phpdbg_watch_element *element) {
    	if (element->watch->type == WATCH_ON_BUCKET) {

    Thus, it is use-after-free.

Callstack 1 (free): php_request_shutdown > … > phpdbg_remove_watchpoint

#0  phpdbg_remove_watchpoint (watch=0x7ffff5260460) at sapi/phpdbg/phpdbg_watch.c:950
#1  0x00005555559ed309 in phpdbg_update_watch_element_watch (element=0x7ffff52613c0) at sapi/phpdbg/phpdbg_watch.c:707
#2  0x00005555559ed3f2 in phpdbg_update_watch_collision_elements (watch=watch@entry=0x7ffff5265180)
    at sapi/phpdbg/phpdbg_watch.c:944
#3  0x00005555559ed4b1 in phpdbg_remove_watchpoint (watch=watch@entry=0x7ffff5265180) at sapi/phpdbg/phpdbg_watch.c:958
#4  0x00005555559edec3 in phpdbg_watch_efree (ptr=ptr@entry=0x7ffff5259188) at sapi/phpdbg/phpdbg_watch.c:1210
#5  0x00005555559d402c in phpdbg_free_wrapper (p=0x7ffff5259188) at sapi/phpdbg/phpdbg.c:1088
#6  0x00005555558fee95 in _zend_hash_del_el_ex (ht=0x555556a1ac10 <executor_globals+304>, idx=8, prev=<optimized out>, 
    p=<optimized out>) at Zend/zend_hash.c:1488
#7  _zend_hash_del_el (ht=0x555556a1ac10 <executor_globals+304>, idx=8, p=<optimized out>) at Zend/zend_hash.c:1515
#8  zend_hash_graceful_reverse_destroy (ht=ht@entry=0x555556a1ac10 <executor_globals+304>) at Zend/zend_hash.c:2040
#9  0x00005555558d74a9 in zend_shutdown_executor_values (fast_shutdown=false) at Zend/zend_execute_API.c:285
#10 0x00005555558d7a5d in shutdown_executor () at Zend/zend_execute_API.c:417
#11 0x00005555558e7f9e in zend_deactivate () at Zend/zend.c:1290
#12 0x000055555587b146 in php_request_shutdown (dummy=dummy@entry=0x0) at main/main.c:1883
#13 0x00005555559d5adb in main (argc=82, argv=0x7fffffffda88) at sapi/phpdbg/phpdbg.c:1685

Callstack 2 (access): watch=0x7ffff5260460

Breakpoint 2, phpdbg_unwatch_parent_ht (element=element@entry=0x7ffff52613c0) at sapi/phpdbg/phpdbg_watch.c:670
670     void phpdbg_unwatch_parent_ht(phpdbg_watch_element *element) {
(gdb) p element->watch
$6 = (phpdbg_watchpoint_t *) 0x7ffff5260460

(gdb) bt
#0  phpdbg_unwatch_parent_ht (element=element@entry=0x7ffff52613c0) at sapi/phpdbg/phpdbg_watch.c:670
#1  0x00005555559ece54 in phpdbg_clean_watch_element (element=0x7ffff52613c0) at sapi/phpdbg/phpdbg_watch.c:973
#2  phpdbg_free_watch_element_tree (element=element@entry=0x7ffff5261480) at sapi/phpdbg/phpdbg_watch.c:902
#3  0x00005555559ee8d1 in phpdbg_automatic_dequeue_free (element=0x7ffff5261480) at sapi/phpdbg/phpdbg_watch.c:771
#4  phpdbg_destroy_watchpoints () at sapi/phpdbg/phpdbg_watch.c:1500
#5  0x00005555559d6812 in zm_shutdown_phpdbg (type=<optimized out>, module_number=<optimized out>)
    at sapi/phpdbg/phpdbg.c:181
#6  0x00005555558f2831 in module_destructor (module=0x555556a02f88 <sapi_phpdbg_module_entry>) at Zend/zend_API.c:3163
#7  0x00005555558fee95 in _zend_hash_del_el_ex (ht=0x555556a1b2c8 <module_registry>, idx=26, prev=<optimized out>, 
    p=<optimized out>) at Zend/zend_hash.c:1488
#8  _zend_hash_del_el (ht=0x555556a1b2c8 <module_registry>, idx=26, p=<optimized out>) at Zend/zend_hash.c:1515
#9  zend_hash_graceful_reverse_destroy (ht=0x555556a1b2c8 <module_registry>) at Zend/zend_hash.c:2040
#10 0x00005555558e7bf1 in zend_shutdown () at Zend/zend.c:1125
#11 0x000055555587caa6 in php_module_shutdown () at main/main.c:2376
#12 0x00005555559d5dd2 in main (argc=82, argv=0x7fffffffda88) at sapi/phpdbg/phpdbg.c:1748

PoC

This bug is also triggered by the test case sapi/phpdbg/tests/bug73927.phpt

Command

sapi/phpdbg/phpdbg -qIb -d opcache.cache_id=worker1 -d output_handler= -d open_basedir= -d disable_functions= -d output_buffering=Off -d error_reporting=32767 -d display_errors=1 -d display_startup_errors=1 -d log_errors=0 -d html_errors=0 -d track_errors=0 -d report_memleaks=1 -d report_zend_debug=0 -d docref_root= -d docref_ext=.html -d error_prepend_string= -d error_append_string= -d auto_prepend_file= -d auto_append_file= -d ignore_repeated_errors=0 -d precision=14 -d serialize_precision=-1 -d memory_limit=128M -d opcache.fast_shutdown=0 -d opcache.file_update_protection=0 -d opcache.revalidate_freq=0 -d opcache.jit_hot_loop=1 -d opcache.jit_hot_func=1 -d opcache.jit_hot_return=1 -d opcache.jit_hot_side_exit=1 -d opcache.jit_max_root_traces=100000 -d opcache.jit_max_side_traces=100000 -d opcache.jit_max_exit_counters=100000 -d opcache.protect_memory=1 -d zend.assertions=1 -d zend.exception_ignore_args=0 -d zend.exception_string_param_max_len=15 -d short_open_tag=0 -d session.auto_start=0 -f sapi/phpdbg/tests/bug73927.php

In phpdbg

prompt> b 18
prompt>r
prompt>c
prompt>w $value
prompt>w $lower[]
prompt>q

Analyze using GDB

b phpdbg_remove_watchpoint

Breakpoint 1, phpdbg_remove_watchpoint (watch=0x7ffff5260500) at sapi/phpdbg/phpdbg_watch.c:950
Breakpoint 1, phpdbg_remove_watchpoint (watch=watch@entry=0x7ffff5265180) at sapi/phpdbg/phpdbg_watch.c:950
Breakpoint 1, phpdbg_remove_watchpoint (watch=0x7ffff5260460) at sapi/phpdbg/phpdbg_watch.c:950
Breakpoint 1, phpdbg_remove_watchpoint (watch=0x7ffff5260320) at sapi/phpdbg/phpdbg_watch.c:950

b phpdbg_unwatch_parent_ht

Breakpoint 2, phpdbg_unwatch_parent_ht (element=element@entry=0x7ffff5261240) at sapi/phpdbg/phpdbg_watch.c:670
670     void phpdbg_unwatch_parent_ht(phpdbg_watch_element *element) {
Breakpoint 2, phpdbg_unwatch_parent_ht (element=element@entry=0x7ffff52613c0) at sapi/phpdbg/phpdbg_watch.c:670
670     void phpdbg_unwatch_parent_ht(phpdbg_watch_element *element) {
(gdb) p element->watch
$6 = (phpdbg_watchpoint_t *) 0x7ffff5260460

Thank you for taking the time to check this bug report!:)

PHP Version

PHP 8.4.0-dev

Operating System

Ubuntu 22.04

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions