summaryrefslogtreecommitdiff
path: root/src/sp_execute.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sp_execute.c')
-rw-r--r--src/sp_execute.c286
1 files changed, 151 insertions, 135 deletions
diff --git a/src/sp_execute.c b/src/sp_execute.c
index 7d078b0..f1ed8d0 100644
--- a/src/sp_execute.c
+++ b/src/sp_execute.c
@@ -3,33 +3,28 @@
3static void (*orig_execute_ex)(zend_execute_data *execute_data) = NULL; 3static void (*orig_execute_ex)(zend_execute_data *execute_data) = NULL;
4static void (*orig_zend_execute_internal)(zend_execute_data *execute_data, 4static void (*orig_zend_execute_internal)(zend_execute_data *execute_data,
5 zval *return_value) = NULL; 5 zval *return_value) = NULL;
6static int (*orig_zend_stream_open)(const char *filename, 6#if PHP_VERSION_ID < 80100
7 zend_file_handle *handle) = NULL; 7static int (*orig_zend_stream_open)(const char *filename, zend_file_handle *handle) = NULL;
8#else
9static zend_result (*orig_zend_stream_open)(zend_file_handle *handle) = NULL;
10#endif
8 11
9// FIXME handle symlink 12// FIXME handle symlink
10ZEND_COLD static inline void terminate_if_writable(const char *filename) { 13ZEND_COLD static inline void terminate_if_writable(const char *filename) {
11 const sp_config_readonly_exec *config_ro_exec = 14 const sp_config_readonly_exec *config_ro_exec = &(SPCFG(readonly_exec));
12 SNUFFLEUPAGUS_G(config).config_readonly_exec;
13
14 if (0 == access(filename, W_OK)) { 15 if (0 == access(filename, W_OK)) {
15 if (config_ro_exec->dump) { 16 if (config_ro_exec->dump) {
16 sp_log_request(config_ro_exec->dump, 17 sp_log_request(config_ro_exec->dump, config_ro_exec->textual_representation);
17 config_ro_exec->textual_representation,
18 SP_TOKEN_READONLY_EXEC);
19 } 18 }
20 if (true == config_ro_exec->simulation) { 19 if (true == config_ro_exec->simulation) {
21 sp_log_simulation("readonly_exec", 20 sp_log_simulation("readonly_exec", "Attempted execution of a writable file (%s).", filename);
22 "Attempted execution of a writable file (%s).",
23 filename);
24 } else { 21 } else {
25 sp_log_drop("readonly_exec", 22 sp_log_drop("readonly_exec", "Attempted execution of a writable file (%s).", filename);
26 "Attempted execution of a writable file (%s).", filename);
27 } 23 }
28 } else { 24 } else {
29 if (EACCES != errno) { 25 if (EACCES != errno) {
30 // LCOV_EXCL_START 26 // LCOV_EXCL_START
31 sp_log_err("Writable execution", "Error while accessing %s: %s", filename, 27 sp_log_err("Writable execution", "Error while accessing %s: %s", filename, strerror(errno));
32 strerror(errno));
33 // LCOV_EXCL_STOP 28 // LCOV_EXCL_STOP
34 } 29 }
35 } 30 }
@@ -44,54 +39,43 @@ inline static void is_builtin_matching(
44 return; 39 return;
45 } 40 }
46 41
47 should_disable_ht( 42 should_disable_ht(EG(current_execute_data), function_name, param_value, param_name, SPCFG(disabled_functions_reg).disabled_functions, ht);
48 EG(current_execute_data), function_name, param_value, param_name,
49 SNUFFLEUPAGUS_G(config).config_disabled_functions_reg->disabled_functions,
50 ht);
51} 43}
52 44
53static void ZEND_HOT 45static void ZEND_HOT is_in_eval_and_whitelisted(const zend_execute_data *execute_data) {
54is_in_eval_and_whitelisted(const zend_execute_data *execute_data) { 46 const sp_config_eval *config_eval = &(SPCFG(eval));
55 const sp_config_eval *config_eval = SNUFFLEUPAGUS_G(config).config_eval;
56 47
57 if (EXPECTED(0 == SNUFFLEUPAGUS_G(in_eval))) { 48 if (EXPECTED(0 == SPG(in_eval))) {
58 return; 49 return;
59 } 50 }
60 51
61 if (EXPECTED(NULL == SNUFFLEUPAGUS_G(config).config_eval->whitelist)) { 52 if (EXPECTED(NULL == config_eval->whitelist)) {
62 return; 53 return;
63 } 54 }
64 55
65 if (zend_is_executing() && !EG(current_execute_data)->func) { 56 if (zend_is_executing() && !EX(func)) {
66 return; // LCOV_EXCL_LINE 57 return; // LCOV_EXCL_LINE
67 } 58 }
68 59
69 if (UNEXPECTED(!(execute_data->func->common.function_name))) { 60 char *function_name = get_complete_function_path(execute_data);
61 if (!function_name) {
70 return; 62 return;
71 } 63 }
72 64
73 zend_string const *const current_function = EX(func)->common.function_name; 65 if (UNEXPECTED(false == check_is_in_eval_whitelist(function_name))) {
74
75 if (EXPECTED(NULL != current_function)) {
76 if (UNEXPECTED(false == check_is_in_eval_whitelist(current_function))) {
77 if (config_eval->dump) { 66 if (config_eval->dump) {
78 sp_log_request(config_eval->dump, config_eval->textual_representation, 67 sp_log_request(config_eval->dump, config_eval->textual_representation);
79 SP_TOKEN_EVAL_WHITELIST);
80 } 68 }
81 if (config_eval->simulation) { 69 if (config_eval->simulation) {
82 sp_log_simulation( 70 sp_log_simulation("Eval_whitelist", "The function '%s' isn't in the eval whitelist, logging its call.", function_name);
83 "Eval_whitelist", 71 goto out;
84 "The function '%s' isn't in the eval whitelist, logging its call.",
85 ZSTR_VAL(current_function));
86 return;
87 } else { 72 } else {
88 sp_log_drop( 73 sp_log_drop("Eval_whitelist", "The function '%s' isn't in the eval whitelist, dropping its call.", function_name);
89 "Eval_whitelist",
90 "The function '%s' isn't in the eval whitelist, dropping its call.",
91 ZSTR_VAL(current_function));
92 } 74 }
93 } 75 }
94 } 76 // }
77out:
78 efree(function_name);
95} 79}
96 80
97/* This function gets the filename in which `eval()` is called from, 81/* This function gets the filename in which `eval()` is called from,
@@ -114,162 +98,194 @@ zend_string *get_eval_filename(const char *const filename) {
114 return clean_filename; 98 return clean_filename;
115} 99}
116 100
117static void sp_execute_ex(zend_execute_data *execute_data) { 101static inline void sp_orig_execute(zend_execute_data *execute_data) {
118 is_in_eval_and_whitelisted(execute_data); 102 SPG(execution_depth)++;
119 const HashTable *config_disabled_functions = 103 if (SPCFG(max_execution_depth) > 0 && SPG(execution_depth) > SPCFG(max_execution_depth)) {
120 SNUFFLEUPAGUS_G(config).config_disabled_functions; 104 sp_log_drop("execute", "Maximum recursion limit reached. Script terminated.");
105 }
106 orig_execute_ex(execute_data);
107 SPG(execution_depth)--;
108}
109
110static inline void sp_check_writable(zend_execute_data *execute_data) {
111 if (execute_data && EX(func) && EX(func)->op_array.filename && SPCFG(readonly_exec).enable) {
112 terminate_if_writable(ZSTR_VAL(EX(func)->op_array.filename));
113 }
114}
115
116static inline void sp_call_orig_execute(INTERNAL_FUNCTION_PARAMETERS, bool internal) {
117 if (internal) {
118 if (UNEXPECTED(NULL != orig_zend_execute_internal)) {
119 orig_zend_execute_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU);
120 } else {
121 EX(func)->internal_function.handler(INTERNAL_FUNCTION_PARAM_PASSTHRU);
122 }
123 } else {
124 sp_orig_execute(execute_data);
125 }
126}
121 127
128static inline void sp_execute_handler(INTERNAL_FUNCTION_PARAMETERS, bool internal) {
122 if (!execute_data) { 129 if (!execute_data) {
123 return; // LCOV_EXCL_LINE 130 return; // LCOV_EXCL_LINE
124 } 131 }
125 132
126 if (UNEXPECTED(EX(func)->op_array.type == ZEND_EVAL_CODE)) { 133 is_in_eval_and_whitelisted(execute_data);
127 const sp_list_node *config = zend_hash_str_find_ptr( 134
128 config_disabled_functions, "eval", sizeof("eval") - 1); 135 if (!internal) {
136 if (UNEXPECTED(EX(func)->op_array.type == ZEND_EVAL_CODE)) {
137 const sp_list_node *config = zend_hash_str_find_ptr(SPCFG(disabled_functions), ZEND_STRL("eval"));
138
139 zend_string *filename = get_eval_filename(zend_get_executed_filename());
140 is_builtin_matching(filename, "eval", NULL, config, SPCFG(disabled_functions));
141 zend_string_release(filename);
142
143 SPG(in_eval)++;
144 sp_orig_execute(execute_data);
145 SPG(in_eval)--;
146 return;
147 }
129 148
130 zend_string *filename = get_eval_filename(zend_get_executed_filename()); 149 sp_check_writable(execute_data);
131 is_builtin_matching(filename, "eval", NULL, config, 150 }
132 config_disabled_functions);
133 zend_string_release(filename);
134 151
135 SNUFFLEUPAGUS_G(in_eval)++; 152 if (!SPG(hook_execute)) {
136 orig_execute_ex(execute_data); 153 sp_call_orig_execute(INTERNAL_FUNCTION_PARAM_PASSTHRU, internal);
137 SNUFFLEUPAGUS_G(in_eval)--;
138 return; 154 return;
139 } 155 }
140 156
141 if (NULL != EX(func)->op_array.filename) { 157 char *function_name = get_complete_function_path(execute_data);
142 if (true == SNUFFLEUPAGUS_G(config).config_readonly_exec->enable) { 158
143 terminate_if_writable(ZSTR_VAL(EX(func)->op_array.filename)); 159 if (!function_name) {
144 } 160 sp_call_orig_execute(INTERNAL_FUNCTION_PARAM_PASSTHRU, internal);
161 return;
145 } 162 }
146 163
147 if (SNUFFLEUPAGUS_G(config).hook_execute) { 164 bool is_hooked = (zend_hash_str_find(SPG(disabled_functions_hook), VAR_AND_LEN(function_name)) || zend_hash_str_find(SPG(disabled_functions_hook), VAR_AND_LEN(function_name)));
148 char *function_name = get_complete_function_path(execute_data); 165 if (is_hooked) {
149 zval ret_val; 166 sp_call_orig_execute(INTERNAL_FUNCTION_PARAM_PASSTHRU, internal);
150 const sp_list_node *config_disabled_functions_reg = 167 return;
151 SNUFFLEUPAGUS_G(config) 168 }
152 .config_disabled_functions_reg->disabled_functions;
153 169
154 if (!function_name) { 170 // If we're at an internal function
155 orig_execute_ex(execute_data); 171 if (!execute_data->prev_execute_data ||
156 return; 172 !execute_data->prev_execute_data->func ||
173 !ZEND_USER_CODE(execute_data->prev_execute_data->func->type) ||
174 !execute_data->prev_execute_data->opline) {
175 should_disable_ht(execute_data, function_name, NULL, NULL, SPCFG(disabled_functions_reg).disabled_functions, SPCFG(disabled_functions));
176 } else { // If we're at a userland function call
177 switch (execute_data->prev_execute_data->opline->opcode) {
178 case ZEND_DO_FCALL:
179 case ZEND_DO_FCALL_BY_NAME:
180 case ZEND_DO_ICALL:
181 case ZEND_DO_UCALL:
182 case ZEND_TICKS:
183 should_disable_ht(execute_data, function_name, NULL, NULL, SPCFG(disabled_functions_reg).disabled_functions, SPCFG(disabled_functions));
184 default:
185 break;
157 } 186 }
187 }
158 188
159 // If we're at an internal function 189 // When a function's return value isn't used, php doesn't store it in the
160 if (!execute_data->prev_execute_data || 190 // execute_data, so we need to use a local variable to be able to match on
161 !execute_data->prev_execute_data->func || 191 // it later.
162 !ZEND_USER_CODE(execute_data->prev_execute_data->func->type) || 192 zval ret_val;
163 !execute_data->prev_execute_data->opline) { 193 if (EX(return_value) == NULL && return_value == NULL) {
164 should_disable_ht(execute_data, function_name, NULL, NULL, 194 memset(&ret_val, 0, sizeof(ret_val));
165 config_disabled_functions_reg, 195 return_value = EX(return_value) = &ret_val;
166 config_disabled_functions); 196 }
167 } else { // If we're at a userland function call
168 switch (execute_data->prev_execute_data->opline->opcode) {
169 case ZEND_DO_FCALL:
170 case ZEND_DO_FCALL_BY_NAME:
171 case ZEND_DO_ICALL:
172 case ZEND_DO_UCALL:
173 should_disable_ht(execute_data, function_name, NULL, NULL,
174 config_disabled_functions_reg,
175 config_disabled_functions);
176 default:
177 break;
178 }
179 }
180 197
181 // When a function's return value isn't used, php doesn't store it in the 198 sp_call_orig_execute(INTERNAL_FUNCTION_PARAM_PASSTHRU, internal);
182 // execute_data, so we need to use a local variable to be able to match on
183 // it later.
184 if (EX(return_value) == NULL) {
185 memset(&ret_val, 0, sizeof(ret_val));
186 EX(return_value) = &ret_val;
187 }
188 199
189 orig_execute_ex(execute_data); 200 should_drop_on_ret_ht(return_value, function_name, SPCFG(disabled_functions_reg_ret).disabled_functions, SPCFG(disabled_functions_ret), execute_data);
190 201
191 should_drop_on_ret_ht( 202 efree(function_name);
192 EX(return_value), function_name,
193 SNUFFLEUPAGUS_G(config)
194 .config_disabled_functions_reg_ret->disabled_functions,
195 SNUFFLEUPAGUS_G(config).config_disabled_functions_ret, execute_data);
196 efree(function_name);
197 203
198 if (EX(return_value) == &ret_val) { 204 if (EX(return_value) == &ret_val) {
199 EX(return_value) = NULL; 205 return_value = EX(return_value) = NULL;
200 }
201 } else {
202 orig_execute_ex(execute_data);
203 } 206 }
207
204} 208}
205 209
206static void sp_zend_execute_internal(INTERNAL_FUNCTION_PARAMETERS) {
207 is_in_eval_and_whitelisted(execute_data);
208 210
209 if (UNEXPECTED(NULL != orig_zend_execute_internal)) { 211static void sp_execute_ex(zend_execute_data *execute_data) {
210 // LCOV_EXCL_START 212 sp_execute_handler(execute_data, execute_data ? EX(return_value) : NULL, false);
211 orig_zend_execute_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU);
212 // LCOV_EXCL_STOP
213 } else {
214 EX(func)->internal_function.handler(INTERNAL_FUNCTION_PARAM_PASSTHRU);
215 }
216} 213}
217 214
218static int sp_stream_open(const char *filename, zend_file_handle *handle) { 215static void sp_zend_execute_internal(INTERNAL_FUNCTION_PARAMETERS) {
216 sp_execute_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
217}
218
219static inline void sp_stream_open_checks(zend_string *zend_filename, zend_file_handle *handle) {
219 zend_execute_data const *const data = EG(current_execute_data); 220 zend_execute_data const *const data = EG(current_execute_data);
220 221
221 if ((NULL == data) || (NULL == data->opline) || 222 if ((NULL == data) || (NULL == data->opline) ||
222 (data->func->type != ZEND_USER_FUNCTION)) { 223 (data->func->type != ZEND_USER_FUNCTION)) {
223 goto end; 224 return;
224 } 225 }
225 226
226 zend_string *zend_filename = zend_string_init(filename, strlen(filename), 0); 227 // zend_string *zend_filename = zend_string_init(filename, strlen(filename), 0);
227 const HashTable *disabled_functions_hooked = 228 const HashTable *disabled_functions_hooked = SPCFG(disabled_functions_hooked);
228 SNUFFLEUPAGUS_G(config).config_disabled_functions_hooked;
229 229
230 switch (data->opline->opcode) { 230 switch (data->opline->opcode) {
231 case ZEND_INCLUDE_OR_EVAL: 231 case ZEND_INCLUDE_OR_EVAL:
232 if (true == SNUFFLEUPAGUS_G(config).config_readonly_exec->enable) { 232 if (SPCFG(readonly_exec).enable) {
233 terminate_if_writable(filename); 233 terminate_if_writable(ZSTR_VAL(zend_filename));
234 } 234 }
235 switch (data->opline->extended_value) { 235 switch (data->opline->extended_value) {
236 case ZEND_INCLUDE: 236 case ZEND_INCLUDE:
237 is_builtin_matching( 237 is_builtin_matching(
238 zend_filename, "include", "inclusion path", 238 zend_filename, "include", "inclusion path",
239 zend_hash_str_find_ptr(disabled_functions_hooked, "include", 239 zend_hash_str_find_ptr(disabled_functions_hooked, ZEND_STRL("include")),
240 sizeof("include") - 1),
241 disabled_functions_hooked); 240 disabled_functions_hooked);
242 break; 241 break;
243 case ZEND_REQUIRE: 242 case ZEND_REQUIRE:
244 is_builtin_matching( 243 is_builtin_matching(
245 zend_filename, "require", "inclusion path", 244 zend_filename, "require", "inclusion path",
246 zend_hash_str_find_ptr(disabled_functions_hooked, "require", 245 zend_hash_str_find_ptr(disabled_functions_hooked, ZEND_STRL("require")),
247 sizeof("require") - 1),
248 disabled_functions_hooked); 246 disabled_functions_hooked);
249 break; 247 break;
250 case ZEND_REQUIRE_ONCE: 248 case ZEND_REQUIRE_ONCE:
251 is_builtin_matching( 249 is_builtin_matching(
252 zend_filename, "require_once", "inclusion path", 250 zend_filename, "require_once", "inclusion path",
253 zend_hash_str_find_ptr(disabled_functions_hooked, "require_once", 251 zend_hash_str_find_ptr(disabled_functions_hooked, ZEND_STRL("require_once")),
254 sizeof("require_once") - 1),
255 disabled_functions_hooked); 252 disabled_functions_hooked);
256 break; 253 break;
257 case ZEND_INCLUDE_ONCE: 254 case ZEND_INCLUDE_ONCE:
258 is_builtin_matching( 255 is_builtin_matching(
259 zend_filename, "include_once", "inclusion path", 256 zend_filename, "include_once", "inclusion path",
260 zend_hash_str_find_ptr(disabled_functions_hooked, "include_once", 257 zend_hash_str_find_ptr(disabled_functions_hooked, ZEND_STRL("include_once")),
261 sizeof("include_once") - 1),
262 disabled_functions_hooked); 258 disabled_functions_hooked);
263 break; 259 break;
264 EMPTY_SWITCH_DEFAULT_CASE(); // LCOV_EXCL_LINE 260 EMPTY_SWITCH_DEFAULT_CASE(); // LCOV_EXCL_LINE
265 } 261 }
266 } 262 }
267 efree(zend_filename); 263 // efree(zend_filename);
268 264
269end: 265// end:
266 // return orig_zend_stream_open(filename, handle);
267}
268
269#if PHP_VERSION_ID < 80100
270
271static int sp_stream_open(const char *filename, zend_file_handle *handle) {
272 zend_string *zend_filename = zend_string_init(filename, strlen(filename), 0);
273
274 sp_stream_open_checks(zend_filename, handle);
275
276 zend_string_release_ex(zend_filename, 0);
270 return orig_zend_stream_open(filename, handle); 277 return orig_zend_stream_open(filename, handle);
271} 278}
272 279
280#else // PHP >= 8.1
281
282static zend_result sp_stream_open(zend_file_handle *handle) {
283 sp_stream_open_checks(handle->filename, handle);
284 return orig_zend_stream_open(handle);
285}
286
287#endif
288
273int hook_execute(void) { 289int hook_execute(void) {
274 TSRMLS_FETCH(); 290 TSRMLS_FETCH();
275 291