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

SIGSEGV in zend_mm_alloc_small while executing spl_perform_autoload with Saxon extension #12124

Closed
ttln opened this issue Sep 4, 2023 · 29 comments

Comments

@ttln
Copy link

ttln commented Sep 4, 2023

Description

Hi there,

I am trying to use Saxon to run XSLT transformations from PHP.
Saxon is an XSLT, XPath, and XQuery processor made by Saxonica.

Saxonica produce a PHP extension that allows to call the processor from PHP.

When I run a simple PHP script from the CLI, the transformation works successfully.
When I run a transformation from PHPUnit or Symfony, I encounter a segmentation fault.

The following code:

<?php

$processor = new \Saxon\SaxonProcessor();
$input = file_get_contents(__DIR__.'/input.xml');
$xdmDoc = $processor->parseXmlFromString($input);
$processor = $processor->newXslt30Processor();
$compiled = $processor->compileFromString(
    file_get_contents(__DIR__.'/template.xslt')
);
$args = [];
$result = $compiled->transformToString($xdmDoc);

echo $result;

Resulted in this output:

<!DOCTYPE HTML>
<html>
   <head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <title>Books</title>
   </head>
   <body>
      
      <p>
         Title:
         XML Developer's Guide</p>
[...]

Which is a successful transformation of that input.xml with the XSL transform.xslt.

However, when I run it inside PHPUnit or Symfony, I encounter an error.

The following code:

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class TestController extends AbstractController
{

    // This is a non-working route, triggered by the use of SaxonProcessor
    // and the creation of a Response.
    // This works too with PHPUnit.
    #[Route('/test', name: 'app_test')]
    public function index(): Response
    {
        $processor = new \Saxon\SaxonProcessor();
        $input = file_get_contents(__DIR__.'/../../input.xml');
        $xdmDoc = $processor->parseXmlFromString($input);
        $processor = $processor->newXslt30Processor();
        $compiled = $processor->compileFromString(
            file_get_contents(__DIR__.'/../../template.xslt')
        );
        $args = [];
        $result = $compiled->transformToString($xdmDoc);

        // Below is the error trigger.
        // If you add 'echo $result; exit;', the transformed result will be shown correctly.
        $response = new Response($result);

        // Never reached.
        return $response;
    }
}

Resulted in this output:

symfony  command "PHP" failed: signal: segmentation fault, waiting 5 seconds before restarting it

But I expected this output instead:

<!DOCTYPE HTML>
<html>
   <head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <title>Books</title>
   </head>
   <body>
      
      <p>
         Title:
         XML Developer's Guide</p>
[...]

I set up GDB, and attached it to my PHP process, and called the Symfony route again. This produced the following backtrace:

(gdb) continue
Continuing.
[New Thread 0x7f7ae79ff6c0 (LWP 128940)]

Thread 1 "php" received signal SIGSEGV, Segmentation fault.
zend_mm_alloc_small (bin_num=5, heap=0x7f7b02800040)
    at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_alloc.c:1313
1313                    heap->free_slot[bin_num] = p->next_free_slot;                                                 
(gdb) bt
#0  zend_mm_alloc_small (bin_num=5, heap=0x7f7b02800040)
    at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_alloc.c:1313
#1  zend_mm_alloc_heap (size=<optimized out>, heap=0x7f7b02800040)
    at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_alloc.c:1384
#2  _emalloc (size=<optimized out>) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_alloc.c:2601
#3  0x00005589013d057a in zend_string_alloc (persistent=false, len=20)
    at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_string.h:152
#4  zend_string_init (persistent=false, len=20, 
    str=0x7f7aea3da3ce "HTTP_PARTIAL_CONTENT = 206;\n    public const HTTP_MULTI_STATUS = 207;          // RFC4918\n    public const HTTP_ALREADY_REPORTED = 208;      // RFC5842\n    public const HTTP_IM_USED = 226;", ' ' <repeats 12 times>...) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_string.h:174
#5  lex_scan (zendlval=zendlval@entry=0x7fff3a293740, elem=0x7fff3a2937d8)
    at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_language_scanner.c:9818
#6  0x00005589013e72a0 in zendlex (elem=elem@entry=0x7fff3a2937d8)
    at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_compile.c:1802
#7  0x00005589013c7883 in zendparse () at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_language_parser.c:4758
#8  0x00005589013ca656 in zend_compile (type=type@entry=2)
    at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_language_scanner.c:601
#9  0x00005589013cbdff in compile_file (file_handle=0x7fff3a294460, type=2)
    at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_language_scanner.c:655
#10 0x00007f7aec7df256 in phar_compile_file (file_handle=0x7fff3a294460, type=2)
    at /usr/src/debug/php-8.2.10-1.fc38.x86_64/ext/phar/phar.c:3355
#11 0x00005589013cbec1 in compile_filename (type=type@entry=2, filename=filename@entry=0x7f7aea383770)
    at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_language_scanner.c:706
#12 0x0000558901441f8f in zend_include_or_eval (inc_filename_zv=<optimized out>, type=2)
    at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_execute.c:4799
#13 0x0000558901450f6f in ZEND_INCLUDE_OR_EVAL_SPEC_CV_HANDLER ()
    at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_vm_execute.h:38960
#14 0x0000558901479886 in execute_ex (ex=0xe34128)
    at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_vm_execute.h:59407
#15 0x00005589013fdbf8 in zend_call_function (fci=fci@entry=0x7fff3a2947a0, fci_cache=<optimized out>, 
    fci_cache@entry=0x7fff3a294780) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_execute_API.c:949
#16 0x00005589013fdf87 in zend_call_known_function (fn=0x7f7b02991498, object=<optimized out>, 
    called_scope=<optimized out>, retval_ptr=retval_ptr@entry=0x0, param_count=param_count@entry=1, 
    params=params@entry=0x7fff3a294830, named_params=0x0)
    at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_execute_API.c:1043
#17 0x00005589012fba15 in spl_perform_autoload (class_name=0x7f7b02902b90, lc_name=0x7f7b029035a0)
    at /usr/src/debug/php-8.2.10-1.fc38.x86_64/ext/spl/php_spl.c:445
#18 0x00005589013fcd77 in zend_lookup_class_ex (name=name@entry=0x7f7b02902b90, key=0x7f7b029035a0, 
    flags=flags@entry=512) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_execute_API.c:1209
#19 0x00005589013fe3ea in zend_fetch_class_by_name (class_name=0x7f7b02902b90, key=<optimized out>, 
    fetch_type=fetch_type@entry=512) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_execute_API.c:1705
#20 0x000055890144dbb7 in ZEND_NEW_SPEC_CONST_UNUSED_HANDLER ()
    at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_vm_execute.h:10277
#21 0x0000558901478190 in execute_ex (ex=0xe34128)
    at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_vm_execute.h:56949
#22 0x0000558901481e11 in zend_execute (op_array=0x7f7b02801100, return_value=<optimized out>)
    at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_vm_execute.h:60408
#23 0x000055890140cdeb in zend_execute_scripts (type=type@entry=8, retval=retval@entry=0x0, 
    file_count=file_count@entry=3) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend.c:1833
#24 0x00005589013a3bda in php_execute_script (primary_file=primary_file@entry=0x7fff3a296db0)
    at /usr/src/debug/php-8.2.10-1.fc38.x86_64/main/main.c:2542
#25 0x0000558901500e64 in php_cli_server_dispatch_script (server=server@entry=0x558901a0e120 <server>, 
    client=client@entry=0x558902f18ab0) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/sapi/cli/php_cli_server.c:2097
--Type <RET> for more, q to quit, c to continue without paging--
#26 0x00005589015014b4 in php_cli_server_dispatch (client=0x558902f18ab0, server=0x558901a0e120 <server>)
    at /usr/src/debug/php-8.2.10-1.fc38.x86_64/sapi/cli/php_cli_server.c:2278
#27 php_cli_server_recv_event_read_request (server=0x558901a0e120 <server>, client=0x558902f18ab0)
    at /usr/src/debug/php-8.2.10-1.fc38.x86_64/sapi/cli/php_cli_server.c:2586
#28 0x00005589014fea86 in php_cli_server_do_event_for_each_fd_callback (_params=_params@entry=0x7fff3a297080, 
    fd=fd@entry=4, event=event@entry=1) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/sapi/cli/php_cli_server.c:2673
#29 0x0000558901502a2d in php_cli_server_poller_iter_on_active (poller=0x558901a0e128 <server+8>, 
    callback=0x5589014fea30 <php_cli_server_do_event_for_each_fd_callback>, opaque=0x7fff3a297080)
    at /usr/src/debug/php-8.2.10-1.fc38.x86_64/sapi/cli/php_cli_server.c:909
#30 php_cli_server_do_event_for_each_fd (whandler=0x558901501d70 <php_cli_server_send_event>, 
    rhandler=0x558901500e70 <php_cli_server_recv_event_read_request>, server=0x558901a0e120 <server>)
    at /usr/src/debug/php-8.2.10-1.fc38.x86_64/sapi/cli/php_cli_server.c:2693
#31 php_cli_server_do_event_loop (server=0x558901a0e120 <server>)
    at /usr/src/debug/php-8.2.10-1.fc38.x86_64/sapi/cli/php_cli_server.c:2704
#32 do_cli_server (argc=argc@entry=6, argv=argv@entry=0x558902ce9c10)
    at /usr/src/debug/php-8.2.10-1.fc38.x86_64/sapi/cli/php_cli_server.c:2835
#33 0x000055890124126e in main (argc=6, argv=0x558902ce9c10)
    at /usr/src/debug/php-8.2.10-1.fc38.x86_64/sapi/cli/php_cli.c:1336

I also produced the following zbacktrace:

(gdb) source .gdbinit
(gdb) zbacktrace
[0x7f7b02813ff0] Symfony\Component\ErrorHandler\DebugClassLoader->loadClass("Symfony\Component\HttpFoundation\Respons
e") /home/u1/code/test2/vendor/symfony/error-handler/DebugClassLoader.php:296 
[0x7f7b02813e10] App\Controller\TestController->index() /home/u1/code/test2/src/Controller/TestController.php:30 
[0x7f7b02813950] Symfony\Component\HttpKernel\HttpKernel->handleRaw(object[0x7f7b028139a0], 1) 
/home/u1/code/test2/vendor/symfony/http-kernel/HttpKernel.php:182 
[0x7f7b028136a0] Symfony\Component\HttpKernel\HttpKernel->handle(object[0x7f7b028136f0], 1, true) 
/home/u1/code/test2/vendor/symfony/http-kernel/HttpKernel.php:76 
[0x7f7b02813510] Symfony\Component\HttpKernel\Kernel->handle(object[0x7f7b02813560]) 
/home/u1/code/test2/vendor/symfony/http-kernel/Kernel.php:197 
[0x7f7b02813410] Symfony\Component\Runtime\Runner\Symfony\HttpKernelRunner->run() 
/home/u1/code/test2/vendor/symfony/runtime/Runner/Symfony/HttpKernelRunner.php:35 
[0x7f7b028130b0] (main) /home/u1/code/test2/vendor/autoload_runtime.php:29 
[0x7f7b02813020] (main) /home/u1/code/test2/public/index.php:5 

I also tried to execute the same code in PHPUnit 9.6.10. It produced the almost identical backtrace:

1313                    heap->free_slot[bin_num] = p->next_free_slot; 
#0  zend_mm_alloc_small (bin_num=5, heap=0x7ffff7200040) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_alloc.c:1313
#1  zend_mm_alloc_heap (size=<optimized out>, heap=0x7ffff7200040) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_alloc.c:1384
#2  _emalloc (size=<optimized out>) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_alloc.c:2601
#3  0x00005555557d057a in zend_string_alloc (persistent=false, len=17) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_string.h:152
#4  zend_string_init (persistent=false, len=17, 
    str=0x7fffe0413546 "customComparators as $comparator) {\n", ' ' <repeats 12 times>, "if ($comparator->accepts($expected, $actual)) {\n", ' ' <repeats 16 times>, "return $comparator;\n", ' ' <repeats 12 times>, "}\n        }\n\n        foreach ($this->defaultComparators "...) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_string.h:174
#5  lex_scan (zendlval=zendlval@entry=0x7fffffff9310, elem=0x7fffffff93a8) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_language_scanner.c:9818
#6  0x00005555557e72a0 in zendlex (elem=elem@entry=0x7fffffff93a8) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_compile.c:1802
#7  0x00005555557c7883 in zendparse () at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_language_parser.c:4758
#8  0x00005555557ca656 in zend_compile (type=type@entry=2) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_language_scanner.c:601
#9  0x00005555557cbdff in compile_file (file_handle=0x7fffffffa030, type=2) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_language_scanner.c:655
#10 0x00007fffe32b9256 in phar_compile_file (file_handle=0x7fffffffa030, type=2) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/ext/phar/phar.c:3355
#11 0x00005555557cbec1 in compile_filename (type=type@entry=2, filename=filename@entry=0x7ffff7322bd0) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_language_scanner.c:706
#12 0x0000555555841f8f in zend_include_or_eval (inc_filename_zv=<optimized out>, type=2) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_execute.c:4799
#13 0x0000555555850f6f in ZEND_INCLUDE_OR_EVAL_SPEC_CV_HANDLER () at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_vm_execute.h:38960
#14 0x0000555555879886 in execute_ex (ex=0xa48938) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_vm_execute.h:59407
#15 0x00005555557fdbf8 in zend_call_function (fci=fci@entry=0x7fffffffa370, fci_cache=<optimized out>, fci_cache@entry=0x7fffffffa350) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_execute_API.c:949
#16 0x00005555557fdf87 in zend_call_known_function (fn=0x7fffe09a0d38, object=<optimized out>, called_scope=<optimized out>, retval_ptr=retval_ptr@entry=0x0, param_count=param_count@entry=1, params=params@entry=0x7fffffffa400, 
    named_params=0x0) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_execute_API.c:1043
#17 0x00005555556fba15 in spl_perform_autoload (class_name=0x7fffe0d94b40, lc_name=0x7fffe0a0da80) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/ext/spl/php_spl.c:445
#18 0x00005555557fcd77 in zend_lookup_class_ex (name=name@entry=0x7fffe0d94b40, key=0x7fffe0a0da80, flags=flags@entry=512) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_execute_API.c:1209
#19 0x00005555557fe3ea in zend_fetch_class_by_name (class_name=0x7fffe0d94b40, key=<optimized out>, fetch_type=fetch_type@entry=512) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_execute_API.c:1705
#20 0x000055555584e5e7 in ZEND_INIT_STATIC_METHOD_CALL_SPEC_CONST_CONST_HANDLER () at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_vm_execute.h:6886
#21 0x0000555555877f5d in execute_ex (ex=0xa48938) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_vm_execute.h:56641
#22 0x0000555555881e11 in zend_execute (op_array=0x7ffff7291000, return_value=<optimized out>) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_vm_execute.h:60408
#23 0x000055555580cdeb in zend_execute_scripts (type=type@entry=8, retval=retval@entry=0x0, file_count=file_count@entry=3) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend.c:1833
#24 0x00005555557a3bda in php_execute_script (primary_file=primary_file@entry=0x7fffffffcaf0) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/main/main.c:2542
#25 0x00005555558f9e7b in do_cli (argc=4, argv=0x555555e10680) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/sapi/cli/php_cli.c:964
#26 0x00005555556413a9 in main (argc=4, argv=0x555555e10680) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/sapi/cli/php_cli.c:1333

And the PHPUnit zbacktrace

[0x7ffff721a700] Symfony\Component\ErrorHandler\DebugClassLoader->loadClass("SebastianBergmann\Comparator\Factory") /home/u1/code/php/test1/vendor/symfony/error-handler/DebugClassLoader.php:294 
[0x7ffff721a630] PHPUnit\Framework\TestCase->unregisterCustomComparators() /home/u1/code/php/test1/vendor/phpunit/phpunit/src/Framework/TestCase.php:2551 
[0x7ffff7219e90] PHPUnit\Framework\TestCase->runBare() /home/u1/code/php/test1/vendor/phpunit/phpunit/src/Framework/TestCase.php:1291 
[0x7ffff7218c90] PHPUnit\Framework\TestResult->run(object[0x7ffff7218ce0]) /home/u1/code/php/test1/vendor/phpunit/phpunit/src/Framework/TestResult.php:728 
[0x7ffff7218080] PHPUnit\Framework\TestCase->run(object[0x7ffff72180d0]) /home/u1/code/php/test1/vendor/phpunit/phpunit/src/Framework/TestCase.php:964 
[0x7ffff7217980] PHPUnit\Framework\TestSuite->run(object[0x7ffff72179d0]) /home/u1/code/php/test1/vendor/phpunit/phpunit/src/Framework/TestSuite.php:684 
[0x7ffff7217280] PHPUnit\Framework\TestSuite->run(object[0x7ffff72172d0]) /home/u1/code/php/test1/vendor/phpunit/phpunit/src/Framework/TestSuite.php:684 
[0x7ffff7216b80] PHPUnit\Framework\TestSuite->run(object[0x7ffff7216bd0]) /home/u1/code/php/test1/vendor/phpunit/phpunit/src/Framework/TestSuite.php:684 
[0x7ffff7214840] PHPUnit\TextUI\TestRunner->run(object[0x7ffff7214890], reference, reference, true) /home/u1/code/php/test1/vendor/phpunit/phpunit/src/TextUI/TestRunner.php:651 
[0x7ffff7214470] PHPUnit\TextUI\Command->run(array(3)[0x7ffff72144c0], true) /home/u1/code/php/test1/vendor/phpunit/phpunit/src/TextUI/Command.php:144 
[0x7ffff7214360] PHPUnit\TextUI\Command->main() /home/u1/code/php/test1/vendor/phpunit/phpunit/src/TextUI/Command.php:97 
[0x7ffff7214020] (main) /home/u1/code/php/test1/vendor/phpunit/phpunit/phpunit:107 

I am not knowledgeable of PHP's internals, but from the two faillings examples it appears that the issue is present when a PHP method 'loadClass' is called. This causes spl_perform_autoload to run to load a class and to fail deeper in the stack.

Simple test case to reproduce

Following that, I changed my simple test to:

<?php

require_once __DIR__ . '/vendor/autoload.php';

$processor = new \Saxon\SaxonProcessor();
$input = file_get_contents(__DIR__.'/input.xml');
$xdmDoc = $processor->parseXmlFromString($input);
$processor = $processor->newXslt30Processor();
$compiled = $processor->compileFromString(
    file_get_contents(__DIR__.'/template.xslt')
);
$args = [];
$result = $compiled->transformToString($xdmDoc);

// Crashes here.
$response = new \Symfony\Component\HttpFoundation\Response();

echo $result;

echo var_dump($response);

With the following backtrace:

(gdb) bt
#0  zend_mm_alloc_small (bin_num=5, heap=0x7ffff7200040) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_alloc.c:1313
#1  zend_mm_alloc_heap (size=<optimized out>, heap=0x7ffff7200040) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_alloc.c:1384
#2  _emalloc (size=<optimized out>) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_alloc.c:2601
#3  0x00005555557d057a in zend_string_alloc (persistent=false, len=20) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_string.h:152
#4  zend_string_init (persistent=false, len=20, 
    str=0x7ffff73413ce "HTTP_PARTIAL_CONTENT = 206;\n    public const HTTP_MULTI_STATUS = 207;          // RFC4918\n    public const HTTP_ALREADY_REPORTED = 208;      // RFC5842\n    public const HTTP_IM_USED = 226;", ' ' <repeats 12 times>...) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_string.h:174
#5  lex_scan (zendlval=zendlval@entry=0x7fffffff9160, elem=0x7fffffff91f8) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_language_scanner.c:9818
#6  0x00005555557e72a0 in zendlex (elem=elem@entry=0x7fffffff91f8) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_compile.c:1802
#7  0x00005555557c7883 in zendparse () at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_language_parser.c:4758
#8  0x00005555557ca656 in zend_compile (type=type@entry=2) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_language_scanner.c:601
#9  0x00005555557cbdff in compile_file (file_handle=0x7fffffff9e80, type=2) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_language_scanner.c:655
#10 0x00007fffe32b9256 in phar_compile_file (file_handle=0x7fffffff9e80, type=2) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/ext/phar/phar.c:3355
#11 0x00005555557cbec1 in compile_filename (type=type@entry=2, filename=filename@entry=0x7ffff7255150) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_language_scanner.c:706
#12 0x0000555555841f8f in zend_include_or_eval (inc_filename_zv=<optimized out>, type=2) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_execute.c:4799
#13 0x0000555555850f6f in ZEND_INCLUDE_OR_EVAL_SPEC_CV_HANDLER () at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_vm_execute.h:38960
#14 0x0000555555879886 in execute_ex (ex=0x193600) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_vm_execute.h:59407
--Type <RET> for more, q to quit, c to continue without paging--
#15 0x00005555557fdbf8 in zend_call_function (fci=fci@entry=0x7fffffffa1c0, fci_cache=<optimized out>, fci_cache@entry=0x7fffffffa1a0) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_execute_API.c:949
#16 0x00005555557fdf87 in zend_call_known_function (fn=0x7ffff7204cd8, object=<optimized out>, called_scope=<optimized out>, retval_ptr=retval_ptr@entry=0x0, param_count=param_count@entry=1, params=params@entry=0x7fffffffa250, 
    named_params=0x0) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_execute_API.c:1043
#17 0x00005555556fba15 in spl_perform_autoload (class_name=0x7ffff727b0f0, lc_name=0x7ffff727b190) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/ext/spl/php_spl.c:445
#18 0x00005555557fcd77 in zend_lookup_class_ex (name=name@entry=0x7ffff727b0f0, key=0x7ffff727b190, flags=flags@entry=512) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_execute_API.c:1209
#19 0x00005555557fe3ea in zend_fetch_class_by_name (class_name=0x7ffff727b0f0, key=<optimized out>, fetch_type=fetch_type@entry=512) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_execute_API.c:1705
#20 0x000055555584dbb7 in ZEND_NEW_SPEC_CONST_UNUSED_HANDLER () at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_vm_execute.h:10277
#21 0x0000555555878190 in execute_ex (ex=0x193600) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_vm_execute.h:56949
#22 0x0000555555881e11 in zend_execute (op_array=0x7ffff7290000, return_value=<optimized out>) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_vm_execute.h:60408
#23 0x000055555580cdeb in zend_execute_scripts (type=type@entry=8, retval=retval@entry=0x0, file_count=file_count@entry=3) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend.c:1833
#24 0x00005555557a3bda in php_execute_script (primary_file=primary_file@entry=0x7fffffffc940) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/main/main.c:2542
#25 0x00005555558f9e7b in do_cli (argc=2, argv=0x555555e10900) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/sapi/cli/php_cli.c:964
#26 0x00005555556413a9 in main (argc=2, argv=0x555555e10900) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/sapi/cli/php_cli.c:1333

And this short zbacktrace:

(gdb) zbacktrace
[0x7ffff72132c0] Composer\Autoload\includeFile("/home/u1/code/test2/vendor/composer/../symfony/http-foundation/Response.php") /home/u1/code/test2/vendor/composer/ClassLoader.php:571 
[0x7ffff7213220] Composer\Autoload\ClassLoader->loadClass("Symfony\Component\HttpFoundation\Response") /home/u1/code/test2/vendor/composer/ClassLoader.php:428 
[0x7ffff7213020] (main) /home/u1/code/test2/simple_test_ko.php:17 

So, I can confirm that using the autoloader after using the extension causes the crash.

Now, I am not sure what to do:

  • Is it a bug in PHP's core that is triggered by this extension?
  • Is it a bug in the extension, which does something wrong, and is only noticed later. If so, I guess I should contact the upstream developers of the extension?
  • Something else I missed?

How to install the extension

sudo dnf install fedora-packager fedora-review
git clone git@gitlab.com:saxon-packaging/saxon-package.git saxon-package
cd saxon-package
git checkout php
cd saxonphp
fedpkg --release f38 mockbuild --enable-network

Then look in results_php-saxon.

PHP Version

PHP 8.2.10

Operating System

Fedora 38 x64-64

@Girgias
Copy link
Member

Girgias commented Sep 5, 2023

I am not knowledgeable of PHP's internals, but from the two faillings examples it appears that the issue is present when a PHP method 'loadClass' is called. This causes spl_perform_autoload to run to load a class and to fail deeper in the stack.

This is not how the autoloader works, the autoloader is triggered when a class is encountered for the first time and it hasn't been loaded yet. As which point it will call whatever functions have been registered in order until the class is loaded or all autoloaders have been called.

Can you reproduce this without relying on composer and without relying on Symfony? I will have a glance at the source code of the extension, but my guess here is that the issue is outside of PHP core. But then the autoloader is a bit wonky.

@ttln
Copy link
Author

ttln commented Sep 5, 2023

Hi @Girgias ,

The last example runs already without the Symfony framework, if that's what you mean. It is only using the Response to trigger the autoloader.

Anyway, I think that with your question I came closer to the problem. Check this code:

// Class that will be required (SimpleClass.php)
<?php

namespace Dummy\Ns1;

class SimpleClass
{
    public function __construct(
        protected string $p1 = ''
    ) {
    }

    public function write(): void {
        echo $this->p1;
    }
}

Code to run (KO)

<?php

// Registers a new loader.
spl_autoload_register(function ($class_name) {
    echo sprintf(">> Autoloading '%s' <<", $class_name);
    require_once __DIR__ . '/SimpleClass.php';
});

$processor = new \Saxon\SaxonProcessor();
$input = file_get_contents(__DIR__.'/input.xml');
$xdmDoc = $processor->parseXmlFromString($input);
$processor = $processor->newXslt30Processor();

$compiled = $processor->compileFromString(
    file_get_contents(__DIR__.'/template.xslt')
);
$args = [];
$result = $compiled->transformToString($xdmDoc);

echo $result;

echo "\n";
// Crash.
$o = new Dummy\Ns1\SimpleClass('Hello');
echo "\n";
$o->write();
echo "\n";
echo var_dump($o);

The result will be zend_mm_heap corrupted

Backtrace

>> Autoloading 'Dummy\Ns1\SimpleClass' <<zend_mm_heap corrupted

Thread 1 "php" received signal SIGABRT, Aborted.
__pthread_kill_implementation (threadid=<optimized out>, signo=signo@entry=6, no_tid=no_tid@entry=0) at pthread_kill.c:44
Downloading source file /usr/src/debug/glibc-2.37-4.fc38.x86_64/nptl/pthread_kill.c
44            return INTERNAL_SYSCALL_ERROR_P (ret) ? INTERNAL_SYSCALL_ERRNO (ret) : 0;                                                                                                                                                 
(gdb) bt
#0  __pthread_kill_implementation (threadid=<optimized out>, signo=signo@entry=6, no_tid=no_tid@entry=0) at pthread_kill.c:44
#1  0x00007ffff75d98b3 in __pthread_kill_internal (signo=6, threadid=<optimized out>) at pthread_kill.c:78
#2  0x00007ffff7588abe in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26
#3  0x00007ffff757187f in __GI_abort () at abort.c:79
#4  0x000055555561ef50 in zend_mm_panic (message=0x555555a4ed92 "zend_mm_heap corrupted") at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_alloc.c:368
#5  0x000055555561f31f in zend_mm_free_heap (ptr=<optimized out>, heap=<optimized out>) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_alloc.c:1426
#6  _efree (ptr=<optimized out>) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_alloc.c:2612
#7  0x000055555580125b in destroy_op_array (op_array=0x7ffff7293200) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_opcode.c:567
#8  0x0000555555850388 in ZEND_INCLUDE_OR_EVAL_SPEC_CONST_HANDLER () at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_vm_execute.h:4958
#9  0x0000555555877f09 in execute_ex (ex=0x3648) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_vm_execute.h:56453
#10 0x00005555557fdbf8 in zend_call_function (fci=fci@entry=0x7fffffffa1c0, fci_cache=<optimized out>, fci_cache@entry=0x7fffffffa1a0) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_execute_API.c:949
#11 0x00005555557fdf87 in zend_call_known_function (fn=0x7ffff728a038, object=<optimized out>, called_scope=<optimized out>, retval_ptr=retval_ptr@entry=0x0, param_count=param_count@entry=1, params=params@entry=0x7fffffffa250, 
    named_params=0x0) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_execute_API.c:1043
#12 0x00005555556fba15 in spl_perform_autoload (class_name=0x7ffff7202720, lc_name=0x7ffff7202750) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/ext/spl/php_spl.c:445
#13 0x00005555557fcd77 in zend_lookup_class_ex (name=name@entry=0x7ffff7202720, key=0x7ffff7202750, flags=flags@entry=512) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_execute_API.c:1209
#14 0x00005555557fe3ea in zend_fetch_class_by_name (class_name=0x7ffff7202720, key=<optimized out>, fetch_type=fetch_type@entry=512) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_execute_API.c:1705
#15 0x000055555584dbb7 in ZEND_NEW_SPEC_CONST_UNUSED_HANDLER () at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_vm_execute.h:10277
#16 0x0000555555878190 in execute_ex (ex=0x3648) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_vm_execute.h:56949
#17 0x0000555555881e11 in zend_execute (op_array=0x7ffff7293000, return_value=<optimized out>) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend_vm_execute.h:60408
#18 0x000055555580cdeb in zend_execute_scripts (type=type@entry=8, retval=retval@entry=0x0, file_count=file_count@entry=3) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/Zend/zend.c:1833
#19 0x00005555557a3bda in php_execute_script (primary_file=primary_file@entry=0x7fffffffc940) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/main/main.c:2542
#20 0x00005555558f9e7b in do_cli (argc=2, argv=0x555555e10900) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/sapi/cli/php_cli.c:964
#21 0x00005555556413a9 in main (argc=2, argv=0x555555e10900) at /usr/src/debug/php-8.2.10-1.fc38.x86_64/sapi/cli/php_cli.c:1333

zbacktrace

(gdb) zbacktrace
[0x7ffff7213240] {closure}("Dummy\Ns1\SimpleClass") /home/ui/code/test2/simple_test_ko.php:6 
[0x7ffff7213020] (main) /home/u1/code/test2/simple_test_ko.php:23 

Other test

Now, I noticed that in my original code I overwrite the $processor variable with the result of the method newXslt3Processor().

$processor = new \Saxon\SaxonProcessor();
$input = file_get_contents(__DIR__.'/input.xml');
$xdmDoc = $processor->parseXmlFromString($input);
$processor = $processor->newXslt30Processor();

I did not intend to do that, so let's change it to a new $processor3 (OK):

<?php

// Registers a new loader.
spl_autoload_register(function ($class_name) {
    echo sprintf(">> Autoloading '%s' <<", $class_name);
    require_once __DIR__ . '/SimpleClass.php';
});

$processor = new \Saxon\SaxonProcessor();
$input = file_get_contents(__DIR__.'/input.xml');
$xdmDoc = $processor->parseXmlFromString($input);
$processor3 = $processor->newXslt30Processor();

$compiled = $processor3->compileFromString(
    file_get_contents(__DIR__.'/template.xslt')
);
$args = [];
$result = $compiled->transformToString($xdmDoc);

echo $result;

echo "\n";
$o = new Dummy\Ns1\SimpleClass('Hello');
echo "\n";
$o->write();
echo "\n";
echo var_dump($o);

It works!

The I changed my example with Symfony (KO):

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class TestController extends AbstractController
{

    // This is a non-working route, triggered by the use of SaxonProcessor
    // and the creation of a Response.
    // This works too with PHPUnit.
    #[Route('/test', name: 'app_test')]
    public function index(): Response
    {
        $processor = new \Saxon\SaxonProcessor();
        $input = file_get_contents(__DIR__.'/../../input.xml');
        $xdmDoc = $processor->parseXmlFromString($input);
        $processor3 = $processor->newXslt30Processor();
        $compiled = $processor3->compileFromString(
            file_get_contents(__DIR__.'/../../template.xslt')
        );
        $args = [];
        $result = $compiled->transformToString($xdmDoc);

        // Below is the error trigger.
        // If you add 'echo $result; exit;', the transformed result will be shown correctly.
        $response = new Response($result);

        // Never reached.
        return $response;
    }
}

And I get a segmentation fault again, with the same backtrace like in my first message, with spl_perform_autoload and zend_mm_alloc_small.

Again, I tried my simple example, this time with Composer (OK):

<?php

require_once __DIR__ . '/vendor/autoload.php';

$processor = new \Saxon\SaxonProcessor();
$input = file_get_contents(__DIR__.'/input.xml');
$xdmDoc = $processor->parseXmlFromString($input);
$processor3 = $processor->newXslt30Processor();

$compiled = $processor3->compileFromString(
   file_get_contents(__DIR__.'/template.xslt')
);
$args = [];
$result = $compiled->transformToString($xdmDoc);

echo $result;

echo "\n";
$r = new Symfony\Component\HttpFoundation\Response();
echo var_dump($r);

In summary:

Reassignment Composer Simple autoloader Symfony Result
X X KO
X X KO
X X X KO
X OK
X X KO
X OK
X OK

@Girgias
Copy link
Member

Girgias commented Sep 6, 2023

Are you executing this with opcache or not? And would it be possible to run the minimal case that doesn't use composer with a debug build, as some calls in the back trace are optimized out.

@ttln
Copy link
Author

ttln commented Sep 6, 2023

Are you executing this with opcache or not?

opcache is disabled in php.ini: opcache.enable=0

And would it be possible to run the minimal case that doesn't use composer with a debug build, as some calls in the back trace are optimized out.

So, I removed the distro-provided package and I built PHP 8.2.10 from source with ./configure --enable-debug, I also rebuilt the Saxon extension. The config is now:

PHP 8.2.10 (cli) (built: Sep  6 2023 12:02:56) (NTS DEBUG)
Copyright (c) The PHP Group
Zend Engine v4.2.10, Copyright (c) Zend Technologies

I tried to run the test case without reassignment and without composer and with the simple autoloader. It did not event start this time:

[New Thread 0x7fffe4fff6c0 (LWP 254722)]

Thread 1 "php" received signal SIGSEGV, Segmentation fault.
0x00007fffe851eac7 in Java_java_lang_ProcessEnvironment_environ () from /lib64/libsaxon-hec-12.3.so
(gdb) bt
#0  0x00007fffe851eac7 in Java_java_lang_ProcessEnvironment_environ () from /lib64/libsaxon-hec-12.3.so
#1  0x00007fffe717d8da in ?? () from /lib64/libsaxon-hec-12.3.so
#2  0x3030303030303030 in ?? ()
#3  0x3038203130203130 in ?? ()
#4  0x00007fffe717d8c7 in ?? () from /lib64/libsaxon-hec-12.3.so
#5  0x00007fffffffac50 in ?? ()
#6  0x0000000000000000 in ?? ()
(gdb) source ./.gdbinit 
(gdb) zbacktrace
[0x7ffff7616250] Saxon\SaxonProcessor->__construct() [internal function]
[0x7ffff7616020] (main) /home/tli/perso/test2/simple_test_ko2.php:9 
(gdb) 

The constructor for a class of the extension doesn't work, so the test case cannot even run further. But the extension seems loaded, because I got:

Warning: Saxon\XdmValue::__toString() implemented without string return type in Unknown on line 0

I do believe now the best is to report it to the extension author.

@iluuu1994
Copy link
Member

Given that the segfault occurs within that extension, it is likely the cause of this issue. zend_alloc usually segfaults when something has corrupted the allocators metadata. You could try compiling PHP + the extension with the --enable-address-sanitizer configure flag and then run using your application with the USE_ZEND_ALLOC=0 environment variable. This might report the location where the buffer overflow occurs.

@iluuu1994
Copy link
Member

I'm closing this issue for now. Let us know if there's anything more we can do.

@iluuu1994 iluuu1994 closed this as not planned Won't fix, can't repro, duplicate, stale Sep 11, 2023
@ttln
Copy link
Author

ttln commented Sep 15, 2023

Thank you all for your suggestions.
I opened an issue on the upstream issues tracker: https://saxonica.plan.io/issues/6197

@ond1
Copy link

ond1 commented Sep 20, 2023

Hi,
I managed to reproduce the segmentation fault running with Symfony on my linux box. It is indeed a SaxonC bug.
The reassigning of the created SaxonProcessor object is causing a GC action on the SaxonProcessor, which the extension tries to delete later. Hence the seg error.

The following code also causes the segmentation error:

$xslt30processor = (new \Saxon\SaxonProcessor())->newXslt30Processor();
I am investigating a resolution to this problem. The workaround is to not reassign the processor variable.

@Girgias
Copy link
Member

Girgias commented Sep 20, 2023

Hi, I managed to reproduce the segmentation fault running with Symfony on my linux box. It is indeed a SaxonC bug. The reassigning of the created SaxonProcessor object is causing a GC action on the SaxonProcessor, which the extension tries to delete later. Hence the seg error.

The following code also causes the segmentation error:

$xslt30processor = (new \Saxon\SaxonProcessor())->newXslt30Processor(); I am investigating a resolution to this problem. The workaround is to not reassign the processor variable.

Thanks for confirming!

I had a quick glance at the extension code, and it seems to be doing some less than optimal stuff at times. Is there a git repo one can send patches to?

@ndw
Copy link

ndw commented Sep 20, 2023

We have a GitHub repository that mirrors our internal development repo. It's currently at https://saxonica.plan.io/projects/saxonmirrorhe/repository

In theory, you should be able to check that out and build HE. In practice, I don't think the build parts have been tested recently. That's probably my fault, so sorry about that.

In any event, we should be able to apply patches that you provide against that repo.

In the longer term, migrating the HE sources to a GitHub repository and improving the build tooling for it is probably a good idea.

@ond1
Copy link

ond1 commented Sep 22, 2023

Hi, to solve this SaxonC GC problem I need to increment the refcounted on \Saxon\SaxonProcessor object, but also decrement it later. Is it possible to get some guidance on this or example code of a PHP extension written in C/C++ which does something similar?

I see that the zend_object has a variable called zend_refcounted. Also the function Z_COUNTED_P() looks useful too.

@iluuu1994
Copy link
Member

Use the GC_ADDREF and GC_DELREF macros. If some user-code might release the object in the meantime, you should wrap the DELREF into an if (GC_DELREF(obj) == 0) { zend_objects_store_del(obj); }.

@ond1
Copy link

ond1 commented Sep 28, 2023

Hi,
Thanks for the advice to use GC_ADDREF and GC_DELREF. The GC_ADDREF seems to have stopped the segmentation error. However, when I use GC_DELREF later in the extension it crashes the script with a segmentation error. Even if I check if the zend_object is null. Is the GC_DELREF necessary?

Just to add, the SaxonProcessor object is the one that is getting deleted too early. with the GC_ADDREF it prevents the GC call on it. And it does get GC at the end without the GC_DELREF.

@iluuu1994
Copy link
Member

@ond1 zend_value.obj should generally not be NULL. I can only see ext-soap doing that for a global. Yes, GC_DELREF is necessary because otherwise you're leaking the object.

And it does get GC at the end without the GC_DELREF.

How can you tell? If this is truly the case, then there's some other refcounting bug somewhere, be it in your extension or in php-src.

Please compile php-src and your extension with ./configure --enable-address-sanitizer. Also run your application/test with USE_ZEND_ALLOC=0. This should give you a better idea of what is happening.

@ond1
Copy link

ond1 commented Sep 28, 2023

Hi, Thanks four your message. I have built PHP from source. But not sure how to build it with my extension. I have dropped my files in the ext directory. But I am thinking I am missing some simple step

@iluuu1994
Copy link
Member

@ond1 It should suffice if you build just your extension with that flag. I suspect the issue is coming from the extension itself.

@ond1
Copy link

ond1 commented Sep 29, 2023

Hi,
I am still have trouble building the SaxonC PHP extension with the php-src. See error below:
In file included from /home/ond1/work/repository/php-src/ext/saxon/php8_saxon.h:47, from main/internal_functions_cli.c:42: /home/ond1/work/repository/php-src/ext/saxon/SaxonProcessor.h:17:10: fatal error: string: No such file or directory 17 | #include <string> | ^~~~~~~~ compilation terminated.

It looks like it's a C++ compiler issue. Please can someone point me to what I am missing? I think there is some option I am missing when I run ./configure. Perhaps with the CC flag.

@iluuu1994
Copy link
Member

@ond1 You don't need to build it with PHP. Just build it like normal and add the --enable-address-sanitizer flag to the ./configure command.

@ond1
Copy link

ond1 commented Sep 29, 2023

ok thanks

@ond1
Copy link

ond1 commented Sep 29, 2023

I tried the following ./configure --enable-saxon --enable-address-sanitizer but configure: WARNING: unrecognized options: --enable-address-sanitizer

@ond1
Copy link

ond1 commented Sep 29, 2023

Compiling extension for PHP 8.1. Is his flag for PHP 8.2?

@iluuu1994
Copy link
Member

@ond1 Try ./configure --enable-saxon CFLAGS="-fsanitize=address". I'm not familiar with the build system for unbundled extensions, so I'm not exactly sure what flags are/aren't available.

@ond1
Copy link

ond1 commented Sep 29, 2023

Thanks. The build worked for the extension. Not seeing any difference in the running of a php script. However with USE_ZEND_ALLOC=0 I am now seeing the following error:

double free or corruption (fasttop) Aborted (core dumped)

Is the flag USE_ZEND_ALLOC some debugger option?

@ond1
Copy link

ond1 commented Sep 29, 2023

Please ignore my last message as I understand what USE_ZEND_ALLOC is used for.

@ond1
Copy link

ond1 commented Jan 12, 2024

Hi @iluuu1994
coming back to this issue. I think I figured out the cause of the memory corruption.
If I comment out the efree(obj) in the following code the PHPUnit tests succeed.

`void SaxonProcessor_free_storage(zend_object *object) {

saxonProcessor_object *obj;

obj = (saxonProcessor_object *)((char *)object -
XtOffsetOf(saxonProcessor_object, std));

SaxonProcessor *saxonProc = obj->saxonProcessor;
if (saxonProc != nullptr) {
delete saxonProc;
}
zend_object_std_dtor(object);

//efree(obj); - corrupts memory
}`

I am failing to understand how and if I should free the saxonProcessor_object given as obj.

In the create_handler function I created the obj as follows:

saxonProcessor_object *obj = (saxonProcessor_object *)ecalloc( 1, sizeof(saxonProcessor_object) + zend_object_properties_size(type));

I then changed it to the following:

saxonProcessor_object *obj = (saxonProcessor_object *)zend_object_alloc(sizeof(saxonProcessor_object), type);

Any advice is appreciated to understand what I should be doing when the free_obj 'SaxonProcessor_free_storage' function is called.
Thanks

@iluuu1994
Copy link
Member

I then changed it to the following:

That looks fine. If there's a double-free, there's something else also calling efree on the object. You should really try to get --enable-address-sanitizer to work, because it will tell you exactly in which 2 locations the object has been freed.

@iluuu1994
Copy link
Member

@ond1 Oh, I didn't look at this well enough. free_storage is not where the object should be freed. The object is freed by zend_objects_store_del once the refcount goes to 0. You shouldn't have to call efree.

@ond1
Copy link

ond1 commented Jan 13, 2024

Thanks @iluuu1994 for your reply. That would explain it . I took a quick look at the zend_objects_store_del code and see what you mean. Are we always guaranteed that the free_storage will be called before zend_objects_store_del? From my understanding the free_storage is required to delete any resources associated with the PHP object for example the C++ object.

I was just not aware of the zend_objects_store_del is there some documentation of how PHP extensions work within PHP. from end to end?

@iluuu1994
Copy link
Member

https://www.phpinternalsbook.com/ is the most up-to-date resource on php-src.

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

6 participants