Search code examples
phpphp-extension

Seg fault in extension after calling PHP function from C


I am trying to write an extension that calls back a PHP function after doing stuff. To check feasibility, I went as per this article: https://devzone.zend.com/303/extension-writing-part-i-introduction-to-php-and-zend/

The basic extension worked fine. Then I added code to call PHP function like this:

PHP_FUNCTION(hello_world)
{
    zval p1;
    INIT_ZVAL(p1);
    ZVAL_STRING(&p1, "From extension", 1);
    zval *params = { &p1 };
    zend_uint param_count = 1;
    zval *retval_ptr = NULL;

    zval function_name;
    INIT_ZVAL(function_name);
    ZVAL_STRING(&function_name, "from_ext", 1);

    if (call_user_function(
            CG(function_table), NULL /* no object */, &function_name,
            retval_ptr, param_count, &params TSRMLS_CC
        ) == SUCCESS
    ) {
        printf("Success returning from PHP");
        if (retval_ptr)
          zval_ptr_dtor(&retval_ptr);
    }

    /* don't forget to free the zvals */
    zval_dtor(&function_name);
    zval_dtor(&p1);

    RETURN_STRING("Hello World", 1);
}

and the calling PHP:

<?
function from_ext($arg) {
  echo "In PHP:", $arg;
  return "hello";
}

echo hello_world();

?>

It does call the PHP function and can see the value, but throws a Seg fault after that:

php -dextension=modules/hello.so test.php
In PHP:From extensionSegmentation fault: 11

I am trying on MacOS 10.12.6 with PHP that came with it (5.6.30).

Any idea how to overcome the Seg fault?


Solution

  • You need to allocate the return value zval on the stack. The pointer passed into call_user_function must be non-NULL. Here's a patch that should fix the issue.

    --- a/mnt/tmpdisk/a.c
    +++ b/mnt/tmpdisk/b.c
    @@ -5,7 +5,7 @@ PHP_FUNCTION(hello_world)
         ZVAL_STRING(&p1, "From extension", 1);
         zval *params = { &p1 };
         zend_uint param_count = 1;
    -    zval *retval_ptr = NULL;
    +    zval retval;
    
         zval function_name;
         INIT_ZVAL(function_name);
    @@ -13,12 +13,11 @@ PHP_FUNCTION(hello_world)
    
         if (call_user_function(
                 CG(function_table), NULL /* no object */, &function_name,
    -            retval_ptr, param_count, &params TSRMLS_CC
    +            &retval, param_count, &params TSRMLS_CC
             ) == SUCCESS
         ) {
             printf("Success returning from PHP");
    -        if (retval_ptr)
    -          zval_ptr_dtor(&retval_ptr);
    +        zval_dtor(&retval);
         }
    
         /* don't forget to free the zvals */
    

    It's perfectly fine to pass in a pointer to stack-allocated memory since the PHP engine will never capture a reference to the return value zval anywhere in the call (since the return value is unnamed in userspace).