summaryrefslogtreecommitdiff
path: root/src/sp_disabled_functions.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sp_disabled_functions.c')
-rw-r--r--src/sp_disabled_functions.c356
1 files changed, 356 insertions, 0 deletions
diff --git a/src/sp_disabled_functions.c b/src/sp_disabled_functions.c
new file mode 100644
index 0000000..55d782b
--- /dev/null
+++ b/src/sp_disabled_functions.c
@@ -0,0 +1,356 @@
1#include "php_snuffleupagus.h"
2
3#include "zend_execute.h"
4#include "zend_hash.h"
5
6ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus);
7
8ZEND_COLD static zend_always_inline bool is_hash_matching(
9 const char* current_filename,
10 sp_disabled_function const* const config_node) {
11 char current_file_hash[SHA256_SIZE * 2];
12 compute_hash(current_filename, current_file_hash);
13 return (0 == strncmp(current_file_hash, config_node->hash, SHA256_SIZE));
14}
15
16static zend_always_inline char* get_complete_function_path(
17 zend_execute_data const* const execute_data) {
18 char const* class_name;
19 char const* const function_name =
20 ZSTR_VAL(execute_data->func->common.function_name);
21 char* complete_path_function = NULL;
22
23 class_name = get_active_class_name(NULL);
24 if (*class_name) {
25 const size_t len = strlen(class_name) + 2 + strlen(function_name) + 1;
26 complete_path_function = emalloc(len);
27 snprintf(complete_path_function, len, "%s::%s", class_name, function_name);
28 } else {
29 complete_path_function = estrdup(function_name);
30 }
31 return complete_path_function;
32}
33
34static bool is_local_var_matching(zend_execute_data *execute_data, const sp_disabled_function *const config_node) {
35 zend_execute_data *orig_execute_data = execute_data;
36
37 /*because execute_data points to hooked function data,
38 which we dont care about */
39 zend_execute_data *current = execute_data->prev_execute_data;
40 zval *value = NULL;
41
42 while (current) {
43 zend_string *key = NULL;
44 EG(current_execute_data) = current;
45 zend_array *symtable = zend_rebuild_symbol_table();
46 ZEND_HASH_FOREACH_STR_KEY_VAL(symtable, key, value) {
47 if (0 == strcmp(config_node->var, key->val)) { // is the var name right?
48 if (Z_TYPE_P(value) == IS_INDIRECT) {
49 value = Z_INDIRECT_P(value);
50 }
51 if (Z_TYPE_P(value) != IS_ARRAY) {
52 char *var_value_str = sp_convert_to_string(value);
53 if (true == sp_match_value(var_value_str, config_node->value, config_node->regexp)) {
54 efree(var_value_str);
55 EG(current_execute_data) = orig_execute_data;
56 return true;
57 }
58 efree(var_value_str);
59 }
60 else {
61 EG(current_execute_data) = orig_execute_data;
62 return sp_match_array_key_recurse(value, config_node->var_array_keys, config_node->value, NULL);
63 }
64 }
65 }
66 ZEND_HASH_FOREACH_END();
67 current = current->prev_execute_data;
68 }
69
70 EG(current_execute_data) = orig_execute_data;
71 return false;
72}
73
74bool should_disable(zend_execute_data* execute_data) {
75 const char* current_filename = zend_get_executed_filename(TSRMLS_C);
76 const sp_node_t* config =
77 SNUFFLEUPAGUS_G(config).config_disabled_functions->disabled_functions;
78 const char* function_name =
79 ZSTR_VAL(execute_data->func->common.function_name);
80 char* complete_path_function;
81 char const* client_ip = sp_getenv("REMOTE_ADDR");
82
83 if (!function_name) {
84 return false;
85 }
86
87 if (!config || !config->data) {
88 return false;
89 }
90
91 complete_path_function = get_complete_function_path(execute_data);
92
93 while (config) {
94 sp_disabled_function const* const config_node =
95 (sp_disabled_function*)(config->data);
96 const char* arg_name = NULL;
97 const char* arg_value_str = NULL;
98
99 if (false == config_node->enable) {
100 goto next;
101 }
102
103 if (config_node->function) { /* Litteral match against the function name. */
104 if (0 != strcmp(config_node->function, complete_path_function)) {
105 goto next;
106 }
107 } else if (config_node->r_function) {
108 if (false ==
109 is_regexp_matching(config_node->r_function, complete_path_function)) {
110 goto next;
111 }
112 }
113 if (config_node->var) {
114 if (false == is_local_var_matching(execute_data, config_node)) {
115 goto next;
116 }
117 }
118
119 if (config_node->filename) { /* Check the current file name. */
120 if (0 != strcmp(current_filename, config_node->filename)) {
121 goto next;
122 }
123 } else if (config_node->r_filename) {
124 if (false ==
125 is_regexp_matching(config_node->r_filename, current_filename)) {
126 goto next;
127 }
128 }
129
130 if (config_node->hash) {
131 if (false == is_hash_matching(current_filename, config_node)) {
132 goto next;
133 }
134 }
135
136 if (client_ip && config_node->cidr &&
137 (false == cidr_match(client_ip, config_node->cidr))) {
138 goto next;
139 }
140
141 /* Check if we filter on parameter value*/
142 if (config_node->param || config_node->r_param) {
143 const unsigned int nb_param = execute_data->func->common.num_args;
144 bool arg_matched = false;
145
146 for (unsigned int i = 0; i < nb_param; i++) {
147 arg_matched = false;
148 if (ZEND_USER_CODE(execute_data->func->type)) { // yay consistency
149 arg_name = ZSTR_VAL(execute_data->func->common.arg_info[i].name);
150 } else {
151 arg_name = execute_data->func->internal_function.arg_info[i].name;
152 }
153
154 const bool arg_matching =
155 config_node->param && (0 == strcmp(arg_name, config_node->param));
156 const bool pcre_matching =
157 config_node->r_param &&
158 (true == is_regexp_matching(config_node->r_param, arg_name));
159
160 /* This is the parameter name we're looking for. */
161 if (true == arg_matching || true == pcre_matching) {
162 zval* arg_value = ZEND_CALL_VAR_NUM(execute_data, i);
163
164 if (config_node->param_type) { // Are we matching on the `type`?
165 if (config_node->param_type == Z_TYPE_P(arg_value)) {
166 arg_matched = true;
167 break;
168 }
169 } else if (Z_TYPE_P(arg_value) == IS_ARRAY) {
170 arg_value_str = estrdup("Array");
171 // match on arr -> match on all key content, if a key is an array,
172 // ignore it
173 // match on arr[foo] -> match only on key foo, if the key is an
174 // array, match on all keys content
175 if (config_node->param_is_array == true) {
176 if (true == sp_match_array_key_recurse(
177 arg_value, config_node->param_array_keys,
178 config_node->value, config_node->regexp)) {
179 arg_matched = true;
180 break;
181 }
182 } else { // match on all keys, but don't go into subarray
183 if (true == sp_match_array_key(arg_value, config_node->value,
184 config_node->regexp)) {
185 arg_matched = true;
186 break;
187 }
188 }
189 } else {
190 arg_value_str = sp_convert_to_string(arg_value);
191 if (true == sp_match_value(arg_value_str, config_node->value,
192 config_node->regexp)) {
193 arg_matched = true;
194 break;
195 }
196 }
197 }
198 }
199 if (false == arg_matched) {
200 goto next;
201 }
202 }
203
204 /* Everything matched.*/
205
206 if (true == config_node->allow) {
207 goto allow;
208 }
209
210 sp_log_disable(complete_path_function, arg_name, arg_value_str,
211 config_node);
212 if (true == config_node->simulation) {
213 goto next;
214 } else { // We've got a match, the function won't be executed
215 efree(complete_path_function);
216 return true;
217 }
218next:
219config = config->next;
220 }
221allow:
222 efree(complete_path_function);
223 return false;
224}
225
226static bool should_drop_on_ret(zval* return_value,
227 const zend_execute_data* const execute_data) {
228 const sp_node_t* config =
229 SNUFFLEUPAGUS_G(config).config_disabled_functions_ret->disabled_functions;
230 char* complete_path_function = get_complete_function_path(execute_data);
231 const char* current_filename = zend_get_executed_filename(TSRMLS_C);
232
233 if (!config || !config->data) {
234 return false;
235 }
236
237 while (config) {
238 char* ret_value_str = NULL;
239 sp_disabled_function const* const config_node =
240 (sp_disabled_function*)(config->data);
241
242 if (false == config_node->enable) {
243 goto next;
244 }
245
246 if (config_node->function) {
247 if (0 != strcmp(config_node->function, complete_path_function)) {
248 goto next;
249 }
250 } else if (config_node->r_function) {
251 if (false ==
252 is_regexp_matching(config_node->r_function, complete_path_function)) {
253 goto next;
254 }
255 }
256
257 if (config_node->filename) { /* Check the current file name. */
258 if (0 != strcmp(current_filename, config_node->filename)) {
259 goto next;
260 }
261 } else if (config_node->r_filename) {
262 if (false ==
263 is_regexp_matching(config_node->r_filename, current_filename)) {
264 goto next;
265 }
266 }
267
268 if (config_node->hash) {
269 if (false == is_hash_matching(current_filename, config_node)) {
270 goto next;
271 }
272 }
273
274 ret_value_str = sp_convert_to_string(return_value); // FIXME memleak
275
276 bool match_type = (config_node->ret_type) &&
277 (config_node->ret_type == Z_TYPE_P(return_value));
278 bool match_value = (config_node->ret || config_node->r_ret) &&
279 (true == sp_match_value(ret_value_str, config_node->ret,
280 config_node->r_ret));
281
282 if (true == match_type || match_value) {
283 if (true == config_node->allow) {
284 efree(complete_path_function);
285 return false;
286 }
287 sp_log_disable_ret(complete_path_function, ret_value_str, config_node);
288 if (false == config_node->simulation) {
289 efree(complete_path_function);
290 return true;
291 }
292 }
293 next:
294 config = config->next;
295 }
296 efree(complete_path_function);
297 return false;
298}
299
300ZEND_FUNCTION(check_disabled_function) {
301 void (*orig_handler)(INTERNAL_FUNCTION_PARAMETERS);
302 const char* current_function_name = get_active_function_name(TSRMLS_C);
303
304 if (true == should_disable(execute_data)) {
305 return;
306 }
307
308 if ((orig_handler = zend_hash_str_find_ptr(
309 SNUFFLEUPAGUS_G(disabled_functions_hook), current_function_name,
310 strlen(current_function_name)))) {
311 orig_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU);
312 if (true == should_drop_on_ret(return_value, execute_data)) {
313 zend_bailout();
314 }
315 } else {
316 sp_log_err(
317 "disabled_functions",
318 "Unable to find the pointer to the original function '%s' in the "
319 "hashtable.\n",
320 current_function_name);
321 }
322}
323
324static int hook_functions(const sp_node_t* config) {
325 while (config && config->data) {
326 const char* function_name = ((sp_disabled_function*)config->data)->function;
327 const pcre* function_name_regexp =
328 ((sp_disabled_function*)config->data)->r_function;
329
330 if (NULL != function_name) { // hook function by name
331 HOOK_FUNCTION(function_name, disabled_functions_hook,
332 PHP_FN(check_disabled_function), false);
333 } else if (NULL != function_name_regexp) { // hook function by regexp
334 HOOK_FUNCTION_BY_REGEXP(function_name_regexp, disabled_functions_hook,
335 PHP_FN(check_disabled_function), false);
336 } else {
337 return FAILURE;
338 }
339
340 config = config->next;
341 }
342 return SUCCESS;
343}
344
345int hook_disabled_functions(void) {
346 TSRMLS_FETCH();
347
348 int ret = SUCCESS;
349
350 ret |= hook_functions(
351 SNUFFLEUPAGUS_G(config).config_disabled_functions->disabled_functions);
352 ret |= hook_functions(SNUFFLEUPAGUS_G(config)
353 .config_disabled_functions_ret->disabled_functions);
354
355 return ret;
356}