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
View Developer Edit
Welcome! If you don't have a Git account, you can't do anything here.
If you reported this bug, you can edit this bug over here.
(description)
Block user comment
Status: Assign to:
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 Nov 02 00:01:29 2024 UTC