Search code examples
phpiteratorphp-extension

Segmentation Fault ZVAL_STR() when accessing current data in iterator


I'm wondering if anyone can help me. I have been working on a php extension for google-sparsehashmap which I have working as I would / need it to, all except iteration.

I have been scratching my head for weeks trying to get it to work, using the classes built in iterator.

According to the backtrace I done on my test file, this is where SIGSEGV is thrown

static zval* php_sparsehashmap_iterator_current_data(php_sparsehashmap_iterator_t *iterator) {
    php_sparsehashmap_iterator_t *io = (php_sparsehashmap_iterator_t*)iterator;

    zend_string *_data;
    zval *data;

    _php_sparsehashmap_t *sp = PHP_SPARSEHASHMAP_FETCH_FROM(Z_OBJ(io->sparsehashmap));
    std::string current_data = sp->shm->current_data();
    _data = zend_string_init(current_data.c_str(), strlen(current_data.c_str()), 0);
    ZVAL_STR(data, _data);

    return data;
}

This is what GDB gives me when I run my test file.

Program received signal SIGSEGV, Segmentation fault.
php_sparsehashmap_iterator_current_data (iterator=<optimized out>) at php-sparsehashmap/iterator.cc:58
58      ZVAL_STR(data, _data);
(gdb) bt
#0  php_sparsehashmap_iterator_current_data (iterator=<optimized out>) at php-sparsehashmap/iterator.cc:58
#1  0x000055555586d215 in ?? ()
#2  0x0000555555875daf in execute_ex ()
#3  0x000055555587da21 in zend_execute ()
#4  0x00005555557f6963 in zend_execute_scripts ()
#5  0x0000555555795cc0 in php_execute_script ()
#6  0x000055555587fb56 in ?? ()
#7  0x000055555565bebb in ?? ()
#8  0x00007ffff764109b in __libc_start_main (main=0x55555565ba70, argc=2, argv=0x7fffffffe5d8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe5c8)
    at ../csu/libc-start.c:308
#9  0x000055555565c01a in _start ()

For a better look at what is happening when the Segmentation Fault occurs, I have run with valgrind with the options --leak-check=full and --track-origin=yes set.

==15401== Invalid write of size 8
==15401==    at 0xA1743D3: php_sparsehashmap_iterator_current_data(_php_sparsehashmap_iterator_t*) (iterator.cc:67)
==15401==    by 0x421214: ??? (in /usr/bin/php7.4)
==15401==    by 0x429DAE: execute_ex (in /usr/bin/php7.4)
==15401==    by 0x431A20: zend_execute (in /usr/bin/php7.4)
==15401==    by 0x3AA962: zend_execute_scripts (in /usr/bin/php7.4)
==15401==    by 0x349CBF: php_execute_script (in /usr/bin/php7.4)
==15401==    by 0x433B55: ??? (in /usr/bin/php7.4)
==15401==    by 0x20FEBA: ??? (in /usr/bin/php7.4)
==15401==    by 0x505B09A: (below main) (libc-start.c:308)
==15401==  Address 0x0 is not stack'd, malloc'd or (recently) free'd
==15401== 
==15401== 
==15401== Process terminating with default action of signal 11 (SIGSEGV)
==15401==  Access not within mapped region at address 0x0
==15401==    at 0xA1743D3: php_sparsehashmap_iterator_current_data(_php_sparsehashmap_iterator_t*) (iterator.cc:67)
==15401==    by 0x421214: ??? (in /usr/bin/php7.4)
==15401==    by 0x429DAE: execute_ex (in /usr/bin/php7.4)
==15401==    by 0x431A20: zend_execute (in /usr/bin/php7.4)
==15401==    by 0x3AA962: zend_execute_scripts (in /usr/bin/php7.4)
==15401==    by 0x349CBF: php_execute_script (in /usr/bin/php7.4)
==15401==    by 0x433B55: ??? (in /usr/bin/php7.4)
==15401==    by 0x20FEBA: ??? (in /usr/bin/php7.4)
==15401==    by 0x505B09A: (below main) (libc-start.c:308)
==15401==  If you believe this happened as a result of a stack
==15401==  overflow in your program's main thread (unlikely but
==15401==  possible), you can try to increase the size of the
==15401==  main thread stack using the --main-stacksize= flag.
==15401==  The main thread stack size used in this run was 8388608.
==15401== 
==15401== HEAP SUMMARY:
==15401==     in use at exit: 3,367,736 bytes in 26,417 blocks
==15401==   total heap usage: 128,270 allocs, 101,853 frees, 22,900,347 bytes allocated
==15401== 
==15401== LEAK SUMMARY:
==15401==    definitely lost: 96 bytes in 4 blocks
==15401==    indirectly lost: 0 bytes in 0 blocks
==15401==      possibly lost: 2,338,288 bytes in 18,657 blocks
==15401==    still reachable: 1,029,352 bytes in 7,756 blocks
==15401==         suppressed: 0 bytes in 0 blocks

To make things a little easier, I've uploaded to a git repo for full visibility on the code, as well as the required class which can be found in this Github Repo.

I know it doesn't look 100% amazing, this is my first attempt at C/C++/php extensions, so I have been learning a lot while building this.

Thanks in advance, any help getting this working finally would be greatly appreciated.


Solution

  • The problem is that data points to uninitialized memory. You need to actually have a zval somewhere, which is usually in the php_<extension_name>_iterator_t structure.

    static zval* php_<extension_iterator_name>_current_data requires a zval* return, but you cannot return an internal function variable, as it will not be initialised outside the scope of the function.

    Instead, create a zval variable within your iterator structure, return the value of the iterator zval in the <extension_iterator_name>_current_data() function. i.e.

    /* {{{ */
    typedef struct _php_sparsehashmap_iterator_t {
        zend_object_iterator it;
        zval zdata;
        zend_long pos;
    } php_sparsehashmap_iterator_t; /* }}} */
    
    /* {{{ */
    zend_object_iterator* php_sparsehashmap_iterator(zend_class_entry *ce, zval *sparsehashmap, int by_ref) {
        zend_string *_data;
        php_sparsehashmap_iterator_t *io = (php_sparsehashmap_iterator_t*) emalloc(sizeof(php_sparsehashmap_iterator_t));
    
        zend_iterator_init((zend_object_iterator*) io);
    
        io->pos = 0;
        io->it.funcs = &php_sparsehashmap_iterator_funcs;
    
        std::string current_data = get_current_data();
        _data = zend_string_init(current_data.c_str(), strlen(current_data.c_str()), 0);
        ZVAL_STR(&io->zdata, _data);
    
        return (zend_object_iterator*) io;
    } /* }}} */
    
    static zval* php_sparsehashmap_iterator_current_data(php_sparsehashmap_iterator_t *iterator) {
        php_sparsehashmap_iterator_t *io = (php_sparsehashmap_iterator_t*)iterator;
    
        return &io->zdata;
    }