Skip to content

Commit 3de9d99

Browse files
henderkesdunglas
andauthored
chore: extra pthread handling safety (#2390)
adds extra safety handling for when frankenphp is forked: - on linux, when the parent thread dies, all children automatically receive SIGKILL - likewise on freebsd, when the parent process dies - other *nix platforms manually listen on kqueue until parent (process) end is closed, then terminate - windows has no forking, so no changes thete unlikely to be needed, but better safe than sorry --------- Signed-off-by: Marc <m@pyc.ac> Co-authored-by: Kévin Dunglas <kevin@dunglas.fr>
1 parent a9893b4 commit 3de9d99

1 file changed

Lines changed: 85 additions & 5 deletions

File tree

frankenphp.c

Lines changed: 85 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,22 @@
2626
#include <stdint.h>
2727
#include <stdio.h>
2828
#include <stdlib.h>
29-
#ifndef ZEND_WIN32
29+
#ifndef PHP_WIN32
3030
#include <unistd.h>
3131
#endif
32-
#ifdef __linux__
32+
#if defined(__linux__)
3333
#include <sys/prctl.h>
34-
#elif defined(__FreeBSD__) || defined(__OpenBSD__)
34+
#elif defined(__FreeBSD__)
3535
#include <pthread_np.h>
36+
#include <sys/procctl.h>
37+
#elif defined(__OpenBSD__)
38+
#include <pthread_np.h>
39+
#endif
40+
#if defined(__APPLE__) || defined(__OpenBSD__) || defined(__NetBSD__) || \
41+
defined(__DragonFly__)
42+
#define FRANKENPHP_KQUEUE_PARENT_DEATH 1
43+
#include <sys/event.h>
44+
#include <sys/types.h>
3645
#endif
3746

3847
#include "_cgo_export.h"
@@ -110,10 +119,63 @@ static inline uintptr_t frankenphp_thread_index(void) {
110119

111120
#ifndef PHP_WIN32
112121
static bool is_forked_child = false;
113-
static void frankenphp_fork_child(void) { is_forked_child = true; }
122+
static pid_t fork_parent_pid = 0;
123+
124+
static void frankenphp_fork_prepare(void) { fork_parent_pid = getpid(); }
125+
126+
#if defined(FRANKENPHP_KQUEUE_PARENT_DEATH)
127+
/* Watcher thread for platforms without a kernel parent-death signal.
128+
* Blocks in kevent() until the parent exits, then force-kills this child. */
129+
static void *frankenphp_parent_death_watcher(void *arg) {
130+
pid_t ppid = (pid_t)(intptr_t)arg;
131+
int kq = kqueue();
132+
if (kq < 0) {
133+
_exit(1);
134+
}
135+
struct kevent kev;
136+
EV_SET(&kev, ppid, EVFILT_PROC, EV_ADD | EV_ONESHOT, NOTE_EXIT, 0, NULL);
137+
if (kevent(kq, &kev, 1, NULL, 0, NULL) < 0) {
138+
_exit(1);
139+
}
140+
struct kevent event;
141+
while (kevent(kq, NULL, 0, &event, 1, NULL) < 0 && errno == EINTR);
142+
_exit(1);
143+
}
144+
#endif
145+
146+
static void frankenphp_fork_child(void) {
147+
is_forked_child = true;
148+
#if defined(__linux__)
149+
// if the parent process dies between fork() and this prctl()
150+
if (prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0) != 0 ||
151+
getppid() != fork_parent_pid) {
152+
_exit(1);
153+
}
154+
#elif defined(__FreeBSD__)
155+
/* FreeBSD analogue of PR_SET_PDEATHSIG - except with
156+
* parent process death, rather than parent thread death. */
157+
int sig = SIGKILL;
158+
if (procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &sig) != 0 ||
159+
getppid() != fork_parent_pid) {
160+
_exit(1);
161+
}
162+
#elif defined(FRANKENPHP_KQUEUE_PARENT_DEATH)
163+
/* No kernel parent-death signal available; spawn a watcher that
164+
* blocks on EVFILT_PROC/NOTE_EXIT. */
165+
if (getppid() != fork_parent_pid) {
166+
_exit(1);
167+
}
168+
pthread_t watcher;
169+
if (pthread_create(&watcher, NULL, frankenphp_parent_death_watcher,
170+
(void *)(intptr_t)fork_parent_pid) != 0) {
171+
_exit(1);
172+
}
173+
pthread_detach(watcher);
174+
#endif
175+
}
114176

115177
static void frankenphp_register_atfork(void) {
116-
pthread_atfork(NULL, NULL, frankenphp_fork_child);
178+
pthread_atfork(frankenphp_fork_prepare, NULL, frankenphp_fork_child);
117179
}
118180
#endif
119181

@@ -939,6 +1001,12 @@ static int frankenphp_startup(sapi_module_struct *sapi_module) {
9391001
static int frankenphp_deactivate(void) { return SUCCESS; }
9401002

9411003
static size_t frankenphp_ub_write(const char *str, size_t str_length) {
1004+
#ifndef PHP_WIN32
1005+
if (UNEXPECTED(is_forked_child)) {
1006+
return 0;
1007+
}
1008+
#endif
1009+
9421010
struct go_ub_write_return result =
9431011
go_ub_write(frankenphp_thread_index(), (char *)str, str_length);
9441012

@@ -950,6 +1018,12 @@ static size_t frankenphp_ub_write(const char *str, size_t str_length) {
9501018
}
9511019

9521020
static int frankenphp_send_headers(sapi_headers_struct *sapi_headers) {
1021+
#ifndef PHP_WIN32
1022+
if (UNEXPECTED(is_forked_child)) {
1023+
return SAPI_HEADER_SEND_FAILED;
1024+
}
1025+
#endif
1026+
9531027
if (SG(request_info).no_headers == 1) {
9541028
return SAPI_HEADER_SENT_SUCCESSFULLY;
9551029
}
@@ -972,6 +1046,12 @@ static int frankenphp_send_headers(sapi_headers_struct *sapi_headers) {
9721046
}
9731047

9741048
static void frankenphp_sapi_flush(void *server_context) {
1049+
#ifndef PHP_WIN32
1050+
if (UNEXPECTED(is_forked_child)) {
1051+
return;
1052+
}
1053+
#endif
1054+
9751055
sapi_send_headers();
9761056
if (go_sapi_flush(frankenphp_thread_index())) {
9771057
php_handle_aborted_connection();

0 commit comments

Comments
 (0)