You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
segmentation fault when sending SIGINT to PHP embedded in another program #9649
I'm embedding PHP in a Go program. When this program receives SIGINT, PHP crashes with the following stack trace (extracted using GDB):
Thread 1 "internal" received signal SIGSEGV, Segmentation fault.
0x00000000007b907c in zend_signal_handler_defer (signo=2, siginfo=0xffffeb3e5600, context=0xffffeb3e5680) at /go/php-src/Zend/zend_signal.c:98
98 if (EXPECTED(SIGG(active))) {
If anyone has an idea of what's happening, I can give them access to a private repository with a simple way to reproduce the crash. I reproduced the issue on Debian and macOS.
There is a good chance that the signal handler is being invoked on a thread that didn't execute PHP. In this case, per-thread resources have not been initialized on the thread, so SIGG(active) dereferences an invalid pointer.
I'm assuming that there is no way to install hooks in Go's thread creations.
One way to prevent this crash would be to mask all signals in all threads except the ones that are ready to handle signals.
An other way would be to disable ZEND_SIGNAL entirely (--disable-zend-signals). Its purpose is to prevent corruption of shared state (such as the opcache) in forking SAPIs, but I believe that it's unnecessary when running a single process. In a multi-process setup, a process could be terminated while in the middle of updating the shared state. This would leave other processes with a corrupted state. In a single-process setup, this doesn't matter.
This fixes the problem in your reproducer, unfortunately, it doesn't play very well with the Go embedding use case because it's not desirable (and probably not possible at all) to change the mask for threads started by Go itself.
Couldn't we detect if it's a thread executing PHP before calling SIGG(active)?
We could detect it at least to avoid the crash. I'm not sure we could do anything else than returning from the signal handler, though.
This fixes the problem in your reproducer, unfortunately, it doesn't play very well with the Go embedding use case because it's not desirable (and probably not possible at all) to change the mask for threads started by Go itself.
If it was possible to set the mask before Go starts any thread (or before Go itself starts), it would be inherited by all threads. It would only be required to mask the signals listed here:
Looks like this might be the stack overflow I was seeing in #8029
#0 zend_signal_handler (signo=2, siginfo=0x8204babb0, context=0x8204ba840)
at Zend/zend_signal.c:176
176 int errno_save = errno;
(gdb) bt
#0 zend_signal_handler (signo=2, siginfo=0x8204babb0, context=0x8204ba840)
at Zend/zend_signal.c:176
#1 0x00000008304a1744 in zend_signal_handler_defer (signo=2,
siginfo=0x8204babb0, context=0x8204ba840) at Zend/zend_signal.c:143
#2 0x00000008304a18bb in zend_signal_handler (signo=2, siginfo=0x8204babb0,
context=0x8204ba840) at Zend/zend_signal.c:216
#3 0x00000008304a1744 in zend_signal_handler_defer (signo=2,
siginfo=0x8204babb0, context=0x8204ba840) at Zend/zend_signal.c:143
#4 0x00000008304a18bb in zend_signal_handler (signo=2, siginfo=0x8204babb0,
context=0x8204ba840) at Zend/zend_signal.c:216
#5 0x00000008304a1744 in zend_signal_handler_defer (signo=2,
siginfo=0x8204babb0, context=0x8204ba840) at Zend/zend_signal.c:143
#6 0x00000008304a18bb in zend_signal_handler (signo=2, siginfo=0x8204babb0,
context=0x8204ba840) at Zend/zend_signal.c:216
#7 0x00000008304a1744 in zend_signal_handler_defer (signo=2,
siginfo=0x8204babb0, context=0x8204ba840) at Zend/zend_signal.c:143
#8 0x00000008304a18bb in zend_signal_handler (signo=2, siginfo=0x8204babb0,
context=0x8204ba840) at Zend/zend_signal.c:216
#9 0x00000008304a1744 in zend_signal_handler_defer (signo=2,
siginfo=0x8204babb0, context=0x8204ba840) at Zend/zend_signal.c:143
#10 0x00000008304a18bb in zend_signal_handler (signo=2, siginfo=0x8204babb0,
context=0x8204ba840) at Zend/zend_signal.c:216
Activity
arnaud-lb commentedon Oct 1, 2022
There is a good chance that the signal handler is being invoked on a thread that didn't execute PHP. In this case, per-thread resources have not been initialized on the thread, so
SIGG(active)dereferences an invalid pointer.I can reproduce the issue with this: https://gist.github.com/arnaud-lb/d0f2282e66def91a6ee34e1537b3ac5f
I'm assuming that there is no way to install hooks in Go's thread creations.
One way to prevent this crash would be to mask all signals in all threads except the ones that are ready to handle signals.
An other way would be to disable ZEND_SIGNAL entirely (
--disable-zend-signals). Its purpose is to prevent corruption of shared state (such as the opcache) in forking SAPIs, but I believe that it's unnecessary when running a single process. In a multi-process setup, a process could be terminated while in the middle of updating the shared state. This would leave other processes with a corrupted state. In a single-process setup, this doesn't matter.dunglas commentedon Oct 3, 2022
Thank you very much for your help @arnaud-lb.
I confirm that disabling
ZEND_SIGNALfixes the issue both on Linux and Mac.I also tried to mask all signals: https://gist.github.com/dunglas/278225a71ba3fc11a27ca8c1705fa0c8
This fixes the problem in your reproducer, unfortunately, it doesn't play very well with the Go embedding use case because it's not desirable (and probably not possible at all) to change the mask for threads started by Go itself.
Couldn't we detect if it's a thread executing PHP before calling
SIGG(active)?arnaud-lb commentedon Oct 3, 2022
We could detect it at least to avoid the crash. I'm not sure we could do anything else than returning from the signal handler, though.
If it was possible to set the mask before Go starts any thread (or before Go itself starts), it would be inherited by all threads. It would only be required to mask the signals listed here:
php-src/Zend/zend_signal.c
Line 74 in 69e6a94
rcmcdonald91 commentedon Oct 4, 2022
Looks like this might be the stack overflow I was seeing in #8029