I recently came across this problem with SELinux on Amazon Linux with PHP7. I used a combination of Russell Coker's excellent LD_PRELOAD
trick (to intercept mmap()
calls and trigger an assertion failure) and gdb (to view the call stack as soon as the assertion failure is triggered) to check which function wants to execmem.
I also came to the conclusion that PHP7 PCRE JIT was the culprit. Putting pcre.jit=0
in php.ini
fixed it for me.
Detailed steps
- log into your machine as root
- download the
mmap.c
source code from here into /root/mmap.c
- build the code with
gcc -shared -g -fPIC mmap.c -o mmap.so
- now run Apache through gdb, intercepting mmap() calls:
LD_PRELOAD=/root/mmap.so gdb /usr/sbin/httpd
- you're thrown into gdb. Because Apache forks child processes, it is important to tell gdb to jump into them by typing
set follow-fork-mode child
after the (gdb)
prompt
- now kick off Apache by typing
run
after the (gdb)
prompt
- wait patiently until some HTTP request triggers the code that in turn triggers the assertion in
mmap()
, you're then back in gdb.
Program received signal SIGABRT, Aborted.
[Switching to Thread 0x7ffff7fe9840 (LWP 28370)]
0x00007ffff638d5f7 in __GI_raise (sig=sig@entry=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:56
56 return INLINE_SYSCALL (tgkill, 3, pid, selftid, sig);
</pre>
8. Type `bt` (backtrace) to see the call stack:
<pre>(gdb) bt
#0 0x00007ffff638d5f7 in __GI_raise (sig=sig@entry=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:56
#1 0x00007ffff638ece8 in __GI_abort () at abort.c:90
#2 0x00007ffff6386566 in __assert_fail_base (fmt=0x7ffff64d6ca8 "%s%s%s:%u: %s%sAssertion `%s' failed.\n%n", assertion=assertion@entry=0x7ffff7bda990 "!(prot & 0x4) || !(prot & 0x2)",
file=file@entry=0x7ffff7bda985 "mmap.c", line=line@entry=27, function=function@entry=0x7ffff7bda9af <__PRETTY_FUNCTION__.2774> "mmap") at assert.c:92
#3 0x00007ffff6386612 in __GI___assert_fail (assertion=0x7ffff7bda990 "!(prot & 0x4) || !(prot & 0x2)", file=0x7ffff7bda985 "mmap.c", line=27, function=0x7ffff7bda9af <__PRETTY_FUNCTION__.2774> "mmap")
at assert.c:101
#4 0x00007ffff7bda93e in mmap (addr=0x0, length=65536, prot=7, flags=34, fd=-1, offset=0) at mmap.c:27
#5 0x00007ffff79a6b86 in alloc_chunk (size=65536) at sljit/sljitExecAllocator.c:101
#6 sljit_malloc_exec (size=4440) at sljit/sljitExecAllocator.c:204
#7 sljit_generate_code (compiler=compiler@entry=0x555555b14ad0) at sljit/sljitNativeX86_common.c:296
#8 0x00007ffff79bf74e in _pcre_jit_compile (re=re@entry=0x555555b14650, extra=extra@entry=0x555555b14750) at pcre_jit_compile.c:6434
#9 0x00007ffff79c1fc3 in pcre_study (external_re=external_re@entry=0x555555b14650, options=1, errorptr=errorptr@entry=0x7fffffffa3c8) at pcre_study.c:1354
#10 0x00007fffed2edcbc in pcre_get_compiled_regex_cache (regex=0x7fffd8a04000) at /usr/src/debug/php-7.0.9/ext/pcre/php_pcre.c:487
#11 0x00007fffed2ef21f in php_do_pcre_match (execute_data=0x7fffec419ab0, return_value=0x7fffec419870, global=1) at /usr/src/debug/php-7.0.9/ext/pcre/php_pcre.c:655
<...>
- the culprit is easily identified. Stack entry 11 gives you a hint to check PCRE and entry 5 is the actual call that goes awry.