[php-src] Issue #11807: Segfault on accessing enum cases
| From: | adlenton | Date: | Thu, 27 Jul 2023 15:05:59 +0000 |
| Subject: | [php-src] Issue #11807: Segfault on accessing enum cases | ||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-245053@lists.php.net to get a copy of this message | ||
Issue: https://github.com/php/php-src/issues/11807
Author: adlenton
### Description
In our project we've started adapting PHP enums and on our first "bigger scale" use
case we're running into a segfault - seemingly on accessing an enum case.
gdb stacktrace:
```
Program received signal SIGSEGV, Segmentation fault.
zval_addref_p (pz=0x4412edb0) at ./Zend/zend_types.h:1240
1240 ./Zend/zend_types.h: No such file or directory.
(gdb) bt
#0 zval_addref_p (pz=0x4412edb0) at ./Zend/zend_types.h:1240
#1 zend_separate_class_constants_table (class_type=class_type@entry=0x4412e5a8) at
./Zend/zend_API.c:1331
#2 0x0000558eeb6306c8 in zend_class_constants_table (ce=0x4412e5a8) at ./Zend/zend_API.h:431
#3 zend_get_class_constant_ex (class_name=0x41758028, constant_name=0x41b8fd30,
scope=scope@entry=0x4708c120, flags=512)
at ./Zend/zend_constants.c:364
#4 0x0000558eeb6ca50c in zend_ast_evaluate (result=result@entry=0x7fff0b412fd0,
ast=ast@entry=0x4708c4c8, scope=0x4708c120)
at ./Zend/zend_ast.c:777
#5 0x0000558eeb6329ec in zval_update_constant_ex (scope=<optimized out>, p=0x7fb736c14440) at
./Zend/zend_execute_API.c:696
#6 zval_update_constant_ex (p=0x7fb736c14440, scope=<optimized out>) at
./Zend/zend_execute_API.c:671
#7 0x0000558eeb6ad06e in ZEND_RECV_INIT_SPEC_CONST_HANDLER () at ./Zend/zend_vm_execute.h:3736
#8 execute_ex (ex=0x4412e5a8) at ./Zend/zend_vm_execute.h:55951
#9 0x0000558eeb6b037d in zend_execute (op_array=0x7fb736c7b000, return_value=0x0) at
./Zend/zend_vm_execute.h:60163
#10 0x0000558eeb6421ed in zend_execute_scripts (type=type@entry=8, retval=retval@entry=0x0,
file_count=file_count@entry=3)
at ./Zend/zend.c:1846
#11 0x0000558eeb5dda41 in php_execute_script (primary_file=primary_file@entry=0x7fff0b415680) at
./main/main.c:2542
#12 0x0000558eeb487e0e in main (argc=<optimized out>, argv=<optimized out>) at
./sapi/fpm/fpm/fpm_main.c:1935
(gdb) zbacktrace
[0x7fb736c143d0] [...]\BoostHintVO->create("happiness_amount", 0)
[...]/BoostHintVO.php:16
[0x7fb736c14280] [...]\BonusOnSetAdjacencyAbilityFactory->buildAdjacencyBonuses(
array(3)[0x7fb736c142d0]) /[...]/BonusOnSetAdjacencyAbilityFactory.php:75
[...]
(gdb) br full
Function "full" not defined.
Make breakpoint pending on future shared library load? (y or [n]) n
(gdb) backtrace
#0 zval_addref_p (pz=0x4412edb0) at ./Zend/zend_types.h:1240
#1 zend_separate_class_constants_table (class_type=class_type@entry=0x4412e5a8) at
./Zend/zend_API.c:1331
#2 0x0000558eeb6306c8 in zend_class_constants_table (ce=0x4412e5a8) at ./Zend/zend_API.h:431
#3 zend_get_class_constant_ex (class_name=0x41758028, constant_name=0x41b8fd30,
scope=scope@entry=0x4708c120, flags=512)
at ./Zend/zend_constants.c:364
#4 0x0000558eeb6ca50c in zend_ast_evaluate (result=result@entry=0x7fff0b412fd0,
ast=ast@entry=0x4708c4c8, scope=0x4708c120)
at ./Zend/zend_ast.c:777
#5 0x0000558eeb6329ec in zval_update_constant_ex (scope=<optimized out>, p=0x7fb736c14440) at
./Zend/zend_execute_API.c:696
#6 zval_update_constant_ex (p=0x7fb736c14440, scope=<optimized out>) at
./Zend/zend_execute_API.c:671
#7 0x0000558eeb6ad06e in ZEND_RECV_INIT_SPEC_CONST_HANDLER () at ./Zend/zend_vm_execute.h:3736
#8 execute_ex (ex=0x4412e5a8) at ./Zend/zend_vm_execute.h:55951
#9 0x0000558eeb6b037d in zend_execute (op_array=0x7fb736c7b000, return_value=0x0) at
./Zend/zend_vm_execute.h:60163
#10 0x0000558eeb6421ed in zend_execute_scripts (type=type@entry=8, retval=retval@entry=0x0,
file_count=file_count@entry=3)
at ./Zend/zend.c:1846
#11 0x0000558eeb5dda41 in php_execute_script (primary_file=primary_file@entry=0x7fff0b415680) at
./main/main.c:2542
#12 0x0000558eeb487e0e in main (argc=<optimized out>, argv=<optimized out>) at
./sapi/fpm/fpm/fpm_main.c:1935
(gdb) backtrace full
#0 zval_addref_p (pz=0x4412edb0) at ./Zend/zend_types.h:1240
No locals.
#1 zend_separate_class_constants_table (class_type=class_type@entry=0x4412e5a8) at
./Zend/zend_API.c:1331
_z = <optimized out>
__ht = 0x4412e658
_p = <optimized out>
_end = 0x4412ec80
mutable_data = <optimized out>
constants_table = 0x7fb7314c14b8
key = 0x41b8fd30
new_c = <optimized out>
c = 0x4412edb0
#2 0x0000558eeb6306c8 in zend_class_constants_table (ce=0x4412e5a8) at ./Zend/zend_API.h:431
mutable_data = <optimized out>
#3 zend_get_class_constant_ex (class_name=0x41758028, constant_name=0x41b8fd30,
scope=scope@entry=0x4708c120, flags=512)
at ./Zend/zend_constants.c:364
ce = 0x4412e5a8
c = 0x0
ret_constant = 0x0
#4 0x0000558eeb6ca50c in zend_ast_evaluate (result=result@entry=0x7fff0b412fd0,
ast=ast@entry=0x4708c4c8, scope=0x4708c120)
at ./Zend/zend_ast.c:777
class_name = <optimized out>
const_name = <optimized out>
zv = <optimized out>
op1 = {value = {lval = 1103188432, dval = 5.450475051406721e-315, counted = 0x41c151d0, str
= 0x41c151d0, arr = 0x41c151d0,
obj = 0x41c151d0, res = 0x41c151d0, ref = 0x41c151d0, ast = 0x41c151d0, zv = 0x41c151d0,
ptr = 0x41c151d0, ce = 0x41c151d0,
func = 0x41c151d0, ww = {w1 = 1103188432, w2 = 0}}, u1 = {type_info = 3949145561, v =
{type = 217 '\331', type_flags = 45 '-',
u = {extra = 60259}}}, u2 = {next = 21902, cache_slot = 21902, opline_num = 21902,
lineno = 21902, num_args = 21902,
fe_pos = 21902, fe_iter_idx = 21902, property_guard = 21902, constant_flags = 21902,
extra = 21902}}
op2 = {value = {lval = 94072325571128, dval = 4.6477904289087367e-310, counted =
0x558eeb8c8238 <zend_autoload>,
str = 0x558eeb8c8238 <zend_autoload>, arr = 0x558eeb8c8238 <zend_autoload>,
obj = 0x558eeb8c8238 <zend_autoload>,
res = 0x558eeb8c8238 <zend_autoload>, ref = 0x558eeb8c8238 <zend_autoload>,
ast = 0x558eeb8c8238 <zend_autoload>,
zv = 0x558eeb8c8238 <zend_autoload>, ptr = 0x558eeb8c8238 <zend_autoload>,
ce = 0x558eeb8c8238 <zend_autoload>,
func = 0x558eeb8c8238 <zend_autoload>, ww = {w1 = 3951854136, w2 = 21902}}, u1 =
{type_info = 7, v = {type = 7 '\a',
type_flags = 0 '\000', u = {extra = 0}}}, u2 = {next = 21902, cache_slot =
21902, opline_num = 21902, lineno = 21902,
num_args = 21902, fe_pos = 21902, fe_iter_idx = 21902, property_guard = 21902,
constant_flags = 21902, extra = 21902}}
ret = SUCCESS
#5 0x0000558eeb6329ec in zval_update_constant_ex (scope=<optimized out>, p=0x7fb736c14440) at
./Zend/zend_execute_API.c:696
tmp = {value = {lval = 776, dval = 3.8339494117280732e-321, counted = 0x308, str = 0x308,
arr = 0x308, obj = 0x308, res = 0x308,
ref = 0x308, ast = 0x308, zv = 0x308, ptr = 0x308, ce = 0x308, func = 0x308, ww = {w1 =
776, w2 = 0}}, u1 = {
type_info = 2236897536, v = {type = 0 '\000', type_flags = 89 'Y', u
= {extra = 34132}}}, u2 = {next = 1646290205,
cache_slot = 1646290205, opline_num = 1646290205, lineno = 1646290205, num_args =
1646290205, fe_pos = 1646290205,
fe_iter_idx = 1646290205, property_guard = 1646290205, constant_flags = 1646290205,
extra = 1646290205}}
ast_ref = 0x4708c4c0
ast_is_refcounted = <optimized out>
result = <optimized out>
ast = 0x4708c4c8
#6 zval_update_constant_ex (p=0x7fb736c14440, scope=<optimized out>) at
./Zend/zend_execute_API.c:671
ast = <optimized out>
name = <optimized out>
zv = <optimized out>
_z1 = <optimized out>
_z2 = <optimized out>
_gc = <optimized out>
_t = <optimized out>
tmp = {value = {lval = <optimized out>, dval = <optimized out>, counted =
<optimized out>, str = <optimized out>,
arr = <optimized out>, obj = <optimized out>, res = <optimized out>,
ref = <optimized out>, ast = <optimized out>,
zv = <optimized out>, ptr = <optimized out>, ce = <optimized out>,
func = <optimized out>, ww = {w1 = <optimized out>,
w2 = <optimized out>}}, u1 = {type_info = <optimized out>, v = {type =
<optimized out>, type_flags = <optimized out>, u = {
--Type <RET> for more, q to quit, c to continue without paging--c
extra = <optimized out>}}}, u2 = {next = <optimized out>, cache_slot =
<optimized out>, opline_num = <optimized out>, lineno = <optimized out>, num_args
= <optimized out>, fe_pos = <optimized out>, fe_iter_idx = <optimized out>,
property_guard = <optimized out>, constant_flags = <optimized out>, extra =
<optimized out>}}
ast_ref = <optimized out>
ast_is_refcounted = <optimized out>
result = <optimized out>
_z1 = <optimized out>
_z2 = <optimized out>
_gc = <optimized out>
_t = <optimized out>
#7 0x0000558eeb6ad06e in ZEND_RECV_INIT_SPEC_CONST_HANDLER () at ./Zend/zend_vm_execute.h:3736
cache_val = 0x7fb7314c14a8
default_value = 0x7fb7306424b0
arg_num = <optimized out>
param = 0x7fb736c14440
arg_num = <optimized out>
param = <optimized out>
default_value = <optimized out>
cache_val = <optimized out>
_z1 = <optimized out>
_z2 = <optimized out>
_gc = <optimized out>
_t = <optimized out>
_z1 = <optimized out>
_z2 = <optimized out>
_gc = <optimized out>
_t = <optimized out>
_z1 = <optimized out>
_z2 = <optimized out>
_gc = <optimized out>
_t = <optimized out>
_z1 = <optimized out>
_z2 = <optimized out>
_gc = <optimized out>
_t = <optimized out>
```
Unfortunately I was not able to construct a minimal reproducable setup, therefore I will add some
context on the project and the enums classes involved.
Our php modulues:
```
php -m
[PHP Modules]
apcu
bcmath
calendar
Core
ctype
curl
date
dba
dom
exif
FFI
fileinfo
filter
ftp
gd
gettext
hash
iconv
igbinary
json
libxml
mbstring
openssl
pcntl
pcre
PDO
pdo_pgsql
pgsql
Phar
posix
readline
redis
Reflection
session
shmop
SimpleXML
sockets
sodium
SPL
standard
sysvmsg
sysvsem
sysvshm
tideways
tokenizer
uuid
xml
xmlreader
xmlwriter
xsl
Zend OPcache
zip
zlib
[Zend Modules]
Zend OPcache
```
The enum we've added:
```php
enum BoostTargetedFeatureEnum: string implements JsonSerializable
{
use EnumSerializableTrait;
public static function getFrom(?string $value): self
{
if (!$value) {
return self::All;
}
return self::tryFrom($value) ?? self::All;
}
case GuildExpedition = 'guild_expedition';
case GuildBattleground = 'battleground';
case All = 'all';
}
<?php
```
The trait we're including:
```php
trait EnumSerializableTrait
{
public function jsonSerialize(): array
{
$value = $this->value ?? $this->name;
return ['__enum__' => $this->getEnumName(), 'value' => $value];
}
public function getEnumName(): string
{
return str_replace('Enum', '', strip_namespace($this));
}
}
```
The BoostHintVO class mentioned in the stacktrace:
```
class BoostHintVO
{
public string $type;
public int $value;
public BoostTargetedFeatureEnum $targetedFeature = BoostTargetedFeatureEnum::All;
public static function create(
string $type,
int $value,
BoostTargetedFeatureEnum $target = BoostTargetedFeatureEnum::All
): BoostHintVO {
$vo = new BoostHintVO();
$vo->type = $type;
$vo->value = $value;
$vo->targetedFeature = $target;
return $vo;
}
}
```
The segfault only occurs with **opcache enabled**. The first request on a freshly restarted php-fpm
process will succeed, the second request fails with the segfault.
The enum case
All is being referenced from 30 places in our codebase, a lot of times as
a default argument on an optional method parameter.
The segfault also occurred when modifying the BoostHintVO::create to accept a nullable
string when we called the BoostTargetedFeatureEnum::getFrom inside the class - in case
it's relevant, this static create fuction (as well as other places in the code that will
reference that new enum) will get called a lot from different factories in our code that produce
these VOs that are sent to clients. If there is any more context I can or should provide please do
let me know!
When searching for other segfaults related to php enums I found #10914 where the faulty/changed code
looks fairly similar to where it's failing according the stacktrace:
https://github.com/php/php-src/blob/badfe4fbdadffde92135aeb8c60135cd262c8e01/Zend/zend_API.c#L1324
vs
https://github.com/nielsdos/php-src/commit/e6abc937a0fb0148bac480c341e2f5acf88b58d3#diff-a9c2ca78a68a3ad33d5b00dd7f1d37aa2ac438dbff79d56d06918aefaf6fe4af
This might be absolutely unrelated as I have no prior experience in php core development, however I
wanted to mention it in case the causes are indeed related.
### PHP Version
PHP 8.1.21
### Operating System
Debian 11.7