php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #79027 Heap corruption in accel_remap_huge_pages
Submitted: 2019-12-24 14:18 UTC Modified: -
Votes:4
Avg. Score:4.5 ± 0.9
Reproduced:3 of 3 (100.0%)
Same Version:3 (100.0%)
Same OS:3 (100.0%)
From: chris at neadwerx dot com Assigned:
Status: Open Package: opcache
PHP Version: Irrelevant OS: Linux
Private report: No CVE-ID: None
Welcome back! If you're the original bug submitter, here's where you can edit the bug or add additional notes.
If you forgot your password, you can retrieve your password here.
Password:
Status:
Package:
Bug Type:
Summary:
From: chris at neadwerx dot com
New email:
PHP Version: OS:

 

 [2019-12-24 14:18 UTC] chris at neadwerx dot com
Description:
------------
Prerequisites:
- System has CPU flag PDPE1GB (basically sandy bridge era or better)
- System configured with hugepage size of 1GB
- PHP version > 7.0
- /etc/php.d/10-opcache.ini:opcache.huge_code_pages=1

Execution:
PHP has a 0.2% chance that execve() loads PHP in the upper 512 4 KiB pages of a 1 GiB page. This causes zend_move_code_to_huge_pages to select start address for the mmap allocation to be 1 GiB aligned (ZEND_MM_ALIGNED_SIZE_EX for the start address - both 2MiB and 1GiB alignment will be the same).

From mmap(2). This will result in a successful allocation of a full 1 GiB, resulting in a corrupted heap. PHP will receive a SIGSEGV when returning from zend_remap_huge_pages and calling fclose on the file pointer in zend_move_code_to_huge_pages()


Remediation:
- Modify zend_remap_huge_pages to call mmap with the MAP_HUGE_2MB flag
or
- Check system default hugepage size by opening /proc/meminfo and reading in Hugepagesize setting - this isn't very portable but ensures that the correct page size can be used, or the function can exit gracefully.

Test script:
---------------
#!/usr/bin/perl -w
while(1)
{
    system( 'php /some/php/file' ); # doesn't need to be a CLI call, can be from apache
}

Expected result:
----------------
Nothing

Actual result:
--------------
0.2% chance of SIGSEGV

It was very difficult to get a useful trace for this, This is the best I could come up with as GDB was being very uncooperative:

#0: 0x7f898aa80ff4 _IO_new_fclose /usr/lib64/libc-2.17.so (offset 0x6dff4)
#1: 0x562b420466c0 ??
#2: 0x200000 ??
#3: 0x7f8963303000 ??
#4: 0x7f8982e617f7 accel_move_code_to_huge_pages /usr/lib64/php/modules/opcache.so (offset 0x107f7)
#5: 0x562b3ff78000 ??
#6: 0x562b403dc000 /usr/bin/php (offset 0x464000)


Patches

accel_get_hugepage_size (last revision 2019-12-31 13:32 UTC by chris at neadwerx dot com)
PHP7_huge_2mb_flag.patch (last revision 2019-12-31 12:37 UTC by chris at neadwerx dot com)

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2019-12-31 12:37 UTC] chris at neadwerx dot com
The following patch has been added/updated:

Patch Name: PHP7_huge_2mb_flag.patch
Revision:   1577795871
URL:        https://bugs.php.net/patch-display.php?bug=79027&patch=PHP7_huge_2mb_flag.patch&revision=1577795871
 [2019-12-31 13:32 UTC] chris at neadwerx dot com
The following patch has been added/updated:

Patch Name: accel_get_hugepage_size
Revision:   1577799143
URL:        https://bugs.php.net/patch-display.php?bug=79027&patch=accel_get_hugepage_size&revision=1577799143
 [2019-12-31 13:36 UTC] chris at neadwerx dot com
Two patches are submitted, The first uses the MAP_HUGE_2MB flag to control mmap() behavior, but this macro may not be defined on all systems, and may need macro support to work properly.

The second patch checks /proc/meminfo to verify the system's default hugepage size, and validates that the allocation size is not smaller than the hugepage size (the 1GiB case) prior to calling accel_remap_huge_pages. If this is not the case, emit an actionable error to the user. Macro definitions for MAP_HUGE_2MB and MAP_HUGE_1GB can be found in mmap(2)
 [2021-01-20 17:26 UTC] evan at eaanderson dot com
I was able to get a complete backtrace for this crash. The system in question is an Epyc 7501 with a Fedora 33 VM. The crash occurs 100% of the time when starting php-fpm. Hugepagesize is 2048kB though, not 1GB, so it seems this bug can occur in more cases than originally suggested.

#0  0x00007f3b550cc599 in __memmove_avx_unaligned_erms () from /lib64/libc.so.6
#1  0x00007f3b54980e1c in memcpy (__len=2539520, __src=0x7f3b4cb85000, __dest=0x5555a4800000 <_init>) at /usr/include/bits/string_fortified.h:29
#2  accel_remap_huge_pages (name=0x7ffef01c6d80 "/usr/sbin/php-fpm", offset=<optimized out>, real_size=2539520, size=4194304, start=0x5555a4800000 <_init>) at /usr/src/debug/php-7.4.14-1.fc33.x86_64/ext/opcache/ZendAccelerator.c:2748
#3  accel_move_code_to_huge_pages () at /usr/src/debug/php-7.4.14-1.fc33.x86_64/ext/opcache/ZendAccelerator.c:2806
#4  0x00007f3b549810f5 in accel_startup (extension=0x5555a6f77a80) at /usr/src/debug/php-7.4.14-1.fc33.x86_64/ext/opcache/ZendAccelerator.c:2897
#5  0x00005555a49d63fc in zend_extension_startup (extension=0x5555a6f77a80) at /usr/src/debug/php-7.4.14-1.fc33.x86_64/Zend/zend_extensions.c:188
#6  0x00005555a49be144 in zend_llist_apply_with_del (l=l@entry=0x5555a501a0a0 <zend_extensions>, func=func@entry=0x5555a49d63e0 <zend_extension_startup>) at /usr/src/debug/php-7.4.14-1.fc33.x86_64/Zend/zend_llist.c:169
#7  0x00005555a49d655b in zend_startup_extensions () at /usr/src/debug/php-7.4.14-1.fc33.x86_64/Zend/zend_extensions.c:210
#8  0x00005555a4967ab3 in php_module_startup (sf=<optimized out>, additional_modules=<optimized out>, num_additional_modules=<optimized out>) at /usr/src/debug/php-7.4.14-1.fc33.x86_64/main/main.c:2335
#9  0x00005555a4a5fd69 in php_cgi_startup (sapi_module=<optimized out>) at /usr/src/debug/php-7.4.14-1.fc33.x86_64/sapi/fpm/fpm/fpm_main.c:775
#10 0x00005555a483c52b in main (argc=2, argv=0x7ffef01c9968) at /usr/src/debug/php-7.4.14-1.fc33.x86_64/sapi/fpm/fpm/fpm_main.c:1772
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Sat Dec 21 16:01:28 2024 UTC