summaryrefslogtreecommitdiff
path: root/src/sp_utils.c
diff options
context:
space:
mode:
authorSebastien Blot2017-09-20 10:11:01 +0200
committerSebastien Blot2017-09-20 10:11:01 +0200
commit868f96c759b6650d88ff9f4fbc5c048302134248 (patch)
treec0de0af318bf77a8959164ef11aeeeb2b7bab294 /src/sp_utils.c
Initial import
Diffstat (limited to 'src/sp_utils.c')
-rw-r--r--src/sp_utils.c425
1 files changed, 425 insertions, 0 deletions
diff --git a/src/sp_utils.c b/src/sp_utils.c
new file mode 100644
index 0000000..087f431
--- /dev/null
+++ b/src/sp_utils.c
@@ -0,0 +1,425 @@
1#include "php_snuffleupagus.h"
2
3#include <fcntl.h>
4#include <stdio.h>
5#include <stdlib.h>
6#include <string.h>
7#include <unistd.h>
8
9static inline void _sp_log_err(const char* fmt, ...) {
10 char* msg;
11 va_list args;
12
13 va_start(args, fmt);
14 vspprintf(&msg, 0, fmt, args);
15 va_end(args);
16 php_log_err(msg);
17}
18
19void sp_log_msg(char const *feature, char const *level, const char* fmt, ...) {
20 char* msg;
21 va_list args;
22
23 va_start(args, fmt);
24 vspprintf(&msg, 0, fmt, args);
25 va_end(args);
26
27 char const * const client_ip = sp_getenv("REMOTE_ADDR");
28 _sp_log_err("[snuffleupagus][%s][%s][%s] %s", client_ip?client_ip:"0.0.0.0",
29 feature, level, msg);
30}
31
32int compute_hash(const char* const filename, char* file_hash) {
33 unsigned char buf[1024];
34 unsigned char digest[SHA256_SIZE];
35 PHP_SHA256_CTX context;
36 size_t n;
37
38 php_stream* stream =
39 php_stream_open_wrapper(filename, "rb", REPORT_ERRORS, NULL);
40 if (!stream) {
41 sp_log_err("hash_computation", "Can not open the file %s to compute its hash.\n", filename);
42 return -1;
43 }
44
45 PHP_SHA256Init(&context);
46 while ((n = php_stream_read(stream, (char*)buf, sizeof(buf))) > 0) {
47 PHP_SHA256Update(&context, buf, n);
48 }
49 PHP_SHA256Final(digest, &context);
50 php_stream_close(stream);
51 make_digest_ex(file_hash, digest, SHA256_SIZE);
52 return 0;
53}
54
55static void construct_filename(char* filename, const char* folder) {
56 time_t t = time(NULL);
57 struct tm* tm = localtime(&t); // FIXME use `localtime_r` instead
58 struct timeval tval;
59 struct stat st = {0};
60
61 if (-1 == stat(folder, &st)) {
62 mkdir(folder, 0700);
63 }
64
65 memcpy(filename, folder, strlen(folder));
66 strcat(filename, "sp_dump_");
67 strftime(filename + strlen(filename), 27, "%F_%T:", tm);
68 gettimeofday(&tval, NULL);
69 sprintf(filename + strlen(filename), "%04ld", tval.tv_usec);
70 strcat(filename, "_");
71
72 char* remote_addr = getenv("REMOTE_ADDR");
73 if (remote_addr) {
74 strcat(filename, remote_addr);
75 } else {
76 strcat(filename, "0.0.0.0");
77 }
78 strcat(filename, ".dump");
79}
80
81int sp_log_request(const char* folder) {
82 FILE* file;
83 const char* current_filename = zend_get_executed_filename(TSRMLS_C);
84 const int current_line = zend_get_executed_lineno(TSRMLS_C);
85 char filename[MAX_FOLDER_LEN] = {0};
86 const struct {
87 const char* str;
88 const int key;
89 } zones[] = {{"GET", TRACK_VARS_GET}, {"POST", TRACK_VARS_POST},
90 {"COOKIE", TRACK_VARS_COOKIE}, {"SERVER", TRACK_VARS_SERVER},
91 {"ENV", TRACK_VARS_ENV}, {NULL, 0}};
92
93 construct_filename(filename, folder);
94 if (NULL == (file = fopen(filename, "a"))) {
95 sp_log_err("request_logging", "Unable to open %s", filename);
96 return -1;
97 }
98
99 fprintf(file, "%s:%d\n", current_filename, current_line);
100 for (size_t i = 0; i < (sizeof(zones) / sizeof(zones[0])) - 1; i++) {
101 zval* variable_value;
102 zend_string* variable_key;
103 size_t params_len = strlen(zones[i].str) + 1;
104 char* param;
105 size_t size_max = 2048;
106
107 if (Z_TYPE(PG(http_globals)[zones[i].key]) == IS_UNDEF) {
108 continue;
109 }
110
111 const HashTable* ht = Z_ARRVAL(PG(http_globals)[zones[i].key]);
112
113 // Compute the size of the allocation
114 ZEND_HASH_FOREACH_STR_KEY_VAL(ht, variable_key, variable_value) {
115 params_len += snprintf(NULL, 0, "%s=%s&", ZSTR_VAL(variable_key),
116 Z_STRVAL_P(variable_value));
117 }
118 ZEND_HASH_FOREACH_END();
119
120 params_len = params_len>size_max?size_max:params_len;
121
122#define NCAT_AND_DEC(a, b, c) strncat(a, b, c); c -= strlen(b);
123
124 // Allocate and copy the data
125 // FIXME Why are we even allocating?
126 param = pecalloc(params_len, 1, 0);
127 NCAT_AND_DEC(param, zones[i].str, params_len);
128 NCAT_AND_DEC(param, ":", params_len);
129 ZEND_HASH_FOREACH_STR_KEY_VAL(ht, variable_key, variable_value) {
130 NCAT_AND_DEC(param, ZSTR_VAL(variable_key), params_len);
131 NCAT_AND_DEC(param, "=", params_len);
132 NCAT_AND_DEC(param, Z_STRVAL_P(variable_value), params_len);
133 NCAT_AND_DEC(param, "&", params_len);
134 }
135 ZEND_HASH_FOREACH_END();
136
137 param[strlen(param) - 1] = '\0';
138
139 fputs(param, file);
140 fputs("\n", file);
141 }
142 fclose(file);
143
144#undef CAT_AND_DEC
145 return 0;
146}
147
148static char *zv_str_to_char(zval *zv) {
149 zval copy;
150 char *ret;
151
152
153 ZVAL_ZVAL(&copy, zv, 1, 0);
154 // str = zend_string_dup(Z_STR_P(zv), 0);
155 for (size_t i = 0; i < Z_STRLEN(copy); i++) {
156 if (Z_STRVAL(copy)[i] == '\0') {
157 Z_STRVAL(copy)[i] = '0';
158 }
159 }
160 ret = estrdup(Z_STRVAL(copy));
161 // zend_string_release(str);
162 return ret;
163}
164
165
166char* sp_convert_to_string(zval* zv) {
167 switch (Z_TYPE_P(zv)) {
168 case IS_FALSE:
169 return estrdup("FALSE");
170 case IS_TRUE:
171 return estrdup("TRUE");
172 case IS_NULL:
173 return estrdup("NULL");
174 case IS_LONG: {
175 char *msg;
176 spprintf(&msg, 0, ZEND_LONG_FMT, Z_LVAL_P(zv));
177 return msg;
178 }
179 case IS_DOUBLE: {
180 char *msg;
181 spprintf(&msg, 0, "%f", Z_DVAL_P(zv));
182 return msg;
183 }
184 case IS_STRING:{
185 return zv_str_to_char(zv);
186 }
187 case IS_OBJECT:
188 return estrdup("OBJECT");
189 case IS_ARRAY:
190 return estrdup("ARRAY");
191 case IS_RESOURCE:
192 return estrdup("RESOURCE");
193 }
194 return estrdup("");
195}
196
197bool sp_match_value(const char* value, const char* to_match, const pcre* rx) {
198 if (to_match) {
199 if (0 == strcmp(value, to_match)) {
200 return true;
201 }
202 } else if (rx) {
203 int substrvec[30];
204 int ret = pcre_exec(rx, NULL, value, strlen(value), 0, 0, substrvec, 30);
205
206 if (ret < 0) {
207 if (ret != PCRE_ERROR_NOMATCH) {
208 sp_log_err("regexp", "Something went wrong with a regexp.");
209 return false;
210 }
211 return false;
212 }
213 return true;
214 }
215 return false;
216}
217
218void sp_log_disable(const char* restrict path, const char* restrict arg_name,
219 const char* restrict arg_value,
220 const sp_disabled_function* config_node) {
221 const char* dump = config_node->dump;
222 const char* alias = config_node->alias;
223 const int sim = config_node->simulation;
224 if (arg_name) {
225 if (alias) {
226 sp_log_msg("disabled_function", sim?LOG_NOTICE:LOG_DROP,
227 "The call to the function '%s' in %s:%d has been disabled, "
228 "because its argument '%s' content (%s) matched the rule '%s'.",
229 path, zend_get_executed_filename(TSRMLS_C),
230 zend_get_executed_lineno(TSRMLS_C), arg_name, arg_value?arg_value:"?",
231 alias);
232 } else {
233 sp_log_msg("disabled_function", sim?LOG_NOTICE:LOG_DROP,
234 "The call to the function '%s' in %s:%d has been disabled, "
235 "because its argument '%s' content (%s) matched a rule.",
236 path, zend_get_executed_filename(TSRMLS_C),
237 zend_get_executed_lineno(TSRMLS_C), arg_name,
238 arg_value?arg_value:"?");
239 }
240 } else {
241 if (alias) {
242 sp_log_msg("disabled_function", sim?LOG_NOTICE:LOG_DROP,
243 "The call to the function '%s' in %s:%d has been disabled, "
244 "because of the the rule '%s'.",path,
245 zend_get_executed_filename(TSRMLS_C),
246 zend_get_executed_lineno(TSRMLS_C), alias);
247 } else {
248 sp_log_msg("disabled_function", sim?LOG_NOTICE:LOG_DROP,
249 "The call to the function '%s' in %s:%d has been disabled.",
250 path, zend_get_executed_filename(TSRMLS_C),
251 zend_get_executed_lineno(TSRMLS_C));
252 }
253 }
254 if (dump) {
255 sp_log_request(config_node->dump);
256 }
257}
258
259void sp_log_disable_ret(const char* restrict path,
260 const char* restrict ret_value,
261 const sp_disabled_function* config_node) {
262 const char* dump = config_node->dump;
263 const char* alias = config_node->alias;
264 const int sim = config_node->simulation;
265 if (alias) {
266 sp_log_msg("disabled_function", sim?LOG_NOTICE:LOG_DROP,
267 "The execution has been aborted in %s:%d, "
268 "because the function '%s' returned '%s', which matched the rule '%s'.",
269 zend_get_executed_filename(TSRMLS_C),
270 zend_get_executed_lineno(TSRMLS_C), path, ret_value?ret_value:"?", alias);
271 } else {
272 sp_log_msg("disabled_function", sim?LOG_NOTICE:LOG_DROP,
273 "The execution has been aborted in %s:%d, "
274 "because the return value (%s) of the function '%s' matched a rule.",
275 zend_get_executed_filename(TSRMLS_C),
276 zend_get_executed_lineno(TSRMLS_C), ret_value?ret_value:"?", path);
277 }
278 if (dump) {
279 sp_log_request(dump);
280 }
281}
282
283int sp_match_array_key(const zval* zv, const char* to_match, const pcre* rx) {
284 zend_string* key;
285 zval* value;
286 char* arg_value_str;
287
288 ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(zv), key, value) {
289 if (Z_TYPE_P(value) == IS_ARRAY) {
290 continue;
291 }
292 arg_value_str = sp_convert_to_string(value);
293 if (!sp_match_value(arg_value_str, to_match, rx)) {
294 efree(arg_value_str);
295 continue;
296 } else {
297 efree(arg_value_str);
298 return 1;
299 }
300 }
301 ZEND_HASH_FOREACH_END();
302
303 (void)key; // silence a compiler warning
304
305 return 0;
306}
307
308int sp_match_array_key_recurse(const zval* arr, sp_node_t* keys,
309 const char* to_match, const pcre* rx) {
310 zend_string* key;
311 zval* value;
312 sp_node_t* current = keys;
313 if (current == NULL) {
314 return 0;
315 }
316 ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(arr), key, value) {
317 if (Z_TYPE_P(value) == IS_ARRAY && !strcmp(ZSTR_VAL(key), current->data)) {
318 return sp_match_array_key_recurse(value, current->next, to_match, rx);
319 }
320 if (!strcmp(ZSTR_VAL(key), current->data) && current->next == NULL) {
321 if (!to_match && !rx) {
322 return 1;
323 }
324 if (Z_TYPE_P(value) == IS_ARRAY) {
325 return sp_match_array_key(value, to_match, rx);
326 } else {
327 char *value_str = sp_convert_to_string(value);
328 if (sp_match_value(value_str, to_match, rx)) {
329 efree(value_str);
330 return 1;
331 } else {
332 efree (value_str);
333 return 0;
334 }
335 }
336 }
337 }
338 ZEND_HASH_FOREACH_END();
339 return 0;
340}
341
342zend_always_inline char* sp_getenv(char* var) {
343 if (sapi_module.getenv) {
344 return sapi_module.getenv(ZEND_STRL(var));
345 } else {
346 return getenv(var);
347 }
348}
349
350zend_always_inline int is_regexp_matching(const pcre* regexp, const char* str) {
351 int vec[30];
352 int ret = pcre_exec(regexp, NULL, str, strlen(str), 0, 0, vec, sizeof(vec));
353 if (ret < 0) {
354 if (ret != PCRE_ERROR_NOMATCH) {
355 sp_log_err("regexp", "Something went wrong with a regexp.");
356 }
357 return false;
358 }
359 return true;
360}
361
362int hook_function(const char* original_name, HashTable* hook_table,
363 void (*new_function)(INTERNAL_FUNCTION_PARAMETERS),
364 bool hook_execution_table) {
365 zend_internal_function* func;
366 HashTable *ht = hook_execution_table==true?EG(function_table):CG(function_table);
367
368 /* The `mb` module likes to hook functions, like strlen->mb_strlen,
369 * so we have to hook both of them. */
370 if (0 == strncmp(original_name, "mb_", 3)) {
371 CG(compiler_options) |= ZEND_COMPILE_NO_BUILTIN_STRLEN;
372 if (zend_hash_str_find(ht,
373 VAR_AND_LEN(original_name + 3))) {
374 hook_function(original_name + 3, hook_table, new_function, hook_execution_table);
375 }
376 } else { // TODO this can be moved somewhere else to gain some marginal perfs
377 CG(compiler_options) |= ZEND_COMPILE_NO_BUILTIN_STRLEN;
378 char* mb_name = pecalloc(strlen(original_name) + 3 + 1, 1, 0);
379 memcpy(mb_name, "mb_", 3);
380 memcpy(mb_name + 3, VAR_AND_LEN(original_name));
381 if (zend_hash_str_find(CG(function_table), VAR_AND_LEN(mb_name))) {
382 hook_function(mb_name, hook_table, new_function, hook_execution_table);
383 }
384 }
385
386 if ((func = zend_hash_str_find_ptr(CG(function_table),
387 VAR_AND_LEN(original_name)))) {
388 if (func->handler == new_function) {
389 /* Success !*/
390 } else if (zend_hash_str_add_new_ptr((hook_table),
391 VAR_AND_LEN(original_name),
392 func->handler) == NULL) {
393 sp_log_err("function_pointer_saving",
394 "Could not save function pointer for %s", original_name);
395 return FAILURE;
396 } else {
397 func->handler = new_function;
398 }
399 }
400 return SUCCESS;
401}
402
403int hook_regexp(const pcre* regexp, HashTable* hook_table,
404 void (*new_function)(INTERNAL_FUNCTION_PARAMETERS),
405 bool hook_execution_table) {
406 zend_string* key;
407 HashTable *ht = hook_execution_table==true?EG(function_table):CG(function_table);
408
409 ZEND_HASH_FOREACH_STR_KEY(ht, key) {
410 if (key) {
411 int vec[30];
412 int ret = pcre_exec(regexp, NULL, key->val, key->len, 0, 0, vec, 30);
413 if (ret < 0) { /* Error or no match*/
414 if (PCRE_ERROR_NOMATCH != ret) {
415 sp_log_err("pcre", "Runtime error with pcre, error code: %d", ret);
416 return FAILURE;
417 }
418 continue;
419 }
420 hook_function(key->val, hook_table, new_function, hook_execution_table);
421 }
422 }
423 ZEND_HASH_FOREACH_END();
424 return SUCCESS;
425}