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

PHP Embed SAPI not saving and restoring signal handlers. #8029

Open
rcmcdonald91 opened this issue Feb 3, 2022 · 7 comments
Open

PHP Embed SAPI not saving and restoring signal handlers. #8029

rcmcdonald91 opened this issue Feb 3, 2022 · 7 comments

Comments

@rcmcdonald91
Copy link

rcmcdonald91 commented Feb 3, 2022

Description

I would expect the the execution context to be restored following the PHP_EMBED_END_BLOCK() macro, but this doesn't seem to be the case for signal handlers.

here is a toy function to call a php function from a specified file:

/* Early work to call a php function from a specified script file */
void
call_php_function(char *file_name, char *function_name)
{
    zend_file_handle file_handle;
    zval retval = {0};
    zend_fcall_info fci = {0};
    zend_fcall_info_cache fci_cache = {0};

    sigset_t       oldsigset;
    struct sigaction oldsigaction[32] = {0,};

    some_signal_save_function(&oldsigset, oldsigaction);

    PHP_EMBED_START_BLOCK(0, NULL)
        zend_stream_init_filename(&file_handle, file_name);

        php_execute_script(&file_handle);

        ZVAL_STRING(&fci.function_name, function_name);

        fci.size = sizeof fci;
        fci.retval = &retval;

        zend_call_function(&fci, &fci_cache);
    PHP_EMBED_END_BLOCK()

    some_signal_restore_function(&oldsigset, oldsigaction);
}

Without the signal save/restore wrapping the embed block, the zend_signal_handler and defer handler is still handling the dispatching of signals, which is likely not what a user would want once outside an embed block.

PHP Version

7.4.27

Operating System

No response

@cmb69
Copy link
Member

cmb69 commented Mar 15, 2022

PHP 7.4 is no longer actively supported, but I assume this affects newer versions as well. The problem is that we don't call zend_signal_shutdown() from php_embed_shutdown() ; actually, there is no such function in the first place. It might be sufficent to call sigemptyset(&global_sigmask).

@rcmcdonald91
Copy link
Author

Ah yes thank you, you are correct. I will retest on newer versions but I think you're correct.

@iluuu1994
Copy link
Member

@theonemcdonald Did you verify if this affects newer PHP versions as well?

@rcmcdonald91
Copy link
Author

rcmcdonald91 commented Jun 7, 2022

Finally had a chance to test on a newer PHP version: v8.1.6 to be specific. This appears to still be an issue.

I've been working on a side project to add scripting support to another project. What I'm seeing here is a stack overflow when the main program is shutting down. It still isn't clear to me if there is something incorrect with the host program or with PHP.

(gdb) backtrace
#0  0x0000000801ba8168 in zend_signal_handler_defer (signo=<error reading variable: Cannot access memory at address 0x7fffdffffffc>,
    siginfo=<error reading variable: Cannot access memory at address 0x7fffdffffff0>, context=<error reading variable: Cannot access memory at address 0x7fffdfffffe8>)
    at Zend/zend_signal.c:83
#1  0x0000000801ba8569 in zend_signal_handler (signo=2, siginfo=0x7fffffffde30, context=0x7fffffffdac0) at Zend/zend_signal.c:216
#2  0x0000000801ba83e4 in zend_signal_handler_defer (signo=2, siginfo=0x7fffffffde30, context=0x7fffffffdac0) at Zend/zend_signal.c:143
#3  0x0000000801ba8569 in zend_signal_handler (signo=2, siginfo=0x7fffffffde30, context=0x7fffffffdac0) at Zend/zend_signal.c:216
#4  0x0000000801ba83e4 in zend_signal_handler_defer (signo=2, siginfo=0x7fffffffde30, context=0x7fffffffdac0) at Zend/zend_signal.c:143
#5  0x0000000801ba8569 in zend_signal_handler (signo=2, siginfo=0x7fffffffde30, context=0x7fffffffdac0) at Zend/zend_signal.c:216
#6  0x0000000801ba83e4 in zend_signal_handler_defer (signo=2, siginfo=0x7fffffffde30, context=0x7fffffffdac0) at Zend/zend_signal.c:143
#7  0x0000000801ba8569 in zend_signal_handler (signo=2, siginfo=0x7fffffffde30, context=0x7fffffffdac0) at Zend/zend_signal.c:216
#8  0x0000000801ba83e4 in zend_signal_handler_defer (signo=2, siginfo=0x7fffffffde30, context=0x7fffffffdac0) at Zend/zend_signal.c:143
#9  0x0000000801ba8569 in zend_signal_handler (signo=2, siginfo=0x7fffffffde30, context=0x7fffffffdac0) at Zend/zend_signal.c:216
#10 0x0000000801ba83e4 in zend_signal_handler_defer (signo=2, siginfo=0x7fffffffde30, context=0x7fffffffdac0) at Zend/zend_signal.c:143
#11 0x0000000801ba8569 in zend_signal_handler (signo=2, siginfo=0x7fffffffde30, context=0x7fffffffdac0) at Zend/zend_signal.c:216
#12 0x0000000801ba83e4 in zend_signal_handler_defer (signo=2, siginfo=0x7fffffffde30, context=0x7fffffffdac0) at Zend/zend_signal.c:143
#13 0x0000000801ba8569 in zend_signal_handler (signo=2, siginfo=0x7fffffffde30, context=0x7fffffffdac0) at Zend/zend_signal.c:216
#14 0x0000000801ba83e4 in zend_signal_handler_defer (signo=2, siginfo=0x7fffffffde30, context=0x7fffffffdac0) at Zend/zend_signal.c:143
#15 0x0000000801ba8569 in zend_signal_handler (signo=2, siginfo=0x7fffffffde30, context=0x7fffffffdac0) at Zend/zend_signal.c:216
#16 0x0000000801ba83e4 in zend_signal_handler_defer (signo=2, siginfo=0x7fffffffde30, context=0x7fffffffdac0) at Zend/zend_signal.c:143
#17 0x0000000801ba8569 in zend_signal_handler (signo=2, siginfo=0x7fffffffde30, context=0x7fffffffdac0) at Zend/zend_signal.c:216
#18 0x0000000801ba83e4 in zend_signal_handler_defer (signo=2, siginfo=0x7fffffffde30, context=0x7fffffffdac0) at Zend/zend_signal.c:143
#19 0x0000000801ba8569 in zend_signal_handler (signo=2, siginfo=0x7fffffffde30, context=0x7fffffffdac0) at Zend/zend_signal.c:216
#20 0x0000000801ba83e4 in zend_signal_handler_defer (signo=2, siginfo=0x7fffffffde30, context=0x7fffffffdac0) at Zend/zend_signal.c:143
#21 0x0000000801ba8569 in zend_signal_handler (signo=2, siginfo=0x7fffffffde30, context=0x7fffffffdac0) at Zend/zend_signal.c:216
#22 0x0000000801ba83e4 in zend_signal_handler_defer (signo=2, siginfo=0x7fffffffde30, context=0x7fffffffdac0) at Zend/zend_signal.c:143
#23 0x0000000801ba8569 in zend_signal_handler (signo=2, siginfo=0x7fffffffde30, context=0x7fffffffdac0) at Zend/zend_signal.c:216
#24 0x0000000801ba83e4 in zend_signal_handler_defer (signo=2, siginfo=0x7fffffffde30, context=0x7fffffffdac0) at Zend/zend_signal.c:143
#25 0x0000000801ba8569 in zend_signal_handler (signo=2, siginfo=0x7fffffffde30, context=0x7fffffffdac0) at Zend/zend_signal.c:216
...

Edit: Some more to the story. The host program has a plugin API with several callbacks. Instead of wrapping the embed call, I decided to try invoking php_embed_init and php_embed_shutdown in tune with the host program's native plugin init and shutdown callbacks and I see this:

Warning: zend_signal: handler was replaced for signal (2) after startup in Unknown on line 0

Warning: zend_signal: handler was replaced for signal (15) after startup in Unknown on line 0

More evidence of signal confusion

For interested parties, the project I am working on adding scripting support to is https://github.com/clicon/clixon, plugin documentation at https://clixon-docs.readthedocs.io/en/latest/plugins.html?highlight=signal#plugin-callback-guidelines, not that I expect anyone here in this community in particular to diagnose an issue there, but there's obviously an edge case here that I think we all could benefit from understanding a root cause

@jansssson
Copy link

I'm not 100% sure (and I only tested with PHP 7.4.11), but I have a problem which I think is related to this.
If you run two or more PHP_EMBED_START_BLOCK/PHP_EMBED_END_BLOCK (such as calling the OP's method twice or more), signalling after that leads to segfault. I'm guessing there's some chaining/deferring of previous ZEND signal handlers causing this. As long as there's only one PHP embed block executed, it still kinda works (signals are deferred to my own handler), but with two or more blocks it ends with a SIGSEGV...

Haven't done much testing, I opt'ed to recompile and disable ZEND_SIGNALS as a workaround.
But below is a simple repro and relevant valgrind output which pointed me to lingering ZEND signal handlers.

#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>
#include <php_embed.h>

namespace {
  std::atomic<bool> shutdown_requested = false;
  static_assert( std::atomic<bool>::is_always_lock_free );
}

void my_signal_handler(int /*signum*/)
{
  shutdown_requested = true;
}

int main()
{
  // setup signal handler
  {
    struct sigaction action;
    action.sa_handler = my_signal_handler;
    sigemptyset(&action.sa_mask);
    action.sa_flags = 0;
    sigaction(SIGINT, &action, NULL);
  }

  // main loop
  while( !shutdown_requested.load() )
  {
    std::cout << "doing work...\n";

    PHP_EMBED_START_BLOCK(0, NULL);
    zend_eval_string((char*)"echo \"Hello\n\";", NULL, (char*)"WP xml" TSRMLS_CC);
    PHP_EMBED_END_BLOCK();

    std::this_thread::sleep_for(std::chrono::seconds(1));
  }

  std::cout << "shutting down\n";

  return 0;
}
Hello
doing work...
Hello
doing work...
Hello
doing work...
Hello
^C
==681== Stack overflow in thread #1: can't grow stack to 0x1ffe801000
==681==
==681== Process terminating with default action of signal 11 (SIGSEGV)
==681==  Access not within mapped region at address 0x1FFE801FF8
==681== Stack overflow in thread #1: can't grow stack to 0x1ffe801000
==681==    at 0x4BA159B: zend_signal_handler (in /opt/build/php-7.4.11-old/lib/libphp7.so)
==681==  If you believe this happened as a result of a stack
==681==  overflow in your program's main thread (unlikely but
==681==  possible), you can try to increase the size of the
==681==  main thread stack using the --main-stacksize= flag.
==681==  The main thread stack size used in this run was 8388608.
==681== Stack overflow in thread #1: can't grow stack to 0x1ffe801000
==681==

@rcmcdonald91
Copy link
Author

I'm tinkering with this again, any thoughts?

@rcmcdonald91
Copy link
Author

I believe I have a fix worth submitting: #9672

Basically the original signal handlers are already being saved in the true global global_orig_handlers, I just reversed the logic used to save them to restore them in zend_signal_deactivate. This resolved the overflow I was seeing above.

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

4 participants