php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #78175 Preloading segfaults at preload time and at runtime
Submitted: 2019-06-17 12:48 UTC Modified: 2019-07-22 09:26 UTC
From: nicolas dot grekas+php at gmail dot com Assigned: dmitry (profile)
Status: Closed Package: opcache
PHP Version: 7.4.0alpha1 OS:
Private report: No CVE-ID: None
 [2019-06-17 12:48 UTC] nicolas dot grekas+php at gmail dot com
Description:
------------
From the code in this archive:
https://send.firefox.com/download/f87c7176853e1dfa/#YxTCKp75r1nMwmq-PJfnSg

Two separate crashes here:

Cannot load the autoloader
--------------------------

Run `bin/console c:c && php -dopcache.preload=var/cache/dev/srcApp_KernelDevDebugContainer.preload.php -S localhost:8000` in this directory.

This should spawn a web server with two ignorable warnings saying "Can't preload already declared class class@anonymous".

Then in another console run `php info.php`.

This makes the server process segfault.

Cannot run preloading script
----------------------------

Add a `return;` line 321 in `vendor/symfony/dependency-injection/Dumper/PhpDumper.php` (just after `$r->getDefaultProperties();`).

Run `bin/console c:c && php -dopcache.preload=var/cache/dev/srcApp_KernelDevDebugContainer.preload.php -S localhost:8000` in this directory.

This fails with a segfault.



Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2019-06-17 14:27 UTC] nikic@php.net
First valgrind warning:

==4693== Invalid read of size 4
==4693==    at 0x93E9A9: zend_gc_delref (zend_types.h:1030)
==4693==    by 0x93FAE1: gc_mark_grey (zend_gc.c:823)
==4693==    by 0x940093: gc_mark_roots (zend_gc.c:958)
==4693==    by 0x9412BE: zend_gc_collect_cycles (zend_gc.c:1434)
==4693==    by 0x8EC2ED: shutdown_executor (zend_execute_API.c:277)
==4693==    by 0x905C9B: zend_deactivate (zend.c:1178)
==4693==    by 0x86950A: php_request_shutdown (main.c:1971)
==4693==    by 0x10C23D4C: accel_finish_startup (ZendAccelerator.c:4340)
==4693==    by 0x10C1F463: accel_post_startup (ZendAccelerator.c:2971)
==4693==    by 0x90577B: zend_post_startup (zend.c:992)
==4693==    by 0x86A265: php_module_startup (main.c:2399)
==4693==    by 0x9EC4B0: php_cli_startup (php_cli.c:408)
==4693==  Address 0x1116e600 is 0 bytes inside a block of size 56 free'd
==4693==    at 0x4C30D3B: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==4693==    by 0x8CCED1: _efree_custom (zend_alloc.c:2392)
==4693==    by 0x8CD012: _efree (zend_alloc.c:2512)
==4693==    by 0x10C4464F: _zend_shared_memdup (zend_shared_alloc.c:386)
==4693==    by 0x10C446C3: zend_shared_memdup_put_free (zend_shared_alloc.c:398)
==4693==    by 0x10C29858: zend_persist_zval (zend_persist.c:212)
==4693==    by 0x10C2C754: zend_persist_class_entry (zend_persist.c:738)
==4693==    by 0x10C2F5B3: zend_accel_persist_class_table (zend_persist.c:1028)
==4693==    by 0x10C2F8CB: zend_accel_script_persist (zend_persist.c:1070)
==4693==    by 0x10C2272B: preload_script_in_shared_memory (ZendAccelerator.c:3939)
==4693==    by 0x10C236EA: accel_preload (ZendAccelerator.c:4187)
==4693==    by 0x10C23D1E: accel_finish_startup (ZendAccelerator.c:4334)
==4693==  Block was alloc'd at
==4693==    at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==4693==    by 0x8CDC4F: __zend_malloc (zend_alloc.c:2895)
==4693==    by 0x8CCE6A: _malloc_custom (zend_alloc.c:2383)
==4693==    by 0x8CCF98: _emalloc (zend_alloc.c:2502)
==4693==    by 0x91A582: _zend_new_array (zend_hash.c:256)
==4693==    by 0x8E31D5: zend_try_ct_eval_array (zend_compile.c:7013)
==4693==    by 0x8E8ABE: zend_eval_const_expr (zend_compile.c:8834)
==4693==    by 0x8E7108: zend_const_expr_to_zval (zend_compile.c:8286)
==4693==    by 0x8E003E: zend_compile_prop_decl (zend_compile.c:6023)
==4693==    by 0x8E02E6: zend_compile_prop_group (zend_compile.c:6072)
==4693==    by 0x8E7629: zend_compile_stmt (zend_compile.c:8409)
==4693==    by 0x8DD43E: zend_compile_stmt_list (zend_compile.c:5176)

Looks like GC is trying to use some structures that have been moved to SHM.

Here is a very simple way to cause a crash (use as preload script).

<?php
register_shutdown_function(function() {
    echo "Shutdown\n";
});

It's not quite the same, but I think the root cause is basically the same.
 [2019-06-17 14:35 UTC] nikic@php.net
-Status: Open +Status: Assigned -Assigned To: +Assigned To: dmitry
 [2019-06-17 14:35 UTC] nikic@php.net
Dmitry, can you take a look?

I think we should create a stronger separation between preloading script and preloaded files. That is, require the use of opcache_compile_file() on everything that should be preloaded -- all other executed code (including include and require) should not be preloaded (or even cached, for that matter).

Does that make sense?
 [2019-06-18 14:19 UTC] dmitry@php.net
Automatic comment on behalf of dmitry@zend.com
Revision: http://git.php.net/?p=php-src.git;a=commit;h=148eb202268b0c2786e9f541b46138e5d1271761
Log: Fixed bug #78175 (Preloading segfaults at preload time and at runtime)
 [2019-06-18 14:19 UTC] dmitry@php.net
-Status: Assigned +Status: Closed
 [2019-06-19 07:10 UTC] nicolas dot grekas+php at gmail dot com
The crash still exists, this time on a second request:
run php info.php twice, the second time crashed the server.
 [2019-06-19 07:48 UTC] dmitry@php.net
-Status: Closed +Status: Re-Opened
 [2019-06-19 07:48 UTC] dmitry@php.net
I don't see crash, but got the following valgrind error on first request (modification of vendor/symfony/dependency-injection/Dumper/PhpDumper.php is not required).

==17283== Invalid read of size 4
==17283==    at 0x87D1339: ZEND_GET_CLASS_SPEC_CV_UNUSED_HANDLER (zend_vm_execute.h:47674)
==17283==    by 0x87DBF1A: execute_ex (zend_vm_execute.h:60625)
==17283==    by 0x87DC538: zend_execute (zend_vm_execute.h:61077)
==17283==    by 0x8728F00: zend_execute_scripts (zend.c:1657)
==17283==    by 0x86B656B: php_execute_script (main.c:2680)
==17283==    by 0x87E5E72: php_cli_server_dispatch_script (php_cli_server.c:1983)
==17283==    by 0x87E6498: php_cli_server_dispatch (php_cli_server.c:2158)
==17283==    by 0x87E6BF9: php_cli_server_recv_event_read_request (php_cli_server.c:2374)
==17283==    by 0x87E6EEE: php_cli_server_do_event_for_each_fd_callback (php_cli_server.c:2452)
==17283==    by 0x87E3B4C: php_cli_server_poller_iter_on_active (php_cli_server.c:841)
==17283==    by 0x87E6F88: php_cli_server_do_event_for_each_fd (php_cli_server.c:2475)
==17283==    by 0x87E6FE2: php_cli_server_do_event_loop (php_cli_server.c:2485)
==17283==  Address 0xa5caeb4 is 1,148 bytes inside a block of size 65,536 free'd
==17283==    at 0x4036729: free (vg_replace_malloc.c:540)
==17283==    by 0x86FC3F2: _efree_custom (zend_alloc.c:2392)
==17283==    by 0x86FC4EF: _efree (zend_alloc.c:2512)
==17283==    by 0x86FDDD2: zend_arena_destroy (zend_arena.h:46)
==17283==    by 0x86FE860: shutdown_compiler (zend_compile.c:384)
==17283==    by 0x872813B: zend_deactivate (zend.c:1185)
==17283==    by 0x86B5640: php_request_shutdown (main.c:1975)
==17283==    by 0x7EA098F: accel_finish_startup (ZendAccelerator.c:4353)
==17283==    by 0x7E9D284: accel_post_startup (ZendAccelerator.c:2969)
==17283==    by 0x8727D36: zend_post_startup (zend.c:992)
==17283==    by 0x86B5FD7: php_module_startup (main.c:2403)
==17283==    by 0x87E2ED8: sapi_cli_server_startup (php_cli_server.c:486)
==17283==  Block was alloc'd at
==17283==    at 0x40356A4: malloc (vg_replace_malloc.c:309)
==17283==    by 0x86FCE7E: __zend_malloc (zend_alloc.c:2895)
==17283==    by 0x86FC39C: _malloc_custom (zend_alloc.c:2383)
==17283==    by 0x86FC48E: _emalloc (zend_alloc.c:2502)
==17283==    by 0x86FDD7F: zend_arena_create (zend_arena.h:34)
==17283==    by 0x86FE7A2: init_compiler (zend_compile.c:365)
==17283==    by 0x8728011: zend_activate (zend.c:1151)
==17283==    by 0x86B528D: php_request_startup (main.c:1836)
==17283==    by 0x7EA08C6: accel_finish_startup (ZendAccelerator.c:4324)
==17283==    by 0x7E9D284: accel_post_startup (ZendAccelerator.c:2969)
==17283==    by 0x8727D36: zend_post_startup (zend.c:992)
==17283==    by 0x86B5FD7: php_module_startup (main.c:2403)
 [2019-06-19 09:22 UTC] dmitry@php.net
The problem caused by assignment object to static property of preloaded class, and opcache doesn't expect objects...

A simpler way to reproduce:

preload.php
-----------
<?php
class Loader {
        static private $loader;

        static function getLoader() {
                if (null !== self::$loader) {
                        return self::$loader;
                }
                return self::$loader = new Loader();
        }
}

Loader::getLoader();
?>

$ USE_ZEND_ALLOC=0 valgrind sapi/cli/php -dopcache.preload=preload.php -r 'var_dump(get_class(Loader::getLoader()));'

Although, this is a bug, it's also an interesting unexpected side effect, that allow initialization and preloading of big arrays as static properties (e.g. application configuration)
 [2019-06-20 12:30 UTC] nicolas dot grekas+php at gmail dot com
> Although, this is a bug, it's also an interesting unexpected side effect, that allow initialization and preloading of big arrays as static properties (e.g. application configuration)

While funny, that's not that interesting to keep: if one wants this, it's always possible to generate a file with the array and load it. Preloading the default static value is what would be the most expected, to me at least :)
 [2019-06-21 10:30 UTC] dmitry@php.net
A partial fix is available at

https://github.com/php/php-src/compare/PHP-7.4...dstogov:bug78175?expand=1

It needs to be extended to support static variables of methods.
 [2019-06-24 17:38 UTC] dmitry@php.net
-Status: Re-Opened +Status: Closed
 [2019-07-21 19:42 UTC] nicolas dot grekas+php at gmail dot com
Sorry, this still segfaults for me, here is the code:
https://github.com/nicolas-grekas/symfony/releases/download/php-bugs/preload-php-bug-78175.zip
 [2019-07-21 19:47 UTC] requinix@php.net
-Status: Closed +Status: Re-Opened
 [2019-07-22 09:26 UTC] dmitry@php.net
-Status: Re-Opened +Status: Closed
 [2019-07-22 09:26 UTC] dmitry@php.net
Commit 60dca2295d4eb87873e85bed24880a948a4049cc fixes a related crash
 [2019-07-24 09:24 UTC] nicolas dot grekas+php at gmail dot com
Thanks for the fix, I have another error, not a segfault this time:
https://github.com/nicolas-grekas/symfony/releases/download/php-bugs/preload-failures-78175.tar.bz2

I run
php -S localhost:8000 -t public/ -dopcache.preload=var/cache/prod/srcApp_KernelProdContainer.preload.php

Then in another console:
php info.php

This fails with an error:
Uncaught Psr\Log\InvalidArgumentException: The log level "critical" does not exist.

Yet it exists. To debug, I added a var_dump in
vendor/symfony/http-kernel/Log/Logger.php

And it displays:
UNKNOWN:0
 
PHP Copyright © 2001-2019 The PHP Group
All rights reserved.
Last updated: Sun Dec 15 07:01:23 2019 UTC