Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Segfault on accessing enum cases with OPCache enabled #11807

Closed
adlenton opened this issue Jul 27, 2023 · 6 comments
Closed

Segfault on accessing enum cases with OPCache enabled #11807

adlenton opened this issue Jul 27, 2023 · 6 comments

Comments

@adlenton
Copy link

adlenton commented Jul 27, 2023

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

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:

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;
    }
}

line 16 from the stacktrace is the line of the constructor with the reference to the ::All case.

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:

ZEND_HASH_FOREACH_STR_KEY_PTR(&class_type->constants_table, key, c) {

vs
nielsdos@e6abc93#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

@adlenton adlenton changed the title Segfault on accessing enum cases Segfault on accessing enum cases with OPCache enabled Jul 27, 2023
@iluuu1994
Copy link
Member

@adlenton Hi! It indeed looks like this might have the same root cause as #10914 (fixed by #11675). You might be able to enable opcache.protect_memory=1 in your development environment to see if your code reports an error. It will check if shared memory is accidentally modified, which happened without the fix provided by @nielsdos. If it doesn't fail, it's possible that we're dealing with something else.

@adlenton
Copy link
Author

adlenton commented Aug 1, 2023

Thank you for providing such quick feedback! Unfortunately I'm unable to reach the segfault when enabling opcache.protect_memory as this leads to another segfaulttriggered from igbinary much earlier (on the "first request" while the original segfault only occurred on the second request).

Program received signal SIGSEGV, Segmentation fault.
0x000055940ab7ba0c in zval_update_constant_ex (scope=<optimized out>, p=0x4527b950) at ./Zend/zend_execute_API.c:704
704	./Zend/zend_execute_API.c: No such file or directory.
(gdb) bt
#0  0x000055940ab7ba0c in zval_update_constant_ex (scope=<optimized out>, p=0x4527b950) at ./Zend/zend_execute_API.c:704
#1  zval_update_constant_ex (p=0x4527b950, scope=<optimized out>) at ./Zend/zend_execute_API.c:671
#2  0x00007fd3c35ac9bd in ?? () from /usr/lib/php/20210902/igbinary.so
#3  0x00007fd3c35ad195 in ?? () from /usr/lib/php/20210902/igbinary.so
#4  0x00007fd3c35ac007 in ?? () from /usr/lib/php/20210902/igbinary.so
#5  0x00007fd3c35ad195 in ?? () from /usr/lib/php/20210902/igbinary.so
#6  0x00007fd3c35ac007 in ?? () from /usr/lib/php/20210902/igbinary.so
#7  0x00007fd3c35ad195 in ?? () from /usr/lib/php/20210902/igbinary.so
#8  0x00007fd3c35ac007 in ?? () from /usr/lib/php/20210902/igbinary.so
#9  0x00007fd3c35ad195 in ?? () from /usr/lib/php/20210902/igbinary.so
#10 0x00007fd3c35b49a0 in igbinary_unserialize () from /usr/lib/php/20210902/igbinary.so
#11 0x00007fd3c35b4c2d in zif_igbinary_unserialize () from /usr/lib/php/20210902/igbinary.so
#12 0x000055940a9c53b6 in ZEND_DO_FCALL_SPEC_OBSERVER_HANDLER () at ./Zend/zend_vm_execute.h:1981
#13 0x000055940a9c6243 in execute_ex (ex=0x7fd3c01458c0) at ./Zend/zend_vm_execute.h:55827
#14 0x000055940abf937d in zend_execute (op_array=0x7fd3c6c7b000, return_value=0x0) at ./Zend/zend_vm_execute.h:60163
#15 0x000055940ab8b1ed in zend_execute_scripts (type=type@entry=8, retval=retval@entry=0x0, file_count=file_count@entry=3) at ./Zend/zend.c:1846
#16 0x000055940ab26a41 in php_execute_script (primary_file=primary_file@entry=0x7ffe56a07810) at ./main/main.c:2542
#17 0x000055940a9d0e0e in main (argc=<optimized out>, argv=<optimized out>) at ./sapi/fpm/fpm/fpm_main.c:1935

Do you think it's benefitial to report this on the igbinary project? This is not happening without the protect_memory option.

@iluuu1994
Copy link
Member

@adlenton That looks similar to the bug in #11675, so it's possible that this is your root cause. opcache.protect_memory should not report errors, anything it reports is likely to be an actual issue. You will likely need to build igbinary with debug symbols to provide more information on where the error is happening.

@bwoebi
Copy link
Member

bwoebi commented Aug 1, 2023

Yes: https://github.com/igbinary/igbinary/blob/102ad68159791e76667f8455cbc171e6ec78253c/src/php7/igbinary.c#L3034 uses &ce->constants_table instead of CE_CONSTANTS_TABLE(ce).

@adlenton
Copy link
Author

adlenton commented Aug 1, 2023

Yes, that appears to be confirmed by the debug backtrace:

Program received signal SIGSEGV, Segmentation fault.
0x0000565163516a0c in zval_update_constant_ex (scope=<optimized out>, p=0x4339dd20) at ./Zend/zend_execute_API.c:704
704	./Zend/zend_execute_API.c: No such file or directory.
(gdb) bt
#0  0x0000565163516a0c in zval_update_constant_ex (scope=<optimized out>, p=0x4339dd20) at ./Zend/zend_execute_API.c:704
#1  zval_update_constant_ex (p=p@entry=0x4339dd20, scope=<optimized out>) at ./Zend/zend_execute_API.c:671
#2  0x00007ff658f0e9bd in igbinary_unserialize_object_enum_case (ce=0x4339d1a0, z=0x7ff656403c48, igsd=0x7fff68bebd90) at ./build-8.1/src/php7/igbinary.c:3050
#3  igbinary_unserialize_object (flags=0, z=0x7ff656403c48, t=igbinary_type_enum_case, igsd=0x7fff68bebd90) at ./build-8.1/src/php7/igbinary.c:3261
#4  igbinary_unserialize_zval (igsd=0x7fff68bebd90, z=0x7ff656403c48, flags=0) at ./build-8.1/src/php7/igbinary.c:3507
#5  0x00007ff658f0f195 in igbinary_unserialize_object_properties (ce=0x4626f760, z=0x7ff653509e80, t=igbinary_type_array8, igsd=0x7fff68bebd90) at ./build-8.1/src/php7/igbinary.c:2900
#6  igbinary_unserialize_object (flags=<optimized out>, z=0x7ff653509e80, t=igbinary_type_array8, igsd=0x7fff68bebd90) at ./build-8.1/src/php7/igbinary.c:3221
#7  igbinary_unserialize_zval (igsd=0x7fff68bebd90, z=0x7ff653509e80, flags=<optimized out>) at ./build-8.1/src/php7/igbinary.c:3507
#8  0x00007ff658f0e007 in igbinary_unserialize_array (create_ref=true, flags=<optimized out>, z=0x7fff68beb9a0, t=<optimized out>, igsd=0x7fff68bebd90) at ./build-8.1/src/php7/igbinary.c:2693
#9  igbinary_unserialize_zval (igsd=0x7fff68bebd90, z=0x7fff68beb9a0, flags=<optimized out>) at ./build-8.1/src/php7/igbinary.c:3514
#10 0x00007ff658f0f195 in igbinary_unserialize_object_properties (ce=0x4624e360, z=0x7ff656309a58, t=igbinary_type_array8, igsd=0x7fff68bebd90) at ./build-8.1/src/php7/igbinary.c:2900
#11 igbinary_unserialize_object (flags=<optimized out>, z=0x7ff656309a58, t=igbinary_type_array8, igsd=0x7fff68bebd90) at ./build-8.1/src/php7/igbinary.c:3221
#12 igbinary_unserialize_zval (igsd=0x7fff68bebd90, z=0x7ff656309a58, flags=<optimized out>) at ./build-8.1/src/php7/igbinary.c:3507
#13 0x00007ff658f0f195 in igbinary_unserialize_object_properties (ce=0x436c4cf0, z=0x7ff65350b9e8, t=igbinary_type_array8, igsd=0x7fff68bebd90) at ./build-8.1/src/php7/igbinary.c:2900
#14 igbinary_unserialize_object (flags=<optimized out>, z=0x7ff65350b9e8, t=igbinary_type_array8, igsd=0x7fff68bebd90) at ./build-8.1/src/php7/igbinary.c:3221
#15 igbinary_unserialize_zval (igsd=0x7fff68bebd90, z=0x7ff65350b9e8, flags=<optimized out>) at ./build-8.1/src/php7/igbinary.c:3507
#16 0x00007ff658f0e007 in igbinary_unserialize_array (create_ref=true, flags=<optimized out>, z=0x7fff68bebc40, t=<optimized out>, igsd=0x7fff68bebd90) at ./build-8.1/src/php7/igbinary.c:2693
#17 igbinary_unserialize_zval (igsd=0x7fff68bebd90, z=0x7fff68bebc40, flags=<optimized out>) at ./build-8.1/src/php7/igbinary.c:3514
#18 0x00007ff658f0f195 in igbinary_unserialize_object_properties (ce=0x462ed1b0, z=0x7ff65c413d70, t=igbinary_type_array8, igsd=0x7fff68bebd90) at ./build-8.1/src/php7/igbinary.c:2900
#19 igbinary_unserialize_object (flags=<optimized out>, z=0x7ff65c413d70, t=igbinary_type_array8, igsd=0x7fff68bebd90) at ./build-8.1/src/php7/igbinary.c:3221
#20 igbinary_unserialize_zval (igsd=0x7fff68bebd90, z=0x7ff65c413d70, flags=<optimized out>) at ./build-8.1/src/php7/igbinary.c:3507
#21 0x00007ff658f169a0 in igbinary_unserialize (buf=0x7ff654ef7a18 "", buf_len=140734950718864, z=0x7ff65c413d70) at ./build-8.1/src/php7/igbinary.c:784
#22 0x00007ff658f16c2d in zif_igbinary_unserialize (execute_data=<optimized out>, return_value=0x7ff65c413d70) at ./build-8.1/src/php7/igbinary.c:830
#23 0x00005651633603b6 in ZEND_DO_FCALL_SPEC_OBSERVER_HANDLER () at ./Zend/zend_vm_execute.h:1981
#24 0x0000565163361243 in execute_ex (ex=0x7ff655ffddc0) at ./Zend/zend_vm_execute.h:55827
#25 0x000056516359437d in zend_execute (op_array=0x7ff65c477000, return_value=0x0) at ./Zend/zend_vm_execute.h:60163
#26 0x00005651635261ed in zend_execute_scripts (type=type@entry=8, retval=retval@entry=0x0, file_count=file_count@entry=3) at ./Zend/zend.c:1846
#27 0x00005651634c1a41 in php_execute_script (primary_file=primary_file@entry=0x7fff68bee540) at ./main/main.c:2542
#28 0x000056516336be0e in main (argc=<optimized out>, argv=<optimized out>) at ./sapi/fpm/fpm/fpm_main.c:1935

Thank you for your support, I will then check the open issues there and raise one if it's not reported yet. 🙏

@iluuu1994
Copy link
Member

Great, it looks like the issue in igbinary was resolved. I'll close this issue. If you're still experiencing anything unexpected, let us know.

@iluuu1994 iluuu1994 closed this as not planned Won't fix, can't repro, duplicate, stale Aug 2, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants