php / php/php-src

use-after-free at phpdbg shutdown

Ouverte
#13,230 1 commentaire 0 réactions 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

Bug SAPI: phpdbg Status: Verified
Langage dominant
C
Étoiles
40.4k
Forks
8.1k
Merge moyen
2 j 13 h
PR mergées (30 j)
96

Description

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

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Piste de recherche

Commencez par sapi/phpdbg/phpdbg_watch.c, en particulier phpdbg_remove_watchpoint, phpdbg_unwatch_parent_ht, phpdbg_clean_watch_element et phpdbg_destroy_watchpoints ; examinez ensuite les chemins d’arrêt dans sapi/phpdbg/phpdbg.c ainsi que les appels d’arrêt de Zend et de PHP référencés. Exécutez sapi/phpdbg/tests/bug73927.phpt avec l’interaction phpdbg indiquée et vérifiez que l’arrêt n’accède plus au watchpoint libéré.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
c
Domaine
cli, devtools
Type d'issue
Bug
Difficulté
4/5
Temps estimé
3-5 jours
Activité
À l'abandon
Clarté
Plutôt claire
Accessibilité débutants
45/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.