[php-src] Issue #11807: Segfault on accessing enum cases

From: 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

« previous php.bugs (#245053) next »