1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) The PHP Group                                          |
4    +----------------------------------------------------------------------+
5    | This source file is subject to version 3.01 of the PHP license,      |
6    | that is bundled with this package in the file LICENSE, and is        |
7    | available through the world-wide-web at the following url:           |
8    | https://www.php.net/license/3_01.txt                                 |
9    | If you did not receive a copy of the PHP license and are unable to   |
10    | obtain it through the world-wide-web, please send a note to          |
11    | license@php.net so we can mail you a copy immediately.               |
12    +----------------------------------------------------------------------+
13    | Author: Kévin Dunglas <kevin@dunglas.dev>                            |
14    +----------------------------------------------------------------------+
15  */
16 
17 #ifdef ZEND_MAX_EXECUTION_TIMERS
18 
19 #include <stdio.h>
20 #include <signal.h>
21 #include <time.h>
22 #include <unistd.h>
23 #include <errno.h>
24 #include <sys/syscall.h>
25 #include <sys/types.h>
26 # ifdef __FreeBSD__
27 # include <pthread_np.h>
28 # endif
29 
30 #include "zend.h"
31 #include "zend_globals.h"
32 #include "zend_portability.h"
33 
34 #if __has_feature(memory_sanitizer)
35 # include <sanitizer/msan_interface.h>
36 #endif
37 
38 // Musl Libc defines this macro, glibc does not
39 // According to "man 2 timer_create" this field should always be available, but it's not: https://sourceware.org/bugzilla/show_bug.cgi?id=27417
40 # ifndef sigev_notify_thread_id
41 # define sigev_notify_thread_id _sigev_un._tid
42 # endif
43 
44 // FreeBSD doesn't support CLOCK_BOOTTIME
45 # ifdef __FreeBSD__
46 # define ZEND_MAX_EXECUTION_TIMERS_CLOCK CLOCK_MONOTONIC
47 # else
48 # define ZEND_MAX_EXECUTION_TIMERS_CLOCK CLOCK_BOOTTIME
49 # endif
50 
zend_max_execution_timer_init(void)51 ZEND_API void zend_max_execution_timer_init(void) /* {{{ */
52 {
53 	pid_t pid = getpid();
54 
55 	if (EG(pid) == pid) {
56 		return;
57 	}
58 
59 	struct sigevent sev;
60 	sev.sigev_notify = SIGEV_THREAD_ID;
61 	sev.sigev_value.sival_ptr = &EG(max_execution_timer_timer);
62 	sev.sigev_signo = SIGRTMIN;
63 # ifdef __FreeBSD__
64 	sev.sigev_notify_thread_id = pthread_getthreadid_np();
65 # else
66 	sev.sigev_notify_thread_id = (pid_t) syscall(SYS_gettid);
67 # endif
68 
69 #if __has_feature(memory_sanitizer)
70 	/* MSan does not intercept timer_create() */
71 	__msan_unpoison(&EG(max_execution_timer_timer),
72 					sizeof(EG(max_execution_timer_timer)));
73 #endif
74 
75 	// Measure wall time instead of CPU time as originally planned now that it is possible https://github.com/php/php-src/pull/6504#issuecomment-1370303727
76 	if (timer_create(ZEND_MAX_EXECUTION_TIMERS_CLOCK, &sev, &EG(max_execution_timer_timer)) != 0) {
77 		zend_strerror_noreturn(E_ERROR, errno, "Could not create timer");
78 	}
79 
80 	EG(pid) = pid;
81 
82 # ifdef MAX_EXECUTION_TIMERS_DEBUG
83 		fprintf(stderr, "Timer %#jx created on thread %d\n", (uintmax_t) EG(max_execution_timer_timer), sev.sigev_notify_thread_id);
84 # endif
85 
86 	sigaction(sev.sigev_signo, NULL, &EG(oldact));
87 }
88 /* }}} */
89 
zend_max_execution_timer_settime(zend_long seconds)90 void zend_max_execution_timer_settime(zend_long seconds) /* {{{ }*/
91 {
92 	/* Timer not initialized or shutdown. */
93 	if (!EG(pid)) {
94 		return;
95 	}
96 
97 	timer_t timer = EG(max_execution_timer_timer);
98 
99 	// Prevent EINVAL error
100 	if (seconds < 0 || seconds > 999999999) {
101 		seconds = 0;
102 	}
103 
104 	struct itimerspec its;
105 	its.it_value.tv_sec = seconds;
106 	its.it_value.tv_nsec = its.it_interval.tv_sec = its.it_interval.tv_nsec = 0;
107 
108 # ifdef MAX_EXECUTION_TIMERS_DEBUG
109 	fprintf(stderr, "Setting timer %#jx on thread %d (%ld seconds)...\n", (uintmax_t) timer, (pid_t) syscall(SYS_gettid), seconds);
110 # endif
111 
112 	if (timer_settime(timer, 0, &its, NULL) != 0) {
113 		zend_strerror_noreturn(E_ERROR, errno, "Could not set timer");
114 	}
115 }
116 /* }}} */
117 
zend_max_execution_timer_shutdown(void)118 void zend_max_execution_timer_shutdown(void) /* {{{ */
119 {
120 	/* Don't try to delete a timer created before a call to fork() */
121 	if (EG(pid) != getpid()) {
122 		return;
123 	}
124 
125 	EG(pid) = 0;
126 
127 	timer_t timer = EG(max_execution_timer_timer);
128 
129 # ifdef MAX_EXECUTION_TIMERS_DEBUG
130 	fprintf(stderr, "Deleting timer %#jx on thread %d...\n", (uintmax_t) timer, (pid_t) syscall(SYS_gettid));
131 # endif
132 
133 	int err = timer_delete(timer);
134 	if (err != 0) {
135 		zend_strerror_noreturn(E_ERROR, errno, "Could not delete timer");
136 	}
137 }
138 /* }}}} */
139 
140 #endif
141