php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #79252 preloading causes php-fpm to segfault during exit
Submitted: 2020-02-10 12:41 UTC Modified: 2020-02-24 15:22 UTC
Votes:2
Avg. Score:5.0 ± 0.0
Reproduced:1 of 1 (100.0%)
Same Version:1 (100.0%)
Same OS:0 (0.0%)
From: jakub at tuenti dot com Assigned:
Status: Closed Package: opcache
PHP Version: 7.4.3RC1 OS: Linux
Private report: No CVE-ID: None
 [2020-02-10 12:41 UTC] jakub at tuenti dot com
Description:
------------
Preloading a class with a static variable causes php-fpm to segfault during exit. I have tested in debian docker images with php 7.4.2 and 7.4.3RC1. Preloading works fine before the exit.

root@a37b6a321c52:/# php-fpm -dzend_extension=opcache -dopcache.preload=/preload.php -dopcache.preload_user=root
[10-Feb-2020 12:34:41] NOTICE: fpm is running, pid 579
[10-Feb-2020 12:34:41] NOTICE: ready to handle connections
^C[10-Feb-2020 12:34:42] NOTICE: Terminating ...
[10-Feb-2020 12:34:42] NOTICE: exiting, bye-bye!
Segmentation fault (core dumped)

I have included a backtrace for php 7.4.2

Test script:
---------------
# /preload.php
<?php
opcache_compile_file("/test.php");

# /test.php
<?php
class A {
  static $x;
}

Actual result:
--------------
#0  zend_cleanup_internal_class_data (ce=0x7f3d0fcbe9a0) at ./Zend/zend_types.h:441
        static_members = 0x303a783a746f6f72
        p = 0x303a783a746f6f72
        end = 0x303a783a746f6f82
#1  0x000055e6aca38988 in destroy_zend_class (zv=<optimized out>) at ./Zend/zend_opcode.c:253
        op_array = <optimized out>
        prop_info = <optimized out>
        ce = 0x7f3d0fcbe9a0
        fn = <optimized out>
#2  0x000055e6aca53905 in zend_hash_destroy (ht=0x55e6addef390) at ./Zend/zend_hash.c:1541
        p = 0x55e6ade943d0
        end = 0x55e6ade943f0
#3  0x000055e6aca43b36 in zend_shutdown () at ./Zend/zend.c:1054
No locals.
#4  0x000055e6ac9e372a in php_module_shutdown () at ./main/main.c:2477
        module_number = <optimized out>
        module_number = <optimized out>
        cb = <optimized out>
#5  php_module_shutdown () at ./main/main.c:2454
        cb = <optimized out>
#6  0x000055e6acad6239 in fpm_php_cleanup (which=<optimized out>, arg=<optimized out>) at ./sapi/fpm/fpm/fpm_php.c:198
No locals.
#7  0x000055e6acace9bd in fpm_cleanups_run (type=type@entry=4) at ./sapi/fpm/fpm/fpm_cleanup.c:43
        c = 0x55e6adef6580
        cl = <optimized out>
#8  0x000055e6acad6ee4 in fpm_pctl_exit () at ./sapi/fpm/fpm/fpm_process_ctl.c:71
        __func__ = "fpm_pctl_exit"
#9  fpm_pctl_action_last () at ./sapi/fpm/fpm/fpm_process_ctl.c:118
No locals.
#10 0x000055e6acad7b3b in fpm_pctl (action=2, new_state=0) at ./sapi/fpm/fpm/fpm_process_ctl.c:260
        __func__ = "fpm_pctl"
#11 fpm_pctl_child_exited () at ./sapi/fpm/fpm/fpm_process_ctl.c:260
No locals.
#12 0x000055e6acace51b in fpm_children_bury () at ./sapi/fpm/fpm/fpm_children.c:266
        wp = 0x55e6adef6c90
        tv1 = {tv_sec = 10543, tv_usec = 512922}
        tv2 = {tv_sec = 22, tv_usec = 495922}
        buf = "on signal 15 (SIGTERM)\000\000\060\254\267\204\377\177\000\000\020/\266\204\377\177\000\000\000\304\370\317\001\000\000\000\001\000\000\000\000\000\000\000\001\000\000\000\000\000\000\000\360.\266\204\377\177\000\000@/\266\204\377\177\000\000@J\304\254\346U\000\000\317\367S\343\245\233\304 \020/\266\204\377\177\000\000\366\237\244\031=\177\000\000\f\000\000\000\000\000\000\000\366\237\244\031=\177\000"
        severity = 1
        restart_child = 1
        status = 15
        pid = 919
        child = <optimized out>
        __func__ = "fpm_children_bury"
#13 0x000055e6acad3703 in fpm_event_fire (ev=0x55e6acc44b60 <children_bury_timer>) at ./sapi/fpm/fpm/fpm_events.c:487
No locals.
#14 fpm_event_loop (err=err@entry=0) at ./sapi/fpm/fpm/fpm_events.c:467
        ev = 0x55e6acc44b60 <children_bury_timer>
        next = 0x55e6adefffe0
--Type <RET> for more, q to quit, c to continue without paging--
        q = 0x55e6adeffd70
        ms = <optimized out>
        tmp = <optimized out>
        timeout = <optimized out>
        ret = <optimized out>
        q2 = <optimized out>
        now = {tv_sec = 10543, tv_usec = 512855}
        signal_fd_event = {fd = 5, timeout = {tv_sec = 0, tv_usec = 0}, frequency = {tv_sec = 0, tv_usec = 0}, callback = 0x55e6acad3890 <fpm_got_signal>, arg = 0x0, flags = 2, index = 5, which = 2}
        __func__ = "fpm_event_loop"
#15 0x000055e6acacdd47 in fpm_run (max_requests=0x7fff84b630fc) at ./sapi/fpm/fpm/fpm.c:113
        wp = 0x0
#16 0x000055e6ac8adad2 in main (argc=6, argv=0x7fff84b636f8) at ./sapi/fpm/fpm/fpm_main.c:1854
        exit_status = 0
        c = -1
        use_extended_info = 0
        file_handle = {handle = {fp = 0x0, stream = {handle = 0x0, isatty = 442194688, reader = 0x1, fsizer = 0x0, closer = 0x7f3d199c3490 <ptmalloc_init>}}, filename = 0x7f3d1a5a7628 "\274", 
          opened_path = 0x7fff84b635f0, type = (ZEND_HANDLE_STREAM | unknown: 442221944), free_filename = 61 '=', buf = 0x7f3d19afec40 <main_arena> "", len = 139900400692384}
        orig_optind = 1
        orig_optarg = 0x0
        ini_entries_len = 80
        max_requests = 0
        requests = 0
        fcgi_fd = 0
        request = <optimized out>
        fpm_config = <optimized out>
        fpm_prefix = 0x0
        fpm_pid = 0x0
        test_conf = 0
        force_daemon = -1
        force_stderr = 0
        php_information = 0
        php_allow_to_run_as_root = 1
        ret = <optimized out>
        __func__ = "main"
        __orig_bailout = <optimized out>
        __bailout = <optimized out>
        __str = <optimized out>



Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2020-02-11 14:55 UTC] nikic@php.net
Doesn't reproduce with builtin server, possibly FPM specific.
 [2020-02-24 12:14 UTC] nikic@php.net
Can't reproduce this with FPM either.
 [2020-02-24 14:23 UTC] jakub at tuenti dot com
It should be reproduceable by starting the official image with those two files, then running php-fpm and pressing Ctrl+C to make it exit.

$ docker run --rm -ti -v $PWD/preload.php:/preload.php -v $PWD/test.php:/test.php php:7.4.3-fpm bash
root@7820c56cbbda:/var/www/html# php-fpm -dzend_extension=opcache -dopcache.enabled=1 -dopcache.preload=/preload.php -dopcache.preload_user=root
[24-Feb-2020 14:17:16] NOTICE: fpm is running, pid 7
[24-Feb-2020 14:17:16] NOTICE: ready to handle connections
^C[24-Feb-2020 14:17:18] NOTICE: Terminating ...
[24-Feb-2020 14:17:18] NOTICE: exiting, bye-bye!
Segmentation fault (core dumped)

Note that you need to have the preload.php and test.php files in the folder where you run the command. In this next example (running the command and then pressing Ctrl+C) the "Segmentation fault" message doesn't appear, but the exit code is the segfault one (139), so it was probably just hidden by Docker:

$ docker run --rm -i -v $PWD/preload.php:/preload.php -v $PWD/test.php:/test.php php:7.4.3-fpm php-fpm -dzend_extension=opcache -dopcache.enabled=1 -dopcache.preload=/preload.php -dopcache.preload_user=root; echo $?
[24-Feb-2020 14:18:06] NOTICE: fpm is running, pid 1
[24-Feb-2020 14:18:06] NOTICE: ready to handle connections
^C[24-Feb-2020 14:18:06] NOTICE: Terminating ...
[24-Feb-2020 14:18:06] NOTICE: exiting, bye-bye!
139
 [2020-02-24 14:30 UTC] jakub at tuenti dot com
I also noted that when running the daemon with -D (daemonize), the daemon starts but the command that started the daemon segfaults (maybe because it also does the cleanup before exit?), so it should be easier to trace this way since you don't need to do the Ctrl+C:

$ docker run --rm -i -v $PWD/preload.php:/preload.php -v $PWD/test.php:/test.php php:7.4.3-fpm php-fpm -D -dzend_extension=opcache -dopcache.enabled=1 -dopcache.preload=/preload.php -dopcache.preload_user=root; echo $?
[24-Feb-2020 14:30:03] NOTICE: fpm is running, pid 9
[24-Feb-2020 14:30:03] NOTICE: ready to handle connections
139
 [2020-02-24 14:48 UTC] nikic@php.net
I see... I can reproduce the issue when running php-fpm under root. Annoyingly running CLI server under root is not sufficient.
 [2020-02-24 15:22 UTC] nikic@php.net
This patch seems to fix it:

diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c
index c7d73a999a..0c8722418c 100644
--- a/ext/opcache/ZendAccelerator.c
+++ b/ext/opcache/ZendAccelerator.c
@@ -4233,9 +4233,11 @@ static void preload_load(void)
 		EG(persistent_classes_count)   = EG(class_table)->nNumUsed;
 	}
 	if (CG(map_ptr_last) != ZCSG(map_ptr_last)) {
+		size_t old_map_ptr_last = CG(map_ptr_last);
 		CG(map_ptr_last) = ZCSG(map_ptr_last);
 		CG(map_ptr_size) = ZEND_MM_ALIGNED_SIZE_EX(CG(map_ptr_last) + 1, 4096);
 		CG(map_ptr_base) = perealloc(CG(map_ptr_base), CG(map_ptr_size) * sizeof(void*), 1);
+		memset(CG(map_ptr_base) + old_map_ptr_last, 0, (CG(map_ptr_last) - old_map_ptr_last) * sizeof(void *));
 	}
 }

I assume the problem is that the relevant process does not execute a PHP request, as such never goes through zend_active and as such never initializes the map region.

Not positive on this being the right fix though, also wondering if it's fine to just zero the whole map area there.
 [2020-02-28 11:50 UTC] nikic@php.net
Automatic comment on behalf of nikita.ppv@gmail.com
Revision: http://git.php.net/?p=php-src.git;a=commit;h=30ee3f48d4046c48314ad4b6953dbaf54337fe6c
Log: Fixed bug #79252
 [2020-02-28 11:50 UTC] nikic@php.net
-Status: Open +Status: Closed
 [2020-03-05 14:47 UTC] jakub at tuenti dot com
I just tested the 7.4.4RC1 version and now I am able to preload all of the files in a project without having segfaults (before I was only able to preload about half of them)! Thank you very much!
 
PHP Copyright © 2001-2020 The PHP Group
All rights reserved.
Last updated: Fri Jul 10 12:01:33 2020 UTC