Search code examples
phpcvalgrindphp-extension

PHP extension use of uninitialized memory on second request


I have written a PHP extension, it works fine. However currently, I'm facing a strange issue. I have a function implemented for "require" files in PHP. There are no memory leaks for the function on the first request, but the memory leak happens on the second request. It makes me feel bad since I don't know whether I went with right way. I thought that it could be caused because I didn't handle right thing for "shutdown" the extension. However, on other hand, I think the PHP will handle each request separately, how can the first request cause the problem for the second request, it makes me confused.

Here is my function:

zval requireFile(char *path) {
    zend_file_handle fileHandle;
    zend_op_array *opArray;
    zval dummy, output;

    ZVAL_UNDEF(&output);

    php_stream_open_for_zend_ex(path, &fileHandle, USE_PATH|STREAM_OPEN_FOR_INCLUDE);

    opArray = zend_compile_file(&fileHandle, ZEND_REQUIRE);

    if (opArray) {
        if (fileHandle.handle.stream.handle) {
            ZVAL_NULL(&dummy);
            if (!fileHandle.opened_path) {
                fileHandle.opened_path = zend_string_init(path, strlen(path), 0);
            }

            zend_hash_add(&EG(included_files), fileHandle.opened_path, &dummy);
            zend_destroy_file_handle(&fileHandle);
        }

        opArray->scope = EG(fake_scope) ? EG(fake_scope) : zend_get_executed_scope();
        zend_execute(opArray, &output);

        destroy_op_array(opArray);
        efree_size(opArray, sizeof(zend_op_array));
    } else {
        zend_destroy_file_handle(&fileHandle);
    }

    return output;
}

Here is the valgrind output if I request more than one request to the server:

==3202== Memcheck, a memory error detector
==3202== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==3202== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info
==3202== Command: /root/php-bin/DEBUG/bin/php -S localhost:8000 test/index.php
==3202== Parent PID: 10
==3202== 
==3202== Conditional jump or move depends on uninitialised value(s)
==3202==    at 0x71B6220: persistent_compile_file (ZendAccelerator.c:2217)
==3202==    by 0x72A4A77: requireFile (require.c:13)
==3202==    by 0x72A8293: zim_I18n_init (i18n.c:39)
==3202==    by 0x76651B: ZEND_DO_FCALL_SPEC_RETVAL_UNUSED_HANDLER (zend_vm_execute.h:1755)
==3202==    by 0x7E8E9F: execute_ex (zend_vm_execute.h:55172)
==3202==    by 0x7ED5A7: zend_execute (zend_vm_execute.h:59499)
==3202==    by 0x725AAB: zend_execute_scripts (zend.c:1694)
==3202==    by 0x83DF3F: php_cli_server_dispatch_router (php_cli_server.c:2168)
==3202==    by 0x83E153: php_cli_server_dispatch (php_cli_server.c:2208)
==3202==    by 0x83ED67: php_cli_server_recv_event_read_request (php_cli_server.c:2529)
==3202==    by 0x83F15B: php_cli_server_do_event_for_each_fd_callback (php_cli_server.c:2615)
==3202==    by 0x83A913: php_cli_server_poller_iter_on_active (php_cli_server.c:869)
==3202==  Uninitialised value was created by a heap allocation
==3202==    at 0x484EFC8: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-arm64-linux.so)
==3202==    by 0x6E79C7: __zend_malloc (zend_alloc.c:3056)
==3202==    by 0x6E649B: _malloc_custom (zend_alloc.c:2418)
==3202==    by 0x6E660F: _emalloc (zend_alloc.c:2537)
==3202==    by 0x753D07: zend_vm_stack_new_page (zend_execute.c:184)
==3202==    by 0x753D6B: zend_vm_stack_init (zend_execute.c:195)
==3202==    by 0x709BE7: init_executor (zend_execute_API.c:148)
==3202==    by 0x7240EF: zend_activate (zend.c:1212)
==3202==    by 0x67510B: php_request_startup (main.c:1725)
==3202==    by 0x83DDEB: php_cli_server_request_startup (php_cli_server.c:2130)
==3202==    by 0x83E0FF: php_cli_server_dispatch (php_cli_server.c:2199)
==3202==    by 0x83ED67: php_cli_server_recv_event_read_request (php_cli_server.c:2529)
==3202== 
==3202== Conditional jump or move depends on uninitialised value(s)
==3202==    at 0x71B6220: persistent_compile_file (ZendAccelerator.c:2217)
==3202==    by 0x72A4A77: requireFile (require.c:13)
==3202==    by 0x72A841B: zim_I18n_init (i18n.c:55)
==3202==    by 0x76651B: ZEND_DO_FCALL_SPEC_RETVAL_UNUSED_HANDLER (zend_vm_execute.h:1755)
==3202==    by 0x7E8E9F: execute_ex (zend_vm_execute.h:55172)
==3202==    by 0x7ED5A7: zend_execute (zend_vm_execute.h:59499)
==3202==    by 0x725AAB: zend_execute_scripts (zend.c:1694)
==3202==    by 0x83DF3F: php_cli_server_dispatch_router (php_cli_server.c:2168)
==3202==    by 0x83E153: php_cli_server_dispatch (php_cli_server.c:2208)
==3202==    by 0x83ED67: php_cli_server_recv_event_read_request (php_cli_server.c:2529)
==3202==    by 0x83F15B: php_cli_server_do_event_for_each_fd_callback (php_cli_server.c:2615)
==3202==    by 0x83A913: php_cli_server_poller_iter_on_active (php_cli_server.c:869)
==3202==  Uninitialised value was created by a heap allocation
==3202==    at 0x484EFC8: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-arm64-linux.so)
==3202==    by 0x6E79C7: __zend_malloc (zend_alloc.c:3056)
==3202==    by 0x6E649B: _malloc_custom (zend_alloc.c:2418)
==3202==    by 0x6E660F: _emalloc (zend_alloc.c:2537)
==3202==    by 0x753D07: zend_vm_stack_new_page (zend_execute.c:184)
==3202==    by 0x753D6B: zend_vm_stack_init (zend_execute.c:195)
==3202==    by 0x709BE7: init_executor (zend_execute_API.c:148)
==3202==    by 0x7240EF: zend_activate (zend.c:1212)
==3202==    by 0x67510B: php_request_startup (main.c:1725)
==3202==    by 0x83DDEB: php_cli_server_request_startup (php_cli_server.c:2130)
==3202==    by 0x83E0FF: php_cli_server_dispatch (php_cli_server.c:2199)
==3202==    by 0x83ED67: php_cli_server_recv_event_read_request (php_cli_server.c:2529)
==3202== 
==3202== 
==3202== HEAP SUMMARY:
==3202==     in use at exit: 3,501 bytes in 10 blocks
==3202==   total heap usage: 15,425 allocs, 15,415 frees, 3,619,041 bytes allocated
==3202== 
==3202== LEAK SUMMARY:
==3202==    definitely lost: 0 bytes in 0 blocks
==3202==    indirectly lost: 0 bytes in 0 blocks
==3202==      possibly lost: 0 bytes in 0 blocks
==3202==    still reachable: 3,501 bytes in 10 blocks
==3202==         suppressed: 0 bytes in 0 blocks
==3202== Reachable blocks (those to which a pointer was found) are not shown.
==3202== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==3202== 
==3202== For lists of detected and suppressed errors, rerun with: -s
==3202== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

Solution

  • The issue happens inside the core in the file ZendAccelerator.c:2217

    if (persistent_script->script.filename) {
        if (!EG(current_execute_data) || !EG(current_execute_data)->opline ||
            !EG(current_execute_data)->func ||
            !ZEND_USER_CODE(EG(current_execute_data)->func->common.type) ||
            EG(current_execute_data)->opline->opcode != ZEND_INCLUDE_OR_EVAL ||
            (EG(current_execute_data)->opline->extended_value != ZEND_INCLUDE_ONCE &&
                EG(current_execute_data)->opline->extended_value != ZEND_REQUIRE_ONCE)) {
    

    For temporary fixing, we can set the fileHandle.filename to NULL; or disable the opcache.

    The issue will not happen with the real environment because with the real environment, we will use the Zend for memory management, it will clean the memory after finish the request.

    Update: The issue is fixed here https://github.com/php/php-src/commit/e488f7b0eb5aa0dbc396a17821386d914899e988