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 PHP8.3 with class type as a method parameter due to memory corruption #529

Open
gnat42 opened this issue Apr 30, 2024 · 4 comments

Comments

@gnat42
Copy link
Contributor

gnat42 commented Apr 30, 2024

Hello,

We have an extension we've used for multiple years that is built against php-cpp. Recently we're looking at upgrading to php 8.3. We recompiled php-cpp and our extension and it all compiled cleanly. However php segfaults on startup. I've dug into the issue and have narrowed it down - however I've not been able to find the root cause. I'll include a full stacktrace at the end.

I narrowed it down to the following:

pdfWriter.method<&PdfWriter::writeImageToPage>("writeImageToPage", Php::Public, {
    Php::ByVal("page",Php::Type::Numeric),
    Php::ByVal("image","PDF\\PdfImage") //<--- This is the problem
});

When I exclude these lines everything starts up fine.

I tried to create a simple reproducer by copying one of the Examples from PHP-CPP and creating a simple NamespacedObject but it didn't crash no matter what I did.

I copied over my pdfWriter object from my extension to the new Examples (commenting out a bunch of implementation code) Changing the PDF\\PdfImage to

  • NonExisting\\Vendor\\NamespacedObject <-- Doesn't exist
  • Vendor\\NamespacedObject <-- Simple php-cpp class with a few getters/constructor __toString etc.
  • ArrayObject <-- part of PHP

PHP doesn't crash on startup for any of those three situations but continues to crash if I provide PDF\\PdfImage

Here's the stack trace deep in PHP functions/macros.

#0  zend_alloc_ce_cache (type_name=type_name@entry=0x7fffe3d721c1) at /usr/src/debug/php-8.3.6-1.fc40.x86_64/Zend/zend.c:2004
#1  0x0000555555833a37 in zend_normalize_internal_type (type=0x555556169b38) at /usr/src/debug/php-8.3.6-1.fc40.x86_64/Zend/zend_API.c:2781
#2  zend_register_functions (scope=scope@entry=0x555556168a70, functions=0x555556168920, function_table=function_table@entry=0x555556168ab0, type=<optimized out>)
    at /usr/src/debug/php-8.3.6-1.fc40.x86_64/Zend/zend_API.c:3029
#3  0x0000555555834029 in do_register_internal_class (orig_class_entry=0x7fffffffbd90, ce_flags=<optimized out>) at /usr/src/debug/php-8.3.6-1.fc40.x86_64/Zend/zend_API.c:3323
#4  0x00007fffe3ced251 in Php::ClassImpl::initialize(Php::ClassBase*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) () from /lib64/libphpcpp.so.2.4
#5  0x00007fffe3cf4bb2 in Php::ExtensionImpl::initialize(int)::{lambda(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&)#1}::operator()(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&) const () from /lib64/libphpcpp.so.2.4
#6  0x00007fffe3cf5cef in void std::__invoke_impl<void, Php::ExtensionImpl::initialize(int)::{lambda(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&)#1}&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&>(std::__invoke_other, Php::ExtensionImpl::initialize(int)::{lambda(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&)#1}&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&) () from /lib64/libphpcpp.so.2.4
#7  0x00007fffe3cf59e8 in std::enable_if<std::is_void<void>::value, void>::type std::__invoke_r<void, Php::ExtensionImpl::initialize(int)::{lambda(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&)#1}&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&>(Php::ExtensionImpl::initialize(int)::{lambda(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&)#1}&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&) () from /lib64/libphpcpp.so.2.4
#8  0x00007fffe3cf5596 in std::_Function_handler<void (std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&), Php::ExtensionImpl::initialize(int):
--Type <RET> for more, q to quit, c to continue without paging--
:{lambda(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&)#1}>::_M_invoke(std::_Any_data const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&) () from /lib64/libphpcpp.so.2.4
#9  0x00007fffe3d0137d in std::function<void (std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&)>::operator()(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&) const () from /lib64/libphpcpp.so.2.4
#10 0x00007fffe3cff519 in Php::Namespace::classes(std::function<void (std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&)> const&)::{lambda(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&)#1}::operator()(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&) const () from /lib64/libphpcpp.so.2.4
#11 0x00007fffe3d00717 in void std::__invoke_impl<void, Php::Namespace::classes(std::function<void (std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&)> const&)::{lambda(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&)#1}&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&>(std::__invoke_other, Php::Namespace::classes(std::function<void (std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&)> const&)::{lambda(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&)#1}&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&) () from /lib64/libphpcpp.so.2.4
#12 0x00007fffe3d00304 in std::enable_if<std::is_void<void>::value, void>::type std::__invoke_r<void, Php::Namespace::classes(std::function<void (std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&)> const&)::{lambda(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&)#1}&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&>(Php::Namespace::classes(std::function<void (std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&)> const&)::{lambda(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&)#1}&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&) () from /lib64/libphpcpp.so.2.4
#13 0x00007fffe3cffe57 in std::_Function_handler<void (std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&), Php::Namespace::classes(std::function<void (std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&)> const&)::{lambda(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&)#1}>::_M_invoke(std::_Any_data const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&) ()
   from /lib64/libphpcpp.so.2.4
#14 0x00007fffe3d0137d in std::function<void (std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&)>::operator()(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&) const () from /lib64/libphpcpp.so.2.4
#15 0x00007fffe3cff66c in Php::Namespace::classes(std::function<void (std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&)> const&) ()
   from /lib64/libphpcpp.so.2.4
#16 0x00007fffe3cff73b in Php::Namespace::classes(std::function<void (std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Php::ClassBase&)> const&) ()
   from /lib64/libphpcpp.so.2.4
#17 0x00007fffe3cf4de1 in Php::ExtensionImpl::initialize(int) () from /lib64/libphpcpp.so.2.4
#18 0x00007fffe3cf43de in Php::ExtensionImpl::processStartup(int, int) () from /lib64/libphpcpp.so.2.4
#19 0x00005555558322a3 in zend_startup_module_ex (module=0x555555f37d30) at /usr/src/debug/php-8.3.6-1.fc40.x86_64/Zend/zend_API.c:2312
#20 0x0000555555832350 in zend_startup_module_zval (zv=<optimized out>) at /usr/src/debug/php-8.3.6-1.fc40.x86_64/Zend/zend_API.c:2327
#21 0x00005555558415ab in zend_hash_apply (ht=ht@entry=0x555555e08c20 <module_registry>, apply_func=apply_func@entry=0x555555832340 <zend_startup_module_zval>)
    at /usr/src/debug/php-8.3.6-1.fc40.x86_64/Zend/zend_hash.c:2086
#22 0x00005555558326bb in zend_startup_modules () at /usr/src/debug/php-8.3.6-1.fc40.x86_64/Zend/zend_API.c:2450
#23 0x00005555557bdb32 in php_module_startup (sf=<optimized out>, additional_module=0x0) at /usr/src/debug/php-8.3.6-1.fc40.x86_64/main/main.c:2225
#24 0x0000555555649db3 in main (argc=1, argv=0x555555e10a60) at /usr/src/debug/php-8.3.6-1.fc40.x86_64/sapi/cli/php_cli.c:1307

Any pointers would be appreciated.

@gnat42
Copy link
Contributor Author

gnat42 commented Apr 30, 2024

I can add that I've inspected type_name where the crash occurs and the type_name->val is "oPage".
I've looked a bit and can confirm that oPage is from the string method name. I modified:

pdfWriter.method<&PdfWriter::writeImageToPage>("writeImageToPage", Php::Public, {
    Php::ByVal("page",Php::Type::Numeric),
    Php::ByVal("image","PDF\\PdfImage")
});
//to
pdfWriter.method<&PdfWriter::writeImageTaPage>("writeImageTbPage", Php::Public, {
    Php::ByVal("page",Php::Type::Numeric),
    Php::ByVal("image","PDF\\PdfImage")
});

and then type_name->val is bPage. Not sure if I'm using gdb properly but get that value with p /s (char*)type_name->val

@gnat42
Copy link
Contributor Author

gnat42 commented May 1, 2024

Ok after more trial/error it looks like its picking up memory from the next method... For example I changed it to:

pdfWriter.method<&PdfWriter::writeImageTdPage>("deadbeefxx", Php::Public, {
            Php::ByVal("page", Php::Type::Numeric),
            Php::ByVal("image","PDF\\PdfImage")
        });

        // PdfImage Methods ========================
        Php::Class<PdfImage> pdfImage("PdfImage", 0);
        pdfImage.method<&PdfImage::__construct>("__construczt", Php::Public, {

When it crashes there type_name->val is __construczt

@gnat42
Copy link
Contributor Author

gnat42 commented May 1, 2024

Ok, now I'm super confused. I dug until I came across the code in callable.h starting lines 251. I added a bunch of poor man's debugging std::cout lines.

    case Type::Object:
              if (arg.classname()) {
                  std::cout << "fill-ClassName: " << arg.classname() << " Encoded: " << arg.encoded() << std::endl;
                  info->type = (zend_type) ZEND_TYPE_INIT_CLASS(arg.encoded(), arg.allowNull(), _ZEND_ARG_INFO_FLAGS(arg.byReference(), 0, 0));
                  if (ZEND_TYPE_HAS_NAME(info->type)) { 
                      std::cout << "fill-HAS NAME" << std::endl;
                      zend_string *typeName = ZEND_TYPE_NAME(info->type);
                      std::cout << "\tfill-type: " << ZSTR_VAL(typeName) << std::endl;
                  } else {
                      std::cout << "\tfill-NO NAME" << std::endl;
                  }
                  break;
              }
              info->type = (zend_type) ZEND_TYPE_INIT_CODE(IS_OBJECT, arg.allowNull(), _ZEND_ARG_INFO_FLAGS(arg.byReference(), 0, 0));
              break;

The output for this function registration ends up being.

fill-ClassName: PDF\PdfImage Encoded: PDF\PdfImage
fill-HAS NAME
        fill-type: __construczt
Fill Argv: PDF\PdfImage
PostFilled: image( __construczt )

I've dug into the ZEND_TYPE_INIT_CODE macro but so far haven't found a significant differences between it and 8.1. I don't see how the data isn't being copied into the zend_entry...

@gnat42
Copy link
Contributor Author

gnat42 commented May 1, 2024

This issue is fixed by #530

@gnat42 gnat42 changed the title Bizarre Crash Segfault on PHP8.3 with class type as a method parameter due to memory corruption May 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant