php / php/php-src

use-after-free at phpdbg shutdown

未关闭
#13,230 1 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

Bug SAPI: phpdbg Status: Verified
主要语言
C
星标
40.4k
派生
8.1k
平均合并
2 天 13 小时
30 天内合并 PR
96

描述

Summary

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

Details
  • OS: Ubuntu 22.04
  • commit: 6fa4286ba4712963c2a07fdb178c239e04ad4da4 (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

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

调研方向

从 sapi/phpdbg/phpdbg_watch.c 开始,重点查看 phpdbg_remove_watchpoint、phpdbg_unwatch_parent_ht、phpdbg_clean_watch_element 和 phpdbg_destroy_watchpoints;然后检查 sapi/phpdbg/phpdbg.c 中的 shutdown 路径,以及所引用的 Zend 和 PHP shutdown 调用。使用报告中的 phpdbg 交互运行 sapi/phpdbg/tests/bug73927.phpt,并验证 shutdown 不再访问已释放的 watchpoint。

由索引模型根据 Issue 内容生成。

评估

技术栈
c
领域
cli, devtools
Issue 类型
缺陷
难度
4/5
预计耗时
3-5 天
活跃度
停滞
描述清晰度
基本清楚
新手友好度
45/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。