summaryrefslogtreecommitdiff
path: root/src/sp_config_keywords.c
diff options
context:
space:
mode:
authorxXx-caillou-xXx2018-07-13 10:36:50 +0200
committerjvoisin2018-07-13 08:36:50 +0000
commit7963580d72a358975133f86f01de2d2eab08ba38 (patch)
tree4bec345d70f687a2a6002b36e2f2fc79318959f6 /src/sp_config_keywords.c
parent12b740bc7bb01ffe397cecc5b6fa25b136304911 (diff)
Massively optimize how rules are handled
This commit does a lot of things: - Use hashtables instead of lists to store the rules - Rules that can be applied at launch time won't be tried at runtime - Improve feedback when writing nonsensical rules - Make intensive use of `zend_string` instead of `char*`
Diffstat (limited to 'src/sp_config_keywords.c')
-rw-r--r--src/sp_config_keywords.c199
1 files changed, 97 insertions, 102 deletions
diff --git a/src/sp_config_keywords.c b/src/sp_config_keywords.c
index 2a570cd..81f43f7 100644
--- a/src/sp_config_keywords.c
+++ b/src/sp_config_keywords.c
@@ -1,39 +1,8 @@
1#include "php_snuffleupagus.h" 1#include "php_snuffleupagus.h"
2#include "zend_types.h"
2 3
3ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus) 4ZEND_DECLARE_MODULE_GLOBALS(snuffleupagus)
4 5
5static const struct {
6 unsigned int type;
7 char *keys[5]; // Update this value if necessary
8} CONSTRUCTS_TYPES[] = {
9 {.type = ZEND_INCLUDE_OR_EVAL,
10 .keys = {"include", "include_once", "require", "require_once", NULL}},
11 {.type = ZEND_ECHO, .keys = {"echo", NULL}},
12 {.type = ZEND_NEW, .keys = {"new", NULL}},
13 {.type = ZEND_EXIT, .keys = {"exit", NULL}},
14 {.type = ZEND_STRLEN, .keys = {"strlen", NULL}},
15 {.type = ZEND_EVAL_CODE, .keys = {"eval", NULL}},
16 {.type = 0, .keys = {NULL}}};
17
18static int get_construct_type(sp_disabled_function const *const df) {
19 for (size_t i = 0; 0 != CONSTRUCTS_TYPES[i].type; i++) {
20 for (size_t j = 0; NULL != CONSTRUCTS_TYPES[i].keys[j]; j++) {
21 assert(df->function || df->r_function);
22 if (df->function) {
23 if (0 == strcmp(df->function, CONSTRUCTS_TYPES[i].keys[j])) {
24 return CONSTRUCTS_TYPES[i].type;
25 }
26 } else {
27 if (true == sp_is_regexp_matching(df->r_function,
28 CONSTRUCTS_TYPES[i].keys[j])) {
29 return CONSTRUCTS_TYPES[i].type;
30 }
31 }
32 }
33 }
34 return -1;
35}
36
37static int parse_enable(char *line, bool *restrict retval, 6static int parse_enable(char *line, bool *restrict retval,
38 bool *restrict simulation) { 7 bool *restrict simulation) {
39 bool enable = false, disable = false; 8 bool enable = false, disable = false;
@@ -137,7 +106,7 @@ int parse_unserialize(char *line) {
137 {parse_str, SP_TOKEN_DUMP, &(unserialize->dump)}, 106 {parse_str, SP_TOKEN_DUMP, &(unserialize->dump)},
138 {0}}; 107 {0}};
139 108
140 unserialize->textual_representation = estrdup(line); 109 unserialize->textual_representation = zend_string_init(line, strlen(line), 1);
141 110
142 int ret = parse_keywords(sp_config_funcs, line); 111 int ret = parse_keywords(sp_config_funcs, line);
143 if (0 != ret) { 112 if (0 != ret) {
@@ -167,7 +136,8 @@ int parse_readonly_exec(char *line) {
167 {parse_str, SP_TOKEN_DUMP, &(readonly_exec->dump)}, 136 {parse_str, SP_TOKEN_DUMP, &(readonly_exec->dump)},
168 {0}}; 137 {0}};
169 138
170 readonly_exec->textual_representation = estrdup(line); 139 readonly_exec->textual_representation =
140 zend_string_init(line, strlen(line), 1);
171 int ret = parse_keywords(sp_config_funcs, line); 141 int ret = parse_keywords(sp_config_funcs, line);
172 142
173 if (0 != ret) { 143 if (0 != ret) {
@@ -196,8 +166,8 @@ int parse_global(char *line) {
196} 166}
197 167
198static int parse_eval_filter_conf(char *line, sp_list_node **list) { 168static int parse_eval_filter_conf(char *line, sp_list_node **list) {
199 char *token; 169 char *token, *tmp;
200 char *rest; 170 zend_string *rest = NULL;
201 sp_config_eval *eval = SNUFFLEUPAGUS_G(config).config_eval; 171 sp_config_eval *eval = SNUFFLEUPAGUS_G(config).config_eval;
202 172
203 sp_config_functions sp_config_funcs[] = { 173 sp_config_functions sp_config_funcs[] = {
@@ -207,15 +177,19 @@ static int parse_eval_filter_conf(char *line, sp_list_node **list) {
207 {parse_str, SP_TOKEN_DUMP, &(SNUFFLEUPAGUS_G(config).config_eval->dump)}, 177 {parse_str, SP_TOKEN_DUMP, &(SNUFFLEUPAGUS_G(config).config_eval->dump)},
208 {0}}; 178 {0}};
209 179
210 eval->textual_representation = estrdup(line); 180 eval->textual_representation = zend_string_init(line, strlen(line), 1);
211 181
212 int ret = parse_keywords(sp_config_funcs, line); 182 int ret = parse_keywords(sp_config_funcs, line);
213 if (0 != ret) { 183 if (0 != ret) {
214 return ret; 184 return ret;
215 } 185 }
216 186
217 while ((token = strtok_r(rest, ",", &rest))) { 187 tmp = ZSTR_VAL(rest);
218 *list = sp_list_insert(*list, token); 188 while ((token = strtok_r(tmp, ",", &tmp))) {
189 *list = sp_list_insert(*list, zend_string_init(token, strlen(token), 1));
190 }
191 if (rest != NULL) {
192 pefree(rest, 1);
219 } 193 }
220 return SUCCESS; 194 return SUCCESS;
221} 195}
@@ -232,7 +206,7 @@ int parse_eval_whitelist(char *line) {
232 206
233int parse_cookie(char *line) { 207int parse_cookie(char *line) {
234 int ret = 0; 208 int ret = 0;
235 char *samesite = NULL; 209 zend_string *samesite = NULL;
236 sp_cookie *cookie = pecalloc(sizeof(sp_cookie), 1, 1); 210 sp_cookie *cookie = pecalloc(sizeof(sp_cookie), 1, 1);
237 211
238 sp_config_functions sp_config_funcs_cookie_encryption[] = { 212 sp_config_functions sp_config_funcs_cookie_encryption[] = {
@@ -274,7 +248,7 @@ int parse_cookie(char *line) {
274 sp_line_no); 248 sp_line_no);
275 return -1; 249 return -1;
276 } 250 }
277 if ((!cookie->name || '\0' == cookie->name[0]) && !cookie->name_r) { 251 if ((!cookie->name || 0 == ZSTR_LEN(cookie->name)) && !cookie->name_r) {
278 sp_log_err("config", 252 sp_log_err("config",
279 "You must specify a cookie name/regexp on line " 253 "You must specify a cookie name/regexp on line "
280 "%zu.", 254 "%zu.",
@@ -289,16 +263,17 @@ int parse_cookie(char *line) {
289 return -1; 263 return -1;
290 } 264 }
291 if (samesite) { 265 if (samesite) {
292 if (0 == strcasecmp(samesite, SP_TOKEN_SAMESITE_LAX)) { 266 if (zend_string_equals_literal_ci(samesite, SP_TOKEN_SAMESITE_LAX)) {
293 cookie->samesite = lax; 267 cookie->samesite = lax;
294 } else if (0 == strcasecmp(samesite, SP_TOKEN_SAMESITE_STRICT)) { 268 } else if (zend_string_equals_literal_ci(samesite,
269 SP_TOKEN_SAMESITE_STRICT)) {
295 cookie->samesite = strict; 270 cookie->samesite = strict;
296 } else { 271 } else {
297 sp_log_err( 272 sp_log_err(
298 "config", 273 "config",
299 "%s is an invalid value to samesite (expected %s or %s) on line " 274 "%s is an invalid value to samesite (expected %s or %s) on line "
300 "%zu.", 275 "%zu.",
301 samesite, SP_TOKEN_SAMESITE_LAX, SP_TOKEN_SAMESITE_STRICT, 276 ZSTR_VAL(samesite), SP_TOKEN_SAMESITE_LAX, SP_TOKEN_SAMESITE_STRICT,
302 sp_line_no); 277 sp_line_no);
303 return -1; 278 return -1;
304 } 279 }
@@ -308,11 +283,22 @@ int parse_cookie(char *line) {
308 return SUCCESS; 283 return SUCCESS;
309} 284}
310 285
286int add_df_to_hashtable(HashTable *ht, sp_disabled_function *df) {
287 zval *list = zend_hash_find(ht, df->function);
288
289 if (NULL == list) {
290 zend_hash_add_ptr(ht, df->function, sp_list_insert(NULL, df));
291 } else {
292 Z_PTR_P(list) = sp_list_insert(Z_PTR_P(list), df);
293 }
294 return SUCCESS;
295}
296
311int parse_disabled_functions(char *line) { 297int parse_disabled_functions(char *line) {
312 int ret = 0; 298 int ret = 0;
313 bool enable = true, disable = false, allow = false, drop = false; 299 bool enable = true, disable = false, allow = false, drop = false;
314 char *pos = NULL, *var = NULL, *param = NULL; 300 zend_string *pos = NULL, *var = NULL, *param = NULL;
315 char *line_number = NULL; 301 zend_string *line_number = NULL;
316 sp_disabled_function *df = pecalloc(sizeof(*df), 1, 1); 302 sp_disabled_function *df = pecalloc(sizeof(*df), 1, 1);
317 df->pos = -1; 303 df->pos = -1;
318 304
@@ -330,7 +316,7 @@ int parse_disabled_functions(char *line) {
330 {parse_empty, SP_TOKEN_DROP, &(drop)}, 316 {parse_empty, SP_TOKEN_DROP, &(drop)},
331 {parse_str, SP_TOKEN_HASH, &(df->hash)}, 317 {parse_str, SP_TOKEN_HASH, &(df->hash)},
332 {parse_str, SP_TOKEN_PARAM, &(param)}, 318 {parse_str, SP_TOKEN_PARAM, &(param)},
333 {parse_regexp, SP_TOKEN_VALUE_REGEXP, &(df->value_r)}, 319 {parse_regexp, SP_TOKEN_VALUE_REGEXP, &(df->r_value)},
334 {parse_str, SP_TOKEN_VALUE, &(df->value)}, 320 {parse_str, SP_TOKEN_VALUE, &(df->value)},
335 {parse_str, SP_TOKEN_KEY, &(df->key)}, 321 {parse_str, SP_TOKEN_KEY, &(df->key)},
336 {parse_regexp, SP_TOKEN_KEY_REGEXP, &(df->r_key)}, 322 {parse_regexp, SP_TOKEN_KEY_REGEXP, &(df->r_key)},
@@ -360,7 +346,7 @@ int parse_disabled_functions(char *line) {
360 return 1; \ 346 return 1; \
361 } 347 }
362 348
363 MUTUALLY_EXCLUSIVE(df->value, df->value_r, "value", "regexp"); 349 MUTUALLY_EXCLUSIVE(df->value, df->r_value, "value", "regexp");
364 MUTUALLY_EXCLUSIVE(df->r_function, df->function, "r_function", "function"); 350 MUTUALLY_EXCLUSIVE(df->r_function, df->function, "r_function", "function");
365 MUTUALLY_EXCLUSIVE(df->filename, df->r_filename, "r_filename", "filename"); 351 MUTUALLY_EXCLUSIVE(df->filename, df->r_filename, "r_filename", "filename");
366 MUTUALLY_EXCLUSIVE(df->ret, df->r_ret, "r_ret", "ret"); 352 MUTUALLY_EXCLUSIVE(df->ret, df->r_ret, "r_ret", "ret");
@@ -375,25 +361,38 @@ int parse_disabled_functions(char *line) {
375 "'.r_param', '.param' and '.pos' are mutually exclusive on line %zu.", 361 "'.r_param', '.param' and '.pos' are mutually exclusive on line %zu.",
376 line, sp_line_no); 362 line, sp_line_no);
377 return -1; 363 return -1;
378 } else if ((df->r_key || df->key) && (df->value_r || df->value)) { 364 } else if ((df->r_key || df->key) && (df->r_value || df->value)) {
379 sp_log_err("config", 365 sp_log_err("config",
380 "Invalid configuration line: 'sp.disabled_functions%s':" 366 "Invalid configuration line: 'sp.disabled_functions%s':"
381 "`key` and `value` are mutually exclusive on line %zu.", 367 "`key` and `value` are mutually exclusive on line %zu.",
382 line, sp_line_no); 368 line, sp_line_no);
383 return -1; 369 return -1;
384 } else if ((df->r_ret || df->ret) && (df->r_param || param)) { 370 } else if ((df->r_ret || df->ret || df->ret_type) && (df->r_param || param)) {
385 sp_log_err("config", 371 sp_log_err("config",
386 "Invalid configuration line: 'sp.disabled_functions%s':" 372 "Invalid configuration line: 'sp.disabled_functions%s':"
387 "`ret` and `param` are mutually exclusive on line %zu.", 373 "`ret` and `param` are mutually exclusive on line %zu.",
388 line, sp_line_no); 374 line, sp_line_no);
389 return -1; 375 return -1;
376 } else if ((df->r_ret || df->ret || df->ret_type) && (var)) {
377 sp_log_err("config",
378 "Invalid configuration line: 'sp.disabled_functions%s':"
379 "`ret` and `var` are mutually exclusive on line %zu.",
380 line, sp_line_no);
381 return -1;
382 } else if ((df->r_ret || df->ret || df->ret_type) &&
383 (df->value || df->r_value)) {
384 sp_log_err("config",
385 "Invalid configuration line: 'sp.disabled_functions%s':"
386 "`ret` and `value` are mutually exclusive on line %zu.",
387 line, sp_line_no);
388 return -1;
390 } else if (!(df->r_function || df->function)) { 389 } else if (!(df->r_function || df->function)) {
391 sp_log_err("config", 390 sp_log_err("config",
392 "Invalid configuration line: 'sp.disabled_functions%s':" 391 "Invalid configuration line: 'sp.disabled_functions%s':"
393 " must take a function name on line %zu.", 392 " must take a function name on line %zu.",
394 line, sp_line_no); 393 line, sp_line_no);
395 return -1; 394 return -1;
396 } else if (df->filename && *df->filename != '/') { 395 } else if (df->filename && *ZSTR_VAL(df->filename) != '/') {
397 sp_log_err("config", 396 sp_log_err("config",
398 "Invalid configuration line: 'sp.disabled_functions%s':" 397 "Invalid configuration line: 'sp.disabled_functions%s':"
399 "'.filename' must be an absolute path on line %zu.", 398 "'.filename' must be an absolute path on line %zu.",
@@ -410,10 +409,10 @@ int parse_disabled_functions(char *line) {
410 if (pos) { 409 if (pos) {
411 errno = 0; 410 errno = 0;
412 char *endptr; 411 char *endptr;
413 df->pos = (int)strtol(pos, &endptr, 10); 412 df->pos = (int)strtol(ZSTR_VAL(pos), &endptr, 10);
414 if (errno != 0 || endptr == pos) { 413 if (errno != 0 || endptr == ZSTR_VAL(pos)) {
415 sp_log_err("config", "Failed to parse arg '%s' of `pos` on line %zu.", 414 sp_log_err("config", "Failed to parse arg '%s' of `pos` on line %zu.",
416 pos, sp_line_no); 415 ZSTR_VAL(pos), sp_line_no);
417 return -1; 416 return -1;
418 } 417 }
419 } 418 }
@@ -421,46 +420,46 @@ int parse_disabled_functions(char *line) {
421 if (line_number) { 420 if (line_number) {
422 errno = 0; 421 errno = 0;
423 char *endptr; 422 char *endptr;
424 df->line = (unsigned int)strtoul(line_number, &endptr, 10); 423 df->line = (unsigned int)strtoul(ZSTR_VAL(line_number), &endptr, 10);
425 if (errno != 0 || endptr == line_number) { 424 if (errno != 0 || endptr == ZSTR_VAL(line_number)) {
426 sp_log_err("config", "Failed to parse arg '%s' of `line` on line %zu.", 425 sp_log_err("config", "Failed to parse arg '%s' of `line` on line %zu.",
427 line_number, sp_line_no); 426 ZSTR_VAL(line_number), sp_line_no);
428 return -1; 427 return -1;
429 } 428 }
430 } 429 }
431 df->allow = allow; 430 df->allow = allow;
432 df->textual_representation = estrdup(line); 431 df->textual_representation = zend_string_init(line, strlen(line), 1);
433 432
434 if (df->function) { 433 if (df->function) {
435 df->functions_list = parse_functions_list(df->function); 434 df->functions_list = parse_functions_list(ZSTR_VAL(df->function));
436 } 435 }
437 436
438 if (param) { 437 if (param) {
439 if (strlen(param) > 0 && param[0] != '$') { 438 if (ZSTR_LEN(param) > 0 && ZSTR_VAL(param)[0] != '$') {
440 /* This is an ugly hack. We're prefixing with a `$` because otherwise 439 /* This is an ugly hack. We're prefixing with a `$` because otherwise
441 * the parser treats this as a constant. 440 * the parser treats this as a constant.
442 * FIXME: Remove this, and improve our (weird) parser. */ 441 * FIXME: Remove this, and improve our (weird) parser. */
443 char *new = pecalloc(strlen(param) + 2, 1, 1); 442 char *new = pecalloc(ZSTR_LEN(param) + 2, 1, 1);
444 new[0] = '$'; 443 new[0] = '$';
445 memcpy(new + 1, param, strlen(param)); 444 memcpy(new + 1, ZSTR_VAL(param), ZSTR_LEN(param));
446 df->param = sp_parse_var(new); 445 df->param = sp_parse_var(new);
447 free(new); 446 free(new);
448 } else { 447 } else {
449 df->param = sp_parse_var(param); 448 df->param = sp_parse_var(ZSTR_VAL(param));
450 } 449 }
451 if (!df->param) { 450 if (!df->param) {
452 sp_log_err("config", "Invalid value '%s' for `param` on line %zu.", param, 451 sp_log_err("config", "Invalid value '%s' for `param` on line %zu.",
453 sp_line_no); 452 ZSTR_VAL(param), sp_line_no);
454 return -1; 453 return -1;
455 } 454 }
456 } 455 }
457 456
458 if (var) { 457 if (var) {
459 if (*var) { 458 if (ZSTR_LEN(var)) {
460 df->var = sp_parse_var(var); 459 df->var = sp_parse_var(ZSTR_VAL(var));
461 if (!df->var) { 460 if (!df->var) {
462 sp_log_err("config", "Invalid value '%s' for `var` on line %zu.", var, 461 sp_log_err("config", "Invalid value '%s' for `var` on line %zu.",
463 sp_line_no); 462 ZSTR_VAL(var), sp_line_no);
464 return -1; 463 return -1;
465 } 464 }
466 } else { 465 } else {
@@ -469,38 +468,33 @@ int parse_disabled_functions(char *line) {
469 } 468 }
470 } 469 }
471 470
472 switch (get_construct_type(df)) {
473 case ZEND_INCLUDE_OR_EVAL:
474 SNUFFLEUPAGUS_G(config)
475 .config_disabled_constructs->construct_include = sp_list_insert(
476 SNUFFLEUPAGUS_G(config).config_disabled_constructs->construct_include,
477 df);
478 return ret;
479 case ZEND_EVAL_CODE:
480 SNUFFLEUPAGUS_G(config)
481 .config_disabled_constructs->construct_eval = sp_list_insert(
482 SNUFFLEUPAGUS_G(config).config_disabled_constructs->construct_eval,
483 df);
484 return ret;
485 case ZEND_ECHO:
486 default:
487 break;
488 }
489
490 if (true == disable) { 471 if (true == disable) {
491 return ret; 472 return ret;
492 } 473 }
493 474
494 if (df->ret || df->r_ret || df->ret_type) { 475 if (df->function && !df->functions_list) {
495 SNUFFLEUPAGUS_G(config).config_disabled_functions_ret->disabled_functions = 476 if (df->ret || df->r_ret || df->ret_type) {
496 sp_list_insert(SNUFFLEUPAGUS_G(config) 477 add_df_to_hashtable(SNUFFLEUPAGUS_G(config).config_disabled_functions_ret,
497 .config_disabled_functions_ret->disabled_functions, 478 df);
498 df); 479 } else {
480 add_df_to_hashtable(SNUFFLEUPAGUS_G(config).config_disabled_functions,
481 df);
482 }
499 } else { 483 } else {
500 SNUFFLEUPAGUS_G(config) 484 if (df->ret || df->r_ret || df->ret_type) {
501 .config_disabled_functions->disabled_functions = sp_list_insert( 485 SNUFFLEUPAGUS_G(config)
502 SNUFFLEUPAGUS_G(config).config_disabled_functions->disabled_functions, 486 .config_disabled_functions_reg_ret->disabled_functions =
503 df); 487 sp_list_insert(
488 SNUFFLEUPAGUS_G(config)
489 .config_disabled_functions_reg_ret->disabled_functions,
490 df);
491 } else {
492 SNUFFLEUPAGUS_G(config)
493 .config_disabled_functions_reg->disabled_functions =
494 sp_list_insert(SNUFFLEUPAGUS_G(config)
495 .config_disabled_functions_reg->disabled_functions,
496 df);
497 }
504 } 498 }
505 return ret; 499 return ret;
506} 500}
@@ -529,20 +523,21 @@ int parse_upload_validation(char *line) {
529 } 523 }
530 SNUFFLEUPAGUS_G(config).config_upload_validation->enable = enable; 524 SNUFFLEUPAGUS_G(config).config_upload_validation->enable = enable;
531 525
532 char const *script = SNUFFLEUPAGUS_G(config).config_upload_validation->script; 526 zend_string const *script =
527 SNUFFLEUPAGUS_G(config).config_upload_validation->script;
533 528
534 if (!script) { 529 if (!script) {
535 sp_log_err("config", 530 sp_log_err("config",
536 "The `script` directive is mandatory in '%s' on line %zu.", line, 531 "The `script` directive is mandatory in '%s' on line %zu.", line,
537 sp_line_no); 532 sp_line_no);
538 return -1; 533 return -1;
539 } else if (-1 == access(script, F_OK)) { 534 } else if (-1 == access(ZSTR_VAL(script), F_OK)) {
540 sp_log_err("config", "The `script` (%s) doesn't exist on line %zu.", script, 535 sp_log_err("config", "The `script` (%s) doesn't exist on line %zu.",
541 sp_line_no); 536 ZSTR_VAL(script), sp_line_no);
542 return -1; 537 return -1;
543 } else if (-1 == access(script, X_OK)) { 538 } else if (-1 == access(ZSTR_VAL(script), X_OK)) {
544 sp_log_err("config", "The `script` (%s) isn't executable on line %zu.", 539 sp_log_err("config", "The `script` (%s) isn't executable on line %zu.",
545 script, sp_line_no); 540 ZSTR_VAL(script), sp_line_no);
546 return -1; 541 return -1;
547 } 542 }
548 543