Skip to content

Freeing a phar alias may invalidate PharFileInfo objects #20302

Closed

Description

@YuanchengJiang

Description

The following code:

<?php
$fname = __DIR__ . '/' . basename(__FILE__, '.php') . '.phar';
$pname = 'phar://' . $fname;
$b = new PharFileInfo($pname . '/a/subdir/here');
require_once 'files/phar_oo_test.inc';
$phar = new Phar($fname);
{
var_dump($ent->getATime());
}

Resulted in this output:

/home/phpfuzz/WorkSpace/flowfusion/php-src/Zend/zend_string.h:329:2: runtime error: member access within misaligned address 0x000000000106 for type 'zend_string' (aka 'struct _zend_string'), which requires 8 byte alignment
0x000000000106: note: pointer points here
<memory cannot be printed>
    #0 0x34e7ea2 in zend_string_efree /home/phpfuzz/WorkSpace/flowfusion/php-src/Zend/zend_string.h:329:2
    #1 0x35075c8 in zim_PharFileInfo___destruct /home/phpfuzz/WorkSpace/flowfusion/php-src/ext/phar/phar_object.c:4469:4
    #2 0x5af4dbf in zend_call_function /home/phpfuzz/WorkSpace/flowfusion/php-src/Zend/zend_execute_API.c:1028:4
    #3 0x5afa84e in zend_call_known_function /home/phpfuzz/WorkSpace/flowfusion/php-src/Zend/zend_execute_API.c:1109:23
    #4 0x6789f84 in zend_call_known_instance_method /home/phpfuzz/WorkSpace/flowfusion/php-src/Zend/zend_API.h:866:2
    #5 0x678281b in zend_call_known_instance_method_with_0_params /home/phpfuzz/WorkSpace/flowfusion/php-src/Zend/zend_API.h:872:2
    #6 0x678139c in zend_objects_destroy_object /home/phpfuzz/WorkSpace/flowfusion/php-src/Zend/zend_objects.c:170:3
    #7 0x3e56af4 in spl_filesystem_object_destroy_object /home/phpfuzz/WorkSpace/flowfusion/php-src/ext/spl/spl_directory.c:109:2
    #8 0x677987a in zend_objects_store_del /home/phpfuzz/WorkSpace/flowfusion/php-src/Zend/zend_objects_API.c:181:4
    #9 0x6890767 in rc_dtor_func /home/phpfuzz/WorkSpace/flowfusion/php-src/Zend/zend_variables.c:57:2
    #10 0x68909ee in i_zval_ptr_dtor /home/phpfuzz/WorkSpace/flowfusion/php-src/Zend/zend_variables.h:45:4
    #11 0x68907a4 in zval_ptr_dtor /home/phpfuzz/WorkSpace/flowfusion/php-src/Zend/zend_variables.c:84:2
    #12 0x63b10e1 in _zend_hash_del_el_ex /home/phpfuzz/WorkSpace/flowfusion/php-src/Zend/zend_hash.c:1493:3
    #13 0x63ae85d in _zend_hash_del_el /home/phpfuzz/WorkSpace/flowfusion/php-src/Zend/zend_hash.c:1520:2
    #14 0x63c81a4 in zend_hash_reverse_apply /home/phpfuzz/WorkSpace/flowfusion/php-src/Zend/zend_hash.c:2236:5
    #15 0x5ad035c in shutdown_destructors /home/phpfuzz/WorkSpace/flowfusion/php-src/Zend/zend_execute_API.c:262:4
    #16 0x68d94bb in zend_call_destructors /home/phpfuzz/WorkSpace/flowfusion/php-src/Zend/zend.c:1336:3
    #17 0x50af9a3 in php_request_shutdown /home/phpfuzz/WorkSpace/flowfusion/php-src/main/main.c:1980:3
    #18 0x6906b41 in do_cli /home/phpfuzz/WorkSpace/flowfusion/php-src/sapi/cli/php_cli.c:1158:3
    #19 0x68fbb1f in main /home/phpfuzz/WorkSpace/flowfusion/php-src/sapi/cli/php_cli.c:1362:18
    #20 0x7962e22abd8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #21 0x7962e22abe3f in __libc_start_main csu/../csu/libc-start.c:392:3
    #22 0x606204 in _start (/home/phpfuzz/WorkSpace/flowfusion/php-src/sapi/cli/php+0x606204)

SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /home/phpfuzz/WorkSpace/flowfusion/php-src/Zend/zend_string.h:329:2

To reproduce:

./php-src/sapi/cli/php -d "phar.require_hash=0" -d "phar.readonly=1" -d "opcache.enable=1" -d "opcache.enable_cli=1" -d "opcache.jit=1205" ./test.php

Commit:

f8656fae35d90f89f2cef6a32c7173aa0c6b27a8

Configurations:

CC="clang-12" CXX="clang++-12" CFLAGS="-DZEND_VERIFY_TYPE_INFERENCE" CXXFLAGS="-DZEND_VERIFY_TYPE_INFERENCE" ./configure --enable-debug --enable-address-sanitizer --enable-undefined-sanitizer --enable-re2c-cgoto --enable-fpm --enable-litespeed --enable-phpdbg-debug --enable-zts --enable-bcmath --enable-calendar --enable-dba --enable-dl-test --enable-exif --enable-ftp --enable-gd --enable-gd-jis-conv --enable-mbstring --enable-pcntl --enable-shmop --enable-soap --enable-sockets --enable-sysvmsg --enable-zend-test --with-zlib --with-bz2 --with-curl --with-enchant --with-gettext --with-gmp --with-mhash --with-ldap --with-libedit --with-readline --with-snmp --with-sodium --with-xsl --with-zip --with-mysqli --with-pdo-mysql --with-pdo-pgsql --with-pgsql --with-sqlite3 --with-pdo-sqlite --with-webp --with-jpeg --with-freetype --enable-sigchild --with-readline --with-pcre-jit --with-iconv

Operating System:

Ubuntu 20.04 Host, Docker 0599jiangyc/flowfusion:latest

This report is automatically generated by FlowFusion

PHP Version

nightly

Operating System

No response

Activity

  1. devnexen commented on Oct 26, 2025

    @devnexen
    Member

    cannot reproduce this one.

  2. YuanchengJiang commented on Oct 26, 2025

    @YuanchengJiang
    Author

    you might need ext/phar/tests/files/phar_oo_test.inc file and -d "phar.require_hash=0" -d "phar.readonly=1" -d "opcache.enable=1" -d "opcache.enable_cli=1" -d "opcache.jit=1205"

  3. devnexen commented on Oct 26, 2025

    @devnexen
    Member

    oh I have these, still cannot.

  4. ndossche commented on Oct 26, 2025

    @ndossche
    Member

    This code will try to open test.phar. However, that file might've been generated by some other code that was executed. That phar file also needs to have the a/subdir/here file entry. Can you share that test.phar file?

  5. YuanchengJiang commented on Oct 27, 2025

    @YuanchengJiang
    Author

    a new self-contained poc (not minimzed), it might need multiple tries to reproduce:

    <?php
    $cls = new ZendTestClassWithMethodWithParameterAttribute();
    $fname = __DIR__ . '/' . basename(__FILE__, '.php') . '.phar';
    $pname = 'phar://' . $fname;
    $file = "<?php
    Phar::mapPhar('hio');
    __HALT_COMPILER(); ?>";
    $files = array();
    $files['a/subdir/here'] = array('cont'=>'a','ulen'=>1,'clen'=>1);
    include 'files/phar_test.inc';
    $b = new PharFileInfo($pname . '/a/subdir');
    try {
    var_dump($b->getCRC32());
    } catch (Exception $e) {
    echo $e->getMessage() . "\n";
    }
    $b = new PharFileInfo($pname . '/a/subdir/here');
    try {
    var_dump($b->getCRC32());
    } catch (Exception $e) {
    echo $e->getMessage() . "\n";
    }
    $a = file_get_contents($pname . '/a/subdir/here');
    try {
    var_dump($b->getCRC32());
    } catch (Exception $e) {
    echo $e->getMessage() . "\n";
    }
    $fusion = $fname;
    require_once 'files/phar_oo_test.inc';
    $phar = new Phar($fname);
    $phar->setInfoClass('SplFileInfo');
    foreach($phar as $name => $ent)
    {
        var_dump(str_replace(str_replace('\\', '/', __DIR__), '*', $name));
        var_dump($ent->getFilename());
        var_dump($ent->getSize());
        var_dump($ent->getType());
        var_dump($ent->isWritable());
        var_dump($fusion->isReadable());
        var_dump($ent->isExecutable());
        var_dump($ent->isFile());
        var_dump($ent->isDir());
        var_dump($ent->isLink());
        var_dump($ent->getCTime());
        var_dump($ent->getMTime());
        var_dump($ent->getATime());
    }
    echo "==RECURSIVE==\n";
    $phar = new Phar($fname);
    foreach(new RecursiveIteratorIterator($phar) as $name => $ent)
    {
        var_dump(str_replace(str_replace('\\', '/', __DIR__), '*', $name));
        var_dump(str_replace('\\', '/', $ent->getFilename()));
        var_dump($ent->getCompressedSize());
        var_dump($ent->isCRCChecked());
        var_dump($ent->isCRCChecked() ? $ent->getCRC32() : NULL);
        var_dump($ent->getPharFlags());
    }
    var_dump(get_defined_vars());
  6. ndossche commented on Oct 27, 2025

    @ndossche
    Member

    Already reduced to this but need to continue further:

    <?php
    $fname = 'x.phar';
    $pname = 'phar://' . $fname;
    $file = "<?php
    __HALT_COMPILER(); ?>";
    $files = array();
    $files['a/subdir/here'] = array('cont' => 'a');
    include 'phar_test.inc';
    $b = new PharFileInfo($pname . '/a/subdir/here');
    require_once 'files/phar_oo_test.inc';
    $phar = new Phar($fname);
  7. ndossche commented on Oct 27, 2025

    @ndossche
    Member

    Further reduced:

    <?php
    $fname = 'x.phar';
    $pname = 'phar://' . $fname;
    $file = "<?php
    __HALT_COMPILER(); ?>";
    $files = array();
    $files['here'] = 'a';
    include 'phar_test.inc';
    $b = new PharFileInfo($pname . '/here');
    
    $fname = 'files/x.phar.php';
    $pname = 'phar://' . $fname;
    include 'phar_test.inc';
    
    $phar = new Phar($fname);

    There are two phars with the same alias, the last phar replaces the first one in memory, but the PharFileInfo object $b still refers to an entry of the first phar. Loading the new phar with the same alias shouldn't be allowed. This can be solved by holding a reference to the original phar, then phar_free_alias won't be able to free the alias and replace the phar.

    diff --git a/ext/phar/phar_object.c b/ext/phar/phar_object.c
    index fba332aa21a..7714375dd6a 100644
    --- a/ext/phar/phar_object.c
    +++ b/ext/phar/phar_object.c
    @@ -4432,6 +4432,8 @@ PHP_METHOD(PharFileInfo, __construct)
     	entry_obj->entry = entry_info;
     	if (!entry_info->is_persistent && !entry_info->is_temp_dir) {
     		++entry_info->fp_refcount;
    +		/* The phar data must exist to keep the alias locked. */
    +		++phar_data->refcount;
     	}
     
     	ZVAL_STRINGL(&arg1, fname, fname_len);
    @@ -4464,19 +4466,23 @@ PHP_METHOD(PharFileInfo, __destruct)
     
     	PHAR_ENTRY_OBJECT_EX(false);
     
    -	if (entry_obj->entry->is_temp_dir) {
    -		if (entry_obj->entry->filename) {
    -			zend_string_efree(entry_obj->entry->filename);
    -			entry_obj->entry->filename = NULL;
    +	phar_entry_info *entry = entry_obj->entry;
    +	if (entry->is_temp_dir) {
    +		if (entry->filename) {
    +			zend_string_efree(entry->filename);
    +			entry->filename = NULL;
     		}
     
    -		efree(entry_obj->entry);
    -	} else if (!entry_obj->entry->is_persistent) {
    -		--entry_obj->entry->fp_refcount;
    -		/* It is necessarily still in the manifest, which will ultimately free this. */
    -	}
    +		efree(entry);
    +		entry_obj->entry = NULL;
    +	} else if (!entry->is_persistent) {
    +		--entry->fp_refcount;
    +		entry_obj->entry = NULL;
     
    -	entry_obj->entry = NULL;
    +		/* The entry itself still lives in the manifest,
    +		 * which will either be freed here if the file info was the last reference; or freed later. */
    +		phar_archive_delref(entry->phar);
    +	}
     }
     /* }}} */
     
    
  8. changed the title [-]SEGV phar zend_string.h[/-] [+]Freeing a phar alias may invalidate PharFileInfo objects[/+] on Oct 30, 2025
  9. added a commit that references this issue on Oct 30, 2025
    42a0100
  10. added 2 commits that reference this issue on Oct 31, 2025
    6fe40de
    ae7117b
  11. added 2 commits that reference this issue on Oct 31, 2025
    1dd866d
    455893e
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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